UNPKG

codewithgarry

Version:

Girish Sharma's NPX business card - DevOps Engineer & Cloud Architect - Connect with me directly via terminal!

1,646 lines (1,381 loc) 43.3 kB
// Advanced Blogging System with Markdown Support // This file contains the complete blogging platform functionality class BlogManager { constructor() { this.posts = []; this.categories = ['DevOps', 'Kubernetes', 'Cloud', 'Terraform', 'Tutorials', 'Best Practices']; this.currentPost = null; this.searchQuery = ''; this.selectedCategory = 'All'; this.sortBy = 'date'; } // Initialize blog system async initialize() { await this.loadMarkdownParser(); this.loadSamplePosts(); this.renderBlogInterface(); } // Load markdown parser async loadMarkdownParser() { return new Promise((resolve) => { if (window.marked) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.2/marked.min.js'; script.onload = () => { // Configure marked options marked.setOptions({ highlight: function(code, lang) { if (window.Prism && lang && Prism.languages[lang]) { return Prism.highlight(code, Prism.languages[lang], lang); } return code; }, breaks: true, gfm: true }); resolve(); }; document.head.appendChild(script); }); } // Load sample blog posts loadSamplePosts() { this.posts = [ { id: 'kubernetes-production-ready', title: 'Making Kubernetes Production Ready: A Complete Guide', excerpt: 'Learn essential practices for running Kubernetes clusters in production environments with confidence.', content: this.getSampleMarkdownContent('kubernetes-production'), author: 'Girish Sharma', date: '2024-03-15', category: 'Kubernetes', tags: ['kubernetes', 'production', 'devops', 'containers'], readTime: '12 min read', featured: true, image: 'https://via.placeholder.com/800x400/1a1a2e/00ff00?text=Kubernetes+Production' }, { id: 'terraform-best-practices', title: 'Terraform Best Practices for Enterprise Infrastructure', excerpt: 'Discover proven patterns and practices for managing large-scale infrastructure with Terraform.', content: this.getSampleMarkdownContent('terraform-practices'), author: 'Girish Sharma', date: '2024-03-10', category: 'Terraform', tags: ['terraform', 'iac', 'devops', 'automation'], readTime: '15 min read', featured: true, image: 'https://via.placeholder.com/800x400/16213e/00bfff?text=Terraform+Best+Practices' }, { id: 'monitoring-with-promql', title: 'Advanced Monitoring with PromQL: Beyond the Basics', excerpt: 'Master PromQL queries to build powerful monitoring and alerting systems.', content: this.getSampleMarkdownContent('promql-monitoring'), author: 'Girish Sharma', date: '2024-03-05', category: 'DevOps', tags: ['promql', 'monitoring', 'prometheus', 'observability'], readTime: '10 min read', featured: false, image: 'https://via.placeholder.com/800x400/0c0c0c/ff00ff?text=PromQL+Monitoring' }, { id: 'gcp-devops-pipeline', title: 'Building CI/CD Pipelines on Google Cloud Platform', excerpt: 'Step-by-step guide to creating robust deployment pipelines using GCP services.', content: this.getSampleMarkdownContent('gcp-pipeline'), author: 'Girish Sharma', date: '2024-02-28', category: 'Cloud', tags: ['gcp', 'cicd', 'cloud-build', 'devops'], readTime: '18 min read', featured: false, image: 'https://via.placeholder.com/800x400/1a1a2e/ffff00?text=GCP+CI%2FCD' } ]; } // Get sample markdown content getSampleMarkdownContent(type) { const content = { 'kubernetes-production': ` # Making Kubernetes Production Ready: A Complete Guide Running Kubernetes in production requires careful planning and implementation of best practices. This comprehensive guide covers everything you need to know. ## Table of Contents 1. [Cluster Setup and Configuration](#cluster-setup) 2. [Security Hardening](#security) 3. [Resource Management](#resources) 4. [Monitoring and Observability](#monitoring) 5. [Backup and Disaster Recovery](#backup) ## Cluster Setup and Configuration {#cluster-setup} ### High Availability Control Plane For production workloads, always deploy a multi-master setup: \`\`\`yaml apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration controlPlaneEndpoint: "k8s-api.example.com:6443" etcd: external: endpoints: - https://etcd1.example.com:2379 - https://etcd2.example.com:2379 - https://etcd3.example.com:2379 \`\`\` ### Node Configuration Ensure proper node configuration for production workloads: \`\`\`bash # Configure kubelet cat > /etc/kubernetes/kubelet-config.yaml << EOF apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd maxPods: 110 serverTLSBootstrap: true rotateCertificates: true EOF \`\`\` ## Security Hardening {#security} ### Network Policies Implement network segmentation using Kubernetes Network Policies: \`\`\`yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all-ingress namespace: production spec: podSelector: {} policyTypes: - Ingress - Egress egress: - to: [] ports: - protocol: TCP port: 53 - protocol: UDP port: 53 \`\`\` ### Pod Security Standards Configure Pod Security Standards for enhanced security: \`\`\`yaml apiVersion: v1 kind: Namespace metadata: name: production labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/warn: restricted \`\`\` ## Resource Management {#resources} ### Resource Quotas Implement resource quotas to prevent resource exhaustion: \`\`\`yaml apiVersion: v1 kind: ResourceQuota metadata: name: production-quota namespace: production spec: hard: requests.cpu: "100" requests.memory: 200Gi limits.cpu: "200" limits.memory: 400Gi persistentvolumeclaims: "10" \`\`\` ### Horizontal Pod Autoscaler Configure HPA for automatic scaling: \`\`\`yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: app-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: app minReplicas: 3 maxReplicas: 100 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 \`\`\` ## Monitoring and Observability {#monitoring} ### Prometheus Configuration Deploy Prometheus for comprehensive monitoring: \`\`\`yaml apiVersion: v1 kind: ConfigMap metadata: name: prometheus-config data: prometheus.yml: | global: scrape_interval: 15s evaluation_interval: 15s rule_files: - "kubernetes-*.rules" scrape_configs: - job_name: 'kubernetes-nodes' kubernetes_sd_configs: - role: node relabel_configs: - source_labels: [__address__] regex: '(.*):10250' target_label: __address__ replacement: '\${1}:9100' \`\`\` ### Alerting Rules Set up critical alerting rules: \`\`\`yaml groups: - name: kubernetes.rules rules: - alert: KubernetesPodCrashLooping expr: rate(kube_pod_container_status_restarts_total[15m]) > 0 for: 5m labels: severity: warning annotations: summary: "Pod {{ $labels.pod }} is crash looping" description: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} is restarting frequently" \`\`\` ## Best Practices Checklist - Multi-master setup for HA - Network policies implemented - Resource quotas configured - Pod security standards enforced - Monitoring and alerting in place - Backup strategy implemented - Cluster autoscaling configured - Image scanning enabled - Secrets management with external tools - Regular security audits ## Conclusion Making Kubernetes production-ready requires attention to many details. Start with these fundamentals and gradually implement more advanced features as your needs grow. > **Pro Tip**: Always test your disaster recovery procedures in a staging environment before relying on them in production. --- *Have questions about Kubernetes in production? Feel free to reach out or check out my [Kubernetes course](/courses/kubernetes-fundamentals) for hands-on practice.* `, 'terraform-practices': ` # Terraform Best Practices for Enterprise Infrastructure Managing infrastructure at scale requires disciplined practices and proper tooling. Here's your guide to Terraform excellence. ## Directory Structure Organize your Terraform code for maintainability: \`\`\` terraform/ ├── environments/ ├── dev/ ├── staging/ └── production/ ├── modules/ ├── vpc/ ├── compute/ └── storage/ └── shared/ ├── variables.tf └── outputs.tf \`\`\` ## Module Development ### Creating Reusable Modules \`\`\`hcl # modules/vpc/main.tf resource "google_compute_network" "vpc" { name = var.network_name auto_create_subnetworks = false routing_mode = "REGIONAL" } resource "google_compute_subnetwork" "subnet" { count = length(var.subnets) name = var.subnets[count.index].name ip_cidr_range = var.subnets[count.index].cidr region = var.region network = google_compute_network.vpc.id } \`\`\` ### Variable Validation \`\`\`hcl variable "environment" { description = "Environment name" type = string validation { condition = can(regex("^(dev|staging|production)$", var.environment)) error_message = "Environment must be dev, staging, or production." } } \`\`\` ## State Management ### Remote State Configuration \`\`\`hcl terraform { backend "gcs" { bucket = "terraform-state-bucket" prefix = "terraform/state" } } \`\`\` ### State Locking Always use state locking to prevent conflicts: \`\`\`hcl terraform { backend "gcs" { bucket = "terraform-state-bucket" prefix = "terraform/state" } } \`\`\` ## Testing Strategy ### Terratest Example \`\`\`go func TestVPCModule(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../modules/vpc", Vars: map[string]interface{}{ "network_name": "test-vpc", "region": "us-central1", }, } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) networkName := terraform.Output(t, terraformOptions, "network_name") assert.Equal(t, "test-vpc", networkName) } \`\`\` ## CI/CD Pipeline ### GitLab CI Example \`\`\`yaml stages: - validate - plan - apply variables: TF_ROOT: terraform/environments/production terraform-validate: stage: validate script: - cd $TF_ROOT - terraform init -backend=false - terraform validate - terraform fmt -check terraform-plan: stage: plan script: - cd $TF_ROOT - terraform init - terraform plan -out=tfplan artifacts: paths: - $TF_ROOT/tfplan terraform-apply: stage: apply script: - cd $TF_ROOT - terraform apply tfplan when: manual only: - main \`\`\` ## Conclusion Following these practices will help you build maintainable, scalable infrastructure with Terraform. `, 'promql-monitoring': ` # Advanced Monitoring with PromQL: Beyond the Basics PromQL is the query language for Prometheus, enabling powerful monitoring and alerting capabilities. ## Query Fundamentals ### Instant Vectors \`\`\`promql # CPU usage by instance 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) # Memory usage percentage (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 \`\`\` ### Range Vectors \`\`\`promql # Request rate over 5 minutes rate(http_requests_total[5m]) # 99th percentile response time histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) \`\`\` ## Advanced Techniques ### Aggregation Operators \`\`\`promql # Top 5 instances by CPU usage topk(5, node_cpu_usage_percent) # Average response time by service avg by (service) (http_request_duration_seconds) \`\`\` ### Binary Operators \`\`\`promql # Compare current vs previous hour rate(http_requests_total[5m]) / rate(http_requests_total[5m] offset 1h) # Calculate error rate rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) \`\`\` ## Kubernetes Specific Queries ### Pod Resource Usage \`\`\`promql # Pod CPU usage rate(container_cpu_usage_seconds_total{pod!=""}[5m]) # Pod memory usage container_memory_working_set_bytes{pod!=""} # Pod network I/O rate(container_network_receive_bytes_total{pod!=""}[5m]) \`\`\` ### Cluster Health \`\`\`promql # Node availability up{job="kubernetes-nodes"} # Pod restart rate increase(kube_pod_container_status_restarts_total[1h]) \`\`\` ## Best Practices 1. **Use appropriate time ranges**: Match your scrape interval 2. **Aggregate before alerting**: Reduce noise with proper grouping 3. **Avoid high cardinality**: Be careful with label combinations 4. **Use recording rules**: Pre-compute expensive queries ## Recording Rules Example \`\`\`yaml groups: - name: kubernetes.rules rules: - record: kubernetes:pod_cpu_usage:rate5m expr: rate(container_cpu_usage_seconds_total{pod!=""}[5m]) - record: kubernetes:pod_memory_usage:bytes expr: container_memory_working_set_bytes{pod!=""} \`\`\` `, 'gcp-pipeline': ` # Building CI/CD Pipelines on Google Cloud Platform Learn to create robust deployment pipelines using GCP's native services. ## Cloud Build Configuration ### Basic Pipeline \`\`\`yaml # cloudbuild.yaml steps: # Build the container image - name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA', '.'] # Push to Container Registry - name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA'] # Deploy to GKE - name: 'gcr.io/cloud-builders/gke-deploy' args: - run - --filename=k8s/ - --image=gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA - --location=us-central1-a - --cluster=production-cluster options: logging: CLOUD_LOGGING_ONLY \`\`\` ### Multi-Environment Pipeline \`\`\`yaml steps: # Test stage - name: 'gcr.io/cloud-builders/docker' args: ['run', '--rm', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA', 'npm', 'test'] # Security scanning - name: 'gcr.io/cloud-builders/gcloud' args: ['beta', 'container', 'images', 'scan', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA'] # Deploy to staging - name: 'gcr.io/cloud-builders/gke-deploy' args: ['run', '--filename=k8s/staging/', '--image=gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA'] \`\`\` ## Terraform Integration ### Infrastructure Pipeline \`\`\`yaml steps: # Terraform init - name: 'hashicorp/terraform:1.0' entrypoint: 'sh' args: - '-c' - | cd terraform/ terraform init # Terraform plan - name: 'hashicorp/terraform:1.0' entrypoint: 'sh' args: - '-c' - | cd terraform/ terraform plan -out=tfplan # Terraform apply - name: 'hashicorp/terraform:1.0' entrypoint: 'sh' args: - '-c' - | cd terraform/ terraform apply tfplan \`\`\` ## Advanced Features ### Parallel Builds \`\`\`yaml steps: # Frontend build - name: 'node:16' id: 'frontend-build' entrypoint: 'npm' args: ['run', 'build:frontend'] # Backend build - name: 'gcr.io/cloud-builders/docker' id: 'backend-build' args: ['build', '-t', 'gcr.io/$PROJECT_ID/backend:$COMMIT_SHA', './backend'] # Integration tests (wait for both builds) - name: 'gcr.io/cloud-builders/docker' args: ['run', '--rm', 'gcr.io/$PROJECT_ID/test-runner', 'npm', 'run', 'test:integration'] waitFor: ['frontend-build', 'backend-build'] \`\`\` ## Monitoring and Notifications ### Slack Integration \`\`\`yaml steps: # ... build steps ... # Notify on success - name: 'gcr.io/cloud-builders/curl' args: - '-X' - 'POST' - '-H' - 'Content-type: application/json' - '--data' - '{"text":"✅ Build successful for commit $COMMIT_SHA"}' - '$_SLACK_WEBHOOK_URL' \`\`\` ## Best Practices 1. **Use semantic versioning**: Tag releases properly 2. **Implement proper testing**: Unit, integration, and security tests 3. **Use secrets management**: Store sensitive data in Secret Manager 4. **Monitor pipeline performance**: Track build times and success rates 5. **Implement approval workflows**: Use manual triggers for production ## Conclusion GCP provides powerful tools for building enterprise-grade CI/CD pipelines. Start simple and add complexity as needed. ` }; return content[type] || '# Sample Content\n\nThis is sample markdown content.'; } // Render blog interface renderBlogInterface() { const container = document.getElementById('blogContent'); if (!container) return; container.innerHTML = ` <div class="blog-header"> <div class="blog-controls"> <div class="search-container"> <input type="text" class="search-input" placeholder="Search articles..." value="${this.searchQuery}" onchange="blogManager.handleSearch(this.value)"> <i class="fas fa-search search-icon"></i> </div> <div class="filter-container"> <select class="category-filter" onchange="blogManager.handleCategoryFilter(this.value)"> <option value="All">All Categories</option> ${this.categories.map(cat => ` <option value="${cat}" ${this.selectedCategory === cat ? 'selected' : ''}>${cat}</option> `).join('')} </select> </div> <div class="sort-container"> <select class="sort-select" onchange="blogManager.handleSort(this.value)"> <option value="date" ${this.sortBy === 'date' ? 'selected' : ''}>Latest First</option> <option value="title" ${this.sortBy === 'title' ? 'selected' : ''}>Title A-Z</option> <option value="readTime" ${this.sortBy === 'readTime' ? 'selected' : ''}>Read Time</option> </select> </div> </div> </div> <div class="featured-posts"> <h2>Featured Articles</h2> <div class="featured-grid"> ${this.renderFeaturedPosts()} </div> </div> <div class="all-posts"> <h2>All Articles</h2> <div class="posts-grid"> ${this.renderAllPosts()} </div> </div> `; } // Render featured posts renderFeaturedPosts() { const featuredPosts = this.getFilteredPosts().filter(post => post.featured); return featuredPosts.map(post => this.renderPostCard(post, 'featured')).join(''); } // Render all posts renderAllPosts() { const allPosts = this.getFilteredPosts(); return allPosts.map(post => this.renderPostCard(post, 'regular')).join(''); } // Render individual post card renderPostCard(post, type = 'regular') { const cardClass = type === 'featured' ? 'post-card featured-post' : 'post-card'; return ` <article class="${cardClass}" onclick="blogManager.openPost('${post.id}')"> <div class="post-image"> <img src="${post.image}" alt="${post.title}" loading="lazy"> <div class="post-category">${post.category}</div> </div> <div class="post-content"> <h3 class="post-title">${post.title}</h3> <p class="post-excerpt">${post.excerpt}</p> <div class="post-meta"> <div class="post-author"> <i class="fas fa-user"></i> <span>${post.author}</span> </div> <div class="post-date"> <i class="fas fa-calendar"></i> <span>${this.formatDate(post.date)}</span> </div> <div class="post-read-time"> <i class="fas fa-clock"></i> <span>${post.readTime}</span> </div> </div> <div class="post-tags"> ${post.tags.map(tag => `<span class="tag">#${tag}</span>`).join('')} </div> </div> </article> `; } // Open full post view openPost(postId) { const post = this.posts.find(p => p.id === postId); if (!post) return; this.currentPost = post; this.renderFullPost(); } // Render full post view renderFullPost() { const container = document.getElementById('blogContent'); if (!container || !this.currentPost) return; container.innerHTML = ` <div class="full-post"> <div class="post-header"> <button class="back-btn" onclick="blogManager.closeFull <i class="fas fa-arrow-left"></i> Back to Blog </button> <div class="post-meta-header"> <span class="post-category">${this.currentPost.category}</span> <span class="post-date">${this.formatDate(this.currentPost.date)}</span> </div> </div> <div class="post-hero"> <img src="${this.currentPost.image}" alt="${this.currentPost.title}" class="hero-image"> <div class="hero-content"> <h1 class="post-title">${this.currentPost.title}</h1> <div class="post-meta"> <span class="author">By ${this.currentPost.author}</span> <span class="read-time">${this.currentPost.readTime}</span> </div> <div class="post-tags"> ${this.currentPost.tags.map(tag => `<span class="tag">#${tag}</span>`).join('')} </div> </div> </div> <div class="post-content"> <div class="content-sidebar"> <div class="table-of-contents"> <h4>Table of Contents</h4> <div id="tocContainer"> ${this.generateTableOfContents(this.currentPost.content)} </div> </div> <div class="post-actions"> <button class="action-btn" onclick="blogManager.sharePost()"> <i class="fas fa-share"></i> Share </button> <button class="action-btn" onclick="blogManager.bookmarkPost()"> <i class="fas fa-bookmark"></i> Bookmark </button> <button class="action-btn" onclick="blogManager.printPost()"> <i class="fas fa-print"></i> Print </button> </div> </div> <div class="content-main"> <div class="markdown-content" id="markdownContent"> ${marked.parse(this.currentPost.content)} </div> <div class="post-footer"> <div class="author-bio"> <div class="author-avatar"> <i class="fas fa-user-circle"></i> </div> <div class="author-info"> <h4>${this.currentPost.author}</h4> <p>DevOps Engineer & Cloud Architect passionate about Kubernetes, Infrastructure as Code, and modern development practices.</p> <div class="social-links"> <a href="https://twitter.com/codewithgarry" target="_blank"> <i class="fab fa-twitter"></i> </a> <a href="https://github.com/codewithgarry" target="_blank"> <i class="fab fa-github"></i> </a> <a href="https://linkedin.com/in/codewithgarry" target="_blank"> <i class="fab fa-linkedin"></i> </a> </div> </div> </div> <div class="related-posts"> <h3>Related Articles</h3> <div class="related-grid"> ${this.renderRelatedPosts()} </div> </div> </div> </div> </div> </div> `; // Apply syntax highlighting if (window.Prism) { Prism.highlightAllUnder(document.getElementById('markdownContent')); } // Smooth scroll for TOC links this.setupTableOfContentsNavigation(); } // Generate table of contents generateTableOfContents(content) { const headings = content.match(/^#{1,3}\s+(.+)/gm) || []; return headings.map(heading => { const level = heading.match(/^#+/)[0].length; const text = heading.replace(/^#+\s+/, ''); const id = text.toLowerCase().replace(/[^a-z0-9]+/g, '-'); return ` <div class="toc-item toc-level-${level}"> <a href="#${id}" onclick="blogManager.scrollToHeading('${id}')">${text}</a> </div> `; }).join(''); } // Setup TOC navigation setupTableOfContentsNavigation() { const headings = document.querySelectorAll('.markdown-content h1, .markdown-content h2, .markdown-content h3'); headings.forEach(heading => { const id = heading.textContent.toLowerCase().replace(/[^a-z0-9]+/g, '-'); heading.id = id; }); } // Scroll to heading scrollToHeading(id) { const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth' }); } } // Render related posts renderRelatedPosts() { const relatedPosts = this.posts .filter(post => post.id !== this.currentPost.id && (post.category === this.currentPost.category || post.tags.some(tag => this.currentPost.tags.includes(tag))) ) .slice(0, 3); return relatedPosts.map(post => ` <div class="related-post" onclick="blogManager.openPost('${post.id}')"> <img src="${post.image}" alt="${post.title}"> <div class="related-content"> <h4>${post.title}</h4> <p>${post.excerpt.substring(0, 100)}...</p> </div> </div> `).join(''); } // Close full post view closeFullPost() { this.currentPost = null; this.renderBlogInterface(); } // Filter and search functionality handleSearch(query) { this.searchQuery = query.toLowerCase(); this.renderBlogInterface(); } handleCategoryFilter(category) { this.selectedCategory = category; this.renderBlogInterface(); } handleSort(sortBy) { this.sortBy = sortBy; this.renderBlogInterface(); } getFilteredPosts() { let filtered = [...this.posts]; // Apply search filter if (this.searchQuery) { filtered = filtered.filter(post => post.title.toLowerCase().includes(this.searchQuery) || post.excerpt.toLowerCase().includes(this.searchQuery) || post.tags.some(tag => tag.toLowerCase().includes(this.searchQuery)) ); } // Apply category filter if (this.selectedCategory !== 'All') { filtered = filtered.filter(post => post.category === this.selectedCategory); } // Apply sorting filtered.sort((a, b) => { switch (this.sortBy) { case 'date': return new Date(b.date) - new Date(a.date); case 'title': return a.title.localeCompare(b.title); case 'readTime': return parseInt(a.readTime) - parseInt(b.readTime); default: return 0; } }); return filtered; } // Utility functions formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } sharePost() { if (navigator.share && this.currentPost) { navigator.share({ title: this.currentPost.title, text: this.currentPost.excerpt, url: window.location.href }); } else { // Fallback: copy URL to clipboard navigator.clipboard.writeText(window.location.href).then(() => { youTubeManager.showNotification('Link copied to clipboard!', 'success'); }); } } bookmarkPost() { if (!this.currentPost) return; const bookmarks = JSON.parse(localStorage.getItem('bookmarkedPosts') || '[]'); const isBookmarked = bookmarks.some(b => b.id === this.currentPost.id); if (isBookmarked) { const updated = bookmarks.filter(b => b.id !== this.currentPost.id); localStorage.setItem('bookmarkedPosts', JSON.stringify(updated)); youTubeManager.showNotification('Bookmark removed', 'info'); } else { bookmarks.push({ id: this.currentPost.id, title: this.currentPost.title, url: window.location.href, timestamp: new Date().toISOString() }); localStorage.setItem('bookmarkedPosts', JSON.stringify(bookmarks)); youTubeManager.showNotification('Post bookmarked!', 'success'); } } printPost() { if (this.currentPost) { window.print(); } } } // Initialize blog manager const blogManager = new BlogManager(); // Export for global use window.blogManager = blogManager; // CSS for blog system const blogCSS = ` .blog-header { margin-bottom: 3rem; } .blog-controls { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; background: var(--bg-secondary); padding: 1.5rem; border-radius: 1rem; border: 1px solid var(--border-color); } .search-container { position: relative; flex: 1; min-width: 300px; } .search-input { width: 100%; padding: 0.75rem 1rem 0.75rem 2.5rem; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 0.5rem; color: var(--text-primary); font-size: 1rem; } .search-input:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 2px rgba(0, 255, 0, 0.2); } .search-icon { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); color: var(--text-muted); } .filter-container, .sort-container { display: flex; align-items: center; gap: 0.5rem; } .category-filter, .sort-select { padding: 0.75rem 1rem; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 0.5rem; color: var(--text-primary); cursor: pointer; } .featured-posts, .all-posts { margin-bottom: 4rem; } .featured-posts h2, .all-posts h2 { color: var(--primary-color); margin-bottom: 2rem; font-size: 2rem; text-align: center; } .featured-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 2rem; margin-bottom: 3rem; } .posts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 2rem; } .post-card { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 1rem; overflow: hidden; transition: all 0.3s ease; cursor: pointer; height: fit-content; } .post-card:hover { transform: translateY(-5px); box-shadow: 0 15px 40px var(--shadow-secondary); border-color: var(--primary-color); } .featured-post { border: 2px solid var(--primary-color); background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary)); } .post-image { position: relative; height: 200px; overflow: hidden; } .post-image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s ease; } .post-card:hover .post-image img { transform: scale(1.05); } .post-category { position: absolute; top: 1rem; left: 1rem; background: var(--primary-color); color: var(--bg-primary); padding: 0.25rem 0.75rem; border-radius: 1rem; font-size: 0.8rem; font-weight: 600; } .post-content { padding: 1.5rem; } .post-title { color: var(--primary-color); margin-bottom: 1rem; font-size: 1.3rem; line-height: 1.4; } .post-excerpt { color: var(--text-secondary); margin-bottom: 1.5rem; line-height: 1.6; } .post-meta { display: flex; gap: 1rem; align-items: center; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-muted); flex-wrap: wrap; } .post-meta > div { display: flex; align-items: center; gap: 0.25rem; } .post-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; } .tag { background: rgba(0, 255, 0, 0.1); color: var(--primary-color); padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.8rem; border: 1px solid rgba(0, 255, 0, 0.3); } /* Full Post Styles */ .full-post { max-width: 1000px; margin: 0 auto; } .post-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border-color); } .back-btn { display: flex; align-items: center; gap: 0.5rem; background: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem 1rem; border-radius: 0.5rem; cursor: pointer; transition: all 0.3s ease; text-decoration: none; } .back-btn:hover { background: var(--primary-color); color: var(--bg-primary); } .post-meta-header { display: flex; gap: 1rem; align-items: center; } .post-hero { position: relative; height: 400px; border-radius: 1rem; overflow: hidden; margin-bottom: 3rem; } .hero-image { width: 100%; height: 100%; object-fit: cover; } .hero-content { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(transparent, rgba(0, 0, 0, 0.8)); padding: 2rem; color: white; } .hero-content .post-title { color: white; font-size: 2.5rem; margin-bottom: 1rem; } .post-content { display: grid; grid-template-columns: 250px 1fr; gap: 3rem; align-items: start; } .content-sidebar { position: sticky; top: 100px; } .table-of-contents { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 0.5rem; padding: 1.5rem; margin-bottom: 2rem; } .table-of-contents h4 { color: var(--primary-color); margin-bottom: 1rem; font-size: 1.1rem; } .toc-item { margin-bottom: 0.5rem; } .toc-item a { color: var(--text-secondary); text-decoration: none; font-size: 0.9rem; transition: color 0.3s ease; } .toc-item a:hover { color: var(--primary-color); } .toc-level-1 { margin-left: 0; font-weight: 600; } .toc-level-2 { margin-left: 1rem; } .toc-level-3 { margin-left: 2rem; font-size: 0.8rem; } .post-actions { display: flex; flex-direction: column; gap: 0.5rem; } .action-btn { display: flex; align-items: center; gap: 0.5rem; background: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 0.5rem; cursor: pointer; transition: all 0.3s ease; font-size: 0.9rem; } .action-btn:hover { background: var(--primary-color); color: var(--bg-primary); } .content-main { min-width: 0; } .markdown-content { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 1rem; padding: 2rem; line-height: 1.7; } .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4 { color: var(--primary-color); margin: 2rem 0 1rem 0; } .markdown-content h1 { font-size: 2rem; } .markdown-content h2 { font-size: 1.6rem; } .markdown-content h3 { font-size: 1.3rem; } .markdown-content p { margin-bottom: 1.5rem; color: var(--text-secondary); } .markdown-content ul, .markdown-content ol { margin-bottom: 1.5rem; padding-left: 2rem; } .markdown-content li { margin-bottom: 0.5rem; color: var(--text-secondary); } .markdown-content blockquote { border-left: 4px solid var(--primary-color); background: rgba(0, 255, 0, 0.1); padding: 1rem 1.5rem; margin: 1.5rem 0; border-radius: 0 0.5rem 0.5rem 0; } .markdown-content blockquote p { margin: 0; font-style: italic; } .markdown-content pre { background: #1e1e1e; padding: 1.5rem; border-radius: 0.5rem; overflow-x: auto; margin: 1.5rem 0; border: 1px solid var(--border-color); } .markdown-content code { font-family: 'Fira Code', monospace; font-size: 0.9em; } .markdown-content :not(pre) > code { background: rgba(0, 255, 0, 0.1); padding: 0.2rem 0.4rem; border-radius: 0.25rem; color: var(--primary-color); } .post-footer { margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--border-color); } .author-bio { display: flex; gap: 1rem; background: var(--bg-tertiary); padding: 1.5rem; border-radius: 0.5rem; margin-bottom: 3rem; } .author-avatar { font-size: 3rem; color: var(--primary-color); } .author-info h4 { color: var(--primary-color); margin-bottom: 0.5rem; } .author-info p { color: var(--text-secondary); margin-bottom: 1rem; } .social-links { display: flex; gap: 1rem; } .social-links a { color: var(--text-muted); font-size: 1.2rem; transition: color 0.3s ease; } .social-links a:hover { color: var(--primary-color); } .related-posts h3 { color: var(--primary-color); margin-bottom: 1.5rem; } .related-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; } .related-post { background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 0.5rem; overflow: hidden; cursor: pointer; transition: all 0.3s ease; } .related-post:hover { transform: translateY(-2px); border-color: var(--primary-color); } .related-post img { width: 100%; height: 120px; object-fit: cover; } .related-content { padding: 1rem; } .related-content h4 { color: var(--primary-color); margin-bottom: 0.5rem; font-size: 1rem; } .related-content p { color: var(--text-muted); font-size: 0.9rem; line-height: 1.4; } /* Responsive Design */ @media (max-width: 1024px) { .post-content { grid-template-columns: 1fr; gap: 2rem; } .content-sidebar { position: static; order: 2; } .table-of-contents { display: none; } } @media (max-width: 768px) { .blog-controls { flex-direction: column; align-items: stretch; } .search-container { min-width: auto; } .featured-grid, .posts-grid { grid-template-columns: 1fr; } .post-header { flex-direction: column; gap: 1rem; align-items: flex-start; } .hero-content .post-title { font-size: 1.8rem; } .related-grid { grid-template-columns: 1fr; } .author-bio { flex-direction: column; text-align: center; } } /* Print Styles */ @media print { .content-sidebar, .post-header, .post-footer { display: none !important; } .post-content { grid-template-columns: 1fr; } .markdown-content { background: white; border: none; padding: 0; } } `; // Add CSS to document const blogStyle = document.createElement('style'); blogStyle.textContent = blogCSS; document.head.appendChild(blogStyle); export { blogManager };