init
This commit is contained in:
parent
e872c3e7be
commit
6aba9148b4
185
README.md
185
README.md
|
|
@ -1,5 +1,184 @@
|
||||||
# PS_Site_Builder
|
# Static Website Generator Template
|
||||||
|
|
||||||
Static HTML5 website generator.
|
A clean, modern template for generating static websites from Markdown files. Perfect for portfolios, business sites, or personal websites.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Clone this repository:**
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd tf_site_template
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Customize your site:**
|
||||||
|
- Edit `build.ps1` and change the site title, header, and footer text (lines 48-50)
|
||||||
|
- Add your content in `content/*.md` files
|
||||||
|
- Add images to `images/` folder
|
||||||
|
|
||||||
|
3. **Build your website:**
|
||||||
|
```powershell
|
||||||
|
pwsh -File build.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **View your site:**
|
||||||
|
Open `dist/index.html` in your browser, or serve it with a local web server.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ **Dynamic Section Detection** - Automatically creates sections from Markdown files
|
||||||
|
- ✅ **Blog System** - Full blog functionality with front matter support
|
||||||
|
- ✅ **Scroll-Snap Navigation** - Smooth scrolling with snap-to-section behavior
|
||||||
|
- ✅ **Responsive Design** - Works on mobile, tablet, and desktop
|
||||||
|
- ✅ **Modern Styling** - Glassmorphism effects and smooth animations
|
||||||
|
- ✅ **Image Management** - Automatic image assignment
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── build.ps1 # Build script (customize site name here)
|
||||||
|
├── styles.css # Website stylesheet
|
||||||
|
├── content/ # Your Markdown content
|
||||||
|
│ ├── about.md # About section
|
||||||
|
│ ├── services.md # Services section
|
||||||
|
│ ├── contact.md # Contact section
|
||||||
|
│ └── blog/ # Blog posts
|
||||||
|
│ └── welcome.md
|
||||||
|
├── images/ # Your images
|
||||||
|
│ ├── background.jpg # Site background (optional)
|
||||||
|
│ ├── about1.webp # Section images (numbered)
|
||||||
|
│ └── ...
|
||||||
|
└── dist/ # Generated website (created on build)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content Format
|
||||||
|
|
||||||
|
### Sections
|
||||||
|
|
||||||
|
Create a `.md` file in `content/` for each section:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Section Title
|
||||||
|
|
||||||
|
First content block.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Second content block.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Third content block.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Image Naming:**
|
||||||
|
- Name images: `{sectionId}{number}.{ext}`
|
||||||
|
- Example: `about1.webp`, `about2.jpg`, `services1.png`
|
||||||
|
- First block uses `about1.webp`, second uses `about2.webp`, etc.
|
||||||
|
|
||||||
|
### Blog Posts
|
||||||
|
|
||||||
|
Create `.md` files in `content/blog/` with front matter:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: My Blog Post
|
||||||
|
date: 2024-01-15
|
||||||
|
author: Your Name
|
||||||
|
tags: [tag1, tag2]
|
||||||
|
image: blog1
|
||||||
|
---
|
||||||
|
|
||||||
|
Your blog post content here...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
More content sections.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Site Information
|
||||||
|
|
||||||
|
Edit `build.ps1` (around line 48-50):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$siteTitle = "My Website"
|
||||||
|
$siteHeader = "Welcome to My Website"
|
||||||
|
$footerText = "My Website"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
Edit `styles.css` to customize:
|
||||||
|
- Colors (CSS variables at the top)
|
||||||
|
- Fonts
|
||||||
|
- Layout
|
||||||
|
- Animations
|
||||||
|
|
||||||
|
### Adding Sections
|
||||||
|
|
||||||
|
1. Create a new `.md` file in `content/` (e.g., `portfolio.md`)
|
||||||
|
2. Add content following the section format
|
||||||
|
3. Add images: `portfolio1.webp`, `portfolio2.jpg`, etc.
|
||||||
|
4. Run the build script - the section appears automatically!
|
||||||
|
|
||||||
|
### Adding Blog Posts
|
||||||
|
|
||||||
|
1. Create a new `.md` file in `content/blog/`
|
||||||
|
2. Add front matter with title, date, author, tags, and image
|
||||||
|
3. Add your content below
|
||||||
|
4. Run the build script - the post appears on the blog page!
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- **PowerShell 7+** (for UTF-8 support)
|
||||||
|
- No other dependencies required!
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pwsh -File build.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated website will be in the `dist/` folder.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
The `dist/` folder contains a standard static website. Deploy it to:
|
||||||
|
|
||||||
|
- **GitHub Pages** - Enable Pages and point to `dist/` folder
|
||||||
|
- **Netlify** - Deploy `dist/` folder
|
||||||
|
- **Vercel** - Deploy `dist/` folder
|
||||||
|
- **Any static host** - Upload `dist/` contents
|
||||||
|
|
||||||
|
## Image Guidelines
|
||||||
|
|
||||||
|
### Section Images
|
||||||
|
- **Naming:** `{sectionId}{number}.{ext}`
|
||||||
|
- **Examples:** `about1.webp`, `services2.jpg`
|
||||||
|
- **Format:** WebP recommended, JPG/PNG also work
|
||||||
|
|
||||||
|
### Blog Images
|
||||||
|
- **Naming:** Any name (e.g., `blog1.webp`)
|
||||||
|
- **Reference:** Use base name in front matter `image:` field
|
||||||
|
|
||||||
|
### Background Image
|
||||||
|
- **Naming:** `background.{ext}`
|
||||||
|
- **Usage:** Automatically used as site background
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- Use WebP format for smaller file sizes
|
||||||
|
- Keep image dimensions reasonable (max 2000px width recommended)
|
||||||
|
- Use descriptive alt text in markdown image syntax: ``
|
||||||
|
- Test your site locally before deploying
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Free to use and modify for your projects!
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions, check the main repository documentation or open an issue.
|
||||||
|
|
||||||
A simple yet powerful HTML5 static website builder that uses powershell.
|
|
||||||
786
build.ps1
Normal file
786
build.ps1
Normal file
|
|
@ -0,0 +1,786 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Build script for static website generator
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script processes Markdown content files and generates a static HTML website.
|
||||||
|
It dynamically detects content sections, processes blog posts, and generates
|
||||||
|
navigation menus. The output is a fully functional static website in the dist/ folder.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Dynamic section detection from content/ folder
|
||||||
|
- Markdown to HTML conversion
|
||||||
|
- Blog post processing with front matter
|
||||||
|
- Automatic image handling
|
||||||
|
- Responsive navigation generation
|
||||||
|
|
||||||
|
.REQUIREMENTS
|
||||||
|
- PowerShell 7+ (for better UTF-8 handling)
|
||||||
|
- Markdown files in content/ folder
|
||||||
|
- Images in images/ folder
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Output: Generated files are placed in dist/ directory
|
||||||
|
#>
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CONFIGURATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Get the directory where this script is located
|
||||||
|
$projectRoot = $PSScriptRoot
|
||||||
|
|
||||||
|
# Define source and output directories
|
||||||
|
$sourceDir = Join-Path $projectRoot "content" # Markdown content source
|
||||||
|
$templateFile = Join-Path $projectRoot "template.html" # Temporary template file
|
||||||
|
$imagesDir = Join-Path $projectRoot "images" # Source images directory
|
||||||
|
|
||||||
|
# Output directory - can be overridden with OUTPUT_DIR environment variable
|
||||||
|
$outputDir = if ($env:OUTPUT_DIR) {
|
||||||
|
Join-Path $projectRoot $env:OUTPUT_DIR
|
||||||
|
} else {
|
||||||
|
Join-Path $projectRoot "dist" # Default output directory
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get current year for copyright
|
||||||
|
$year = (Get-Date).Year
|
||||||
|
|
||||||
|
# Site configuration - customize these values
|
||||||
|
$siteTitle = "My Website"
|
||||||
|
$siteHeader = "Welcome to My Website"
|
||||||
|
$footerText = "My Website"
|
||||||
|
|
||||||
|
# Temporary output files (will be moved to dist/ at the end)
|
||||||
|
$tempOutputFile = Join-Path $projectRoot "index.html"
|
||||||
|
$tempBlogOutputFile = Join-Path $projectRoot "blog.html"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# HELPER FUNCTIONS
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Finds the background image file in the images directory
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Searches for any file matching "background.*" pattern and returns
|
||||||
|
the relative path for use in CSS background-image property.
|
||||||
|
|
||||||
|
.PARAMETER imagesDir
|
||||||
|
Path to the images directory
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
String - Relative path to background image (e.g., "images/background.jpg")
|
||||||
|
Empty string if no background image found
|
||||||
|
#>
|
||||||
|
function Get-BackgroundImagePath {
|
||||||
|
param (
|
||||||
|
[string]$imagesDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$backgroundImages = Get-ChildItem (Join-Path $imagesDir "background.*") -ErrorAction SilentlyContinue
|
||||||
|
if ($backgroundImages.Count -gt 0) {
|
||||||
|
return "images/" + $backgroundImages[0].Name
|
||||||
|
}
|
||||||
|
return "" # Return empty string if no background image found
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
if (-not (Test-Path $outputDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $outputDir | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy static files to dist first
|
||||||
|
Copy-Item "styles.css" -Destination $outputDir -Force
|
||||||
|
if (Test-Path "images") {
|
||||||
|
Copy-Item "images" -Destination $outputDir -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set output files after copying
|
||||||
|
$outputFile = Join-Path $outputDir "index.html"
|
||||||
|
$blogOutputFile = Join-Path $outputDir "blog.html"
|
||||||
|
|
||||||
|
# Get background image path
|
||||||
|
$backgroundPath = Get-BackgroundImagePath -imagesDir $imagesDir
|
||||||
|
$backgroundStyle = if ($backgroundPath) {
|
||||||
|
"background-image: url('$backgroundPath');"
|
||||||
|
} else {
|
||||||
|
"" # No background image
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Generates navigation menu HTML from section IDs
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Creates an unordered list of navigation links for all detected sections,
|
||||||
|
plus a Blog link. Section names are capitalized for display.
|
||||||
|
|
||||||
|
.PARAMETER sections
|
||||||
|
Array of section IDs (e.g., @("about", "services", "contact"))
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
String - HTML navigation menu items
|
||||||
|
#>
|
||||||
|
function Get-NavigationHtml {
|
||||||
|
param (
|
||||||
|
[array]$sections
|
||||||
|
)
|
||||||
|
|
||||||
|
$navItems = @()
|
||||||
|
foreach ($section in $sections) {
|
||||||
|
# Capitalize first letter of section name
|
||||||
|
$sectionName = $section.Substring(0,1).ToUpper() + $section.Substring(1)
|
||||||
|
$navItems += " <li><a href=`"#$section`">$sectionName</a></li>"
|
||||||
|
}
|
||||||
|
# Always add Blog link
|
||||||
|
$navItems += " <li><a href=`"blog.html`">Blog</a></li>"
|
||||||
|
return $navItems -join "`n"
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Finds all numbered images for a section
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Searches for images matching the pattern {sectionId}{number}.*
|
||||||
|
(e.g., "about1.webp", "about2.jpg", "services3.png")
|
||||||
|
and returns them as a hashtable indexed by number.
|
||||||
|
|
||||||
|
.PARAMETER sectionId
|
||||||
|
The section identifier (e.g., "about", "services")
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
Hashtable - Images indexed by number (e.g., @{1="about1.webp"; 2="about2.jpg"})
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-SectionImages -sectionId "about"
|
||||||
|
Returns: @{1="about1.webp"; 2="about2.jpg"; 3="about3.jpg"}
|
||||||
|
#>
|
||||||
|
function Get-SectionImages {
|
||||||
|
param (
|
||||||
|
[string]$sectionId
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get all files matching the pattern sectionX.* (e.g., about1.webp, about2.png)
|
||||||
|
$images = Get-ChildItem (Join-Path $imagesDir "$sectionId[0-9]*.*") -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Create a hashtable to store images by number
|
||||||
|
$imagesByNumber = @{}
|
||||||
|
|
||||||
|
foreach ($image in $images) {
|
||||||
|
# Extract the number from the filename using regex
|
||||||
|
# Matches: sectionId followed by one or more digits
|
||||||
|
if ($image.BaseName -match "$sectionId(\d+)") {
|
||||||
|
$number = [int]$matches[1]
|
||||||
|
$imagesByNumber[$number] = $image.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $imagesByNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Converts a Markdown file to an HTML section
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Processes a Markdown file and converts it to a full HTML section with:
|
||||||
|
- Section heading extracted from first # heading
|
||||||
|
- Content blocks separated by "---" dividers
|
||||||
|
- Alternating left/right image alignment
|
||||||
|
- Automatic image assignment from images directory
|
||||||
|
|
||||||
|
.PARAMETER file
|
||||||
|
Path to the Markdown file to convert
|
||||||
|
|
||||||
|
.PARAMETER sectionId
|
||||||
|
The section ID to use (e.g., "about", "services")
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
String - Complete HTML section markup
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Markdown format:
|
||||||
|
- First line: # Section Title
|
||||||
|
- Content blocks separated by "---"
|
||||||
|
- Each block becomes a section-content div with text and image
|
||||||
|
#>
|
||||||
|
function Convert-MarkdownToSection {
|
||||||
|
param (
|
||||||
|
[string]$file,
|
||||||
|
[string]$sectionId
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read markdown content and split by "---" dividers
|
||||||
|
$content = Get-Content $file -Raw
|
||||||
|
$sections = $content -split "---" | ForEach-Object { $_.Trim() }
|
||||||
|
|
||||||
|
# Get available numbered images for this section (e.g., about1.webp, about2.jpg)
|
||||||
|
$sectionImages = Get-SectionImages -sectionId $sectionId
|
||||||
|
|
||||||
|
# Extract title from first section's # heading, or capitalize section ID as fallback
|
||||||
|
$title = if ($sections[0] -match "^#\s+(.+)$") {
|
||||||
|
$matches[1]
|
||||||
|
} else {
|
||||||
|
$sectionId.Substring(0,1).ToUpper() + $sectionId.Substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start building section HTML
|
||||||
|
$sectionHtml = @"
|
||||||
|
<section id="$sectionId" class="snap-section">
|
||||||
|
<h2>$title</h2>
|
||||||
|
<div class="content-container">
|
||||||
|
"@
|
||||||
|
|
||||||
|
# Process each content section (separated by "---")
|
||||||
|
for ($i = 0; $i -lt $sections.Count; $i++) {
|
||||||
|
$text = $sections[$i]
|
||||||
|
|
||||||
|
# Image selection priority:
|
||||||
|
# 1. Explicit markdown image syntax: 
|
||||||
|
# 2. Numbered section image (e.g., about1.webp for first block)
|
||||||
|
# 3. First available numbered image if exact match not found
|
||||||
|
# 4. Default fallback: sectionId.webp
|
||||||
|
$imageMatch = $text -match '!\[([^\]]*)\]\(([^\)]+)\)'
|
||||||
|
$imageSrc = if ($imageMatch) {
|
||||||
|
$text = $text -replace '!\[([^\]]*)\]\(([^\)]+)\)', '' # Remove image markdown from text
|
||||||
|
$matches[2] # Use specified image path
|
||||||
|
} elseif ($sectionImages.ContainsKey($i + 1)) {
|
||||||
|
"images/" + $sectionImages[$i + 1] # Use numbered section image (e.g., about1.webp)
|
||||||
|
} elseif ($sectionImages.Count -gt 0) {
|
||||||
|
# Use first available image if numbered image doesn't exist
|
||||||
|
$sortedKeys = $sectionImages.Keys | Sort-Object
|
||||||
|
$firstKey = $sortedKeys[0]
|
||||||
|
"images/" + $sectionImages[$firstKey]
|
||||||
|
} else {
|
||||||
|
"images/$sectionId.webp" # Default fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate alt text: use markdown alt text if provided, otherwise generate descriptive text
|
||||||
|
$imageAlt = if ($imageMatch) {
|
||||||
|
$matches[1] # Use specified alt text from markdown
|
||||||
|
} else {
|
||||||
|
"$sectionId image $($i + 1)" # Default alt text
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove title heading from first section (already extracted above)
|
||||||
|
$text = $text -replace "^#\s+.+`n", ""
|
||||||
|
|
||||||
|
# Convert markdown links to HTML anchor tags
|
||||||
|
$text = $text -replace '\[([^\]]+)\]\(([^\)]+)\)', '<a href="$2">$1</a>'
|
||||||
|
|
||||||
|
# Alternate alignment: even indices = left-align (image on right), odd = right-align (image on left)
|
||||||
|
$alignment = if ($i % 2 -eq 0) { "left-align" } else { "right-align" }
|
||||||
|
|
||||||
|
$sectionHtml += @"
|
||||||
|
<div class="section-content $alignment">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>$text</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="$imageSrc" alt="$imageAlt">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"@
|
||||||
|
}
|
||||||
|
|
||||||
|
$sectionHtml += @"
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
"@
|
||||||
|
|
||||||
|
return $sectionHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# MAIN BUILD PROCESS - INDEX PAGE
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Dynamically detect all .md files in content folder (excluding blog subdirectory)
|
||||||
|
# Each .md file becomes a section on the index page
|
||||||
|
$contentHtml = ""
|
||||||
|
$markdownFiles = Get-ChildItem -Path $sourceDir -Filter "*.md" -File | Where-Object { $_.DirectoryName -eq $sourceDir }
|
||||||
|
$sectionIds = @()
|
||||||
|
|
||||||
|
Write-Host "Found $($markdownFiles.Count) section markdown files:"
|
||||||
|
foreach ($file in $markdownFiles) {
|
||||||
|
$sectionId = $file.BaseName # Use filename (without .md) as section ID
|
||||||
|
$sectionIds += $sectionId
|
||||||
|
Write-Host " - $sectionId"
|
||||||
|
# Convert each markdown file to an HTML section
|
||||||
|
$contentHtml += Convert-MarkdownToSection -file $file.FullName -sectionId $sectionId
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate navigation menu HTML from detected sections
|
||||||
|
$navigationHtml = Get-NavigationHtml -sections $sectionIds
|
||||||
|
|
||||||
|
# Create HTML template with placeholders
|
||||||
|
$template = @"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>$siteTitle</title>
|
||||||
|
<style>
|
||||||
|
body::before {
|
||||||
|
$backgroundStyle
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>$siteHeader</h1>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
{{navigation}}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="snap-container">
|
||||||
|
{{content}}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© $year $footerText. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"@
|
||||||
|
|
||||||
|
# Replace template placeholders with actual content
|
||||||
|
$finalHtml = $template -replace "{{content}}", $contentHtml -replace "{{navigation}}", $navigationHtml
|
||||||
|
$finalHtml | Out-File $tempOutputFile -Encoding utf8
|
||||||
|
|
||||||
|
Write-Host "Building site from $projectRoot"
|
||||||
|
Write-Host "Content directory: $sourceDir"
|
||||||
|
Write-Host "Images directory: $imagesDir"
|
||||||
|
|
||||||
|
Write-Host "Build complete! Output saved to $tempOutputFile"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# BLOG BUILD PROCESS
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Blog posts are stored in content/blog/ subdirectory
|
||||||
|
$blogDir = Join-Path $sourceDir "blog"
|
||||||
|
$blogOutputFile = Join-Path $outputDir "blog.html"
|
||||||
|
|
||||||
|
Write-Host "building blog"
|
||||||
|
Write-Host "Blog dir: $blogDir"
|
||||||
|
Write-Host "Blog output file: $blogOutputFile"
|
||||||
|
|
||||||
|
# Blog template
|
||||||
|
$blogTemplate = @"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>$siteTitle - Blog</title>
|
||||||
|
<style>
|
||||||
|
body::before {
|
||||||
|
$backgroundStyle
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>$siteTitle</h1>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
{{navigation}}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="blog-container">
|
||||||
|
<div class="blog-grid">
|
||||||
|
{{content}}
|
||||||
|
</div>
|
||||||
|
{{pagination}}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© $year $footerText. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"@
|
||||||
|
|
||||||
|
# Check if blog directory exists
|
||||||
|
Write-Host "Building blog from: $blogDir"
|
||||||
|
if (Test-Path $blogDir) {
|
||||||
|
Write-Host "Blog directory exists"
|
||||||
|
$files = Get-ChildItem (Join-Path $blogDir "*.md")
|
||||||
|
Write-Host "Found blog files: $($files.Count)"
|
||||||
|
$files | ForEach-Object { Write-Host " - $($_.Name)" }
|
||||||
|
} else {
|
||||||
|
Write-Host "Blog directory does not exist at: $blogDir"
|
||||||
|
# Create the directory
|
||||||
|
New-Item -ItemType Directory -Path $blogDir -Force
|
||||||
|
Write-Host "Created blog directory"
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Finds the image file for a blog post
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Searches for blog post images using multiple strategies:
|
||||||
|
1. Image name specified in front matter
|
||||||
|
2. Post filename (without extension)
|
||||||
|
|
||||||
|
.PARAMETER imageName
|
||||||
|
Image name from front matter (e.g., "blog1")
|
||||||
|
|
||||||
|
.PARAMETER postFileName
|
||||||
|
Name of the blog post file (e.g., "ai-tools.md")
|
||||||
|
|
||||||
|
.PARAMETER imagesDir
|
||||||
|
Path to images directory
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
String - Relative path to image (e.g., "images/blog1.webp")
|
||||||
|
$null if no image found
|
||||||
|
#>
|
||||||
|
function Get-BlogPostImage {
|
||||||
|
param (
|
||||||
|
[string]$imageName,
|
||||||
|
[string]$postFileName,
|
||||||
|
[string]$imagesDir
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build search list: try specified name first, then post filename
|
||||||
|
$searchNames = @()
|
||||||
|
if ($imageName) {
|
||||||
|
$searchNames += $imageName
|
||||||
|
}
|
||||||
|
$postBaseName = [System.IO.Path]::GetFileNameWithoutExtension($postFileName)
|
||||||
|
$searchNames += $postBaseName
|
||||||
|
|
||||||
|
# Search for image files matching any of the names
|
||||||
|
foreach ($name in $searchNames) {
|
||||||
|
$imageFiles = Get-ChildItem (Join-Path $imagesDir "$name.*") -ErrorAction SilentlyContinue
|
||||||
|
if ($imageFiles.Count -gt 0) {
|
||||||
|
return "images/" + $imageFiles[0].Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Converts Markdown text to HTML with proper formatting
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Processes Markdown syntax and converts to HTML:
|
||||||
|
- Headings (#, ##, ###, ####)
|
||||||
|
- Links [text](url)
|
||||||
|
- Lists (bulleted and numbered)
|
||||||
|
- Code blocks and inline code
|
||||||
|
- Paragraphs
|
||||||
|
|
||||||
|
.PARAMETER markdown
|
||||||
|
Markdown text to convert
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
String - HTML formatted text
|
||||||
|
#>
|
||||||
|
function Convert-MarkdownToHtml {
|
||||||
|
param (
|
||||||
|
[string]$markdown
|
||||||
|
)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($markdown)) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = $markdown
|
||||||
|
|
||||||
|
# Convert code blocks first (before other processing)
|
||||||
|
$html = $html -replace '(?ms)```(\w+)?\r?\n(.*?)```', '<pre><code>$2</code></pre>'
|
||||||
|
|
||||||
|
# Convert inline code (`code`)
|
||||||
|
$html = $html -replace '`([^`]+)`', '<code>$1</code>'
|
||||||
|
|
||||||
|
# Convert headings (##, ###, etc.)
|
||||||
|
$html = $html -replace '(?m)^####\s+(.+)$', '<h4>$1</h4>'
|
||||||
|
$html = $html -replace '(?m)^###\s+(.+)$', '<h3>$1</h3>'
|
||||||
|
$html = $html -replace '(?m)^##\s+(.+)$', '<h2>$1</h2>'
|
||||||
|
$html = $html -replace '(?m)^#\s+(.+)$', '<h1>$1</h1>'
|
||||||
|
|
||||||
|
# Convert markdown links [text](url)
|
||||||
|
$html = $html -replace '\[([^\]]+)\]\(([^\)]+)\)', '<a href="$2">$1</a>'
|
||||||
|
|
||||||
|
# Process line by line to handle lists properly
|
||||||
|
$lines = $html -split "`r?`n"
|
||||||
|
$output = @()
|
||||||
|
$inList = $false
|
||||||
|
$listType = ""
|
||||||
|
$listItems = @()
|
||||||
|
|
||||||
|
foreach ($line in $lines) {
|
||||||
|
$trimmed = $line.Trim()
|
||||||
|
|
||||||
|
# Check for numbered list
|
||||||
|
if ($trimmed -match '^\d+\.\s+(.+)$') {
|
||||||
|
if (-not $inList -or $listType -ne "ol") {
|
||||||
|
# Close previous list if exists
|
||||||
|
if ($inList) {
|
||||||
|
$output += "</$listType>"
|
||||||
|
}
|
||||||
|
$inList = $true
|
||||||
|
$listType = "ol"
|
||||||
|
$listItems = @()
|
||||||
|
}
|
||||||
|
$listItems += "<li>$($matches[1])</li>"
|
||||||
|
}
|
||||||
|
# Check for bullet list
|
||||||
|
elseif ($trimmed -match '^[-*]\s+(.+)$') {
|
||||||
|
if (-not $inList -or $listType -ne "ul") {
|
||||||
|
# Close previous list if exists
|
||||||
|
if ($inList) {
|
||||||
|
$output += "</$listType>"
|
||||||
|
}
|
||||||
|
$inList = $true
|
||||||
|
$listType = "ul"
|
||||||
|
$listItems = @()
|
||||||
|
}
|
||||||
|
$listItems += "<li>$($matches[1])</li>"
|
||||||
|
}
|
||||||
|
# Empty line - close list if open
|
||||||
|
elseif ([string]::IsNullOrWhiteSpace($trimmed)) {
|
||||||
|
if ($inList) {
|
||||||
|
$output += "<$listType>" + ($listItems -join "`n") + "</$listType>"
|
||||||
|
$inList = $false
|
||||||
|
$listType = ""
|
||||||
|
$listItems = @()
|
||||||
|
}
|
||||||
|
$output += ""
|
||||||
|
}
|
||||||
|
# Regular content line
|
||||||
|
else {
|
||||||
|
# Close list if open
|
||||||
|
if ($inList) {
|
||||||
|
$output += "<$listType>" + ($listItems -join "`n") + "</$listType>"
|
||||||
|
$inList = $false
|
||||||
|
$listType = ""
|
||||||
|
$listItems = @()
|
||||||
|
}
|
||||||
|
$output += $trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Close any open list
|
||||||
|
if ($inList) {
|
||||||
|
$output += "<$listType>" + ($listItems -join "`n") + "</$listType>"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Join lines and split into paragraphs
|
||||||
|
$joined = $output -join "`n"
|
||||||
|
$paragraphs = $joined -split "`n`n" | Where-Object { $_.Trim() -ne "" }
|
||||||
|
$processedParagraphs = @()
|
||||||
|
|
||||||
|
foreach ($para in $paragraphs) {
|
||||||
|
$para = $para.Trim()
|
||||||
|
if ($para -ne "") {
|
||||||
|
# Don't wrap if it's already a block element
|
||||||
|
if ($para -match '^<(pre|h[1-4]|ul|ol|p)') {
|
||||||
|
$processedParagraphs += $para
|
||||||
|
} else {
|
||||||
|
$processedParagraphs += "<p>$para</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $processedParagraphs -join "`n"
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Converts a blog post Markdown file to HTML
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Processes blog posts with front matter (YAML-like metadata between --- markers):
|
||||||
|
- Extracts title, date, author, tags, image
|
||||||
|
- Converts markdown content to HTML
|
||||||
|
- Generates complete blog post article HTML
|
||||||
|
|
||||||
|
.PARAMETER file
|
||||||
|
Path to blog post Markdown file
|
||||||
|
|
||||||
|
.PARAMETER imagesDir
|
||||||
|
Path to images directory for blog post images
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
String - Complete HTML article markup
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Front matter format:
|
||||||
|
---
|
||||||
|
title: Post Title
|
||||||
|
date: YYYY-MM-DD
|
||||||
|
author: Author Name
|
||||||
|
tags: [tag1, tag2, tag3]
|
||||||
|
image: imageName
|
||||||
|
---
|
||||||
|
#>
|
||||||
|
function Convert-BlogPostToHtml {
|
||||||
|
param (
|
||||||
|
[string]$file,
|
||||||
|
[string]$imagesDir
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "Converting file: $file"
|
||||||
|
$content = Get-Content $file -Raw
|
||||||
|
Write-Host "Content length: $($content.Length)"
|
||||||
|
|
||||||
|
# Updated regex pattern to match exactly three dashes
|
||||||
|
if ($content -match "(?ms)^---[\r\n](.*?)[\r\n]---[\r\n](.*)$") {
|
||||||
|
Write-Host "Found front matter"
|
||||||
|
$frontMatter = $matches[1]
|
||||||
|
$content = $matches[2].Trim()
|
||||||
|
|
||||||
|
Write-Host "Front matter: $frontMatter"
|
||||||
|
Write-Host "Content after front matter: $content"
|
||||||
|
|
||||||
|
# Parse front matter into hashtable
|
||||||
|
$metadata = @{}
|
||||||
|
foreach ($line in ($frontMatter -split "`n")) {
|
||||||
|
if ($line -match "^(\w+):\s*(.*)$") {
|
||||||
|
$key = $matches[1]
|
||||||
|
$value = $matches[2].Trim()
|
||||||
|
if ($key -eq "tags") {
|
||||||
|
$value = $value.Trim("[]").Split(",").ForEach({ $_.Trim() })
|
||||||
|
}
|
||||||
|
$metadata[$key] = $value
|
||||||
|
Write-Host "Parsed metadata: $key = $value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find blog post image
|
||||||
|
$imagePath = Get-BlogPostImage -imageName $metadata.image -postFileName (Split-Path $file -Leaf) -imagesDir $imagesDir
|
||||||
|
$imageHtml = if ($imagePath) {
|
||||||
|
"<div class=`"blog-post-image`"><img src=`"$imagePath`" alt=`"$($metadata.title)`"></div>"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert tags to HTML
|
||||||
|
$tagsHtml = $metadata.tags | ForEach-Object {
|
||||||
|
"<span class='blog-tag'>$_</span>"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process content sections with proper markdown formatting
|
||||||
|
$sections = $content -split "---" | ForEach-Object { $_.Trim() }
|
||||||
|
$processedContent = $sections | ForEach-Object {
|
||||||
|
if ($_.Trim() -ne "") {
|
||||||
|
Convert-MarkdownToHtml -markdown $_
|
||||||
|
}
|
||||||
|
} | Where-Object { $_ -ne "" }
|
||||||
|
|
||||||
|
$html = @"
|
||||||
|
<article class="blog-post">
|
||||||
|
<div class="blog-post-header">
|
||||||
|
<h2 class="blog-post-title">$($metadata.title)</h2>
|
||||||
|
<div class="blog-post-meta">
|
||||||
|
<span>$($metadata.date)</span> • <span>$($metadata.author)</span>
|
||||||
|
</div>
|
||||||
|
<div class="blog-post-tags">
|
||||||
|
$($tagsHtml -join '')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
$imageHtml
|
||||||
|
<div class="blog-post-content">
|
||||||
|
$($processedContent -join "`n")
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
"@
|
||||||
|
Write-Host "Generated HTML length: $($html.Length)"
|
||||||
|
return $html
|
||||||
|
}
|
||||||
|
Write-Host "No front matter found in file"
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process blog posts: read all .md files, extract dates, and sort by date (newest first)
|
||||||
|
$blogPosts = Get-ChildItem (Join-Path $blogDir "*.md") | ForEach-Object {
|
||||||
|
Write-Host "Processing blog post: $($_.Name)"
|
||||||
|
$content = Get-Content $_.FullName -Raw
|
||||||
|
Write-Host "Content read: $($content.Length) characters"
|
||||||
|
|
||||||
|
# Updated regex pattern to match exactly three dashes
|
||||||
|
if ($content -match "(?ms)^---[\r\n](.*?)[\r\n]---") {
|
||||||
|
Write-Host "Found front matter in $($_.Name)"
|
||||||
|
$frontMatter = $matches[1]
|
||||||
|
Write-Host "Front matter: $frontMatter"
|
||||||
|
|
||||||
|
if ($frontMatter -match "date:\s*(.*)") {
|
||||||
|
Write-Host "Found date: $($matches[1])"
|
||||||
|
$date = [datetime]::Parse($matches[1])
|
||||||
|
@{
|
||||||
|
File = $_
|
||||||
|
Date = $date
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "No date found in front matter"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "No front matter found in $($_.Name)"
|
||||||
|
}
|
||||||
|
} | Sort-Object { $_.Date } -Descending
|
||||||
|
|
||||||
|
Write-Host "Sorted blog posts: $($blogPosts.Count)"
|
||||||
|
|
||||||
|
# Convert each blog post to HTML and combine
|
||||||
|
$blogHtml = ""
|
||||||
|
foreach ($post in $blogPosts) {
|
||||||
|
Write-Host "Converting blog post to HTML: $($post.File.Name)"
|
||||||
|
$html = Convert-BlogPostToHtml -file $post.File.FullName -imagesDir $imagesDir
|
||||||
|
Write-Host "Generated HTML length: $($html.Length)"
|
||||||
|
$blogHtml += $html
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Total blog HTML length: $($blogHtml.Length)"
|
||||||
|
|
||||||
|
# Generate blog navigation (same as main page)
|
||||||
|
$blogNavHtml = Get-NavigationHtml -sections $sectionIds
|
||||||
|
|
||||||
|
# Generate final blog page HTML by replacing template placeholders
|
||||||
|
$finalBlogHtml = $blogTemplate -replace "{{content}}", $blogHtml -replace "{{pagination}}", "" -replace "{{navigation}}", $blogNavHtml
|
||||||
|
$finalBlogHtml | Out-File $tempBlogOutputFile -Encoding utf8
|
||||||
|
|
||||||
|
Write-Host "Blog build complete"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# FINAL OUTPUT - COPY FILES TO DIST DIRECTORY
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
if (-not (Test-Path $outputDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $outputDir | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy all built files to dist directory
|
||||||
|
Copy-Item $tempOutputFile -Destination (Join-Path $outputDir "index.html") -Force
|
||||||
|
Copy-Item $tempBlogOutputFile -Destination (Join-Path $outputDir "blog.html") -Force
|
||||||
|
Copy-Item "styles.css" -Destination $outputDir -Force
|
||||||
|
|
||||||
|
# Copy images directory recursively
|
||||||
|
if (Test-Path "images") {
|
||||||
|
Copy-Item "images" -Destination $outputDir -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean up temporary files created during build
|
||||||
|
Remove-Item $tempOutputFile -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $tempBlogOutputFile -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Host "Files copied to dist directory"
|
||||||
|
Write-Host "Build complete! Website is ready in: $outputDir"
|
||||||
|
|
||||||
12
content/about.md
Normal file
12
content/about.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# About
|
||||||
|
|
||||||
|
Welcome to our website! This is a demo About section to show you how the content format works.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This is the second content block. You can add as many content blocks as you want by separating them with `---` dividers. Each block will automatically get an image assigned from your images folder.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The third content block demonstrates how images are automatically assigned. Name your images following the pattern: `{sectionId}{number}.{ext}` (e.g., `about1.webp`, `about2.jpg`, `about3.png`).
|
||||||
|
|
||||||
33
content/blog/welcome.md
Normal file
33
content/blog/welcome.md
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
title: Welcome to Your New Website
|
||||||
|
date: 2024-01-01
|
||||||
|
author: Your Name
|
||||||
|
tags: [welcome, getting-started]
|
||||||
|
image: blog1
|
||||||
|
---
|
||||||
|
|
||||||
|
Congratulations! You've successfully set up your static website generator. This is your first blog post.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Here are some tips to help you get started:
|
||||||
|
|
||||||
|
- Edit the Markdown files in `content/` to customize your sections
|
||||||
|
- Add images to the `images/` folder following the naming pattern
|
||||||
|
- Create new blog posts in `content/blog/` with front matter
|
||||||
|
- Run `pwsh -File build.ps1` to rebuild your site
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
You can customize:
|
||||||
|
- Site title and header in `build.ps1`
|
||||||
|
- Colors and styling in `styles.css`
|
||||||
|
- Content in the `content/` folder
|
||||||
|
- Images in the `images/` folder
|
||||||
|
|
||||||
|
Happy building!
|
||||||
|
|
||||||
12
content/contact.md
Normal file
12
content/contact.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Contact
|
||||||
|
|
||||||
|
Get in touch with us! This is a demo contact section.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You can add your contact information, social media links, or any other details here. The format is flexible - just use Markdown!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Feel free to customize this section with your own contact details and information.
|
||||||
|
|
||||||
12
content/services.md
Normal file
12
content/services.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Services
|
||||||
|
|
||||||
|
Our first service offering is detailed here. This content block will appear with an image on the right side.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The second service is described in this block. Notice how the image alignment alternates - this block will have the image on the left side.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You can customize this content to match your needs. Simply edit the Markdown files in the `content/` folder and rebuild the site!
|
||||||
|
|
||||||
3
dist/.gitkeep
vendored
Normal file
3
dist/.gitkeep
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# This file ensures the dist directory is tracked by git
|
||||||
|
# The dist folder will be empty until you run the build script
|
||||||
|
|
||||||
65
dist/blog.html
vendored
Normal file
65
dist/blog.html
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>My Website - Blog</title>
|
||||||
|
<style>
|
||||||
|
body::before {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>My Website</h1>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#about">About</a></li>
|
||||||
|
<li><a href="#contact">Contact</a></li>
|
||||||
|
<li><a href="#services">Services</a></li>
|
||||||
|
<li><a href="blog.html">Blog</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="blog-container">
|
||||||
|
<div class="blog-grid">
|
||||||
|
<article class="blog-post">
|
||||||
|
<div class="blog-post-header">
|
||||||
|
<h2 class="blog-post-title">Welcome to Your New Website</h2>
|
||||||
|
<div class="blog-post-meta">
|
||||||
|
<span>2024-01-01</span> • <span>Your Name</span>
|
||||||
|
</div>
|
||||||
|
<div class="blog-post-tags">
|
||||||
|
<span class='blog-tag'>welcome</span><span class='blog-tag'>getting-started</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blog-post-content">
|
||||||
|
<p>Congratulations! You've successfully set up your static website generator. This is your first blog post.</p>
|
||||||
|
<h2>Getting Started</h2>
|
||||||
|
<p>Here are some tips to help you get started:</p>
|
||||||
|
<ul><li>Edit the Markdown files in <code>content/</code> to customize your sections</li>
|
||||||
|
<li>Add images to the <code>images/</code> folder following the naming pattern</li>
|
||||||
|
<li>Create new blog posts in <code>content/blog/</code> with front matter</li>
|
||||||
|
<li>Run <code>pwsh -File build.ps1</code> to rebuild your site</li></ul>
|
||||||
|
<h2>Customization</h2>
|
||||||
|
<p>You can customize:
|
||||||
|
<ul><li>Site title and header in <code>build.ps1</code></li>
|
||||||
|
<li>Colors and styling in <code>styles.css</code></li>
|
||||||
|
<li>Content in the <code>content/</code> folder</li>
|
||||||
|
<li>Images in the <code>images/</code> folder</li></ul></p>
|
||||||
|
<p>Happy building!</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2025 My Website. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
47
dist/images/README.md
vendored
Normal file
47
dist/images/README.md
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Images Directory
|
||||||
|
|
||||||
|
Place your images here following these naming conventions:
|
||||||
|
|
||||||
|
## Section Images
|
||||||
|
|
||||||
|
Name images for sections using the pattern: `{sectionId}{number}.{ext}`
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `about1.webp` - First image for About section
|
||||||
|
- `about2.jpg` - Second image for About section
|
||||||
|
- `services1.webp` - First image for Services section
|
||||||
|
- `contact1.png` - First image for Contact section
|
||||||
|
|
||||||
|
## Blog Images
|
||||||
|
|
||||||
|
Name blog images any way you like, then reference them in the blog post front matter:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: My Post
|
||||||
|
image: my-image-name
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
The build script will look for `my-image-name.*` in this directory.
|
||||||
|
|
||||||
|
## Background Image
|
||||||
|
|
||||||
|
Name your background image: `background.{ext}`
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `background.jpg`
|
||||||
|
- `background.webp`
|
||||||
|
- `background.png`
|
||||||
|
|
||||||
|
## Image Tips
|
||||||
|
|
||||||
|
- **Format:** WebP recommended for smaller file sizes
|
||||||
|
- **Size:** Keep images reasonable (max 2000px width recommended)
|
||||||
|
- **Quality:** Optimize images before adding them
|
||||||
|
- **Naming:** Use lowercase with numbers (e.g., `about1.webp` not `About1.WEBP`)
|
||||||
|
|
||||||
|
## Demo Images
|
||||||
|
|
||||||
|
For demo purposes, you can use placeholder images or add your own. The build will work even without images - sections will just show placeholder paths.
|
||||||
|
|
||||||
117
dist/index.html
vendored
Normal file
117
dist/index.html
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>My Website</title>
|
||||||
|
<style>
|
||||||
|
body::before {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Welcome to My Website</h1>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#about">About</a></li>
|
||||||
|
<li><a href="#contact">Contact</a></li>
|
||||||
|
<li><a href="#services">Services</a></li>
|
||||||
|
<li><a href="blog.html">Blog</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="snap-container">
|
||||||
|
<section id="about" class="snap-section">
|
||||||
|
<h2>About</h2>
|
||||||
|
<div class="content-container"> <div class="section-content left-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>
|
||||||
|
Welcome to our website! This is a demo About section to show you how the content format works.</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/about.webp" alt="about image 1">
|
||||||
|
</div>
|
||||||
|
</div> <div class="section-content right-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>This is the second content block. You can add as many content blocks as you want by separating them with `</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/about.webp" alt="about image 2">
|
||||||
|
</div>
|
||||||
|
</div> <div class="section-content left-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>` dividers. Each block will automatically get an image assigned from your images folder.</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/about.webp" alt="about image 3">
|
||||||
|
</div>
|
||||||
|
</div> <div class="section-content right-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>The third content block demonstrates how images are automatically assigned. Name your images following the pattern: `{sectionId}{number}.{ext}` (e.g., `about1.webp`, `about2.jpg`, `about3.png`).</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/about.webp" alt="about image 4">
|
||||||
|
</div>
|
||||||
|
</div> </div>
|
||||||
|
</section> <section id="contact" class="snap-section">
|
||||||
|
<h2>Contact</h2>
|
||||||
|
<div class="content-container"> <div class="section-content left-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>
|
||||||
|
Get in touch with us! This is a demo contact section.</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/contact.webp" alt="contact image 1">
|
||||||
|
</div>
|
||||||
|
</div> <div class="section-content right-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>You can add your contact information, social media links, or any other details here. The format is flexible - just use Markdown!</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/contact.webp" alt="contact image 2">
|
||||||
|
</div>
|
||||||
|
</div> <div class="section-content left-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>Feel free to customize this section with your own contact details and information.</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/contact.webp" alt="contact image 3">
|
||||||
|
</div>
|
||||||
|
</div> </div>
|
||||||
|
</section> <section id="services" class="snap-section">
|
||||||
|
<h2>Services</h2>
|
||||||
|
<div class="content-container"> <div class="section-content left-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>
|
||||||
|
Our first service offering is detailed here. This content block will appear with an image on the right side.</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/services.webp" alt="services image 1">
|
||||||
|
</div>
|
||||||
|
</div> <div class="section-content right-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>The second service is described in this block. Notice how the image alignment alternates - this block will have the image on the left side.</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/services.webp" alt="services image 2">
|
||||||
|
</div>
|
||||||
|
</div> <div class="section-content left-align">
|
||||||
|
<div class="text-content">
|
||||||
|
<p>You can customize this content to match your needs. Simply edit the Markdown files in the `content/` folder and rebuild the site!</p>
|
||||||
|
</div>
|
||||||
|
<div class="image-content">
|
||||||
|
<img src="images/services.webp" alt="services image 3">
|
||||||
|
</div>
|
||||||
|
</div> </div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2025 My Website. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
817
dist/styles.css
vendored
Normal file
817
dist/styles.css
vendored
Normal file
|
|
@ -0,0 +1,817 @@
|
||||||
|
/* ============================================================================
|
||||||
|
STATIC WEBSITE STYLESHEET
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
This stylesheet provides all styling for the static website.
|
||||||
|
It includes:
|
||||||
|
- CSS reset and base styles
|
||||||
|
- Layout and navigation
|
||||||
|
- Section styling with scroll-snap
|
||||||
|
- Blog post styling
|
||||||
|
- Responsive design
|
||||||
|
- Dark/light mode support via CSS variables
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
RESET & BASE STYLES
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Reset default browser styles and set box-sizing for consistent sizing */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base body styling - uses flexbox for layout, prevents double scrollbars */
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden; /* Prevent double scrollbars */
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
HEADER & NAVIGATION
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Fixed header at top of page with backdrop blur effect */
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
padding: 1rem 5%;
|
||||||
|
z-index: 100;
|
||||||
|
height: 160px; /* Doubled from 80px */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title styles */
|
||||||
|
header h1 {
|
||||||
|
font-size: 1.5rem; /* Adjust size as needed */
|
||||||
|
margin: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
margin: 0; /* Remove any margin */
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 0; /* Remove any margin */
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
MAIN CONTENT & LAYOUT
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Main container - no max-width to allow full-width sections */
|
||||||
|
main {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section base styling */
|
||||||
|
section {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section headings */
|
||||||
|
h2 {
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Paragraph text with max-width for readability */
|
||||||
|
p {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
FOOTER
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Fixed footer at bottom of page */
|
||||||
|
footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
height: 60px; /* Give footer a fixed height */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for header */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
header {
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
flex-direction: row; /* Keep navigation horizontal on mobile */
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
SCROLL-SNAP CONTAINER
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Implements smooth scroll-snap behavior where each section snaps into view.
|
||||||
|
Scrollbar is hidden for cleaner appearance while maintaining functionality.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Main scroll container with vertical snap behavior */
|
||||||
|
.snap-container {
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scroll-snap-type: y mandatory; /* Enable vertical snap */
|
||||||
|
-webkit-scroll-snap-type: y mandatory; /* Safari support */
|
||||||
|
scroll-padding-top: 0; /* Reset any scroll padding */
|
||||||
|
scrollbar-width: none; /* Firefox - hide scrollbar */
|
||||||
|
-ms-overflow-style: none; /* Internet Explorer 10+ - hide scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar in WebKit browsers (Chrome, Safari, etc.) */
|
||||||
|
.snap-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
SECTION STYLING
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Each section uses full viewport height with scroll-snap alignment.
|
||||||
|
Sections have semi-transparent backgrounds with backdrop blur for modern look.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Individual snap section - full viewport height with snap alignment */
|
||||||
|
.snap-section {
|
||||||
|
min-height: 100vh;
|
||||||
|
height: 100vh;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
-webkit-scroll-snap-align: start; /* Safari support */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 200px 2rem 60px 2rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
justify-content: flex-start; /* Change to flex-start to better control spacing */
|
||||||
|
overflow-y: auto; /* Allow scrolling within sections if needed */
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px); /* Safari support */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center all section titles */
|
||||||
|
.snap-section h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
SECTION CONTENT LAYOUT
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Content blocks alternate between left and right alignment for visual interest.
|
||||||
|
Each block contains text content and an image side-by-side.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Content block container - flexbox for text/image layout */
|
||||||
|
.section-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1200px;
|
||||||
|
gap: 3rem;
|
||||||
|
margin-bottom: 3rem; /* Add bottom margin for spacing between content blocks */
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text content styling */
|
||||||
|
.text-content {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 500px;
|
||||||
|
text-align: left; /* Keep all text left-aligned */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Image content styling */
|
||||||
|
.image-content {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 500px;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-content img {
|
||||||
|
display: block; /* Show the images */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 4px 8px var(--shadow-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 4px 15px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the placeholder boxes */
|
||||||
|
.image-content:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the different colors for placeholder boxes since we're using real images now */
|
||||||
|
#about .image-content:after,
|
||||||
|
#services .image-content:after,
|
||||||
|
#portfolio .image-content:after,
|
||||||
|
#contact .image-content:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left alignment (image on right) */
|
||||||
|
.left-align {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right alignment (image on left) */
|
||||||
|
.right-align {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove these conflicting styles */
|
||||||
|
.left-align .text-content,
|
||||||
|
.right-align .text-align {
|
||||||
|
text-align: left; /* Override previous right-align */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust responsive design for multiple content blocks */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.snap-section {
|
||||||
|
padding: 100px 1rem 60px 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
flex-direction: column !important;
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add some different background colors to distinguish sections */
|
||||||
|
#about {
|
||||||
|
background-color: var(--section-bg-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#services {
|
||||||
|
background-color: var(--section-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#portfolio {
|
||||||
|
background-color: var(--section-bg-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#contact {
|
||||||
|
background-color: var(--section-bg-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
CSS VARIABLES - THEME COLORS
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
CSS custom properties for theming. Supports both light and dark modes
|
||||||
|
via media query. All colors are defined here for easy theme customization.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Light mode color variables (default) */
|
||||||
|
:root {
|
||||||
|
/* Light mode colors (default) */
|
||||||
|
--bg-color: rgba(240, 245, 255, 0.9);
|
||||||
|
--text-color: #2C3E50;
|
||||||
|
--section-bg-1: #f0f0f0;
|
||||||
|
--section-bg-2: #e0e0e0;
|
||||||
|
--section-bg-3: #d0d0d0;
|
||||||
|
--section-bg-4: #c0c0c0;
|
||||||
|
--header-footer-bg: rgba(28, 34, 56, 0.95);
|
||||||
|
--shadow-color: rgba(62, 84, 172, 0.1);
|
||||||
|
--accent-color: #4A90E2;
|
||||||
|
--accent-glow: rgba(74, 144, 226, 0.2);
|
||||||
|
--border-color: rgba(74, 144, 226, 0.2);
|
||||||
|
--section-bg-1-transparent: rgba(236, 240, 255, 0.85);
|
||||||
|
--section-bg-2-transparent: rgba(226, 232, 255, 0.85);
|
||||||
|
--section-bg-3-transparent: rgba(216, 224, 255, 0.85);
|
||||||
|
--section-bg-4-transparent: rgba(206, 216, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
/* Dark mode colors */
|
||||||
|
--bg-color: rgba(16, 20, 34, 0.9);
|
||||||
|
--text-color: #E0E6FF;
|
||||||
|
--section-bg-1: #2a2a2a;
|
||||||
|
--section-bg-2: #303030;
|
||||||
|
--section-bg-3: #383838;
|
||||||
|
--section-bg-4: #404040;
|
||||||
|
--header-footer-bg: rgba(18, 22, 38, 0.95);
|
||||||
|
--shadow-color: rgba(62, 84, 172, 0.3);
|
||||||
|
--accent-color: #64A9FF;
|
||||||
|
--accent-glow: rgba(100, 169, 255, 0.2);
|
||||||
|
--border-color: rgba(100, 169, 255, 0.2);
|
||||||
|
--section-bg-1-transparent: rgba(22, 26, 44, 0.85);
|
||||||
|
--section-bg-2-transparent: rgba(26, 30, 50, 0.85);
|
||||||
|
--section-bg-3-transparent: rgba(30, 34, 56, 0.85);
|
||||||
|
--section-bg-4-transparent: rgba(34, 38, 62, 0.85);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update existing color references */
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px); /* Safari support */
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
color: var(--text-color);
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-content img {
|
||||||
|
box-shadow: 0 4px 8px var(--shadow-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update section background colors */
|
||||||
|
#about {
|
||||||
|
background-color: var(--section-bg-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#services {
|
||||||
|
background-color: var(--section-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#portfolio {
|
||||||
|
background-color: var(--section-bg-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#contact {
|
||||||
|
background-color: var(--section-bg-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content container styles */
|
||||||
|
.content-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure markdown-generated paragraphs maintain styles */
|
||||||
|
.text-content p {
|
||||||
|
margin: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add background image styles */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update section backgrounds to be semi-transparent */
|
||||||
|
#about {
|
||||||
|
background-color: var(--section-bg-1-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#services {
|
||||||
|
background-color: var(--section-bg-2-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#portfolio {
|
||||||
|
background-color: var(--section-bg-3-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#contact {
|
||||||
|
background-color: var(--section-bg-4-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add subtle border glow to sections */
|
||||||
|
.snap-section {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px); /* Safari support */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update header and footer style */
|
||||||
|
header, footer {
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px); /* Safari support */
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update navigation links */
|
||||||
|
nav a {
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update blog post styling */
|
||||||
|
.blog-post {
|
||||||
|
background-color: var(--section-bg-1-transparent);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px); /* Safari support */
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 5px 25px var(--accent-glow);
|
||||||
|
background-color: var(--section-bg-2-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update blog tags */
|
||||||
|
.blog-tag {
|
||||||
|
background-color: var(--accent-glow);
|
||||||
|
border: 1px solid var(--accent-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-tag:hover {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 2px 8px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update scrollbar styling */
|
||||||
|
.blog-container::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--accent-color);
|
||||||
|
border: 3px solid var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add subtle animations */
|
||||||
|
.section-content {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update image styling */
|
||||||
|
.image-content img {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 4px 15px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update headings */
|
||||||
|
h1, h2 {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0;
|
||||||
|
width: 60px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add subtle transitions to all interactive elements */
|
||||||
|
a, button {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update section transitions */
|
||||||
|
.snap-section {
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
BLOG PAGE STYLING
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Blog-specific styles for the blog.html page. Includes:
|
||||||
|
- Blog container and grid layout
|
||||||
|
- Blog post card styling
|
||||||
|
- Blog post metadata and tags
|
||||||
|
- Content formatting
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Main blog container - scrollable area with padding for header/footer */
|
||||||
|
.blog-container {
|
||||||
|
padding-top: 200px;
|
||||||
|
padding-bottom: 100px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: calc(100vh - 220px); /* Account for header and footer */
|
||||||
|
overflow-y: auto; /* Enable scrolling */
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2.5rem;
|
||||||
|
padding-bottom: 2rem; /* Add some padding at the bottom */
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-title::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 80px;
|
||||||
|
height: 3px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-meta {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content {
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content p {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content ul,
|
||||||
|
.blog-post-content ol {
|
||||||
|
margin-left: 2rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content h1,
|
||||||
|
.blog-post-content h2,
|
||||||
|
.blog-post-content h3,
|
||||||
|
.blog-post-content h4 {
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content h2::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0;
|
||||||
|
width: 60px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-image {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 4px 15px var(--accent-glow);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-image:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 6px 20px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-link {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background-color: var(--section-bg-1-transparent);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-link:hover {
|
||||||
|
background-color: var(--section-bg-2-transparent);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-link.active {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
box-shadow: 0 0 15px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add scrollbar styling for better visibility */
|
||||||
|
.blog-container::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-container::-webkit-scrollbar-track {
|
||||||
|
background: var(--section-bg-1-transparent);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-container::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--accent-color);
|
||||||
|
border: 3px solid var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure content is visible on all browsers */
|
||||||
|
@supports (scrollbar-width: thin) {
|
||||||
|
.blog-container {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--header-footer-bg) var(--section-bg-1-transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
47
images/README.md
Normal file
47
images/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Images Directory
|
||||||
|
|
||||||
|
Place your images here following these naming conventions:
|
||||||
|
|
||||||
|
## Section Images
|
||||||
|
|
||||||
|
Name images for sections using the pattern: `{sectionId}{number}.{ext}`
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `about1.webp` - First image for About section
|
||||||
|
- `about2.jpg` - Second image for About section
|
||||||
|
- `services1.webp` - First image for Services section
|
||||||
|
- `contact1.png` - First image for Contact section
|
||||||
|
|
||||||
|
## Blog Images
|
||||||
|
|
||||||
|
Name blog images any way you like, then reference them in the blog post front matter:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: My Post
|
||||||
|
image: my-image-name
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
The build script will look for `my-image-name.*` in this directory.
|
||||||
|
|
||||||
|
## Background Image
|
||||||
|
|
||||||
|
Name your background image: `background.{ext}`
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `background.jpg`
|
||||||
|
- `background.webp`
|
||||||
|
- `background.png`
|
||||||
|
|
||||||
|
## Image Tips
|
||||||
|
|
||||||
|
- **Format:** WebP recommended for smaller file sizes
|
||||||
|
- **Size:** Keep images reasonable (max 2000px width recommended)
|
||||||
|
- **Quality:** Optimize images before adding them
|
||||||
|
- **Naming:** Use lowercase with numbers (e.g., `about1.webp` not `About1.WEBP`)
|
||||||
|
|
||||||
|
## Demo Images
|
||||||
|
|
||||||
|
For demo purposes, you can use placeholder images or add your own. The build will work even without images - sections will just show placeholder paths.
|
||||||
|
|
||||||
817
styles.css
Normal file
817
styles.css
Normal file
|
|
@ -0,0 +1,817 @@
|
||||||
|
/* ============================================================================
|
||||||
|
STATIC WEBSITE STYLESHEET
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
This stylesheet provides all styling for the static website.
|
||||||
|
It includes:
|
||||||
|
- CSS reset and base styles
|
||||||
|
- Layout and navigation
|
||||||
|
- Section styling with scroll-snap
|
||||||
|
- Blog post styling
|
||||||
|
- Responsive design
|
||||||
|
- Dark/light mode support via CSS variables
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
RESET & BASE STYLES
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Reset default browser styles and set box-sizing for consistent sizing */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base body styling - uses flexbox for layout, prevents double scrollbars */
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden; /* Prevent double scrollbars */
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
HEADER & NAVIGATION
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Fixed header at top of page with backdrop blur effect */
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
padding: 1rem 5%;
|
||||||
|
z-index: 100;
|
||||||
|
height: 160px; /* Doubled from 80px */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title styles */
|
||||||
|
header h1 {
|
||||||
|
font-size: 1.5rem; /* Adjust size as needed */
|
||||||
|
margin: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
margin: 0; /* Remove any margin */
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 0; /* Remove any margin */
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
MAIN CONTENT & LAYOUT
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Main container - no max-width to allow full-width sections */
|
||||||
|
main {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section base styling */
|
||||||
|
section {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section headings */
|
||||||
|
h2 {
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Paragraph text with max-width for readability */
|
||||||
|
p {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
FOOTER
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Fixed footer at bottom of page */
|
||||||
|
footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
height: 60px; /* Give footer a fixed height */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for header */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
header {
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
flex-direction: row; /* Keep navigation horizontal on mobile */
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
SCROLL-SNAP CONTAINER
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Implements smooth scroll-snap behavior where each section snaps into view.
|
||||||
|
Scrollbar is hidden for cleaner appearance while maintaining functionality.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Main scroll container with vertical snap behavior */
|
||||||
|
.snap-container {
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scroll-snap-type: y mandatory; /* Enable vertical snap */
|
||||||
|
-webkit-scroll-snap-type: y mandatory; /* Safari support */
|
||||||
|
scroll-padding-top: 0; /* Reset any scroll padding */
|
||||||
|
scrollbar-width: none; /* Firefox - hide scrollbar */
|
||||||
|
-ms-overflow-style: none; /* Internet Explorer 10+ - hide scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar in WebKit browsers (Chrome, Safari, etc.) */
|
||||||
|
.snap-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
SECTION STYLING
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Each section uses full viewport height with scroll-snap alignment.
|
||||||
|
Sections have semi-transparent backgrounds with backdrop blur for modern look.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Individual snap section - full viewport height with snap alignment */
|
||||||
|
.snap-section {
|
||||||
|
min-height: 100vh;
|
||||||
|
height: 100vh;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
-webkit-scroll-snap-align: start; /* Safari support */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 200px 2rem 60px 2rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
justify-content: flex-start; /* Change to flex-start to better control spacing */
|
||||||
|
overflow-y: auto; /* Allow scrolling within sections if needed */
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px); /* Safari support */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center all section titles */
|
||||||
|
.snap-section h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
SECTION CONTENT LAYOUT
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Content blocks alternate between left and right alignment for visual interest.
|
||||||
|
Each block contains text content and an image side-by-side.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Content block container - flexbox for text/image layout */
|
||||||
|
.section-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1200px;
|
||||||
|
gap: 3rem;
|
||||||
|
margin-bottom: 3rem; /* Add bottom margin for spacing between content blocks */
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text content styling */
|
||||||
|
.text-content {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 500px;
|
||||||
|
text-align: left; /* Keep all text left-aligned */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Image content styling */
|
||||||
|
.image-content {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 500px;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-content img {
|
||||||
|
display: block; /* Show the images */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 4px 8px var(--shadow-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 4px 15px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the placeholder boxes */
|
||||||
|
.image-content:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the different colors for placeholder boxes since we're using real images now */
|
||||||
|
#about .image-content:after,
|
||||||
|
#services .image-content:after,
|
||||||
|
#portfolio .image-content:after,
|
||||||
|
#contact .image-content:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left alignment (image on right) */
|
||||||
|
.left-align {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right alignment (image on left) */
|
||||||
|
.right-align {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove these conflicting styles */
|
||||||
|
.left-align .text-content,
|
||||||
|
.right-align .text-align {
|
||||||
|
text-align: left; /* Override previous right-align */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust responsive design for multiple content blocks */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.snap-section {
|
||||||
|
padding: 100px 1rem 60px 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
flex-direction: column !important;
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add some different background colors to distinguish sections */
|
||||||
|
#about {
|
||||||
|
background-color: var(--section-bg-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#services {
|
||||||
|
background-color: var(--section-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#portfolio {
|
||||||
|
background-color: var(--section-bg-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#contact {
|
||||||
|
background-color: var(--section-bg-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
CSS VARIABLES - THEME COLORS
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
CSS custom properties for theming. Supports both light and dark modes
|
||||||
|
via media query. All colors are defined here for easy theme customization.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Light mode color variables (default) */
|
||||||
|
:root {
|
||||||
|
/* Light mode colors (default) */
|
||||||
|
--bg-color: rgba(240, 245, 255, 0.9);
|
||||||
|
--text-color: #2C3E50;
|
||||||
|
--section-bg-1: #f0f0f0;
|
||||||
|
--section-bg-2: #e0e0e0;
|
||||||
|
--section-bg-3: #d0d0d0;
|
||||||
|
--section-bg-4: #c0c0c0;
|
||||||
|
--header-footer-bg: rgba(28, 34, 56, 0.95);
|
||||||
|
--shadow-color: rgba(62, 84, 172, 0.1);
|
||||||
|
--accent-color: #4A90E2;
|
||||||
|
--accent-glow: rgba(74, 144, 226, 0.2);
|
||||||
|
--border-color: rgba(74, 144, 226, 0.2);
|
||||||
|
--section-bg-1-transparent: rgba(236, 240, 255, 0.85);
|
||||||
|
--section-bg-2-transparent: rgba(226, 232, 255, 0.85);
|
||||||
|
--section-bg-3-transparent: rgba(216, 224, 255, 0.85);
|
||||||
|
--section-bg-4-transparent: rgba(206, 216, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
/* Dark mode colors */
|
||||||
|
--bg-color: rgba(16, 20, 34, 0.9);
|
||||||
|
--text-color: #E0E6FF;
|
||||||
|
--section-bg-1: #2a2a2a;
|
||||||
|
--section-bg-2: #303030;
|
||||||
|
--section-bg-3: #383838;
|
||||||
|
--section-bg-4: #404040;
|
||||||
|
--header-footer-bg: rgba(18, 22, 38, 0.95);
|
||||||
|
--shadow-color: rgba(62, 84, 172, 0.3);
|
||||||
|
--accent-color: #64A9FF;
|
||||||
|
--accent-glow: rgba(100, 169, 255, 0.2);
|
||||||
|
--border-color: rgba(100, 169, 255, 0.2);
|
||||||
|
--section-bg-1-transparent: rgba(22, 26, 44, 0.85);
|
||||||
|
--section-bg-2-transparent: rgba(26, 30, 50, 0.85);
|
||||||
|
--section-bg-3-transparent: rgba(30, 34, 56, 0.85);
|
||||||
|
--section-bg-4-transparent: rgba(34, 38, 62, 0.85);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update existing color references */
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px); /* Safari support */
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background: var(--header-footer-bg);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
color: var(--text-color);
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-content img {
|
||||||
|
box-shadow: 0 4px 8px var(--shadow-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update section background colors */
|
||||||
|
#about {
|
||||||
|
background-color: var(--section-bg-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#services {
|
||||||
|
background-color: var(--section-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#portfolio {
|
||||||
|
background-color: var(--section-bg-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#contact {
|
||||||
|
background-color: var(--section-bg-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content container styles */
|
||||||
|
.content-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure markdown-generated paragraphs maintain styles */
|
||||||
|
.text-content p {
|
||||||
|
margin: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add background image styles */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update section backgrounds to be semi-transparent */
|
||||||
|
#about {
|
||||||
|
background-color: var(--section-bg-1-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#services {
|
||||||
|
background-color: var(--section-bg-2-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#portfolio {
|
||||||
|
background-color: var(--section-bg-3-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#contact {
|
||||||
|
background-color: var(--section-bg-4-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add subtle border glow to sections */
|
||||||
|
.snap-section {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px); /* Safari support */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update header and footer style */
|
||||||
|
header, footer {
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px); /* Safari support */
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update navigation links */
|
||||||
|
nav a {
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update blog post styling */
|
||||||
|
.blog-post {
|
||||||
|
background-color: var(--section-bg-1-transparent);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px); /* Safari support */
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 5px 25px var(--accent-glow);
|
||||||
|
background-color: var(--section-bg-2-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update blog tags */
|
||||||
|
.blog-tag {
|
||||||
|
background-color: var(--accent-glow);
|
||||||
|
border: 1px solid var(--accent-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-tag:hover {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 2px 8px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update scrollbar styling */
|
||||||
|
.blog-container::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--accent-color);
|
||||||
|
border: 3px solid var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add subtle animations */
|
||||||
|
.section-content {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update image styling */
|
||||||
|
.image-content img {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 4px 15px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update headings */
|
||||||
|
h1, h2 {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0;
|
||||||
|
width: 60px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add subtle transitions to all interactive elements */
|
||||||
|
a, button {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update section transitions */
|
||||||
|
.snap-section {
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
BLOG PAGE STYLING
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Blog-specific styles for the blog.html page. Includes:
|
||||||
|
- Blog container and grid layout
|
||||||
|
- Blog post card styling
|
||||||
|
- Blog post metadata and tags
|
||||||
|
- Content formatting
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Main blog container - scrollable area with padding for header/footer */
|
||||||
|
.blog-container {
|
||||||
|
padding-top: 200px;
|
||||||
|
padding-bottom: 100px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: calc(100vh - 220px); /* Account for header and footer */
|
||||||
|
overflow-y: auto; /* Enable scrolling */
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2.5rem;
|
||||||
|
padding-bottom: 2rem; /* Add some padding at the bottom */
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-title::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 80px;
|
||||||
|
height: 3px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-meta {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content {
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content p {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content ul,
|
||||||
|
.blog-post-content ol {
|
||||||
|
margin-left: 2rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content h1,
|
||||||
|
.blog-post-content h2,
|
||||||
|
.blog-post-content h3,
|
||||||
|
.blog-post-content h4 {
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content h2::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0;
|
||||||
|
width: 60px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-image {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 4px 15px var(--accent-glow);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-image:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 6px 20px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-link {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background-color: var(--section-bg-1-transparent);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-link:hover {
|
||||||
|
background-color: var(--section-bg-2-transparent);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-link.active {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
box-shadow: 0 0 15px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add scrollbar styling for better visibility */
|
||||||
|
.blog-container::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-container::-webkit-scrollbar-track {
|
||||||
|
background: var(--section-bg-1-transparent);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-container::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--accent-color);
|
||||||
|
border: 3px solid var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure content is visible on all browsers */
|
||||||
|
@supports (scrollbar-width: thin) {
|
||||||
|
.blog-container {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--header-footer-bg) var(--section-bg-1-transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user