init
This commit is contained in:
parent
e872c3e7be
commit
6aba9148b4
187
README.md
187
README.md
|
|
@ -1,5 +1,184 @@
|
|||
# PS_Site_Builder
|
||||
# Static Website Generator Template
|
||||
|
||||
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.
|
||||
|
||||
Static HTML5 website generator.
|
||||
|
||||
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