---
layout: default
---

<!-- Theme Toggle Button -->
<div class="theme-toggle-container" style="position: fixed; top: 20px; right: 20px; z-index: 1000;">
  <button id="theme-toggle" class="btn btn-sm" style="background-color: var(--primary-btnbg-color); color: white; border: none; border-radius: 20px; padding: 8px 16px;">
    <i class="fas fa-moon" id="theme-icon"></i>
  </button>
</div>

<article class="lab-content">
    <div class="hidden-when-embedded">
      <h1>{{ page.title }}</h1>
      {% if page.description %}
        <p class="lab-description">{{ page.description }}</p>
      {% endif %}
      
      {% if page.overview %}
        <h2>Lab Overview</h2>
        <div class="overview-content">{{ page.overview | markdownify }}</div>
      {% endif %}
    </div>
    
    <div class="lab-metadata hidden-when-embedded">
      {% if page.author %}
        <div class="metadata-item">
          <strong>{% if page.author.first %}Authors:{% else %}Author:{% endif %}</strong> 
          {% if page.author.first %}
            {% assign author_count = page.author.size %}
            {% for author in page.author %}
              {% if forloop.last and author_count > 1 %}and {% endif %}{{ author }}{% unless forloop.last %}, {% endunless %}
            {% endfor %}
          {% else %}
            {{ page.author }}
          {% endif %}
        </div>
      {% endif %}
      {% if page.license %}
        <div class="metadata-item">
          <strong>License:</strong> {{ page.license }}
        </div>
      {% endif %}
      {% if page.difficulty %}
        <div class="metadata-item">
          <strong>Difficulty:</strong> {{ page.difficulty }}
        </div>
      {% endif %}
      {% if page.duration %}
        <div class="metadata-item">
          <strong>Estimated Duration:</strong> {{ page.duration }}
        </div>
      {% endif %}
      {% if page.prerequisites %}
        <div class="metadata-item">
          <strong>Prerequisites:</strong> {{ page.prerequisites }}
        </div>
      {% endif %}
      <div class="hidden-when-embedded">
      {% if page.cybok %}
        <div class="cybok">
          <strong>CyBOK Knowledge Areas:</strong>
          {% for cybok_item in page.cybok %}
            <span class="cybok-ka">{{ cybok_item.ka }}: {{ cybok_item.topic }}</span>
            {% for keyword in cybok_item.keywords %}
              <span class="cybok-keyword">{{ keyword }}</span>
            {% endfor %}
          {% endfor %}
        </div>
      {% endif %}
      {% if page.tags %}
          <div class="tags">
            <strong>Tags:</strong>
            {% for tag in page.tags %}
              <span class="tag">{{ tag }}</span>
            {% endfor %}
          </div>
      {% endif %}
      </div>

    </div>
  <br />

  <div class="lab-content-body">
    <div id="toc-container" class="toc-container">
      <h2>Contents</h2>
      <ul id="toc-list"></ul>
    </div>
    {{ content }}
  </div>

  <footer class="lab-footer">
    <a href="https://hacktivity.co.uk" class="back-link">← Back to Hacktivity</a>
  </footer>
</article>

<style>
.lab-content {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

.embedded-mode .lab-content {
  max-width: 100%;
}
.embedded-mode .hidden-when-embedded {
  display: none;
}

.lab-header {
  border-bottom: 2px solid var(--panelborder-color);
  padding-bottom: 1.5rem;
  margin-bottom: 2rem;
}

.lab-header h1 {
  margin-bottom: 0.5rem;
  color: var(--fg-color);
  font-weight: normal;
}

.lab-description {
  font-size: 1.125rem;
  color: var(--fg-color);
  opacity: 0.8;
  margin-bottom: 1rem;
}


.overview-content {
  color: var(--fg-color);
  line-height: 1.6;
}


.lab-metadata {
  display: grid;
  gap: 0.5rem;
  background-color: var(--panelbg-color);
  padding: 1rem;
  border-radius: 6px;
  border: 1px solid var(--panelborder-color);
}

.metadata-item {
  font-size: 0.875rem;
  color: var(--fg-color);
}

.metadata-item strong {
  color: var(--fg-color);
  font-weight: 600;
}


.lab-content-body {
  line-height: 1.6;
  color: var(--fg-color);
}

.lab-content-body h2 {
  border-bottom: 1px solid var(--panelborder-color);
  padding-bottom: 0.3rem;
  margin-top: 2rem;
  margin-bottom: 1rem;
  color: var(--fg-color);
}

.lab-content-body h3 {
  margin-top: 1.5rem;
  margin-bottom: 0.75rem;
  color: var(--fg-color);
}

.lab-footer {
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--panelborder-color);
}

.back-link {
  color: var(--link-color);
  text-decoration: none;
  font-weight: 500;
}

.back-link:hover {
  text-decoration: underline;
  color: var(--primary-btnhov-color);
}

.toc-container {
  background-color: var(--panelbg-color);
  border: 1px solid var(--panelborder-color);
  border-radius: 6px;
  padding: 1rem;
  margin-bottom: 2rem;
}

.toc-container h3 {
  margin-top: 0;
  margin-bottom: 1rem;
  color: var(--fg-color);
  font-size: 1.1rem;
  border-bottom: 1px solid var(--panelborder-color);
  padding-bottom: 0.5rem;
}

#toc-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

#toc-list li {
  margin: 0.25rem 0;
}

#toc-list a {
  color: var(--link-color);
  text-decoration: none;
  display: block;
  padding: 0.25rem 0;
  border-radius: 3px;
  transition: background-color 0.2s;
}

#toc-list a:hover {
  background-color: var(--highlight-color);
  text-decoration: none;
}

/* Indentation based on heading level */
#toc-list li[data-level="1"] { padding-left: 0; }
#toc-list li[data-level="2"] { padding-left: 1rem; }
#toc-list li[data-level="3"] { padding-left: 2rem; }
#toc-list li[data-level="4"] { padding-left: 3rem; }
#toc-list li[data-level="5"] { padding-left: 4rem; }
#toc-list li[data-level="6"] { padding-left: 5rem; }
</style>

<script>
// Theme toggle functionality
document.addEventListener('DOMContentLoaded', function() {
  const themeToggle = document.getElementById('theme-toggle');
  const themeIcon = document.getElementById('theme-icon');
  const body = document.body;

  // Check for saved theme preference or default to dark mode
  const currentTheme = localStorage.getItem('theme') || 'dark';
  body.setAttribute('data-theme', currentTheme);
  updateThemeIcon(currentTheme);

  themeToggle.addEventListener('click', function() {
    const currentTheme = body.getAttribute('data-theme');
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

    body.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
    updateThemeIcon(newTheme);
  });

  function updateThemeIcon(theme) {
    if (theme === 'dark') {
      themeIcon.className = 'fas fa-sun';
    } else {
      themeIcon.className = 'fas fa-moon';
    }
  }
});

            // Process ==highlight== syntax and > TIP: patterns
            document.addEventListener('DOMContentLoaded', function() {
              const contentBody = document.querySelector('.lab-content-body');
              if (contentBody) {
                // Replace specific highlight types first
                contentBody.innerHTML = contentBody.innerHTML.replace(/==action:\s*([^=]+)==/gi, '<span class="action-highlight">⚡ $1</span>');
                contentBody.innerHTML = contentBody.innerHTML.replace(/==tip:\s*([^=]+)==/gi, '<span class="tip-highlight">💡 $1</span>');
                contentBody.innerHTML = contentBody.innerHTML.replace(/==hint:\s*([^=]+)==/gi, '<span class="hint-highlight">💭 $1</span>');
                contentBody.innerHTML = contentBody.innerHTML.replace(/==note:\s*([^=]+)==/gi, '<span class="note-highlight">📝 $1</span>');
                contentBody.innerHTML = contentBody.innerHTML.replace(/==warning:\s*([^=]+)==/gi, '<span class="warning-highlight">⚠️ $1</span>');
                contentBody.innerHTML = contentBody.innerHTML.replace(/==VM:\s*([^=]+)==/gi, '<span class="vm-highlight">🖥️ $1</span>');
                contentBody.innerHTML = contentBody.innerHTML.replace(/==question:\s*([^=]+)==/gi, '<span class="question-highlight">❓ $1</span>');
                
                // Process edit highlights BEFORE other processing to avoid syntax highlighting interference
                // But exclude content that's already inside block-level elements (hint, tip, etc.)
                // Handle both regular quotes and HTML-encoded quotes
                contentBody.innerHTML = contentBody.innerHTML.replace(/==edit:\s*([^=]+)==/gi, function(match, p1, offset, string) {
                  // Check if this match is inside a blockquote or other block element
                  const beforeMatch = string.substring(0, offset);
                  const afterMatch = string.substring(offset + match.length);
                  
                  // Count unclosed blockquote tags before this position
                  const openBlockquotes = (beforeMatch.match(/<blockquote>/g) || []).length;
                  const closedBlockquotes = (beforeMatch.match(/<\/blockquote>/g) || []).length;
                  
                  // If we're inside a blockquote, don't process this edit highlight
                  if (openBlockquotes > closedBlockquotes) {
                    return match; // Return unchanged
                  }
                  
                  return '<span class="edit-highlight">✏️ ' + p1 + '</span>';
                });
                
                // Also handle HTML-encoded quotes (like &quot;==edit:content==&quot;)
                contentBody.innerHTML = contentBody.innerHTML.replace(/&quot;==edit:\s*([^=]+)==&quot;/gi, function(match, p1, offset, string) {
                  // Check if this match is inside a blockquote or other block element
                  const beforeMatch = string.substring(0, offset);
                  
                  // Count unclosed blockquote tags before this position
                  const openBlockquotes = (beforeMatch.match(/<blockquote>/g) || []).length;
                  const closedBlockquotes = (beforeMatch.match(/<\/blockquote>/g) || []).length;
                  
                  // If we're inside a blockquote, don't process this edit highlight
                  if (openBlockquotes > closedBlockquotes) {
                    return match; // Return unchanged
                  }
                  
                  return '&quot;<span class="edit-highlight">✏️ ' + p1 + '</span>&quot;';
                });
                
                // Replace generic ==text== with <mark>text</mark> (but not edit highlights)
                // But exclude content that's already inside block-level elements
                contentBody.innerHTML = contentBody.innerHTML.replace(/==(?!edit:)([^=]+)==/g, function(match, p1, offset, string) {
                  // Check if this match is inside a blockquote or other block element
                  const beforeMatch = string.substring(0, offset);
                  
                  // Count unclosed blockquote tags before this position
                  const openBlockquotes = (beforeMatch.match(/<blockquote>/g) || []).length;
                  const closedBlockquotes = (beforeMatch.match(/<\/blockquote>/g) || []).length;
                  
                  // If we're inside a blockquote, don't process this highlight
                  if (openBlockquotes > closedBlockquotes) {
                    return match; // Return unchanged
                  }
                  
                  return '<mark>' + p1 + '</mark>';
                });
                
                // Replace > TIP: patterns with tip-item divs
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*<em>Tip:<\/em>\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="tip-item">$1</div>'
                );
                
                // Handle > *Tip: ANYTHINGHERE* (entire content in italics)
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*<em>Tip:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/em>\s*<\/p>\s*<\/blockquote>/gi,
                  '<div class="tip-item">$1</div>'
                );
                
                // Also handle > TIP: without italics
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*Tip:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="tip-item">$1</div>'
                );
                
                // Handle block-level action, warning, note, hint, flag patterns
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*Action:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="action-item">$1</div>'
                );
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*Warning:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="warning-item">$1</div>'
                );
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*Note:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="note-item">$1</div>'
                );
                
                // Post-process note items to handle ==edit:== syntax that was preserved
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<div class="note-item">([^<]*(?:<[^>]+>[^<]*)*?)<\/div>/gi,
                  function(match, content) {
                    // Process any remaining ==edit:== syntax in note content
                    let processedContent = content.replace(/==edit:\s*([^=]+)==/gi, '<span class="edit-highlight">✏️ $1</span>');
                    // Also handle HTML-encoded quotes
                    processedContent = processedContent.replace(/&quot;==edit:\s*([^=]+)==&quot;/gi, '&quot;<span class="edit-highlight">✏️ $1</span>&quot;');
                    return '<div class="note-item">' + processedContent + '</div>';
                  }
                );
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*Hint:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="hint-item">Hint: $1</div>'
                );
                
                // Post-process hint items to handle ==edit:== syntax that was preserved
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<div class="hint-item">Hint: ([^<]*(?:<[^>]+>[^<]*)*?)<\/div>/gi,
                  function(match, content) {
                    // Process any remaining ==edit:== syntax in hint content
                    let processedContent = content.replace(/==edit:\s*([^=]+)==/gi, '<span class="edit-highlight">✏️ $1</span>');
                    // Also handle HTML-encoded quotes
                    processedContent = processedContent.replace(/&quot;==edit:\s*([^=]+)==&quot;/gi, '&quot;<span class="edit-highlight">✏️ $1</span>&quot;');
                    return '<div class="hint-item">Hint: ' + processedContent + '</div>';
                  }
                );
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*Question:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="question-item">$1</div>'
                );
                contentBody.innerHTML = contentBody.innerHTML.replace(
                  /<blockquote>\s*<p>\s*Flag:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
                  '<div class="flag-item">$1</div>'
                );
                
                // Add copy functionality to code blocks
                const codeBlocks = contentBody.querySelectorAll('pre code');
                codeBlocks.forEach(block => {
                  const pre = block.parentElement;
                  if (pre.classList.contains('highlight') || pre.closest('.highlight')) {
                    pre.style.position = 'relative';
                    const copyBtn = document.createElement('button');
                    copyBtn.textContent = 'Copy';
                    copyBtn.className = 'copy-btn';
                    copyBtn.style.cssText = `
                      position: absolute;
                      top: 0.5rem;
                      right: 0.5rem;
                      background: #007bff;
                      color: white;
                      border: none;
                      padding: 0.25rem 0.5rem;
                      border-radius: 3px;
                      font-size: 0.75rem;
                      cursor: pointer;
                      opacity: 0;
                      transition: all 0.2s ease;
                      z-index: 10;
                    `;
                    copyBtn.addEventListener('click', () => {
                      navigator.clipboard.writeText(block.textContent).then(() => {
                        copyBtn.textContent = 'Copied!';
                        setTimeout(() => copyBtn.textContent = 'Copy', 2000);
                      });
                    });
                    pre.addEventListener('mouseenter', () => copyBtn.style.opacity = '1');
                    pre.addEventListener('mouseleave', () => copyBtn.style.opacity = '0');
                    pre.appendChild(copyBtn);
                  }
                });
                
                // Process edit highlights that got broken up by syntax highlighting
                // 
                // PROBLEM: Syntax highlighters (like Pygments) break up ==edit:content== patterns
                // across multiple HTML spans, making them impossible for regex to match.
                // 
                // COMMON PATTERNS TO HANDLE:
                // 1. Quoted: "==edit:content==" → <span>"==edit:</span><span>content</span><span>=="</span>
                // 2. Unquoted: ==edit:content== → <span>==</span><span>edit</span><span>:content</span><span>==</span>
                // 3. HTML-encoded: &quot;==edit:content==&quot; → <span>&quot;==edit:</span><span>content</span><span>=="</span>
                // 4. Mixed classes: Different CSS classes (s2, se, o, n, ss) depending on content type
                //
                // SOLUTION: Add regex patterns for each common breakup pattern
                // TROUBLESHOOTING: If new patterns don't work, inspect the HTML output to see
                // how the syntax highlighter is breaking up the ==edit:== patterns
                const codeElements = contentBody.querySelectorAll('code, pre code, .highlight code');
                codeElements.forEach(codeElement => {
                  // Look for the pattern: <span class="o">==</span>edit:content<span class="o">==</span>
                  const html = codeElement.innerHTML;
                  if (html.includes('==') && html.includes('edit:')) {
                    // Debug: Log the HTML structure to help troubleshoot
                    if (html.includes('==edit:') || html.includes('==<span')) {
                      console.log('Found ==edit: pattern in code block:', html);
                    }
                    
                    // Debug: Check for the specific Ruby pattern
                    if (html.includes('<span class="o">==</span><span class="n">edit</span><span class="ss">:')) {
                      console.log('Found Ruby ==edit: pattern:', html);
                      console.log('Attempting to match pattern...');
                    }
                    // PATTERN 1: Basic syntax highlighting breakup
                    // Handles: <span class="o">==</span>edit:content<span class="o">==</span>
                    codeElement.innerHTML = html.replace(
                      /<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                    
                    // PATTERN 2: With mark tags in the middle (from previous highlighting)
                    // Handles: <span class="o">==</span>edit:content<span class="o"><mark></mark></span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+(?:<[^>]+>[^<]*)*?)<span[^>]*class="[^"]*o[^"]*"[^>]*><mark><\/mark><\/span>/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                    
                    // PATTERN 3: Specific mark tag pattern
                    // Handles: ==edit:content<span class="o"><mark></mark></span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)<span[^>]*class="[^"]*o[^"]*"[^>]*><mark><\/mark><\/span>/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                    
                    // PATTERN 4: Variable highlighting in the middle
                    // Handles: ==edit:content <span class="nv">VAR</span>==
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)\s*<span[^>]*class="[^"]*nv[^"]*"[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
                      '<span class="edit-highlight">✏️$1 $2</span>'
                    );
                    // PATTERN 5: Quoted strings with s2 class
                    // Handles: <span class="s2">==edit:</span>content<span class="s2">==</span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*s[^"]*"[^>]*>==edit:<\/span>([^<]+)<span[^>]*class="[^"]*s[^"]*"[^>]*>==<\/span>/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                    
                    // PATTERN 5B: Single quotes with s1 class
                    // Handles: <span class="s1">'==edit:</span>content<span class="s1">=='</span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*s[^"]*"[^>]*>'==edit:<\/span>([^<]+)<span[^>]*class="[^"]*s[^"]*"[^>]*>=='<\/span>/gi,
                      '<span class="s1">\'</span><span class="edit-highlight">✏️$1</span><span class="s1">\'</span>'
                    );
                    
                    // PATTERN 6: Single span with entire pattern
                    // Handles: <span class="s2">==edit:content==</span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*s[^"]*"[^>]*>==edit:([^<]+)==<\/span>/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                    
                    // PATTERN 7: Quoted with hex content (se class for escape sequences)
                    // Handles: <span class="s2">"==edit:</span><span class="se">\x78\x56</span><span class="s2">=="</span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*s[^"]*"[^>]*>"==edit:<\/span><span[^>]*class="[^"]*se[^"]*"[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*s[^"]*"[^>]*>=="<\/span>/gi,
                      '<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
                    );
                    
                    // PATTERN 8: Different class combinations
                    // Handles: Various class combinations for quoted patterns
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*s[^"]*"[^>]*>"==edit:<\/span><span[^>]*class="[^"]*[^"]*"[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*s[^"]*"[^>]*>=="<\/span>/gi,
                      '<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
                    );
                    
                    // PATTERN 9: Quoted patterns with mixed classes
                    // Handles: <span class="s2">"==edit:</span><span>content</span><span class="s2">=="</span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*s[^"]*"[^>]*>"==edit:<\/span><span[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*s[^"]*"[^>]*>=="<\/span>/gi,
                      '<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
                    );
                    
                    // PATTERN 10: Most general quoted case
                    // Handles: Any span structure with "==edit:" pattern
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*>"==edit:<\/span><span[^>]*>([^<]+)<\/span><span[^>]*>=="<\/span>/gi,
                      '<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
                    );
                    
                    // PATTERN 11: HTML-encoded quotes
                    // Handles: &quot;==edit:content==&quot; patterns
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*>&quot;==edit:<\/span><span[^>]*>([^<]+)<\/span><span[^>]*>=="<\/span>/gi,
                      '<span class="s2">&quot;</span><span class="edit-highlight">✏️$1</span><span class="s2">&quot;</span>'
                    );
                    
                    // PATTERN 12: Unquoted with specific class breakdown
                    // Handles: <span class="o">==</span><span class="n">edit</span><span class="ss">:offset</span><span class="o">==</span>
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span><span[^>]*class="[^"]*n[^"]*"[^>]*>edit<\/span><span[^>]*class="[^"]*ss[^"]*"[^>]*>:([^<]+)<\/span><span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                    
                    // PATTERN 13: General unquoted case
                    // Handles: Any span structure with ==edit: pattern (no quotes)
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /<span[^>]*>==<\/span><span[^>]*>edit<\/span><span[^>]*>:([^<]+)<\/span><span[^>]*>==<\/span>/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                    
                    // FALLBACK: Catch any remaining unprocessed patterns
                    // This should handle any patterns not caught by the above
                    codeElement.innerHTML = codeElement.innerHTML.replace(
                      /==edit:\s*([^=]+)==/gi,
                      '<span class="edit-highlight">✏️$1</span>'
                    );
                  }
                });
                
                // TROUBLESHOOTING GUIDE FOR FUTURE ISSUES:
                // ===========================================
                // If ==edit:== patterns aren't working in code blocks:
                // 
                // 1. INSPECT THE HTML: Right-click on the code block and "Inspect Element"
                //    Look for how the syntax highlighter broke up the ==edit:== pattern
                // 
                // 2. COMMON BREAKUP PATTERNS:
                //    - Quoted: "==edit:content==" → <span>"==edit:</span><span>content</span><span>=="</span>
                //    - Unquoted: ==edit:content== → <span>==</span><span>edit</span><span>:content</span><span>==</span>
                //    - HTML-encoded: &quot;==edit:content==&quot; → <span>&quot;==edit:</span><span>content</span><span>=="</span>
                // 
                // 3. CSS CLASSES TO LOOK FOR:
                //    - s2, s1: String literals
                //    - se: String escape sequences (\x78, \n, etc.)
                //    - o: Operators (==, +, etc.)
                //    - n: Names/identifiers
                //    - ss: String symbols
                //    - nv: Name variables
                // 
                // 4. ADDING NEW PATTERNS:
                //    - Copy an existing pattern above
                //    - Modify the regex to match the new HTML structure
                //    - Test with the specific case that's failing
                //    - Add a comment explaining what the pattern handles
                // 
                // 5. TESTING:
                //    - Use browser dev tools to see the actual HTML structure
                //    - Test with both quoted and unquoted ==edit:== patterns
                //    - Test with different content types (hex, text, variables)
                // 
                // 6. COMMON ISSUES:
                //    - Order matters: More specific patterns should come first
                //    - CSS class matching: Use [^"]* to match any class containing the target
                //    - HTML entities: Check for &quot; instead of " in patterns
                //    - Nested spans: Use (?:<[^>]+>[^<]*)*? for complex nested structures
                
                // Convert YouTube links to embedded videos
                const youtubeLinks = contentBody.querySelectorAll('a[href*="youtu.be"], a[href*="youtube.com/watch"]');
                youtubeLinks.forEach(link => {
                  const href = link.href;
                  let videoId = '';
                  
                  // Extract video ID from different YouTube URL formats
                  if (href.includes('youtu.be/')) {
                    videoId = href.split('youtu.be/')[1].split('?')[0];
                  } else if (href.includes('youtube.com/watch?v=')) {
                    videoId = href.split('v=')[1].split('&')[0];
                  }
                  
                  if (videoId) {
                    // Create embedded iframe
                    const iframe = document.createElement('iframe');
                    iframe.src = `https://www.youtube.com/embed/${videoId}`;
                    iframe.width = '560';
                    iframe.height = '315';
                    iframe.frameBorder = '0';
                    iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
                    iframe.allowFullscreen = true;
                    iframe.style.cssText = 'max-width: 100%; height: auto; border-radius: 8px; margin: 1rem 0; transition: all 0.3s ease;';
                    
                    // Create container div
                    const container = document.createElement('div');
                    container.className = 'youtube-embed';
                    container.style.cssText = 'text-align: center; margin: 1rem 0; cursor: pointer;';
                    
                    // Add hover effects with delay
                    let hoverTimeout = null;
                    
                    container.addEventListener('mouseenter', function() {
                      // Clear any existing timeout
                      if (hoverTimeout) {
                        clearTimeout(hoverTimeout);
                        hoverTimeout = null;
                      }
                      
                      iframe.style.transform = 'scale(1.05)';
                      iframe.style.height = '400px'; // Make it taller on hover
                    });
                    
                    container.addEventListener('mouseleave', function() {
                      // Set a timeout to return to normal size after 30 seconds
                      hoverTimeout = setTimeout(function() {
                        iframe.style.transform = 'scale(1)';
                        iframe.style.height = 'auto'; // Return to original height
                        hoverTimeout = null;
                      }, 30000); // 30 seconds delay
                    });
                    
                    // Add the link text as a caption
                    const caption = document.createElement('p');
                    caption.textContent = link.textContent;
                    caption.style.cssText = 'font-style: italic; color: var(--fg-color); opacity: 0.8; margin-top: 0.5rem;';
                    
                    container.appendChild(iframe);
                    container.appendChild(caption);
                    
                    // Replace the link with the embedded video
                    link.parentNode.replaceChild(container, link);
                  }
                });
              }
            });

// Auto-generate Table of Contents
document.addEventListener('DOMContentLoaded', function() {
  const tocList = document.getElementById('toc-list');
  const contentBody = document.querySelector('.lab-content-body');
  
  if (tocList && contentBody) {
    // Find all headings in the content
    const headings = contentBody.querySelectorAll('h1, h2, h3, h4, h5, h6');
    
    if (headings.length === 0) {
      // Hide TOC if no headings found
      document.getElementById('toc-container').style.display = 'none';
      return;
    }
    
    // Generate TOC items
    headings.forEach((heading, index) => {
      // Skip "Contents" heading
      if (heading.textContent.trim() === 'Contents') {
        return;
      }
      
      // Create ID if it doesn't exist
      if (!heading.id) {
        heading.id = heading.textContent
          .toLowerCase()
          .replace(/[^a-z0-9\s-]/g, '')
          .replace(/\s+/g, '-')
          .replace(/^-+|-+$/g, '');
      }
      
      // Create TOC item
      const li = document.createElement('li');
      const level = parseInt(heading.tagName.charAt(1));
      li.setAttribute('data-level', level);
      
      const a = document.createElement('a');
      a.href = '#' + heading.id;
      a.textContent = heading.textContent;
      
      // Add click handler for smooth scrolling
      a.addEventListener('click', function(e) {
        e.preventDefault();
        heading.scrollIntoView({ behavior: 'smooth' });
      });
      
      li.appendChild(a);
      tocList.appendChild(li);
    });
  }
});
</script>