Update index.php

D David Veksler · 1 year ago 58f1ed08fd62c4faef626be3fd86b2e5c8effb43
Parent: f2b3ee2f1

1 file changed +289 −327

Diff

diff --git a/index.php b/index.php
index 6a4dce7..e83cc5d 100644
--- a/index.php
+++ b/index.php
@@ -7,13 +7,12 @@ $excludedItems = [
     '.',
     '..',
     'index.php', // Exclude this script itself
-    // Add any other specific files or directories to exclude by name:
-    // 'old_gallery.html',
-    'images',    // Example: Exclude images directory if it's in the root
+    'images',    // Exclude images directory if it's in the root
     'LICENSE',
     'README.md',
-    'PROMPT.txt',    
-    // Add any other files/directories to exclude by name
+    'PROMPT.txt', // Assuming this was part of your dev files
+    // Add any other specific files or directories to exclude by name:
+    // 'old_gallery.html',    
 ];
 
 $cheatsheetDir = '.'; // Current directory where cheatsheet HTML files are located
@@ -21,47 +20,44 @@ $cheatsheetDir = '.'; // Current directory where cheatsheet HTML files are locat
 // --- Base URL Calculation ---
 $scheme = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
 $host = $_SERVER['HTTP_HOST'];
-// Calculate script directory, ensuring it's correct even if index.php is in a subdirectory
-$scriptName = $_SERVER['SCRIPT_NAME']; // e.g., /index.php or /path/to/index.php
+$scriptName = $_SERVER['SCRIPT_NAME'];
 $scriptDir = dirname($scriptName);
-// If script is in root, dirname might return '\' or '.', normalize to empty string for base URL
 $scriptDir = ($scriptDir === '.' || $scriptDir === DIRECTORY_SEPARATOR) ? '' : $scriptDir;
 $baseUrl = rtrim($scheme . '://' . $host . $scriptDir, '/') . '/';
 
 
 // --- Helper Function: Extract Metadata ---
 function extractMetadata(string $filepath): array {
-    global $baseUrl, $scheme, $host; // Access global vars needed for URL construction
+    global $baseUrl, $scheme, $host; // Access global vars
 
     $filename = basename($filepath);
+    $defaultTitle = preg_replace('/\.html$/i', '', $filename); // Remove .html extension
+    $defaultTitle = ucwords(str_replace(['-', '_'], ' ', $defaultTitle)); // Capitalize and replace hyphens/underscores
+
     $metadata = [
-        'title' => pathinfo($filename, PATHINFO_FILENAME), // Default title from filename
-        'description' => 'Explore this cheatsheet to learn more.', // Default description
-        'image' => null, // Default image
-        'url' => $baseUrl . $filename, // Construct full URL for the cheatsheet page
+        'title' => $defaultTitle,
+        'description' => 'Explore this ' . htmlspecialchars($defaultTitle) . ' cheatsheet for a concise overview of key concepts.',
+        'image' => null,
+        'url' => $baseUrl . $filename,
         'error' => null
     ];
 
-    // Suppress warnings for potentially malformed HTML and file access issues
     $content = @file_get_contents($filepath);
     if ($content === false) {
         $metadata['error'] = "Could not read file: " . htmlspecialchars($filename);
         return $metadata;
     }
 
-    // Use DOMDocument for robust parsing
     $dom = new DOMDocument();
-    // Suppress warnings during loading of potentially invalid HTML
-    @$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content, LIBXML_NOERROR | LIBXML_NOWARNING); // Added XML encoding hint
+    // Suppress warnings during loading of potentially invalid HTML, and add XML encoding hint for better parsing
+    @$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content, LIBXML_NOERROR | LIBXML_NOWARNING);
     $xpath = new DOMXPath($dom);
 
-    // Extract Title
     $titleNode = $xpath->query('//title')->item(0);
     if ($titleNode) {
         $metadata['title'] = trim($titleNode->textContent);
     }
 
-    // Extract Meta Description (prefer name="description", fallback to og:description)
     $descNode = $xpath->query('//meta[@name="description"]/@content')->item(0);
     if ($descNode) {
         $metadata['description'] = trim($descNode->nodeValue);
@@ -72,46 +68,20 @@ function extractMetadata(string $filepath): array {
         }
     }
 
-    // Extract Open Graph Image and ensure it's an absolute URL
     $imgNode = $xpath->query('//meta[@property="og:image"]/@content')->item(0);
     if ($imgNode) {
         $imageUrl = trim($imgNode->nodeValue);
-        if (preg_match('/^https?:\/\//i', $imageUrl)) { // Already absolute
+        if (preg_match('/^https?:\/\//i', $imageUrl)) { // Absolute URL
             $metadata['image'] = $imageUrl;
-        } elseif (str_starts_with($imageUrl, '/')) { // Root-relative URL (e.g., /images/foo.png)
+        } elseif (str_starts_with($imageUrl, '/')) { // Root-relative URL
             $metadata['image'] = $scheme . '://' . $host . $imageUrl;
-        } else { // Document-relative URL (e.g., images/foo.png or ../images/foo.png)
-                 // This path is relative to the cheatsheet file itself.
-                 // Base URL of the cheatsheet: dirname($metadata['url'])
+        } else { // Relative URL to the cheatsheet's path
             $baseCheatsheetWebPath = dirname($metadata['url']);
             $resolvedImageUrl = rtrim($baseCheatsheetWebPath, '/') . '/' . $imageUrl;
 
-            // Basic normalization: resolve . and .. segments
-            $parts = explode('/', $resolvedImageUrl);
-            $absolutes = [];
-            foreach ($parts as $part) {
-                if ('.' == $part) continue;
-                if ('..' == $part) {
-                    array_pop($absolutes);
-                } else {
-                    $absolutes[] = $part;
-                }
-            }
-            // Reconstruct, ensuring "scheme://" is not duplicated if it was part of $baseCheatsheetWebPath
-            $finalPath = implode('/', $absolutes);
-            if (strpos($finalPath, $scheme . '://') === 0) {
-                 $metadata['image'] = $finalPath;
-            } else {
-                 // This case should ideally not happen if $baseCheatsheetWebPath was correct.
-                 // Fallback or reconstruct carefully. For now, assume $scheme needed.
-                 // This might occur if $baseCheatsheetWebPath was just a path without scheme/host.
-                 // However, $metadata['url'] (and thus $baseCheatsheetWebPath) should be absolute.
-                 $urlParts = parse_url($metadata['url']); // $metadata['url'] is the cheatsheet's absolute URL
-                 $metadata['image'] = $urlParts['scheme'] . '://' . $urlParts['host'] . (isset($urlParts['port']) ? ':' . $urlParts['port'] : '') . '/' . trim(implode('/', array_slice(explode('/', $finalPath),3)), '/'); // Rebuild from scheme/host + normalized path
-            }
-             // A simple way to re-assemble if parse_url was used on $resolvedImageUrl
-             $parsedResolved = parse_url($resolvedImageUrl);
-             if ($parsedResolved && isset($parsedResolved['scheme']) && isset($parsedResolved['host']) && isset($parsedResolved['path'])) {
+            // Normalize path (e.g., /path/../image.png to /image.png)
+            $parsedResolved = parse_url($resolvedImageUrl);
+            if ($parsedResolved && isset($parsedResolved['scheme']) && isset($parsedResolved['host']) && isset($parsedResolved['path'])) {
                 $path = $parsedResolved['path'];
                 $newPathParts = [];
                 $pathSegments = explode('/', $path);
@@ -123,20 +93,24 @@ function extractMetadata(string $filepath): array {
                         $newPathParts[] = $segment;
                     }
                 }
-                $metadata['image'] = $parsedResolved['scheme'] . '://' . $parsedResolved['host'] . (isset($parsedResolved['port']) ? ':' . $parsedResolved['port'] : '') . '/' . implode('/', $newPathParts);
-             } else {
-                // Fallback if parse_url failed or basic reconstruction not possible.
-                // This might indicate an issue with $resolvedImageUrl formation.
-                // For safety, could log an error here.
-                $metadata['image'] = $resolvedImageUrl; // Use as is, hoping browser resolves
-             }
+                $finalPath = '/' . implode('/', $newPathParts);
+                 // Correct for cases like /../ resolving to root
+                if (empty($newPathParts) && str_starts_with($path, '/')) {
+                    $finalPath = '/';
+                }
+
+                $metadata['image'] = $parsedResolved['scheme'] . '://' . $parsedResolved['host'] .
+                                     (isset($parsedResolved['port']) ? ':' . $parsedResolved['port'] : '') .
+                                     $finalPath;
+            } else {
+                $metadata['image'] = $resolvedImageUrl; // Fallback if parsing or normalization fails
+            }
         }
     }
 
-
-     // Limit description length for display consistency
-    if (mb_strlen($metadata['description']) > 150) { // Use mb_strlen for multi-byte safety
-        $metadata['description'] = mb_substr($metadata['description'], 0, 147) . '...'; // Use mb_substr
+    // Ensure description isn't excessively long for the card display
+    if (mb_strlen($metadata['description']) > 150) {
+        $metadata['description'] = mb_substr($metadata['description'], 0, 147) . '...';
     }
 
     return $metadata;
@@ -153,8 +127,7 @@ try {
     }
 
     foreach ($files as $file) {
-        $filePath = $cheatsheetDir . '/' . $file;
-        // Skip excluded items, non-files, non-readable files, and non-HTML files
+        $filePath = rtrim($cheatsheetDir, '/') . '/' . $file;
         if (in_array($file, $excludedItems, true) || !is_file($filePath) || !is_readable($filePath) || !str_ends_with(strtolower($file), '.html')) {
             continue;
         }
@@ -165,14 +138,12 @@ try {
             $cheatsheets[] = $meta;
         }
     }
-    // Optional: Sort cheatsheets alphabetically by title for consistent initial order
+    // Sort cheatsheets alphabetically by title
     usort($cheatsheets, fn($a, $b) => strcasecmp($a['title'], $b['title']));
 
 } catch (Exception $e) {
     $errors[] = "An error occurred: " . $e->getMessage();
 }
-
-// --- HTML Output ---
 ?>
 <!DOCTYPE html>
 <html lang="en">
@@ -182,10 +153,10 @@ try {
 
     <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>">
     
-    <!-- === Refined SEO & Portfolio Metadata === -->
+    <!-- === SEO & Portfolio Metadata === -->
     <title>David Veksler's Cheatsheet Portfolio | Custom Cheatsheet Design Services</title>
     <meta name="description" content="Explore a portfolio of expertly crafted cheatsheets by David Veksler covering tech, philosophy, AI safety, and more. Hire David to create custom, visually engaging reference guides tailored to your needs.">
-    <meta name="keywords" content="cheatsheets, portfolio, custom cheatsheets, information design, technical writing, data visualization, reference guide, programming, tech, philosophy, ai safety, bitcoin, leadership, david veksler, hire, freelance, consultant">
+    <meta name="keywords" content="cheatsheets, portfolio, custom cheatsheets, information design, technical writing, data visualization, reference guide, programming, tech, philosophy, ai safety, bitcoin, leadership, david veksler, hire, freelance, consultant, interactive, learning, development, web design">
     <meta name="author" content="David Veksler">
     <link rel="canonical" href="<?php echo htmlspecialchars($baseUrl); ?>">
 
@@ -195,7 +166,7 @@ try {
     <meta property="og:type" content="website">
     <meta property="og:url" content="<?php echo htmlspecialchars($baseUrl); ?>">
     <meta property="og:image" content="<?php echo htmlspecialchars(rtrim($baseUrl, '/')); ?>/images/cheatsheets-og-portfolio.png">
-    <meta property="og:image:alt" content="David Veksler Cheatsheet Portfolio Showcase">
+    <meta property="og:image:alt" content="A collage showcasing various cheatsheets from David Veksler's portfolio against a clean background.">
     <meta property="og:site_name" content="David Veksler's Cheatsheets">
     <meta property="og:locale" content="en_US">
 
@@ -205,150 +176,133 @@ try {
     <meta name="twitter:description" content="Showcasing expertise in creating clear, interactive cheatsheets. Hire David Veksler for custom reference guide design.">
     <meta name="twitter:url" content="<?php echo htmlspecialchars($baseUrl); ?>">
     <meta name="twitter:image" content="<?php echo htmlspecialchars(rtrim($baseUrl, '/')); ?>/images/cheatsheets-og-portfolio.png">
-    <meta name="twitter:image:alt" content="David Veksler Cheatsheet Portfolio Showcase">
-    <meta name="twitter:creator" content="@DavidVeksler">
-
+    <meta name="twitter:image:alt" content="A collage showcasing various cheatsheets from David Veksler's portfolio.">
+    <!-- <meta name="twitter:creator" content="@YourTwitterHandle"> --> <!-- Uncomment if David Veksler has a relevant Twitter handle -->
+
+    <!-- Schema.org Markup for CollectionPage -->
+    <script type="application/ld+json">
+    {
+      "@context": "https://schema.org",
+      "@type": "CollectionPage",
+      "name": "David Veksler's Cheatsheet Portfolio | Custom Cheatsheet Design Services",
+      "description": "A curated collection of cheatsheets designed by David Veksler, showcasing expertise in information design and technical communication. Available for custom cheatsheet creation projects.",
+      "url": "<?php echo htmlspecialchars($baseUrl); ?>",
+      "author": {
+        "@type": "Person",
+        "name": "David Veksler",
+        "url": "https://www.linkedin.com/in/davidveksler/"
+      },
+      "publisher": {
+        "@type": "Person", 
+        "name": "David Veksler",
+        "url": "https://www.linkedin.com/in/davidveksler/"
+      },
+      "mainEntity": {
+        "@type": "ItemList",
+        "itemListElement": [
+          <?php foreach ($cheatsheets as $index => $sheet): ?>
+          {
+            "@type": "ListItem",
+            "position": <?php echo $index + 1; ?>,
+            "item": {
+              "@type": "CreativeWork",
+              "name": "<?php echo htmlspecialchars($sheet['title']); ?>",
+              "url": "<?php echo htmlspecialchars($sheet['url']); ?>",
+              "description": "<?php echo htmlspecialchars($sheet['description']); ?>"
+              <?php if (!empty($sheet['image'])): ?>
+              ,"image": "<?php echo htmlspecialchars($sheet['image']); ?>"
+              <?php endif; ?>
+            }
+          }<?php if ($index < count($cheatsheets) - 1) echo ','; ?>
+          <?php endforeach; ?>
+        ]
+      }
+    }
+    </script>
 
-    <!-- Bootstrap CSS -->
     <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
-    <!-- Bootstrap Icons -->
     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
 
     <style>
         :root {
-             --card-lift-height: -7px;
-             --card-shadow-intensity: rgba(0, 0, 0, .15);
-        }
-        html {
-            scroll-behavior: smooth;
-        }
-        body {
-            display: flex;
-            flex-direction: column;
-            min-height: 100vh;
-            background-color: #f8f9fa;
-        }
-        .main-content {
-            flex: 1;
-        }
-        .navbar {
-             background-image: linear-gradient(to bottom, #343a40, #212529);
+             --card-lift-height: -5px; /* Slightly more subtle lift */
+             --card-shadow-intensity: rgba(0, 0, 0, .1); /* Softer shadow */
         }
+        html { scroll-behavior: smooth; }
+        body { display: flex; flex-direction: column; min-height: 100vh; background-color: #f8f9fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #333; }
+        .main-content { flex: 1; }
+        .navbar { background-image: linear-gradient(to bottom, #343a40, #2c3034); } /* Darker, less contrast gradient */
+        .navbar-brand { font-weight: 500; color: #f8f9fa !important; }
         .card {
-            transition: transform .25s ease-in-out, box-shadow .25s ease-in-out;
-            border: 1px solid #dee2e6;
-            border-radius: .375rem;
-            overflow: hidden;
-            background-color: #fff;
-        }
-        .card:hover {
-            transform: translateY(var(--card-lift-height));
-            box-shadow: 0 0.8rem 1.6rem var(--card-shadow-intensity);
+            transition: transform .15s ease-out, box-shadow .15s ease-out; /* Quicker, smoother transition */
+            border: 1px solid #dee2e6; border-radius: .3rem; overflow: hidden; background-color: #fff; display: flex; flex-direction: column;
         }
-        .card-img-top, .iframe-preview-container {
+        .card:hover { transform: translateY(var(--card-lift-height)); box-shadow: 0 0.5rem 1rem var(--card-shadow-intensity); }
+        .card-img-top-container { /* New container for fixed aspect ratio */
             aspect-ratio: 16 / 9;
-            object-fit: cover;
+            width: 100%;
+            overflow: hidden;
             background-color: #e9ecef;
             border-bottom: 1px solid #dee2e6;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            color: #adb5bd;
-            position: relative; /* Needed for iframe positioning and ::before icon */
-        }
-         .card-img-top::before, .iframe-preview-container::before {
-             font-family: 'bootstrap-icons';
-             content: "\F48B"; /* bi-image-alt */
-             font-size: 2.5rem;
-             display: block;
-             position: absolute; /* Center the icon */
-             top: 50%;
-             left: 50%;
-             transform: translate(-50%, -50%);
-        }
-         .card-img-top[src]:not([src=""])::before, /* Hide icon if img src is valid */
-         .iframe-preview-container iframe.loaded::before { /* Hide icon if iframe is loaded */
-             display: none;
-        }
-        /* Ensure iframe content also hides its own ::before if it's an .iframe-preview-container */
-         .iframe-preview-container iframe[src]:not([src=""])::before {
-            display: none;
-         }
-
-
-        .iframe-preview-container {
-            /* position: relative; */ /* Already set above */
-            overflow: hidden;
-        }
-        .iframe-preview-container iframe {
-            position: absolute;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: 0;
-            background-color: #fff; /* Background for iframe before it loads */
-            opacity: 0; /* Start hidden */
-            transition: opacity 0.5s ease-in-out;
+            position: relative; /* For ::before pseudo-element */
+            display: flex; align-items: center; justify-content: center;
         }
-        .iframe-preview-container iframe.loaded {
-             opacity: 1; /* Fade in when loaded */
+        .card-img-top-container::before { /* Placeholder icon, improved */
+             font-family: 'bootstrap-icons'; content: "\F31F"; /* bi-card-image or F48B bi-image-alt */
+             font-size: 3rem; color: #adb5bd; position: absolute;
         }
-        .card-title a {
-            text-decoration: none;
-            color: inherit;
-            font-weight: 600;
+        .card-img-top-container img {
+            width: 100%; height: 100%; object-fit: cover; display: block;
         }
-        .card-title a:hover {
-            color: #0d6efd;
-            text-decoration: underline;
+        .card-img-top-container img[src]:not([src=""]):not(.error) { /* Hide icon if img src is valid and not errored */
+             position: relative; /* To ensure it covers the ::before */
+             z-index: 1;
         }
-        .card-body {
-            display: flex;
-            flex-direction: column;
-            padding: 1.25rem;
+        .card-img-top-container img[src]:not([src=""]):not(.error) + ::before,
+        .card-img-top-container img.error + ::before {
+            display: block; /* Show icon if image errors */
         }
-        .card-text {
-             flex-grow: 1;
-             margin-bottom: 1.25rem;
-             color: #495057;
-             font-size: 0.9rem;
-             min-height: 60px; /* Ensure consistent card body height */
+        .card-img-top-container img:not(.error)::before {
+             display:none; /* Hide icon if image loaded */
         }
-        .card-footer {
-            background-color: #f8f9fa;
-            border-top: 1px solid #dee2e6;
-            padding: 0.75rem 1.25rem;
+        .iframe-preview-container { /* Used when image fails or is not primary */
+            aspect-ratio: 16 / 9; width: 100%; position: relative; display: flex; align-items: center; justify-content: center;
+            background-color: #e9ecef; border-bottom: 1px solid #dee2e6; color: #adb5bd;
         }
-        .footer {
-             background-color: #e9ecef;
-             color: #6c757d;
-             border-top: 1px solid #ced4da;
-        }
-        .display-5.fw-bold {
-            color: #343a40;
-        }
-        #filterInput:focus {
-            border-color: #86b7fe;
-            box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
-        }
-        .cta-scroll-link {
-            font-size: 0.9rem;
-            text-decoration: none;
-        }
-        .cta-scroll-link:hover {
-            text-decoration: underline;
+        .iframe-preview-container::before { /* Placeholder icon for iframe */
+             font-family: 'bootstrap-icons'; content: "\F423"; /* bi-window-fullscreen or similar */
+             font-size: 2.5rem; display: block; position: absolute; top: 50%; left: 50%;
+             transform: translate(-50%, -50%);
         }
-        .cta-section-bottom {
-            border-top: 1px solid #dee2e6;
-            padding-top: 2rem;
-            padding-bottom: 1rem;
-            margin-top: 3rem;
+        .iframe-preview-container iframe {
+            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
+            border: 0; background-color: #fff; opacity: 0;
+            transition: opacity 0.4s ease-in-out .1s; /* Slight delay for smoother visual */
         }
+        .iframe-preview-container iframe.loaded { opacity: 1; z-index: 2; /* Ensure iframe is above ::before when loaded */ }
+        .iframe-preview-container iframe.loaded + ::before { display: none; } /* Hide icon when iframe loaded */
+
+        .card-title a { text-decoration: none; color: #1a508b; font-weight: 600; } /* Darker blue */
+        .card-title a:hover { color: #003d73; text-decoration: underline; }
+        .card-body { flex-grow: 1; display: flex; flex-direction: column; padding: 1rem; }
+        .card-text { flex-grow: 1; margin-bottom: .8rem; color: #495057; font-size: 0.875rem; line-height:1.5; min-height: calc(0.875rem * 1.5 * 3); /* Approx 3 lines */ }
+        .card-footer { background-color:transparent; border-top: 1px solid #e9ecef; padding: 0.75rem 1rem; text-align:center; margin-top: auto; }
+        .footer { background-color: #343a40; color: #adb5bd; border-top: 1px solid #495057; }
+        .footer a { color: #f8f9fa; } .footer a:hover { color: #ced4da; }
+        .page-hero { padding: 3rem 0; margin-bottom: 2rem; background-color: #e9ecef; border-bottom: 1px solid #dee2e6;}
+        .page-hero h1 { color: #212529; font-weight: 600;}
+        .page-hero .lead { color: #495057; font-size: 1.1rem; margin-bottom: 1.25rem;}
+        #filterInput { border-radius: .25rem; font-size: 1rem; padding: .6rem 1rem; }
+        #filterInput:focus { border-color: #86b7fe; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
+        .cta-scroll-link { font-size: 0.9rem; text-decoration: none; color: #0056b3; font-weight:500; }
+        .cta-scroll-link:hover { text-decoration: underline; }
+        .cta-section { background-color: #ffffff; border-top: 1px solid #dee2e6; border-bottom: 1px solid #dee2e6; padding: 3rem 0; margin: 3rem 0;}
+        .cta-section h3 {font-weight: 600; color: #212529;}
+        .portfolio-item .card { height: 100%; } /* Ensures cards in a row are same height */
     </style>
 </head>
 <body class="d-flex flex-column min-vh-100">
-    <nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top shadow-sm">
+    <nav class="navbar navbar-expand-lg navbar-dark sticky-top shadow-sm">
         <div class="container">
             <a class="navbar-brand" href="<?php echo htmlspecialchars($baseUrl); ?>">
                  <i class="bi bi-journal-richtext me-2"></i>David Veksler's Cheatsheet Portfolio
@@ -356,190 +310,198 @@ try {
         </div>
     </nav>
 
-    <main class="main-content container mt-4 mb-5">
-        <header class="text-center mb-5">
-            <h1 class="display-5 fw-bold">Cheatsheet Design Showcase</h1>
-            <p class="lead text-muted">Explore examples of clear, concise, and interactive reference guides.</p>
-            <a href="#custom-cheatsheets" class="cta-scroll-link link-secondary">
-                <i class="bi bi-tools me-1"></i>Need a custom cheatsheet?
-            </a>
+    <main class="main-content">
+        <header class="page-hero text-center">
+            <div class="container">
+                <h1 class="display-5">Cheatsheet Design Showcase</h1>
+                <p class="lead">Explore examples of clear, concise, and interactive reference guides meticulously crafted by David Veksler.</p>
+                <a href="#custom-cheatsheet-cta" class="cta-scroll-link">
+                    <i class="bi bi-tools me-1"></i>Need Custom Cheatsheet Design Services? Let's Talk!
+                </a>
+            </div>
         </header>
 
-        <div class="row mb-4 justify-content-center">
-            <div class="col-md-8 col-lg-6">
-                <div class="input-group input-group-lg shadow-sm">
-                    <span class="input-group-text bg-white border-end-0" id="filter-addon"><i class="bi bi-search text-primary"></i></span>
-                    <input type="search" id="filterInput" class="form-control border-start-0" placeholder="Filter cheatsheets by title or topic..." aria-label="Filter cheatsheets" aria-describedby="filter-addon">
+        <div class="container mt-4 mb-5">
+            <div class="row mb-4 justify-content-center">
+                <div class="col-md-8 col-lg-6">
+                    <div class="input-group input-group-lg shadow-sm">
+                        <span class="input-group-text bg-white border-end-0 text-primary" id="filter-addon"><i class="bi bi-search"></i></span>
+                        <input type="search" id="filterInput" class="form-control border-start-0" placeholder="Filter by title or topic (e.g., Buddhism, Python)..." aria-label="Filter cheatsheets" aria-describedby="filter-addon">
+                    </div>
                 </div>
             </div>
-        </div>
 
-        <?php if (!empty($errors)): ?>
-            <div class="alert alert-warning alert-dismissible fade show" role="alert">
-                <h4 class="alert-heading">Notice</h4>
-                <p>There were some issues loading details for all cheatsheets:</p>
-                <ul>
-                    <?php foreach ($errors as $error): ?>
-                        <li><?php echo $error; /* Already htmlspecialchars'd in extractMetadata if needed */ ?></li>
-                    <?php endforeach; ?>
-                </ul>
-                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
-            </div>
-        <?php endif; ?>
+            <?php if (!empty($errors)): ?>
+                <div class="alert alert-warning alert-dismissible fade show" role="alert">
+                    <h4 class="alert-heading"><i class="bi bi-exclamation-triangle-fill me-2"></i>Notice</h4>
+                    <p>There were some issues loading details for all cheatsheets:</p>
+                    <ul class="mb-0">
+                        <?php foreach ($errors as $error): ?>
+                            <li><?php echo $error; ?></li>
+                        <?php endforeach; ?>
+                    </ul>
+                    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+                </div>
+            <?php endif; ?>
 
-        <?php if (empty($cheatsheets) && empty($errors)): ?>
-            <div class="alert alert-info text-center mt-4" role="alert">
-                <i class="bi bi-info-circle me-2"></i>No cheatsheet examples found. Check back soon!
-            </div>
-        <?php endif; ?>
-
-        <div id="cheatsheetGrid" class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
-            <?php foreach ($cheatsheets as $sheet): ?>
-                <div class="col d-flex align-items-stretch portfolio-item">
-                    <div class="card h-100 shadow-sm">
-                        <?php if (!empty($sheet['image'])): ?>
-                            <a href="<?php echo htmlspecialchars($sheet['url']); ?>" target="_blank" rel="noopener" aria-label="Preview image for <?php echo htmlspecialchars($sheet['title']); ?>">
-                                <img src="<?php echo htmlspecialchars($sheet['image']); ?>" class="card-img-top" alt="Preview for <?php echo htmlspecialchars($sheet['title']); ?>" loading="lazy"
-                                     onerror="this.style.display='none'; this.parentElement.nextElementSibling.style.display='block'; this.parentElement.nextElementSibling.querySelector('iframe').classList.add('loaded');">
-                            </a>
-                            <div class="iframe-preview-container" style="display: none;"> <!-- Initially hidden if image exists -->
-                                <iframe src="<?php echo htmlspecialchars($sheet['url']); ?>"
-                                        title="Interactive preview of <?php echo htmlspecialchars($sheet['title']); ?>"
-                                        loading="lazy"
-                                        frameborder="0"
-                                        scrolling="no"
-                                        referrerpolicy="no-referrer"
-                                        onload="this.classList.add('loaded');">
-                                </iframe>
-                            </div>
-                        <?php else: ?>
-                            <!-- Fallback: Display iframe preview directly if no image -->
-                            <div class="iframe-preview-container">
-                                <iframe src="<?php echo htmlspecialchars($sheet['url']); ?>"
-                                        title="Interactive preview of <?php echo htmlspecialchars($sheet['title']); ?>"
-                                        loading="lazy"
-                                        frameborder="0"
-                                        scrolling="no"
-                                        referrerpolicy="no-referrer"
-                                        onload="this.classList.add('loaded');">
-                                </iframe>
+            <?php if (empty($cheatsheets) && empty($errors)): ?>
+                <div class="alert alert-info text-center mt-4 py-4" role="alert">
+                    <i class="bi bi-info-circle-fill me-2 fs-4 align-middle"></i>No cheatsheet examples found. Please check back soon for updates!
+                </div>
+            <?php endif; ?>
+
+            <div id="cheatsheetGrid" class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
+                <?php foreach ($cheatsheets as $sheet): ?>
+                    <div class="col portfolio-item">
+                        <article class="card shadow-sm">
+                            <?php if (!empty($sheet['image'])): ?>
+                                <a href="<?php echo htmlspecialchars($sheet['url']); ?>" target="_blank" rel="noopener" class="card-img-top-container" aria-label="Preview image for <?php echo htmlspecialchars($sheet['title']); ?>">
+                                    <img src="<?php echo htmlspecialchars($sheet['image']); ?>" alt="Preview for <?php echo htmlspecialchars($sheet['title']); ?>" loading="lazy"
+                                         onerror="this.classList.add('error'); this.style.display='none'; this.closest('.card').querySelector('.iframe-preview-container').style.display='flex';">
+                                </a>
+                                <div class="iframe-preview-container" style="display: none;"> <!-- Initially hidden if image exists and loads -->
+                                    <iframe src="<?php echo htmlspecialchars($sheet['url']); ?>"
+                                            title="Interactive preview of <?php echo htmlspecialchars($sheet['title']); ?>"
+                                            loading="lazy"
+                                            frameborder="0"
+                                            scrolling="no"
+                                            referrerpolicy="no-referrer"
+                                            onload="this.classList.add('loaded');">
+                                    </iframe>
+                                </div>
+                            <?php else: ?>
+                                <div class="iframe-preview-container" style="display: flex;"> <!-- Display flex to show ::before icon as no image provided -->
+                                    <iframe src="<?php echo htmlspecialchars($sheet['url']); ?>"
+                                            title="Interactive preview of <?php echo htmlspecialchars($sheet['title']); ?>"
+                                            loading="lazy"
+                                            frameborder="0"
+                                            scrolling="no"
+                                            referrerpolicy="no-referrer"
+                                            onload="this.classList.add('loaded');">
+                                    </iframe>
+                                </div>
+                            <?php endif; ?>
+                            <div class="card-body">
+                                <h5 class="card-title">
+                                    <a href="<?php echo htmlspecialchars($sheet['url']); ?>" target="_blank" rel="noopener">
+                                        <?php echo htmlspecialchars($sheet['title']); ?>
+                                    </a>
+                                </h5>
+                                <p class="card-text">
+                                    <?php echo htmlspecialchars($sheet['description']); ?>
+                                </p>
                             </div>
-                        <?php endif; ?>
-                        <div class="card-body">
-                            <h5 class="card-title">
-                                <a href="<?php echo htmlspecialchars($sheet['url']); ?>" target="_blank" rel="noopener">
-                                    <?php echo htmlspecialchars($sheet['title']); ?>
+                            <div class="card-footer">
+                                <a href="<?php echo htmlspecialchars($sheet['url']); ?>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-primary w-100">
+                                    View Cheatsheet <i class="bi bi-box-arrow-up-right ms-1"></i>
                                 </a>
-                            </h5>
-                            <p class="card-text text-muted small">
-                                <?php echo htmlspecialchars($sheet['description']); ?>
-                            </p>
-                        </div>
-                        <div class="card-footer text-center">
-                            <a href="<?php echo htmlspecialchars($sheet['url']); ?>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-primary">
-                                View Cheatsheet <i class="bi bi-box-arrow-up-right ms-1"></i>
-                            </a>
-                        </div>
+                            </div>
+                        </article>
                     </div>
-                </div>
-            <?php endforeach; ?>
-        </div>
+                <?php endforeach; ?>
+            </div>
 
-        <div id="noResults" class="alert alert-warning text-center mt-4 d-none" role="alert">
-            <i class="bi bi-emoji-frown me-2"></i>No cheatsheets match your filter criteria. Try broadening your search.
+            <div id="noResults" class="alert alert-warning text-center mt-4 py-3 d-none" role="alert">
+                <i class="bi bi-emoji-frown me-2"></i>No cheatsheets match your filter. Try a different term.
+            </div>
         </div>
+    </main>
 
-         <section id="custom-cheatsheets" class="cta-section-bottom text-center">
-              <h3 class="h4 fw-normal mb-3">Need a Custom Cheatsheet?</h3>
-              <p class="text-muted mb-3 mx-auto" style="max-width: 600px;">I can help design professional, tailored cheatsheets for your specific needs – documentation, training, marketing, and more.</p>
-              <a href="https://www.linkedin.com/in/davidveksler/" target="_blank" rel="noopener noreferrer" class="btn btn-outline-primary btn-sm">
-                  <i class="bi bi-linkedin me-1"></i> Discuss Your Project on LinkedIn
+    <section id="custom-cheatsheet-cta" class="cta-section text-center">
+        <div class="container">
+              <h3 class="mb-3">Need a Custom-Designed Cheatsheet?</h3>
+              <p class="text-muted mb-4 mx-auto" style="max-width: 700px;">
+                  Leverage David Veksler's expertise to get professional, engaging, and easy-to-understand cheatsheets tailored for your specific needs — perfect for technical documentation, educational material, marketing content, or internal training programs.
+              </p>
+              <a href="https://www.linkedin.com/in/davidveksler/" target="_blank" rel="noopener noreferrer" class="btn btn-primary btn-lg px-4 py-3">
+                  <i class="bi bi-linkedin me-2"></i>Discuss Your Project on LinkedIn
               </a>
-          </section>
-    </main>
+        </div>
+    </section>
 
-    <footer class="footer py-4 mt-auto border-top bg-light">
+    <footer class="footer py-4 mt-auto">
         <div class="container text-center">
-            <p class="mb-2 text-muted">
-                Cheatsheet Portfolio & Design by David Veksler © <?php echo date("Y"); ?>
+            <p class="mb-2 small">
+                Cheatsheet Portfolio © <?php echo date("Y"); ?> David Veksler. All rights reserved.
             </p>
             <div>
-              <a href="https://www.linkedin.com/in/davidveksler/" title="David Veksler on LinkedIn" target="_blank" rel="noopener noreferrer" class="mx-2 link-secondary">
-                <i class="bi bi-linkedin"></i> LinkedIn Profile
+              <a href="https://www.linkedin.com/in/davidveksler/" title="David Veksler on LinkedIn" target="_blank" rel="noopener noreferrer" class="mx-2 small">
+                <i class="bi bi-linkedin"></i> LinkedIn
               </a>
-              <span class="text-muted mx-1">|</span>
-              <a href="<?php echo htmlspecialchars($baseUrl); ?>" title="Browse All Cheatsheets" class="mx-2 link-secondary">
-                <i class="bi bi-collection"></i> View All Examples
+              <span class="mx-1 small">|</span>
+              <a href="<?php echo htmlspecialchars($baseUrl); ?>" title="Browse All Cheatsheet Examples" class="mx-2 small">
+                <i class="bi bi-collection-fill"></i> View All Examples
               </a>
             </div>
         </div>
     </footer>
 
     <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
-
     <script>
-        document.addEventListener('DOMContentLoaded', function() {
-            const filterInput = document.getElementById('filterInput');
-            const grid = document.getElementById('cheatsheetGrid');
-            const noResultsMessage = document.getElementById('noResults');
-            // Get items only if grid exists
-            const items = grid ? grid.querySelectorAll('.portfolio-item') : [];
-
-
-            if (!filterInput || !grid || items.length === 0) {
-                if(filterInput) filterInput.disabled = true;
-                // If no items at all (e.g. $cheatsheets was empty), don't show "no results" from filter.
-                // The PHP part will show a "No cheatsheet examples found" message.
-                // So, we only want the filter's "noResults" message if there were items to begin with.
-                if (items.length > 0 && noResultsMessage) noResultsMessage.classList.add('d-none');
-                return;
-            }
+    document.addEventListener('DOMContentLoaded', function() {
+        const filterInput = document.getElementById('filterInput');
+        const grid = document.getElementById('cheatsheetGrid');
+        const noResultsMessage = document.getElementById('noResults');
+        const items = grid ? Array.from(grid.querySelectorAll('.portfolio-item')) : [];
 
+        if (filterInput && grid && items.length > 0) {
             filterInput.addEventListener('input', function() {
                 const filterText = filterInput.value.toLowerCase().trim();
                 let itemsVisible = 0;
 
                 items.forEach(item => {
-                    const card = item.querySelector('.card');
-                    if (!card) return;
-
-                    const titleElement = card.querySelector('.card-title a');
-                    const descriptionElement = card.querySelector('.card-text');
-
+                    const titleElement = item.querySelector('.card-title a');
+                    const descriptionElement = item.querySelector('.card-text');
                     const title = titleElement ? titleElement.textContent.toLowerCase() : '';
                     const description = descriptionElement ? descriptionElement.textContent.toLowerCase() : '';
+                    
                     const isVisible = filterText === '' || title.includes(filterText) || description.includes(filterText);
 
                     if (isVisible) {
-                        item.classList.remove('d-none'); // Ensure item is displayed
+                        item.style.display = ''; 
                         itemsVisible++;
                     } else {
-                        item.classList.add('d-none'); // Hide item
+                        item.style.display = 'none';
                     }
                 });
+                noResultsMessage.classList.toggle('d-none', itemsVisible > 0 || filterText === '');
+            });
+        } else if (filterInput) {
+             filterInput.disabled = true; 
+             filterInput.placeholder = "No cheatsheets available to filter.";
+        }
 
-                if (noResultsMessage) {
-                    if (itemsVisible === 0 && filterText !== '') {
-                        noResultsMessage.classList.remove('d-none');
-                    } else {
-                        noResultsMessage.classList.add('d-none');
+        // Enhanced image error handling for images within card-img-top-container
+        document.querySelectorAll('.card-img-top-container img').forEach(img => {
+            img.addEventListener('error', function() {
+                this.classList.add('error'); // Mark as errored
+                this.style.display = 'none'; // Hide broken image
+                const card = this.closest('.card');
+                if (card) {
+                    const iframeContainer = card.querySelector('.iframe-preview-container');
+                    if (iframeContainer) {
+                        iframeContainer.style.display = 'flex'; // Show iframe container
                     }
                 }
             });
-
-            // Ensure iframes in initially hidden containers load correctly when revealed by image error
-            document.querySelectorAll('img.card-img-top').forEach(img => {
-                img.addEventListener('error', function() {
-                    const iframeContainer = this.parentElement.nextElementSibling;
-                    if (iframeContainer && iframeContainer.classList.contains('iframe-preview-container')) {
-                        const iframe = iframeContainer.querySelector('iframe');
-                        // Force load or ensure visibility triggers load - modern browsers are usually good
-                        // but explicitly adding 'loaded' ensures CSS transition if needed.
-                        if (iframe) iframe.classList.add('loaded');
+             // If image loads, but was previously hidden by error fallback, ensure it's shown
+            img.addEventListener('load', function() {
+                if (!this.classList.contains('error')) { // Only if it didn't error before
+                    this.style.display = 'block';
+                    const card = this.closest('.card');
+                     if (card) {
+                        const iframeContainer = card.querySelector('.iframe-preview-container');
+                        // If an image loads successfully, we might want to hide the iframe container if it was only a fallback
+                        // This depends on the desired logic: always show image if available, or primary iframe?
+                        // The current setup implies image is primary if present.
+                        // if (iframeContainer && this.src && this.src !== '') {
+                        //    iframeContainer.style.display = 'none';
+                        // }
                     }
-                });
+                }
             });
         });
+    });
     </script>
 </body>
 </html>
\ No newline at end of file