#!/usr/bin/env python3
"""
Static job board generator — fast, modern, and SEO-optimized.

This script is configured for: PartTimeJobs (omnijobs.eu.com)
Ad implementation reverted to direct injection (like 1p24-flight-old-003.py).
[REMOVED] JavaScript for resizing main content based on ad width is removed.

Usage:
    pip install orjson lxml beautifulsoup4
    python 1psimple27-add.py
"""
import os
import random
import re
import datetime
import logging
import shutil
import argparse
import sys
import concurrent.futures
from datetime import timezone, timedelta
from textwrap import wrap
import html
import textwrap # <-- ADDED FOR IMPROVED IMAGE GENERATION

# ---------- JSON helpers (robust across orjson / stdlib) ----------
try:
    import orjson as _oj
    _USE_OJ = True
    logging.info("Using 'orjson' for faster JSON processing.")
except ImportError:
    _oj = None
    _USE_OJ = False
    logging.info("Using standard 'json' library. For a speed boost, consider 'pip install orjson'.")
import json as _sj

def jloads(s):
    """Safely load a JSON string."""
    return _oj.loads(s) if _USE_OJ else _sj.loads(s)

def jdumps(obj, indent=False):
    """Safely dump an object to a JSON string."""
    if _USE_OJ:
        opt = _oj.OPT_INDENT_2 if indent else 0
        return _oj.dumps(obj, option=opt).decode('utf-8')
    return _sj.dumps(obj, ensure_ascii=False, indent=2 if indent else None)

# ---------- HTML parsing ----------
try:
    import lxml  # noqa: F401
    BS4_PARSER = 'lxml'
    logging.info("Using 'lxml' parser.")
except ImportError:
    BS4_PARSER = 'html.parser'
    logging.info("Using 'html.parser'. For speed: pip install lxml.")
from bs4 import BeautifulSoup, Comment

# ---------- Config ----------
CONFIG = {
    "NDJSON_PATH": r'./schema.ndjson',
    "OUTPUT_DIR": r'./jobs',
    "PAGE_LIMIT": 300000,
    "SITE_BASE_URL": 'https://parttime.veltracareer.us.com', # Kept base URL as requested
    "STATIC_SITE_HEADER_TITLE": "PartTimeJobs", # CHANGED
    "SITE_AUTHOR": "PartTimeJobs", # CHANGED
    "SITE_LOGO_URL": "https://parttime.veltracareer.us.com/logo.png", 
    "FALLBACK_SALARY_TEXT": "Negotiable", 
    "JOBS_LISTING_PAGE_SLUG": "jobs",
    "JOBS_LISTING_BREADCRUMB_NAME": "All Jobs",
    "JOB_LISTING_PAGE_TITLE": "New Part Time Jobs", # CHANGED
    "JOBS_PER_PAGE": 12,
    "DEFAULT_JOB_PAGE_IMAGE_URL": "https://parttime.veltracareer.us.com/logo.png", 
    "HOMEPAGE_TITLE": "PartTimeJobs | Find Flexible Part Time Jobs", # CHANGED
    "HOMEPAGE_META_DESCRIPTION": "Discover thousands of part-time job opportunities. Find flexible roles in your area and apply today.", # CHANGED
    "HOMEPAGE_OG_IMAGE_URL": "https://parttime.veltracareer.us.com/logo.png", 
    "HOMEPAGE_TWITTER_HANDLE": "@PartTimeJobs_official", # CHANGED
    "SOCIAL_LINKS": [ # CHANGED
        "https://www.facebook.com/PartTimeJobs_official",
        "https://www.linkedin.com/company/PartTimeJobs_official",
        "https://twitter.com/PartTimeJobs_official"
    ],
    "GENERATE_ONLY_JOB_POSTS": False,
    "APPLY_EMAIL": "contact@parttime.veltracareer.us.com", 

    # --- New Static Page Content ---
    "ABOUT_PAGE_TITLE": "About PartTimeJobs", # CHANGED
    "ABOUT_PAGE_META_DESCRIPTION": "Learn more about PartTimeJobs, our mission, and our dedication to the flexible work industry.", # CHANGED
    "ABOUT_PAGE_CONTENT": ( # CHANGED
        "<h2>Our Mission</h2>"
        "<p>Welcome to PartTimeJobs, your number one source for all things part-time work. "
        "We're dedicated to giving you the very best of job listings, with a focus on "
        "reliability, customer service, and uniqueness.</p>"
        "<p>Founded in 2024, PartTimeJobs has come a long way from its beginnings. "
        "We now serve customers all over the world and are thrilled to be a part of the "
        "dynamic wing of the flexible work industry.</p>"
        "<h2>Why Choose Us?</h2>"
        "<p>We specialize exclusively in part-time and flexible jobs, from retail and hospitality "
        "to remote gigs and freelance tasks. Our platform connects the world's top talent "
        "with the best flexible opportunities available.</p>"
    ),
    "CONTACT_PAGE_TITLE": "Contact Us | PartTimeJobs", # CHANGED
    "CONTACT_PAGE_META_DESCRIPTION": "Get in touch with the PartTimeJobs team. We're here to help with your questions, feedback, or support needs.", # CHANGED
    "CONTACT_SUPPORT_EMAIL": "support@parttime.veltracareer.us.com", 
    "CONTACT_PAGE_CONTENT": ( # Content is generic, no change needed
        "<p>We'd love to hear from you! Whether you have a question about a job listing, "
        "need help with your account, or want to provide feedback, our team is ready to assist.</p>"
        "<p><strong>For general inquiries, support, or feedback:</strong><br>"
        "Please email us at: <a href='mailto:{{ support_email }}'>{{ support_email }}</a></p>"
        "<p><strong>For employers and advertisers (from APPLY_EMAIL):</strong><br>"
        "Please reach out to our partnerships team at: <a href='mailto:{{ apply_email }}'>{{ apply_email }}</a></p>"
        "<p>We strive to respond to all inquiries within 24-48 business hours.</p>"
    ),

    # --- THEME COLORS FOR IMAGE GENERATION ---
    "THEME_COLORS": { # REVERTED
        "card_bg": "#ffffff",
        "primary_text": "#111827",      # Near-black for titles
        "secondary_text": "#4B5563",    # Gray for labels/pre-titles
        "accent": "#4F46E5",            # Indigo for dots/highlights
        "logo_bg": "#34D399",           # Green for logo box
        "logo_text": "#ffffff",
        "bar_bg": "#E5E7EB",            # Light gray for salary bar
        "bar_fill": "#4F46E5"           # Indigo for salary dot
    }
}

# ---------- CSS ----------
def minify_css(css_text):
    """Removes comments and unnecessary whitespace from a CSS string."""
    css_text = re.sub(r'/\*.*?\*/', '', css_text, flags=re.DOTALL)
    css_text = re.sub(r'\s+', ' ', css_text)
    css_text = re.sub(r'\s*([;:{},])\s*', r'\1', css_text)
    css_text = css_text.replace(';}', '}')
    return css_text.strip()

# [THEME] New 'Indigo' color theme.
# HSL (Hue, Saturation, Lightness) is used for easy themeing.
# By just changing the --primary-hue, we can change the entire site's color.
# Hue 220 is a nice, professional indigo/blue.
CSS_STYLES = minify_css(r"""
:root{
    --primary-hue: 220; # REVERTED
    --primary-saturation: 70%;
    --primary-lightness: 50%;
    --secondary-hue: 220;
    --secondary-saturation: 15%;
    --primary-color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
    --primary-color-dark: hsl(var(--primary-hue), var(--primary-saturation), 40%);
    --primary-color-light: hsl(var(--primary-hue), var(--primary-saturation), 97%);
    --text-color: #2d3748;
    --text-color-light: #718096;
    --border-color: hsl(var(--secondary-hue), var(--secondary-saturation), 90%);
    --background-color: #f7fafc;
    --white: #fff;
    /* [STYLE] Softened shadows for a more polished feel */
    --card-shadow: 0 4px 6px -1px rgba(0,0,0,.04), 0 2px 4px -2px rgba(0,0,0,.04);
    --card-shadow-hover: 0 10px 15px -3px rgba(0,0,0,.06), 0 4px 6px -4px rgba(0,0,0,.05);
}
/* [STYLE] Add smooth scrolling to the whole page */
html {
    scroll-behavior: smooth;
}
*,::before,::after{box-sizing:border-box;margin:0;padding:0}
body{
    font-family:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    background-color: var(--background-color);
    color: var(--text-color);
    line-height: 1.65;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
a{color:var(--primary-color);text-decoration:none;transition:color .2s ease}
a:hover{color:var(--primary-color-dark)}
.container{width:100%;max-width:80rem;margin-left:auto;margin-right:auto;padding-left:1.5rem;padding-right:1.5rem}
.nav-header{background:var(--white);border-bottom:1px solid var(--border-color);position:sticky;top:0;z-index:100;padding:1.25rem 0}
.nav-header .container{display:flex;justify-content:space-between;align-items:center}
.nav-header .logo-link{font-weight:800;font-size:1.6rem;color:var(--text-color);letter-spacing:-.5px}
.nav-header .logo-link:hover{color:var(--primary-color)}
.breadcrumb-nav ol{list-style:none;display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;font-size:.9rem;margin-bottom:1rem}
.breadcrumb-nav a{color:var(--text-color-light);transition:color .2s ease}
.breadcrumb-nav a:hover{color:var(--primary-color)}
.breadcrumb-nav li:not(:last-child)::after{content:'>';margin-left:.5rem;color:var(--border-color);font-weight:600}
.job-page-main{padding-top:1.5rem;padding-bottom:3rem}
.job-card{background:var(--white);border-radius:.75rem;padding:2.5rem;box-shadow:var(--card-shadow);border:1px solid var(--border-color);transition:box-shadow .2s ease}
.job-card .job-title{font-size:2.5rem;font-weight:800;color:var(--text-color);line-height:1.2;margin-bottom:.5rem}
.job-card .posted-date{color:var(--text-color-light);font-size:.9rem;margin-bottom:1.5rem}
.job-info-tags{display:flex;flex-wrap:wrap;gap:2rem;margin-bottom:2.5rem;padding-bottom:2.5rem;border-bottom:1px solid var(--border-color)}
.job-info-tags .tag{display:flex;align-items:center;gap:.6rem;font-size:.95rem;color:var(--text-color-light)}
.job-info-tags .tag svg{width:1.25em;height:1.25em;fill:currentColor}
.toc-container{background-color:var(--background-color);border:1px solid var(--border-color);border-left:4px solid var(--primary-color);padding:1.5rem;margin:2rem 0;border-radius:.5rem}
.toc-container h3{font-weight:700;margin-bottom:1rem;font-size:1.1rem;color:var(--text-color)}
.toc-container ul{list-style:none}
.toc-container li{margin-bottom:.5rem}
.toc-container a{font-weight:500;font-size:.95rem}
.job-content{line-height:1.8;color:var(--text-color);font-size:1.05rem}
/* 'scroll-margin-top' adds space above the heading when you jump to it,
   preventing it from being hidden under the sticky nav header. */
.job-content h2{font-size:1.6rem;font-weight:700;margin-top:3rem;margin-bottom:1.25rem;scroll-margin-top:100px;color:var(--text-color)}
.job-content p{margin-bottom:1.5rem}
.job-content ul{margin:1.5rem 0;padding-left:1.5rem}
.job-content li{margin-bottom:.75rem}
.related-jobs-card{background:var(--white);border-radius:.75rem;padding:2.5rem;margin-top:2.5rem;box-shadow:var(--card-shadow);border:1px solid var(--border-color)}
.related-jobs-card h2{font-size:1.5rem;font-weight:700;margin-bottom:1.5rem}
.related-job-item a{display:block;padding:1rem 1.25rem;border-radius:.5rem;transition:background-color .2s ease,color .2s ease;font-weight:600;border:1px solid transparent}
.related-job-item a:hover{background-color:var(--primary-color-light);color:var(--primary-color-dark);border-color:var(--border-color)}
.back-to-jobs{display:inline-block;text-align:center;margin-top:2.5rem;font-weight:600;padding:.8rem 2rem;border:1px solid var(--border-color);border-radius:999px;background:var(--white);transition:all .2s ease}
.back-to-jobs:hover{background-color:var(--primary-color);color:var(--white);border-color:var(--primary-color)}
.site-footer{background-color:var(--white);border-top:1px solid var(--border-color);margin-top:auto;padding:3rem 0;text-align:center;font-size:.9rem;color:var(--text-color-light)}
.hero-section{background:linear-gradient(45deg, hsl(var(--primary-hue), 50%, 50%), hsl(var(--primary-hue), 70%, 60%));color:var(--white);padding:6rem 1.5rem;text-align:center}
.hero-section h1{font-size:3.5rem;font-weight:800;margin-bottom:1rem}
.hero-section p{font-size:1.25rem;max-width:600px;margin:0 auto 2rem;opacity:.9}
.search-form{max-width:700px;margin:0 auto;display:flex;gap:.5rem;background:var(--white);padding:.5rem;border-radius:.75rem;box-shadow:0 10px 25px -5px rgba(0,0,0,.2)}
.search-form input{width:100%;border:none;padding:1rem;font-size:1rem;background:transparent;color:var(--text-color);outline:none}
.search-form button{background:var(--primary-color);color:var(--white);font-weight:600;padding:0 2rem;border:none;border-radius:.5rem;cursor:pointer;transition:background .2s ease}
.search-form button:hover{background:var(--primary-color-dark)}
.section{padding:5rem 1.5rem}
.section-title{text-align:center;font-size:2.5rem;font-weight:800;margin-bottom:3rem}
.features-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:2rem}
.feature-card{text-align:center}
.feature-card .icon{font-size:3rem;margin-bottom:1rem;color:var(--primary-color)}
.feature-card h3{font-size:1.25rem;font-weight:700;margin-bottom:.5rem}
.latest-jobs-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:2rem}
.job-listing-card{background:var(--white);border:1px solid var(--border-color);border-radius:.75rem;padding:1.5rem;box-shadow:var(--card-shadow);transition:all .2s ease}
.job-listing-card:hover{transform:translateY(-5px);box-shadow:var(--card-shadow-hover)}
.job-listing-card .company{font-size:.9rem;font-weight:600;color:var(--text-color-light);margin-bottom:.5rem}
.job-listing-card .title{font-size:1.2rem;font-weight:700;color:var(--text-color);margin-bottom:.75rem;display:block}
.job-listing-card .location,.job-listing-card .salary{display:flex;align-items:center;gap:.5rem;font-size:.9rem;color:var(--text-color-light);margin-bottom:.25rem}
.job-listing-card svg{width:1em;height:1em}
.pagination{display:flex;justify-content:center;gap:.5rem;margin-top:3rem}
.pagination a,.pagination span{padding:.75rem 1.25rem;border-radius:.5rem;font-weight:600}
.pagination a{background:var(--white);border:1px solid var(--border-color);color:var(--text-color)}
.pagination a:hover{background:var(--primary-color-light)}
.pagination .current{background:var(--primary-color);color:var(--white);border:1px solid var(--primary-color)}
.post-thumbnail{margin-bottom:2rem;border-radius:.75rem;overflow:hidden;box-shadow:var(--card-shadow-hover)}
.post-thumbnail img{display:block;width:100%;height:auto;aspect-ratio:1200 / 630;}

/* [AD REVERT] Removed .ad-container and .ad-label CSS rules */

/* [PERF] Add content-visibility to below-the-fold sections */
.related-jobs-card, .site-footer {
    content-visibility: auto;
    contain-intrinsic-size: 1px 600px;
}

/* Media queries for responsiveness */
@media(max-width:768px){
    .hero-section h1{font-size:2.5rem}
    .job-card .job-title{font-size:2rem}
    .job-info-tags{gap:1rem}
    .job-card{padding:1.5rem}
    .section{padding:3rem 1.5rem}
    /* [NEW] Add responsive headings for tablet */
    .section-title{font-size:2rem}
    .job-content h2{font-size:1.4rem}
}
@media(max-width:480px){
    .hero-section{padding:4rem 1rem}
    .hero-section h1{font-size:1.75rem} /* [MODIFIED] Was 2rem */
    /* Stack the search form on small screens */
    .search-form{flex-direction:column;padding:1rem}
    .search-form button{padding:1rem}
    .job-card{padding:1.5rem 1rem} /* [MODIFIED] Was 1.5rem .5rem */
    .container{padding-left:1rem;padding-right:1rem}
    /* [NEW] Add responsive headings for mobile */
    .section-title{font-size:1.75rem}
    .job-card .job-title{font-size:1.5rem}
    .job-content h2{font-size:1.25rem}
}
""")

# [PERF] Extract critical, above-the-fold CSS to be inlined
CRITICAL_CSS = minify_css(r"""
:root{
    --primary-hue: 220; # REVERTED
    --primary-saturation: 70%;
    --primary-lightness: 50%;
    --secondary-hue: 220;
    --secondary-saturation: 15%;
    --primary-color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
    --primary-color-dark: hsl(var(--primary-hue), var(--primary-saturation), 40%);
    --primary-color-light: hsl(var(--primary-hue), var(--primary-saturation), 97%);
    --text-color: #2d3748;
    --text-color-light: #718096;
    --border-color: hsl(var(--secondary-hue), var(--secondary-saturation), 90%);
    --background-color: #f7fafc;
    --white: #fff;
    --card-shadow: 0 4px 6px -1px rgba(0,0,0,.04), 0 2px 4px -2px rgba(0,0,0,.04);
    --card-shadow-hover: 0 10px 15px -3px rgba(0,0,0,.06), 0 4px 6px -4px rgba(0,0,0,.05);
}
html {
    scroll-behavior: smooth;
}
*,::before,::after{box-sizing:border-box;margin:0;padding:0}
body{
    font-family:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    background-color: var(--background-color);
    color: var(--text-color);
    line-height: 1.65;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
a{color:var(--primary-color);text-decoration:none;transition:color .2s ease}
a:hover{color:var(--primary-color-dark)}
.container{width:100%;max-width:80rem;margin-left:auto;margin-right:auto;padding-left:1.5rem;padding-right:1.5rem}
.nav-header{background:var(--white);border-bottom:1px solid var(--border-color);position:sticky;top:0;z-index:100;padding:1.25rem 0}
.nav-header .container{display:flex;justify-content:space-between;align-items:center}
.nav-header .logo-link{font-weight:800;font-size:1.6rem;color:var(--text-color);letter-spacing:-.5px}
.nav-header .logo-link:hover{color:var(--primary-color)}
""")

# ---------- Templates ----------
# [LEARNING] Comments added to HTML template to explain key tags.
HTML_TEMPLATE = r"""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ seo_title }}</title>

    <!-- Preconnect hints speed up connections to critical domains -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link rel="dns-prefetch" href="//www.highperformanceformat.com">

    <!-- Preloading critical CSS and fonts tells the browser to fetch them sooner -->
    <link rel="preload" as="style" href="{{ css_path }}">
    <link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" as="style">
    <!-- [PERF] Preload LCP image -->
    <link rel="preload" as="image" href="{{ page_image_url }}">

    <!-- SEO Meta Tags -->
    <meta name="description" content="{{ meta_description }}">
    <meta name="keywords" content="{{ meta_keywords }}">
    <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
    <meta name="author" content="{{ site_author }}">

    <!-- Open Graph (OG) tags for social media sharing (Facebook, LinkedIn, etc.) -->
    <meta property="og:title" content="{{ seo_title }}">
    <meta property="og:description" content="{{ meta_description }}">
    <meta property="og:type" content="article">
    <meta property="og:url" content="{{ job_url }}">
    <meta property="og:image" content="{{ og_image_url_abs }}">
    <meta property="article:published_time" content="{{ date_posted_iso }}">
    <meta property="article:modified_time" content="{{ date_posted_iso }}">
    <meta property="og:site_name" content="{{ site_name }}">
    <meta property="og:image:alt" content="Job Opportunity: {{ job_title }}">

    <!-- Twitter Card tags for Twitter sharing -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="{{ seo_title }}">
    <meta name="twitter:description" content="{{ meta_description }}">
    <meta name="twitter:url" content="{{ job_url }}">
    <meta name="twitter:image" content="{{ og_image_url_abs }}">

    <!-- Canonical URL tells Google the 'master' version of this page -->
    <link rel="canonical" href="{{ job_url }}" />
    <link rel="icon" type="image/x-icon" href="/favicon.ico">
    <!-- [PERF] Inline critical CSS -->
    <style>{{ critical_css }}</style>
    <link rel="stylesheet" href="{{ css_path }}">
    <!-- [PERF] Load fonts non-blockingly -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" media="print" onload="this.media='all'">
    <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"></noscript>

    <!-- Schema.org markup for Rich Snippets -->
    {{ job_posting_schema_script }}
    {{ breadcrumb_schema_script }}
    {{ organization_schema_script }}
    {{ article_schema_script }}
</head>
<body>
    <header class="nav-header">
        <nav class="container">
            <a href="{{ site_base_url }}/" class="logo-link" aria-label="{{ site_name }} - Home">
                {{ site_name }}
            </a>
        </nav>
    </header>

    <!-- [AD RESIZE REMOVED] Removed ID from main -->
    <main class="container job-page-main">
        <div class="max-w-4xl mx-auto">
            {{ breadcrumb_html }}

            <div class="post-thumbnail">
                <!-- [PERF] LCP image: Removed loading="lazy", added fetchpriority="high" and decoding="async" -->
                <img src="{{ page_image_url }}" alt="{{ image_alt_text }}" width="1200" height="630" fetchpriority="high" decoding="async">
            </div>

            <!-- [AD REVERT] Ad block 1 inserted directly -->
            <!-- [AD RESIZE REMOVED] Removed ID from ad div -->
            <div style="text-align: center; margin: 1.5rem 0;">
                {{ ad_section_1 }}
            </div>

            <article class="job-card">
                <h1 class="job-title">{{ job_title }}</h1>
                <p class="posted-date">Posted on {{ date_posted_display }}</p>

                <div class="job-info-tags">
                    <span class="tag">
                        <!-- Inline SVG for icons is fast and scalable -->
                        <svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 20l-4.95-5.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" /></svg>
                        {{ location }}
                    </span>
                    <span class="tag">
                        <svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.414-1.415L11 9.586V6z" clip-rule="evenodd" /></svg>
                        {{ employment_type_display }}
                    </span>
                    <span class="tag">
                        <svg viewBox="0 0 20 20" fill="currentColor"><path d="M8.433 7.418c.158-.103.346-.196.567-.267v1.698a2.5 2.5 0 00-1.134 0v-1.43zM9 3.5a2.5 2.5 0 00-2.5 2.5v.598c0 .447.16.86.434 1.182.273.322.614.54.996.65v1.698a2.5 2.5 0 001.134 0v-1.698c.382-.11.723-.328.996-.65.273-.322.434-.735.434-1.182V6A2.5 2.5 0 009 3.5z" /><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm-1-8a1 1 0 10-2 0v.5a1 1 0 001 1h1a1 1 0 100-2v-.5z" clip-rule="evenodd" /></svg>
                        {{ salary_display }}
                    </span>
                </div>

                <!-- Table of Contents is dynamically inserted here -->
                {{ table_of_contents }}
                <div class="job-content">
                    <!-- The main job description HTML is inserted here -->
                    {{ job_body_html }}
                </div>
            </article>

            <aside class="related-jobs-card">
                <h2>Related Opportunities</h2>
                <div class="related-jobs-list">
                    <!-- [LEARNING] This is a template loop. The build script
                         replaces this block with actual HTML list items. -->
                    {% for job in similar_jobs %}
                    <div class="related-job-item">
                        <a href="{{ job.url }}">{{ job.title }}</a>
                    </div>
                    {% endfor %}
                </div>
            </aside>

            <!-- [AD REVERT] Ad block 2 inserted directly -->
            <!-- [AD RESIZE REMOVED] Removed ID from ad div -->
             <div style="text-align: center; margin: 1.5rem 0;">
                {{ ad_section_2 }}
            </div>

            <div class="text-center">
                <a href="{{ jobs_listing_url }}" class="back-to-jobs">
                    &larr; View All Jobs
                </a>
            </div>
        </div>
    </main>

    <footer class="site-footer">
        <div class="container">
             {{ footer_links_html }}
             <p>&copy; <span id="currentYear"></span> {{ site_name }}. All Rights Reserved.</p>
        </div>
    </footer>

    <!-- [PERF] Defer non-critical JS -->
    <script defer>document.getElementById('currentYear').textContent = new Date().getFullYear();</script>
    <!-- Smooth scroll script is only added if a TOC exists -->
    {{ smooth_scroll_script }}
    <!-- [AD RESIZE REMOVED] Removed ad_resize_script placeholder -->
</body>
</html>
"""

# [LEARNING] This script enables smooth scrolling for on-page anchor links
# (like the Table of Contents) without needing a heavy library like jQuery.
# [PERF] Added defer to make script non-blocking.
SMOOTH_SCROLL_JS = """
<script defer>
document.addEventListener('DOMContentLoaded', function() {
    /* Find all <a> tags whose href starts with a '#' */
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function (e) {
            e.preventDefault(); /* Stop the browser's default "jump" behavior */
            const targetId = this.getAttribute('href');
            try {
                const targetElement = document.querySelector(targetId);
                if (targetElement) {
                    /* Use the browser's built-in smooth scroll feature */
                    targetElement.scrollIntoView({
                        behavior: 'smooth',
                        block: 'start' /* Align to the top of the element */
                    });
                }
            } catch (error) {
                /* Log if the target element doesn't exist */
                console.error("Could not scroll to element:", error);
            }
        });
    });
});
</script>
"""

# --- [AD RESIZE REMOVED] Removed AD_RESIZE_JS definition ---

# --- NEW: Static Page Template ---
# A simpler template for pages like "About Us" and "Contact Us".
STATIC_PAGE_TEMPLATE = r"""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ seo_title }}</title>
    <meta name="description" content="{{ meta_description }}">
    <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
    <link rel="canonical" href="{{ page_url }}" />
    
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link rel="preload" as="style" href="{{ css_path }}">
    <link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" as="style">
    
    <link rel="icon" type="image/x-icon" href="/favicon.ico">
    <!-- [PERF] Inline critical CSS -->
    <style>{{ critical_css }}</style>
    <link rel="stylesheet" href="{{ css_path }}">
    <!-- [PERF] Load fonts non-blockingly -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" media="print" onload="this.media='all'">
    <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"></noscript>
    
    <!-- Schema.org markup for WebPage -->
    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "WebPage",
      "url": "{{ page_url }}",
      "name": "{{ seo_title }}",
      "description": "{{ meta_description }}",
      "publisher": {
        "@type": "Organization",
        "name": "{{ site_name }}",
        "logo": {
          "@type": "ImageObject",
          "url": "{{ site_logo_url }}"
        }
      }
    }
    </script>
</head>
<body>
    <header class="nav-header">
        <nav class="container">
            <a href="{{ site_base_url }}/" class="logo-link" aria-label="{{ site_name }} - Home">
                {{ site_name }}
            </a>
        </nav>
    </header>

    <main class="container job-page-main">
        <div class="max-w-4xl mx-auto">
            <!-- Static content card -->
            <article class="job-card">
                <h1 class="job-title">{{ page_title }}</h1>
                <div class="job-content" style="margin-top: 2rem;">
                    <!-- The static page content is inserted here -->
                    {{ page_content_html }}
                </div>
            </article>
            
            <div class="text-center">
                <a href="{{ jobs_listing_url }}" class="back-to-jobs" style="margin-top: 2.5rem;">
                    &larr; View All Jobs
                </a>
            </div>
        </div>
    </main>

    <footer class="site-footer">
        <div class="container">
             {{ footer_links_html }}
             <p>&copy; <span id="currentYear"></span> {{ site_name }}. All Rights Reserved.</p>
        </div>
    </footer>
    <!-- [PERF] Defer non-critical JS -->
    <script defer>document.getElementById('currentYear').textContent = new Date().getFullYear();</script>
</body>
</html>
"""


# ---------- Logging ----------
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# ---------- Helpers ----------
def read_ad_file(filename):
    """Reads an ad file, returning empty string if not found."""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            return f.read()
    except FileNotFoundError:
        logging.warning(f"Ad file not found: {filename}. Skipping this ad section.")
        return ""

def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=50, fill='█'):
    """Displays a simple progress bar in the console."""
    if total == 0: total = 1
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
    filled_length = int(length * iteration // total)
    bar = fill * filled_length + '-' * (length - filled_length)
    sys.stdout.write(f'\r{prefix} |{bar}| {percent}% {suffix}')
    sys.stdout.flush()
    if iteration == total:
        sys.stdout.write('\n')
        sys.stdout.flush()

# [PERF] Add HTML minification function
def minify_html(html_text:str)->str:
    """
    Minifies HTML text, skipping JSON-LD and standard script blocks more reliably.
    """
    result_lines = []
    in_script = False
    for line in html_text.splitlines():
        original_line = line # Keep the original line content

        # --- Script Block Detection ---
        is_script_start = False
        if not in_script: # Only check for start if not already inside
            if "<script type=\"application/ld+json\">" in line or \
               ("<script" in line and "src=" not in line) or \
               "<script defer>" in line:
                is_script_start = True
                in_script = True

        is_script_end = False
        if in_script and "</script>" in line:
             is_script_end = True
             # Don't set in_script = False until *after* processing this line

        # --- Processing ---
        if in_script:
            # If we are inside a script block (including the start/end lines),
            # append the original line without modification.
            result_lines.append(original_line)
        else:
            # Only apply minification if we are outside a script block
            minified_line = re.sub(r'<!--(?!\[if).*?-->', '', line, flags=re.DOTALL) # drop comments
            minified_line = re.sub(r'>\s+<', '><', minified_line)           # collapse gaps between tags
            minified_line = minified_line.strip()                          # strip leading/trailing whitespace
            if minified_line: # Only add if there's content left after minifying
                result_lines.append(minified_line)

        # Update in_script status *after* processing the line containing </script>
        if is_script_end:
            in_script = False

    # Join the processed lines back together
    return '\n'.join(result_lines)

def slugify(title_str):
    """Generates a clean, URL-safe slug from a string, with a random suffix."""
    if not title_str: title_str = "untitled-job"
    s = title_str.lower().strip()
    s = re.sub(r'[\s_-]+', '-', s)  # Replace spaces/underscores with dashes
    s = re.sub(r'[^\w-]', '', s)    # Remove invalid characters
    s = re.sub(r'-+', '-', s).strip('-') # Remove duplicate/trailing dashes
    words = s.split('-')[:7] # Limit to 7 words
    slug_base = '-'.join(words)
    # Add a random suffix to prevent URL collisions
    rand_suffix = ''.join(random.choices('0123456789abcdef', k=6))
    return f"{slug_base}-{rand_suffix}" if slug_base else f"job-{rand_suffix}"

def get_field(data_dict, *field_keys, default_val=None):
    """
    Safely retrieves a nested value from a dictionary.
    Example: get_field(data, 'a', 'b', 'c', default_val='-')
    """
    current_level = data_dict
    for key in field_keys:
        if isinstance(current_level, dict) and key in current_level and current_level[key] is not None:
            current_level = current_level[key]
        else:
            return default_val
    return current_level

def format_location_display(job_location_schema):
    """Formats the schema 'jobLocation' into a readable string."""
    if not job_location_schema: return "Location not specified"
    if isinstance(job_location_schema, list):
        if not job_location_schema: return "Location not specified"
        loc = job_location_schema[0]
    else:
        loc = job_location_schema
    if get_field(loc, '@type') == "Place":
        address = get_field(loc, 'address', default_val={})
        city = get_field(address, 'addressLocality')
        region = get_field(address, 'addressRegion')
        if city and region: return f"{city}, {region}"
        if city: return city
        if region: return region
    return "Remote or Unspecified"

def format_employment_type_display(employment_type_data):
    """Formats the schema 'employmentType' into a readable string."""
    if not employment_type_data: return "Not Specified"
    if isinstance(employment_type_data, list):
        return ', '.join([str(et).replace('_', ' ').title() for et in employment_type_data])
    return str(employment_type_data).replace('_', ' ').title()

def format_display_date(date_str, output_format="%B %d, %Y"):
    """Formats an ISO date string into a human-readable format."""
    if not date_str: return "N/A"
    try:
        return datetime.datetime.fromisoformat(date_str.replace("Z", "+00:00")).strftime(output_format)
    except (ValueError, TypeError):
        return date_str # Fallback to original string if parsing fails

def format_salary_display(monetary_amount_schema, config):
    """Formats the schema 'baseSalary' into a readable string."""
    if not monetary_amount_schema or not isinstance(monetary_amount_schema, dict):
        return config['FALLBACK_SALARY_TEXT']
    currency_map = {"USD": "$", "EUR": "€", "GBP": "£"}
    currency = get_field(monetary_amount_schema, 'currency', default_val='USD')
    currency_symbol = currency_map.get(currency, currency + " ")
    value_node = get_field(monetary_amount_schema, 'value')
    if not value_node or not isinstance(value_node, dict):
        # Handle simple value (e.g., "value": "Negotiable")
        return str(get_field(monetary_amount_schema, 'value', default_val=config['FALLBACK_SALARY_TEXT']))
    min_val = get_field(value_node, 'minValue')
    max_val = get_field(value_node, 'maxValue')
    try:
        # Format as a range, e.g., "$50,000 - $70,000"
        if min_val and max_val:
            return f"{currency_symbol}{int(float(min_val)):,} - {currency_symbol}{int(float(max_val)):,}"
        if min_val:
            return f"From {currency_symbol}{int(float(min_val)):,}"
    except (ValueError, TypeError):
        pass # Fallback if values are not numeric
    return config['FALLBACK_SALARY_TEXT']

def parse_salary_for_chart(base_salary_schema):
    """Extracts min/max salary as floats for the SVG chart."""
    min_val, max_val = None, None
    try:
        value_node = get_field(base_salary_schema, 'value', default_val={})
        raw_min = get_field(value_node, 'minValue')
        raw_max = get_field(value_node, 'maxValue')
        if raw_min is not None:
            min_val = float(raw_min)
        if raw_max is not None:
            max_val = float(raw_max)
    except (ValueError, TypeError):
        return None, None
    return min_val, max_val

def process_job_body_and_create_toc(html_string):
    """
    Cleans the job description HTML and generates a Table of Contents
    based on <h2> tags.
    """
    if not html_string or not isinstance(html_string, str):
        return '<p>Job description not provided.</p>', ""
    
    # If the input is plain text with newlines, convert it to <p> tags.
    if '<' not in html_string and '\n' in html_string:
        html_string = "".join([f"<p>{p.strip()}</p>" for p in html_string.split('\n') if p.strip()])

    soup = BeautifulSoup(html_string, BS4_PARSER)

    # Unwrap 'html' and 'body' tags if they exist
    for tag in soup.find_all(['html', 'body']):
        tag.unwrap()

    # Sanitize: Remove tags that could break the layout or cause security issues
    for tag in soup.find_all(['script', 'style', 'iframe', 'link', 'meta']):
        tag.decompose()
    # Remove HTML comments
    for comment in soup.find_all(string=lambda text: isinstance(text, Comment)):
        comment.extract()

    headings = soup.find_all('h2')
    toc_html = ""
    # Only create a TOC if there are 2 or more H2s
    if len(headings) > 1:
        toc_items = []
        for h in headings:
            text = h.get_text(strip=True)
            if text:
                slug = slugify(text)
                h['id'] = slug # Add an 'id' attribute to the H2 for anchoring
                toc_items.append({'href': f'#{slug}', 'text': text})

        # Final check, only render if we have multiple valid items
        if len(toc_items) > 1:
            list_items = "".join([f'<li><a href="{item["href"]}">{item["text"]}</a></li>' for item in toc_items])
            toc_html = f'<nav class="toc-container"><h3>On this page</h3><ul>{list_items}</ul></nav>'

    return str(soup), toc_html

# Keywords builder
STOPWORDS = set("a an the in at to for of on and or with from by into over under within near as is are be being been it this that those these their our your you".split())
def build_meta_keywords(title, company):
    """Generates a comma-separated list of keywords from the title and company."""
    tokens = re.findall(r"[a-z0-9]+", f"{title} {company}".lower())
    cleaned = [t for t in tokens if t not in STOPWORDS and len(t) > 2]
    dedup = []
    for t in cleaned:
        if t not in dedup:
            dedup.append(t)
    # Ensure company name is prioritized
    if company and company.lower() not in dedup:
        dedup.insert(0, company.lower())
    return ", ".join(dedup[:12]) # Cap at 12 keywords

def update_job_schema(job_schema_input, job_slug, full_job_url, config):
    """Enriches the raw JobPosting schema with required and default values."""
    schema = job_schema_input.copy() if isinstance(job_schema_input, dict) else {}
    schema['@context'] = "https://schema.org/"
    schema['@type'] = "JobPosting"

    # Set 'datePosted' and 'validThrough' (e.g., 60 days from now)
    usa_timezone = timezone(timedelta(hours=-5)) # Example timezone
    now = datetime.datetime.now(usa_timezone)
    schema['datePosted'] = now.isoformat()
    schema['validThrough'] = (now + datetime.timedelta(days=200)).isoformat()

    # Add identifier and URL
    schema['identifier'] = {"@type": "PropertyValue", "name": get_field(schema, 'hiringOrganization', 'name', default_val=config['STATIC_SITE_HEADER_TITLE']), "value": job_slug}
    schema['url'] = full_job_url

    # Ensure hiringOrganization is well-formed
    org = get_field(schema, 'hiringOrganization', default_val={})
    schema['hiringOrganization'] = {
        "@type": "Organization",
        "name": get_field(org, 'name', default_val=config['STATIC_SITE_HEADER_TITLE']),
        "sameAs": get_field(org, 'sameAs', default_val=config['SITE_BASE_URL']),
        "logo": get_field(org, 'logo', default_val=config['SITE_LOGO_URL'])
    }

    # Ensure description exists
    schema['description'] = str(get_field(job_schema_input, 'description', default_val="<p>No job description provided.</p>"))
    return schema

def get_footer_links_html(config):
    """Generates the HTML for footer links."""
    footer_links_style = "display: flex; justify-content: center; gap: 1.5rem; margin-bottom: 1.5rem; font-weight: 500;"
    return f"""
    <div class="footer-links" style="{footer_links_style}">
        <a href="{config['SITE_BASE_URL']}/about.html">About Us</a>
        <a href="{config['SITE_BASE_URL']}/contact.html">Contact Us</a>
        <a href="{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/">All Jobs</a>
    </div>
    """

def render_html_template(template_str, data_dict):
    """
    A simple template renderer. Replaces '{{ key }}' and handles
    a basic '{% for job in similar_jobs %}' loop.
    """
    # Handle simple {{ key }} replacements
    for key, value in data_dict.items():
        if not isinstance(value, list):
            template_str = template_str.replace(f'{{{{ {key} }}}}', str(value))

    # Handle the specific 'similar_jobs' loop
    list_pattern = re.compile(r'{% for job in similar_jobs %}(.*?){% endfor %}', re.DOTALL)
    match = list_pattern.search(template_str)
    if match:
        loop_template = match.group(1)
        rendered_html = ""
        for job in data_dict.get('similar_jobs', []):
            item_html = loop_template.replace('{{ job.url }}', job.get('url', '#')).replace('{{ job.title }}', job.get('title', 'Untitled Job'))
            rendered_html += item_html
            
        # --- THE FIX IS HERE ---
        # Use a lambda (function) to return the string. 
        # This treats backslashes as literal text, preventing the "bad escape" error.
        template_str = list_pattern.sub(lambda m: rendered_html, template_str)

    return template_str

def write_stylesheet(output_dir):
    """Writes the minified CSS to style.css in the specified directory."""
    css_path = os.path.join(output_dir, "style.css")
    try:
        os.makedirs(output_dir, exist_ok=True)
        with open(css_path, 'w', encoding='utf-8') as f:
            f.write(CSS_STYLES)
        logging.info(f"Successfully created stylesheet: {css_path}")
    except IOError as e:
        logging.error(f"Error writing stylesheet {css_path}: {e}")

# --- START: NEW IMAGE GENERATION CODE ---

def wrap_text(text, char_width, max_lines=2):
    """
    Wraps text to a specified character width.
    Returns a list of lines, truncating with "..." if over max_lines.
    """
    # Use Python's built-in textwrap for robust wrapping
    lines = textwrap.wrap(
        text, 
        width=char_width, 
        break_long_words=True, 
        replace_whitespace=True
    )
    
    if not lines:
        return [""]
    
    if len(lines) > max_lines:
        # If wrapped text exceeds max lines, truncate the last line
        lines = lines[:max_lines]
        lines[-1] = lines[-1][: char_width - 3] + "..."
        
    return [html.escape(line) for line in lines]

def generate_job_thumbnail_svg(job_data, base_salary_schema):
    """
    Generates a professional, clear, and colorful 1200x630 SVG thumbnail.
    
    This new version specifically addresses text-overflow issues by:
    1. Adding the company name as a "pre-title".
    2. Intelligently wrapping the main job title onto multiple lines.
    3. Adjusting font sizes and positions for a clean, professional layout.
    4. Using the theme colors from the global CONFIG.
    5. Adding accessibility tags (<title>, <desc>).
    """
    # --- 1. Get Data & Define Colors ---
    # Use html.escape for all user-facing data
    company = html.escape(job_data.get('company_name', 'Company'))
    title = job_data.get('title', 'Job Title') # Escaping handled by wrap_text
    location = html.escape(job_data.get('location_str', 'Remote'))
    job_type = html.escape(job_data.get('employment_type', 'Full Time'))
    
    # Use the existing helper to format salary text
    salary_text = html.escape(format_salary_display(base_salary_schema, CONFIG))

    # Calculate salary percentage for the bar
    # We don't have min/max in this scope, so we'll use a default
    salary_perc = 70 # Default to a visually appealing 70%
    
    # Pull colors from global config
    colors = CONFIG.get("THEME_COLORS", {})
    C_CARD_BG = colors.get("card_bg", "#ffffff")
    C_PRIMARY = colors.get("primary_text", "#111827")
    C_SECONDARY = colors.get("secondary_text", "#4B5563")
    C_LOGO_BG = colors.get("logo_bg", "#34D399")
    C_LOGO_TEXT = colors.get("logo_text", "#ffffff")
    C_BAR_BG = colors.get("bar_bg", "#E5E7EB")
    C_BAR_FILL = colors.get("bar_fill", "#4F46E5") # Using accent for the dot

    # --- 2. Define SVG Structure & Styles (Targeting 1200x630) ---
    svg_width = 1200
    svg_height = 630
    font_family = "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
    
    # Accessibility: Create descriptive title and description
    svg_title_text = f"{title} - {company} Job Opening in {location}"
    # REVERTED Description Text
    svg_desc_text = (
        f"PartTimeJobs job post for a {job_type} {title} position at {company} in {location}. "
        f"The salary is {salary_text}. Find this and other Part-time jobs on PartTimeJobs."
    )

    # Adjusted layout for 1200x630
    svg_content = f'''<svg width="{svg_width}" height="{svg_height}" viewBox="0 0 {svg_width} {svg_height}" fill="none" xmlns="http://www.w3.org/2000/svg"
     aria-labelledby="svg-title svg-desc">
<title id="svg-title">{html.escape(svg_title_text)}</title>
<desc id="svg-desc">{html.escape(svg_desc_text)}</desc>

<style>
    /* Using Inter from the main page, with fallbacks */
    .title {{ font-size: 64px; font-weight: 800; fill: {C_PRIMARY}; font-family: {font_family}; }}
    .pre-title {{ font-size: 30px; font-weight: 500; fill: {C_SECONDARY}; font-family: {font_family}; }}
    .label {{ font-size: 20px; font-weight: 600; fill: {C_SECONDARY}; letter-spacing: 0.05em; text-transform: uppercase; font-family: {font_family}; }}
    .value {{ font-size: 30px; font-weight: 600; fill: {C_PRIMARY}; font-family: {font_family}; }}
    .logo-text {{ font-size: 32px; font-weight: 700; fill: {C_LOGO_TEXT}; font-family: {font_family}; }}
</style>

<!-- Background Card (dark, similar to your original image) -->
<rect width="{svg_width}" height="{svg_height}" rx="36" fill="#1F2937"/>

<!-- White content card -->
<rect x="40" y="40" width="1120" height="550" rx="24" fill="{C_CARD_BG}"/>

<!-- Logo Box (Top Right) -->
<rect x="850" y="80" width="270" height="68" rx="16" fill="{C_LOGO_BG}"/>
<text x="985" y="125" text-anchor="middle" class="logo-text">{html.escape(CONFIG.get("STATIC_SITE_HEADER_TITLE", "PartTimeJobs"))}</text>

<!-- Pre-Title (Company Name) -->
<text x="80" y="130" class="pre-title">{company} is hiring a</text>

<!-- Main Title (Wrapped) -->
<g transform="translate(80, 210)">
'''
    # --- 3. Text Wrapping Logic ---
    # Wrap title text to ~35 characters for the larger font
    title_lines = wrap_text(title, 35, max_lines=2)
    
    y_offset = 0
    line_height = 76 # 64px font + 12px leading
    
    for i, line in enumerate(title_lines):
        svg_content += f'<text x="0" y="{y_offset}" class="title">{line}</text>\n'
        y_offset += line_height

    svg_content += '</g>\n' # End of title group
    
    # --- 4. Bottom Row (Location, Type, Salary) ---
    
    # --- Location ---
    svg_content += '<text x="80" y="470" class="label">LOCATION</text>\n'
    svg_content += f'<text x="80" y="515" class="value">{location}</text>\n'
    
    # --- Job Type ---
    svg_content += '<text x="450" y="470" class="label">JOB TYPE</text>\n'
    svg_content += f'<text x="450" y="515" class="value">{job_type}</text>\n'

    # --- Salary Range ---
    svg_content += '<text x="820" y="470" class="label">SALARY RANGE</text>\n'
    svg_content += f'<text x="820" y="515" class="value">{salary_text}</text>\n'
    
    # --- Salary Bar ---
    svg_content += f'<rect x="820" y="545" width="300" height="10" rx="5" fill="{C_BAR_BG}"/>\n'
    # Position the dot
    bar_dot_x = 820 + (300 * (salary_perc / 100))
    # Clamp dot position to be within the bar
    bar_dot_x = max(825, min(bar_dot_x, 1115)) 
    svg_content += f'<circle cx="{bar_dot_x}" cy="550" r="10" fill="{C_BAR_FILL}" stroke="{C_CARD_BG}" stroke-width="2"/>\n'

    svg_content += '</svg>' # Close SVG tag
    
    # --- 5. Return SVG string ---
    return svg_content.strip()

# --- END: NEW IMAGE GENERATION CODE ---


def generate_single_job_page(task_args):
    """
    The main function for building a single job page.
    This is designed to be run in parallel by a ProcessPoolExecutor.
    """
    current_job_meta, config, job_postings_output_dir, html_template, smooth_scroll_js, ad_content_1, ad_content_2 = task_args
    try:
        # --- 1. Load and Process Data ---
        job_schema_input = jloads(current_job_meta['raw_json'])
        job_posting_schema = update_job_schema(job_schema_input, current_job_meta['slug'], current_job_meta['url'], config)

        job_title = current_job_meta['title']
        hiring_org_name = current_job_meta['company_name']

        # Create meta description from the first 150 chars of the description
        snippet = ' '.join(BeautifulSoup(get_field(job_posting_schema, 'description', default_val=''), BS4_PARSER).get_text(separator=' ').split())[:150]
        meta_description = f"Apply for the {job_title} position at {hiring_org_name}. {snippet}..."
        keywords = build_meta_keywords(job_title, hiring_org_name)

        # --- 2. Generate Thumbnail Image ---
        thumbnail_dir = os.path.join(os.path.dirname(job_postings_output_dir), "thumbnails")
        os.makedirs(thumbnail_dir, exist_ok=True)
        thumbnail_path = os.path.join(thumbnail_dir, f"{current_job_meta['slug']}.svg")
        base_salary_schema = get_field(job_posting_schema, 'baseSalary')
        thumbnail_data = {
            'title': job_title,
            'company_name': hiring_org_name,
            'location_str': current_job_meta['location_str'],
            'employment_type': current_job_meta['employment_type']
        }
        
        # --- [MODIFICATION] Check if thumbnail already exists before generating ---
        # If the file already exists, skip generating it to save time
        if not os.path.exists(thumbnail_path):
            # --- THIS IS THE KEY CHANGE: CALLING THE NEW FUNCTION ---
            svg_content = generate_job_thumbnail_svg(thumbnail_data, base_salary_schema)
            with open(thumbnail_path, 'w', encoding='utf-8') as f:
                f.write(svg_content)
        # --- End modification ---
        
        # [IMPROVED] Use absolute URLs for social/SEO, relative for on-page <img>
        og_image_url_abs = f"{config['SITE_BASE_URL']}/thumbnails/{current_job_meta['slug']}.svg"
        page_image_url = f"../thumbnails/{current_job_meta['slug']}.svg"

        # --- 3. Generate Schema.org JSON-LD ---
        breadcrumb_schema = {
            "@context": "https://schema.org/", "@type": "BreadcrumbList",
            "itemListElement": [
                {"@type": "ListItem", "position": 1, "name": config['STATIC_SITE_HEADER_TITLE'], "item": f"{config['SITE_BASE_URL']}/"},
                {"@type": "ListItem", "position": 2, "name": config['JOBS_LISTING_BREADCRUMB_NAME'], "item": f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/"},
                {"@type": "ListItem", "position": 3, "name": job_title, "item": current_job_meta['url']}
            ]
        }
        organization_schema = {
            "@context": "https://schema.org", "@type": "Organization",
            "name": config['STATIC_SITE_HEADER_TITLE'], "url": f"{config['SITE_BASE_URL']}/",
            "logo": config['SITE_LOGO_URL'], "sameAs": config.get('SOCIAL_LINKS', []),
            "contactPoint": {
                "@type": "ContactPoint",
                "contactType": "customer support",
                "email": config['APPLY_EMAIL']
            }
        }
        article_schema = {
            "@context": "https://schema.org", "@type": "Article",
            "url": current_job_meta['url'], # [FIX] Added "url"
            "mainEntityOfPage": {"@type": "WebPage", "@id": current_job_meta['url']},
            "headline": job_title,
            "image": og_image_url_abs,
            "datePublished": job_posting_schema.get('datePosted'),
            "dateModified": job_posting_schema.get('datePosted'),
            "author": {"@type": "Organization", "name": config['SITE_AUTHOR']},
            "publisher": {"@type": "Organization", "name": config['STATIC_SITE_HEADER_TITLE'], "logo": {"@type": "ImageObject", "url": config['SITE_LOGO_URL']}}
        }
        
        def create_schema_script(schema_data):
            """Wraps JSON-LD in a <script> tag."""
            return f'<script type="application/ld+json">{jdumps(schema_data, indent=True)}</script>'

        # --- 4. Generate HTML Components ---
        breadcrumb_html = '<nav aria-label="Breadcrumb" class="breadcrumb-nav"><ol>'
        for item in breadcrumb_schema["itemListElement"]:
            if item['position'] < 3:
                breadcrumb_html += f'<li><a href="{item["item"]}">{item["name"]}</a></li>'
            else:
                # The last item is the current page, so it's not a link
                breadcrumb_html += f'<li><span aria-current="page">{item["name"]}</span></li>'
        breadcrumb_html += '</ol></nav>'

        job_body_html, toc_html = process_job_body_and_create_toc(get_field(job_posting_schema, 'description'))

        # --- 5. Assemble and Render Template ---

        # --- NEW: Create dynamic, keyword-rich alt text ---
        salary_for_alt = format_salary_display(get_field(job_posting_schema, 'baseSalary'), CONFIG)
        # REVERTED Alt Text
        image_alt_text = (
            f"PartTimeJobs Job Posting: {job_title} at {hiring_org_name}. "
            f"This is a {current_job_meta['employment_type']} role in {current_job_meta['location_str']}. "
            f"Salary: {salary_for_alt}."
        )

        render_data = {
            "seo_title": f"{job_title} | {hiring_org_name}",
            "meta_description": meta_description, "meta_keywords": keywords,
            "site_author": config['SITE_AUTHOR'], "job_url": current_job_meta['url'],
            "date_posted_iso": job_posting_schema.get('datePosted'),
            "site_name": config['STATIC_SITE_HEADER_TITLE'],
            "og_image_url_abs": og_image_url_abs,
            "page_image_url": page_image_url,
            "image_alt_text": html.escape(image_alt_text), # <-- ADDED
            "job_title": job_title,
            "hiring_org_name": hiring_org_name, # Pass for alt text
            "site_base_url": config['SITE_BASE_URL'],
            "job_posting_schema_script": create_schema_script(job_posting_schema),
            "breadcrumb_schema_script": create_schema_script(breadcrumb_schema),
            "organization_schema_script": create_schema_script(organization_schema),
            "article_schema_script": create_schema_script(article_schema),
            "breadcrumb_html": breadcrumb_html,
            "date_posted_display": format_display_date(job_posting_schema.get('datePosted')),
            "location": current_job_meta['location_str'],
            "employment_type_display": current_job_meta['employment_type'],
            "salary_display": format_salary_display(get_field(job_posting_schema, 'baseSalary'), CONFIG),
            "table_of_contents": toc_html, "job_body_html": job_body_html,
            "similar_jobs": current_job_meta.get('similar_jobs', []),
            "jobs_listing_url": f"/{config['JOBS_LISTING_PAGE_SLUG'].strip('/')}/",
            "css_path": "../style.css", # Relative path from /jobs/slug.html
            "smooth_scroll_script": smooth_scroll_js if toc_html else "",
            # [AD REVERT] Pass raw ad content directly
            "ad_section_1": ad_content_1,
            "ad_section_2": ad_content_2,
            # [AD RESIZE REMOVED] Removed ad_resize_script from render data
            "critical_css": CRITICAL_CSS,
            "footer_links_html": get_footer_links_html(config), # <-- ADDED
        }
 
        final_html = render_html_template(html_template, render_data)

        # [PERF] Minify HTML before writing
        final_html = minify_html(final_html)

        # --- 6. Write File ---
        output_path = os.path.join(job_postings_output_dir, f"{current_job_meta['slug']}.html")
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(final_html)

        return True # Signal success
    except Exception as e:
        # Log error instead of crashing the whole build
        sys.stderr.write(f"Failed to generate page for '{current_job_meta.get('title', 'N/A')}'. Error: {e}\n")
        return False # Signal failure

def _website_schema(config):
    """Generates the Website schema for the homepage."""
    return {
        "@context": "https://schema.org",
        "@type": "WebSite",
        "url": config['SITE_BASE_URL'] + "/",
        "name": config['STATIC_SITE_HEADER_TITLE'],
        "potentialAction": {
            "@type": "SearchAction",
            "target": f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/?q={{search_term_string}}",
            "query-input": "required name=search_term_string"
        }
    }

def generate_main_homepage(config, latest_jobs):
    """Generates the main index.html for the site root."""
    logging.info("Generating main homepage (index.html)...")
    site_root_dir = os.path.dirname(config['OUTPUT_DIR'].rstrip('/\\')) or '.'
    homepage_path = os.path.join(site_root_dir, "index.html")

    latest_jobs_html = ""
    if latest_jobs:
        for job in latest_jobs:
            # URL is relative to the root
            job_url = f"{config['JOBS_LISTING_PAGE_SLUG']}/{job['slug']}.html"
            latest_jobs_html += f"""
            <a href="{job_url}" class="job-listing-card">
                <p class="company">{html.escape(job['company_name'])}</p>
                <h3 class="title">{html.escape(job['title'])}</h3>
                <p class="location"><svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 20l-4.95-5.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path></svg> {html.escape(job['location_str'])}</p>
            </a>"""
    else:
        latest_jobs_html = "<p style='text-align: center; grid-column: 1 / -1;'>No recent jobs to display.</p>"

    website_schema_script = f'<script type="application/ld+json">{jdumps(_website_schema(config), indent=True)}</script>'
    footer_links_html = get_footer_links_html(config) # <-- ADDED
 
    # [PERF] Add inlined critical CSS
    inlined_critical_css = f"<style>{CRITICAL_CSS}</style>"

    # REVERTED Homepage Content
    homepage_html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{html.escape(config['HOMEPAGE_TITLE'])}</title>
    <meta name="description" content="{html.escape(config['HOMEPAGE_META_DESCRIPTION'])}">
    <meta name="robots" content="index,follow,max-snippet:-1,max-image-preview:large,max-video-preview:-1">
    <link rel="canonical" href="{config['SITE_BASE_URL']}/" />

    <meta property="og:title" content="{html.escape(config['HOMEPAGE_TITLE'])}">
    <meta property="og:description" content="{html.escape(config['HOMEPAGE_META_DESCRIPTION'])}">
    <meta property="og:type" content="website">
    <meta property="og:url" content="{config['SITE_BASE_URL']}/">
    <meta property="og:image" content="{config['HOMEPAGE_OG_IMAGE_URL']}">
    <meta property="og:site_name" content="{html.escape(config['STATIC_SITE_HEADER_TITLE'])}">
    <meta name="twitter:card" content="summary_large_image">

    <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link rel="preload" as="style" href="style.css">
    <link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" as="style">
    <!-- Links to RSS and Sitemap for crawlers -->
    <link rel="alternate" type="application/rss+xml" title="{html.escape(config['STATIC_SITE_HEADER_TITLE'])} RSS" href="/feed.xml" />
    <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
    <link rel="stylesheet" href="style.css">
    <!-- [PERF] Load fonts non-blockingly -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" media="print" onload="this.media='all'">
    <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"></noscript>
    {website_schema_script}
    {inlined_critical_css}
</head>
<body>
    <header class="nav-header"><nav class="container"><a href="/" class="logo-link">{html.escape(config['STATIC_SITE_HEADER_TITLE'])}</a></nav></header>
    <main>
        <section class="hero-section">
            <h1>Find Your Career in the Clouds</h1>
            <p>The premier destination for flexible work. Search and apply for the best part-time jobs in your industry.</p>
            <form action="/{config['JOBS_LISTING_PAGE_SLUG']}/" class="search-form" method="get">
                <input type="search" name="q" placeholder="Search by job title, company, or keyword..." aria-label="Search jobs">
                <button type="submit">Search</button>
            </form>
        </section>
        <section class="section container">
            <h2 class="section-title">Latest Job Openings</h2>
            <div class="latest-jobs-grid">{latest_jobs_html}</div>
            <div style="text-align: center; margin-top: 3rem;">
                <a href="/{config['JOBS_LISTING_PAGE_SLUG']}/" class="back-to-jobs">View All Jobs</a>
            </div>
        </section>
        <section class="section" style="background-color: var(--primary-color-light);">
            <div class="container">
                <h2 class="section-title">Why PartTimeJobs?</h2>
                <div class="features-grid">
                    <div class="feature-card"><div class="icon">✈️</div><h3>Industry Focused</h3><p>We specialize exclusively in part-time jobs, from flexible evening work to weekend roles.</p></div>
                    <div class="feature-card"><div class="icon">🌍</div><h3>Access part-time job listings from leading local and national companies.</p></div>
                    <div class="feature-card"><div class="icon">✅</div><h3>Simple & Fast</h3><p>Our streamlined process makes finding and applying for part-time jobs easier than ever.</p></div>
                </div>
            </div>
        </section>
    </main>
    <footer class="site-footer"><div class="container">
        {footer_links_html}
        <p>&copy; <span id="currentYear"></span> {html.escape(config['STATIC_SITE_HEADER_TITLE'])}. All Rights Reserved.</p>
    </div></footer>
    <!-- [PERF] Defer non-critical JS -->
    <script defer>document.getElementById('currentYear').textContent = new Date().getFullYear();</script>
</body>
</html>"""
 
    # [PERF] Minify HTML before writing
    homepage_html = minify_html(homepage_html)
    with open(homepage_path, 'w', encoding='utf-8') as f:
        f.write(homepage_html)
 
def _collection_page_schema(page_title, page_url, jobs_this_page, site_name, config, page_num): # Added page_num
    """Generates the CollectionPage schema for a job listing page."""
    item_list = []
    for idx, j in enumerate(jobs_this_page, start=1):
        item_list.append({
            "@type": "ListItem",
            "position": idx,
            "url": j['url'],
            "name": j['title']
        })
    # REVERTED Description
    return {
        "@context": "https.schema.org",
        "@type": "CollectionPage",
        "name": page_title,
        "url": page_url,
        "description": f"Page {page_num} of the latest part-time job listings from leading local and national companies on PartTimeJobs.", # [SEO] Added description
        "breadcrumb": {
            "@type": "BreadcrumbList",
            "itemListElement": [
                {"@type":"ListItem","position":1,"name":site_name,"item": f"{config['SITE_BASE_URL']}/"},
                {"@type":"ListItem","position":2,"name":config['JOBS_LISTING_BREADCRUMB_NAME'],"item": f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/"}
            ]
        },
        "mainEntity": {
            "@type": "ItemList",
            "itemListElement": item_list
        }
    }

def _listing_page_html(page_num, total_pages, page_jobs, output_dir, config, prefetch_html, rel_prev, rel_next):
    """Generates the HTML for a single paginated listing page."""
    jobs_cards = []
    for job in page_jobs:
        job_url_rel = f"{job['slug']}.html"  # relative to /jobs/
        job_json = jloads(job['raw_json'])
        salary_display = format_salary_display(job_json.get('baseSalary'), CONFIG)
        jobs_cards.append(f"""
            <a href="{job_url_rel}" class="job-listing-card">
                <p class="company">{html.escape(job['company_name'])}</p>
                <h3 class="title">{html.escape(job['title'])}</h3>
                <p class="location"><svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 20l-4.95-5.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path></svg> {html.escape(job['location_str'])}</p>
                <p class="salary"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M8.433 7.418c.158-.103.346-.196.567-.267v1.698a2.5 2.5 0 00-1.134 0v-1.43zM9 3.5a2.5 2.5 0 00-2.5 2.5v.598c0 .447.16.86.434 1.182.273.322.614.54.996.65v1.698a2.5 2.5 0 001.134 0v-1.698c.382-.11.723-.328.996-.65.273-.322.434-.735.434-1.182V6A2.5 2.5 0 009 3.5z"></path><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm-1-8a1 1 0 10-2 0v.5a1 1 0 001 1h1a1 1 0 100-2v-.5z" clip-rule="evenodd"></path></svg> {html.escape(salary_display)}</p>
            </a>
        """)

    # --- Pagination UI Logic ---
    pagination_html = "<nav class='pagination' aria-label='Pagination'>"
    if rel_prev:
        pagination_html += f'<a rel="prev" href="{rel_prev}">&larr;</a>'

    page_numbers = []
    total_pages_int = int(total_pages)
    
    # Add first page
    if 1 not in page_numbers: page_numbers.append(1)
    # Add '...' if current page is far from start
    if page_num > 4: page_numbers.append('...')
    # Add pages around the current page
    for i in range(max(2, page_num - 1), min(total_pages_int - 1, page_num + 2)):
        if i not in page_numbers: page_numbers.append(i)
    # Add '...' if current page is far from end
    if page_num < total_pages_int - 3: page_numbers.append('...')
    # Add last page
    if total_pages_int > 1 and total_pages_int not in page_numbers: page_numbers.append(total_pages_int)

    # Render page number links
    for p in page_numbers:
        if p == '...':
            pagination_html += f'<span>{p}</span>'
        elif p == page_num:
            pagination_html += f'<span class="current">{p}</span>'
        else:
            page_link = "index.html" if p == 1 else f"page-{p}.html"
            pagination_html += f'<a href="{page_link}">{p}</a>'

    if rel_next:
        pagination_html += f'<a rel="next" href="{rel_next}">&rarr;</a>'
    pagination_html += "</nav>"
    # --- End Pagination UI ---

    # REVERTED Page Title
    page_title = f"{config['JOB_LISTING_PAGE_TITLE']} - Page {page_num} | PartTimeJobs"
    page_url = f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/" if page_num == 1 else f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/page-{page_num}.html"
    # Pass config to schema generator
    schema_script = f'<script type="application/ld+json">{jdumps(_collection_page_schema(page_title, page_url, page_jobs, config["STATIC_SITE_HEADER_TITLE"], config, page_num), indent=None)}</script>'

    link_rel_prev = f'<link rel="prev" href="{rel_prev}">' if rel_prev else ''
    link_rel_next = f'<link rel="next" href="{rel_next}">' if rel_next else ''
    footer_links_html = get_footer_links_html(config) # <-- ADDED
    inlined_critical_css = f"<style>{CRITICAL_CSS}</style>"

    html_out = f"""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{html.escape(page_title)}</title>
    <meta name="robots" content="index,follow,max-snippet:-1,max-image-preview:large,max-video-preview:-1">
    <link rel="canonical" href="{page_url}">
    <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link rel="preload" as="style" href="../style.css">
    <link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" as="style">
    <link rel="stylesheet" href="../style.css">
    <!-- [PERF] Load fonts non-blockingly -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" media="print" onload="this.media='all'">
    <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"></noscript>
    {prefetch_html}
    {link_rel_prev}
    {link_rel_next}
    {schema_script}
    {inlined_critical_css}
</head>
<body>
    <header class="nav-header"><nav class="container"><a href="/" class="logo-link">{html.escape(config['STATIC_SITE_HEADER_TITLE'])}</a></nav></header>
    <main class="section container">
        <h1 class="section-title">Job Listings</h1>
        <div class="latest-jobs-grid">
            {''.join(jobs_cards)}
        </div>
        {pagination_html}
    </main>
    <footer class="site-footer"><div class="container">
        {footer_links_html}
        <p>&copy; <span id="currentYear"></span> {html.escape(config['STATIC_SITE_HEADER_TITLE'])}. All Rights Reserved.</p>
    </div></footer>
    <!-- [PERF] Defer non-critical JS -->
    <script defer>document.getElementById('currentYear').textContent = new Date().getFullYear();</script>
</body>
</html>"""
    # [PERF] Minify HTML before returning
    return minify_html(html_out)

def generate_listing_pages_html(all_jobs, output_dir, config):
    """Generate paginated listing pages; ALWAYS writes jobs/index.html."""
    os.makedirs(output_dir, exist_ok=True)

    # [FIX] If no jobs, still create an empty index.html
    if not all_jobs:
        logging.warning("No jobs found in NDJSON. Creating an empty job listing page.")
        footer_links_html = get_footer_links_html(config)
        inlined_critical_css = f"<style>{CRITICAL_CSS}</style>"
        
        # REVERTED empty page title
        empty_page_title = f"No Current Job Openings | {config['STATIC_SITE_HEADER_TITLE']}"
        page_url = f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/"
        # Generate schema even for empty page
        schema_script = f'<script type="application/ld+json">{jdumps(_collection_page_schema(empty_page_title, page_url, [], config["STATIC_SITE_HEADER_TITLE"], config, 1), indent=None)}</script>'

        page_content = f"""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{html.escape(empty_page_title)}</title>
    <meta name="robots" content="noindex, follow">
    <link rel="canonical" href="{page_url}">
    <link rel="stylesheet" href="../style.css">
    {schema_script}
    {inlined_critical_css}
</head>
<body>
    <header class="nav-header"><nav class="container"><a href="/" class="logo-link">{html.escape(config['STATIC_SITE_HEADER_TITLE'])}</a></nav></header>
    <main class="section container">
        <h1 class="section-title">Job Listings</h1>
        <div style="text-align: center;"><p>No job openings are available at this time. Please check back later.</p></div>
    </main>
    <footer class="site-footer"><div class="container">
        {footer_links_html}
        <p>&copy; <span id="currentYear"></span> {html.escape(config['STATIC_SITE_HEADER_TITLE'])}. All Rights Reserved.</p>
    </div></footer>
    <!-- [PERF] Defer non-critical JS -->
    <script defer>document.getElementById('currentYear').textContent = new Date().getFullYear();</script>
</body>
</html>"""
        # [PERF] Minify HTML before writing
        page_content = minify_html(page_content)
        with open(os.path.join(output_dir, 'index.html'), 'w', encoding='utf-8') as f:
            f.write(page_content)
        return

    logging.info(f"Generating {len(all_jobs)} paginated job listing pages...")
    total_jobs = len(all_jobs)
    jobs_per_page = CONFIG['JOBS_PER_PAGE']
    total_pages = (total_jobs + jobs_per_page - 1) // jobs_per_page

    for page_num in range(1, total_pages + 1):
        start_index = (page_num - 1) * jobs_per_page
        end_index = start_index + jobs_per_page
        page_jobs = all_jobs[start_index:end_index]

        # Prefetch the *next* page to speed up navigation
        prefetch_html = ""
        if page_num < total_pages:
            next_page_url = f"page-{page_num + 1}.html"
            prefetch_html = f'<link rel="prefetch" href="{next_page_url}">'

        # Get relative URLs for prev/next links
        rel_prev = "index.html" if page_num == 2 else (f"page-{page_num - 1}.html" if page_num > 2 else "")
        rel_next = f"page-{page_num + 1}.html" if page_num < total_pages else ""

        page_content = _listing_page_html(page_num, total_pages, page_jobs, output_dir, config, prefetch_html, rel_prev, rel_next)

        # Page 1 is 'index.html', others are 'page-N.html'
        file_path = os.path.join(output_dir, 'index.html' if page_num == 1 else f'page-{page_num}.html')
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(page_content)

def load_and_prepare_data(config):
    """Loads all jobs from NDJSON, enriches, and sorts them."""
    logging.info("Stage 1: Loading and processing job data...")
    all_jobs_metadata = []
    
    # Check for file existence first
    if not os.path.exists(config['NDJSON_PATH']):
        logging.error(f"NDJSON file not found: {config['NDJSON_PATH']}")
        return None
        
    # Count lines safely
    try:
        with open(config['NDJSON_PATH'], 'rb') as f:
            total_lines = sum(1 for _ in f)
        if total_lines == 0:
            logging.warning(f"NDJSON file is empty: {config['NDJSON_PATH']}")
            return []
    except Exception as e:
        logging.error(f"Error counting lines in {config['NDJSON_PATH']}: {e}")
        return None

    # Process file line by line
    try:
        with open(config['NDJSON_PATH'], 'r', encoding='utf-8') as f:
            print_progress_bar(0, total_lines, prefix='Processing NDJSON:')
            for i, line in enumerate(f):
                try:
                    job_schema = jloads(line)
                    title = get_field(job_schema, 'title', default_val=f'job-{i+1}')
                    slug = slugify(title)
                    all_jobs_metadata.append({
                        'slug': slug,
                        'url': f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/{slug}.html",
                        'title': title,
                        'company_name': get_field(job_schema, 'hiringOrganization', 'name', default_val='N/A'),
                        'location_str': format_location_display(get_field(job_schema, 'jobLocation')),
                        'employment_type': format_employment_type_display(get_field(job_schema, 'employmentType')),
                        'date_posted_iso': get_field(job_schema, 'datePosted', default_val=datetime.date.today().isoformat()),
                        'raw_json': line # Keep raw JSON to pass to parallel workers
                    })
                except Exception:
                    logging.warning(f"Skipping malformed JSON on line {i+1}")
                if i % 100 == 0 or i == total_lines - 1:
                    print_progress_bar(i + 1, total_lines, prefix='Processing NDJSON:')
    except Exception as e:
        logging.error(f"Failed to read {config['NDJSON_PATH']}: {e}")
        return None

    logging.info(f"Successfully processed {len(all_jobs_metadata)} jobs.")
    logging.info("Stage 2: Sorting jobs and applying limits...")
    # Sort by date, newest first
    all_jobs_metadata.sort(key=lambda x: x.get('date_posted_iso', '1970-01-01'), reverse=True)

    # Apply page limit if set
    if config.get('PAGE_LIMIT') and len(all_jobs_metadata) > config['PAGE_LIMIT']:
        logging.info(f"Truncating jobs to the most recent {config['PAGE_LIMIT']}.")
        all_jobs_metadata = all_jobs_metadata[:config['PAGE_LIMIT']]

    return all_jobs_metadata

def precompute_similar_jobs(all_jobs_metadata):
    """
    Pre-calculates 3 random 'similar jobs' for each job.
    This is faster than doing it dynamically during page generation.
    """
    logging.info("Stage 3: Pre-computing related jobs for each page...")
    total_jobs = len(all_jobs_metadata)
    if total_jobs < 2:
        return # Not enough jobs to relate
    
    # [IMPROVED] Use clean relative URLs (slug.html)
    job_pool = [{'title': j['title'], 'url': f"{j['slug']}.html"} for j in all_jobs_metadata]

    print_progress_bar(0, total_jobs, prefix='Pre-computing:')
    for i in range(total_jobs):
        sample_size = min(3, max(0, len(job_pool) - 1))
        # Get all jobs *except* the current one
        choices = [job_pool[idx] for idx in range(total_jobs) if idx != i]
        all_jobs_metadata[i]['similar_jobs'] = random.sample(choices, sample_size) if choices and sample_size > 0 else []
        if i % 100 == 0 or i == total_jobs - 1:
            print_progress_bar(i + 1, total_jobs, prefix='Pre-computing:')

def _write_sitemap_and_robots(site_root, config, all_jobs):
    """Generates sitemap.xml and robots.txt."""
    logging.info("Writing sitemap.xml and robots.txt ...")
    urls = [
        f"{config['SITE_BASE_URL']}/",
        f"{config['SITE_BASE_URL']}/about.html",    # <-- ADDED
        f"{config['SITE_BASE_URL']}/contact.html",  # <-- ADDED
        f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/"
    ]
    
    # Add paginated listing pages
    if all_jobs:
        total_pages = (len(all_jobs) + CONFIG['JOBS_PER_PAGE'] - 1) // CONFIG['JOBS_PER_PAGE']
        for p in range(2, total_pages + 1):
            urls.append(f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/page-{p}.html")
        # Add all individual job pages
        for j in all_jobs:
            urls.append(f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/{j['slug']}.html")

    sitemap_items = "\n".join([f"  <url><loc>{html.escape(u)}</loc><changefreq>daily</changefreq><priority>0.8</priority></url>" for u in urls])
    sitemap_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{sitemap_items}
</urlset>
"""
    with open(os.path.join(site_root, "sitemap.xml"), "w", encoding="utf-8") as f:
        f.write(sitemap_xml)

    robots_txt = f"""User-agent: *
Allow: /
Sitemap: {config['SITE_BASE_URL']}/sitemap.xml
"""
    with open(os.path.join(site_root, "robots.txt"), "w", encoding='utf-8') as f:
        f.write(robots_txt)

def _write_rss(site_root, config, all_jobs):
    """Generates a feed.xml (RSS 2.0) file."""
    logging.info("Writing feed.xml (RSS) ...")
    now_rfc2822 = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:M:%S +0000')
    items = []
    # Add up to 50 most recent jobs
    for j in all_jobs[:3000]:
        link = f"{config['SITE_BASE_URL']}/{config['JOBS_LISTING_PAGE_SLUG']}/{j['slug']}.html"
        title = html.escape(j['title'])
        desc = html.escape(j.get('company_name', '') + " – " + j.get('location_str', ''))
        items.append(f"<item><title>{title}</title><link>{link}</link><guid>{link}</guid><pubDate>{now_rfc2822}</pubDate><description>{desc}</description></item>")
    
    feed = f"""<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>{html.escape(config['STATIC_SITE_HEADER_TITLE'])}</title>
    <link>{config['SITE_BASE_URL']}/</link>
    <description>{html.escape(config['HOMEPAGE_META_DESCRIPTION'])}</description>
    <lastBuildDate>{now_rfc2822}</lastBuildDate>
    <language>en-us</language>
    {''.join(items)}
  </channel>
</rss>
"""
    with open(os.path.join(site_root, "feed.xml"), "w", encoding="utf-8") as f:
        f.write(feed)


# --- NEW: Static Page Generators ---

def generate_about_page(site_root, config):
    """Generates the about.html page."""
    logging.info("Generating About Us page (about.html)...")
    page_url = f"{config['SITE_BASE_URL']}/about.html"
    seo_title = config.get("ABOUT_PAGE_TITLE", "About Us")
    meta_desc = config.get("ABOUT_PAGE_META_DESCRIPTION", "")
    
    render_data = {
        "seo_title": seo_title,
        "meta_description": meta_desc,
        "page_url": page_url,
        "css_path": "style.css", # Relative to site root
        "site_name": config['STATIC_SITE_HEADER_TITLE'],
        "site_base_url": config['SITE_BASE_URL'],
        "site_logo_url": config['SITE_LOGO_URL'],
        "page_title": "About Us",
        "page_content_html": config.get("ABOUT_PAGE_CONTENT", "<p>Content coming soon.</p>"),
        "jobs_listing_url": f"/{config['JOBS_LISTING_PAGE_SLUG'].strip('/')}/",
        "footer_links_html": get_footer_links_html(config),
        "critical_css": CRITICAL_CSS,
    }
    
    final_html = render_html_template(STATIC_PAGE_TEMPLATE, render_data)
    # [PERF] Minify HTML before writing
    final_html = minify_html(final_html)
    output_path = os.path.join(site_root, "about.html")
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(final_html)

def generate_contact_page(site_root, config):
    """Generates the contact.html page."""
    logging.info("Generating Contact Us page (contact.html)...")
    page_url = f"{config['SITE_BASE_URL']}/contact.html"
    seo_title = config.get("CONTACT_PAGE_TITLE", "Contact Us")
    meta_desc = config.get("CONTACT_PAGE_META_DESCRIPTION", "")
    
    # Simple replace for emails in contact content
    content = config.get("CONTACT_PAGE_CONTENT", "<p>Content coming soon.</p>")
    content = content.replace("{{ support_email }}", config.get("CONTACT_SUPPORT_EMAIL", "support@example.com"))
    content = content.replace("{{ apply_email }}", config.get("APPLY_EMAIL", "contact@example.com"))

    render_data = {
        "seo_title": seo_title,
        "meta_description": meta_desc,
        "page_url": page_url,
        "css_path": "style.css", # Relative to site root
        "site_name": config['STATIC_SITE_HEADER_TITLE'],
        "site_base_url": config['SITE_BASE_URL'],
        "site_logo_url": config['SITE_LOGO_URL'],
        "page_title": "Contact Us",
        "page_content_html": content,
        "jobs_listing_url": f"/{config['JOBS_LISTING_PAGE_SLUG'].strip('/')}/",
        "footer_links_html": get_footer_links_html(config),
        "critical_css": CRITICAL_CSS,
    }
    
    final_html = render_html_template(STATIC_PAGE_TEMPLATE, render_data)
    # [PERF] Minify HTML before writing
    final_html = minify_html(final_html)
    output_path = os.path.join(site_root, "contact.html")
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(final_html)


def main():
    """Main execution function."""
    parser = argparse.ArgumentParser(description="Generate a static job board website.")
    parser.add_argument("--only-posts", action="store_true", help="Only generate individual job posting files.")
    args = parser.parse_args()

    output_dir = CONFIG['OUTPUT_DIR']
    # site_root is the directory *containing* the output dir, e.g., '.'
    site_root = os.path.dirname(output_dir.rstrip('/\\')) or '.'
    thumbnail_dir = os.path.join(site_root, "thumbnails")

    # --- Directory and File Cleanup Logic ---
    # 1. Always ensure output directories exist.
    os.makedirs(output_dir, exist_ok=True)
    os.makedirs(thumbnail_dir, exist_ok=True)

    # 2. On a full build (--only-posts is FALSE), log the mode.
    #    [USER REQUEST] All file/directory cleaning has been disabled
    #    to preserve old files on every run.
    if not args.only_posts:
        logging.info("Full build mode: (File cleaning disabled, will only add/overwrite files)")
        
        # --- START: MODIFIED CLEANUP CODE (ALL DELETION DISABLED) ---
        # logging.info("Full build mode: Cleaning ancillary files and thumbnails...")
        # ... (cleaning code remains commented out as per previous request) ...
        # --- END: MODIFIED CLEANUP CODE ---

    # --- End Cleanup Logic ---

    # Ensure stylesheet exists at site root for all builds
    write_stylesheet(site_root)

    # Read ad files
    ads = (read_ad_file('ads1.txt'), read_ad_file('ads2.txt'))
    
    # --- Load Data ---
    # [FIX] Changed 'config' to 'CONFIG' to fix NameError
    all_jobs = load_and_prepare_data(CONFIG)

    # Proceed even if NDJSON is missing to ensure site structure
    if all_jobs is None:
        logging.warning("Proceeding with zero jobs because NDJSON is missing/unreadable.")
        all_jobs = []

    if all_jobs:
        precompute_similar_jobs(all_jobs)

    # --- Generate Job Detail Pages (All Build Types) ---
    if all_jobs:
        logging.info(f"Stage 4: Generating {len(all_jobs)} job pages in parallel...")
        # Create a list of arguments for each parallel task
        tasks = [(job, CONFIG, output_dir, HTML_TEMPLATE, SMOOTH_SCROLL_JS, *ads) for job in all_jobs]
        success_count = 0
        try:
            # Use ProcessPoolExecutor for CPU-bound tasks (HTML/SVG generation)
            with concurrent.futures.ProcessPoolExecutor() as executor:
                for i, result in enumerate(executor.map(generate_single_job_page, tasks)):
                    if result: success_count += 1
                    print_progress_bar(i + 1, len(tasks), prefix='Generating Pages:')
        except Exception as e:
            # Fallback to ThreadPoolExecutor if ProcessPool fails (e.g., in some envs)
            logging.warning(f"ProcessPool failed ({e}); falling back to ThreadPoolExecutor.")
            with concurrent.futures.ThreadPoolExecutor() as executor:
                for i, result in enumerate(executor.map(generate_single_job_page, tasks)):
                    if result: success_count += 1
                    print_progress_bar(i + 1, len(tasks), prefix='Generating Pages:')
        logging.info(f"Successfully generated {success_count} job pages.")
    else:
        logging.info("No new job pages to generate.")

    # --- Generate Ancillary Files (Full Build Only) ---
    if not args.only_posts:
        logging.info("Generating ancillary files and listing pages...")
        generate_main_homepage(CONFIG, all_jobs[:12] if all_jobs else [])
        generate_listing_pages_html(all_jobs, output_dir, CONFIG)
        generate_about_page(site_root, CONFIG)
        generate_contact_page(site_root, CONFIG)
        _write_sitemap_and_robots(site_root, CONFIG, all_jobs)
        _write_rss(site_root, CONFIG, all_jobs)
    else:
        logging.info("--only-posts mode: Skipping generation of ancillary files.")

    logging.info("Website generation complete.")

if __name__ == '__main__':
    main()

