project-root/
├── cat/
│   ├── bin/
│   │   └── cat.js
│   └── src/
│       └── compiler.js
├── index.html
├── package.json
└── cat.config.json

Syntax

```
{filename}.comp.html
<{comp name}>

</{comp name}>

<script>

</script>

<style>

</style>

{filename}.page.html
<page route="path">

</page>

Any .html file
<load route="path"
```

bin.js
```
#!/usr/bin/env node

const { program } = require('commander');
const path = require('path');
const fs = require('fs-extra');
const compiler = require('../src/compiler');
const express = require('express');

/**
 * Initialize the package.json and cat.config.json
 */
function initializeProject() {
  const projectRoot = process.cwd();
  const packageJsonPath = path.join(projectRoot, 'package.json');
  const configPath = path.join(projectRoot, 'cat.config.json');

  // Initialize package.json if it doesn't exist
  if (!fs.existsSync(packageJsonPath)) {
    const defaultPackageJson = {
      name: path.basename(projectRoot),
      version: "1.0.0",
      description: "A project using the Cat framework.",
      main: "index.html",
      scripts: {
        cat: "node ./cat/bin/cat.js"
      },
      dependencies: {
        "commander": "^10.0.0",
        "express": "^4.18.2",
        "fs-extra": "^11.1.1",
        "glob": "^10.3.3"
      },
      author: "Your Name",
      license: "MIT"
    };
    fs.writeJsonSync(packageJsonPath, defaultPackageJson, { spaces: 2 });
    console.log('✅ package.json has been created at the project root.');
    console.log('👉 Please run "npm install" to install the dependencies.');
  } else {
    console.log('ℹ️  package.json already exists.');
  }

  // Initialize cat.config.json if it doesn't exist
  if (!fs.existsSync(configPath)) {
    const defaultConfig = {
      root: 'index.html'
    };
    fs.writeJsonSync(configPath, defaultConfig, { spaces: 2 });
    console.log('✅ cat.config.json has been created at the project root.');
  } else {
    console.log('ℹ️  cat.config.json already exists.');
  }
}

/**
 * Start the development server
 */
async function startServer() {
  const projectRoot = process.cwd();
  const configPath = path.join(projectRoot, 'cat.config.json');

  if (!fs.existsSync(configPath)) {
    console.error('❌ Error: cat.config.json not found. Please run "npm run cat init" first.');
    process.exit(1);
  }

  const config = fs.readJsonSync(configPath);
  const indexPath = path.join(projectRoot, config.root);

  if (!fs.existsSync(indexPath)) {
    console.error(`❌ Error: Root file "${config.root}" not found.`);
    process.exit(1);
  }

  const app = express();
  const PORT = 3000;

  app.get('*', async (req, res) => {
    try {
      const compiledHTML = await compiler.compile(indexPath);
      res.send(compiledHTML);
    } catch (error) {
      console.error('❌ Compilation Error:', error);
      res.status(500).send('Internal Server Error');
    }
  });

  app.listen(PORT, () => {
    console.log(`🚀 Development server is running at http://localhost:${PORT}`);
  });
}

// Define the version
program.version('1.0.0');

// Define the init command
program
  .command('init')
  .description('Initialize package.json and cat.config.json at the project root')
  .action(initializeProject);

// Define the start command
program
  .command('start')
  .description('Start the development server')
  .action(startServer);

// Parse the command-line arguments
program.parse(process.argv);
```

compiler
```
// cat/src/compiler.js

const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');

/**
 * Parse a component file to extract tagName, HTML, script, and style sections.
 * @param {string} filePath - Path to the .comp.html file.
 * @returns {Promise<{ tagName: string, html: string, script: string, style: string }>} - Component details.
 */
async function parseComponent(filePath) {
  const content = await fs.readFile(filePath, 'utf-8');

  // Extract the outermost tag name
  const tagRegex = /<\s*([\w-]+)(\s+[^>]*)?>/i;
  const tagMatch = content.match(tagRegex);

  if (!tagMatch) {
    throw new Error(`No valid root tag found in ${path.basename(filePath)}`);
  }

  let tagName = tagMatch[1];

  // Ensure the tag name contains a hyphen
  if (!tagName.includes('-')) {
    tagName = `${tagName}-cat`;
  }

  // Extract HTML content inside the root tag
  const htmlRegex = new RegExp(`<${tagMatch[1]}[^>]*>([\\s\\S]*?)<\\/${tagMatch[1]}>`, 'i');
  const htmlMatch = content.match(htmlRegex);
  const htmlContent = htmlMatch ? htmlMatch[1].trim() : '';

  // Extract <script> section
  const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/i;
  const scriptMatch = content.match(scriptRegex);
  const scriptContent = scriptMatch ? scriptMatch[1].trim() : '';

  // Extract <style> section
  const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/i;
  const styleMatch = content.match(styleRegex);
  const styleContent = styleMatch ? styleMatch[1].trim() : '';

  return {
    tagName,
    html: htmlContent,
    script: scriptContent,
    style: styleContent
  };
}

/**
 * Identify reactive expressions within a string.
 * @param {string} str - The string to search for expressions.
 * @returns {Array<string>} - Array of expression names.
 */
function identifyExpressions(str) {
  const exprRegex = /\{([\w$]+)\}/g;
  const expressions = [];
  let match;
  while ((match = exprRegex.exec(str)) !== null) {
    expressions.push(match[1]);
  }
  return expressions;
}

/**
 * Generate the class name in PascalCase from a tag name.
 * @param {string} tagName - The custom element tag name.
 * @returns {string} - PascalCase class name.
 */
function toPascalCase(tagName) {
  return tagName
    .split('-')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join('');
}

/**
 * Escape backticks in templates to avoid breaking template literals.
 * @param {string} str - The template string.
 * @returns {string} - Escaped string.
 */
function escapeBackticks(str) {
  return str.replace(/`/g, '\\`');
}

/**
 * Generate the registration script for the used components.
 * @param {Map<string, string>} components - Map of tagName to compiled class code.
 * @returns {string} - JavaScript code for registering components.
 */
function generateRegistrationScript(components) {
  let script = '';

  components.forEach((classCode, tagName) => {
    script += `
${classCode}
customElements.define('${tagName}', ${toPascalCase(tagName)});
`;
  });

  return script;
}

/**
 * Compiler Function
 * @param {string} indexPath - Path to the index.html file.
 * @returns {Promise<string>} - Compiled HTML content with component registrations and page injections.
 */
async function compile(indexPath) {
  // Read the index.html content
  let indexContent = await fs.readFile(indexPath, 'utf-8');

  // Define project root
  const projectRoot = path.dirname(indexPath);

  // -----------------------
  // Step 1: Process Components
  // -----------------------

  // Find all .comp.html files in the project directory
  const compFiles = glob.sync('**/*.comp.html', {
    cwd: projectRoot,
    absolute: true,
    ignore: ['node_modules/**', 'cat/**'] // Adjust ignore patterns as needed
  });

  const componentMap = new Map(); // Map of tagName to compiled class code
  const parsedComponents = new Set(); // To track parsed components

  // Parse and compile each component file
  for (const file of compFiles) {
    try {
      const { tagName, html, script, style } = await parseComponent(file);

      if (parsedComponents.has(tagName)) {
        console.log(`ℹ️  Component <${tagName}> from ${path.relative(projectRoot, file)} has already been parsed.`);
        continue;
      }

      // Identify expressions in HTML
      const expressions = identifyExpressions(html);

      // Parse script to extract properties and methods
      const scriptLines = script.split('\n').map(line => line.trim()).filter(line => line);
      const properties = [];
      const methods = [];

      scriptLines.forEach(line => {
        // Match variable declarations: let name = 'value';
        const varMatch = line.match(/^let\s+([\w$]+)\s*=\s*['"`](.*)['"`];?$/);
        if (varMatch) {
          properties.push({ name: varMatch[1], value: varMatch[2] });
          return;
        }

        // Match function declarations: function funcName() { ... }
        const funcMatch = line.match(/^function\s+([\w$]+)\s*\(([^)]*)\)\s*\{([\s\S]*)\}$/);
        if (funcMatch) {
          const [, funcName, params, body] = funcMatch;
          methods.push({ name: funcName, params, body });
          return;
        }
      });

      // Generate class code
      const className = toPascalCase(tagName);
      let classCode = `class ${className} extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
`;

      // Initialize properties
      properties.forEach(prop => {
        classCode += `    this._${prop.name} = \`${escapeBackticks(prop.value)}\`;\n`;
      });

      // Build DOM
      classCode += `
    this._buildDOM();
  }

  _buildDOM() {
    const fragment = document.createDocumentFragment();

`;

      // Add styles
      if (style) {
        classCode += `    const style = document.createElement('style');
    style.textContent = \`
${escapeBackticks(style)}
    \`;
    fragment.appendChild(style);

`;
      }

      // Parse HTML and generate DOM creation code
      const domCode = generateDOMCode(html);
      classCode += domCode;

      classCode += `
    this.shadowRoot.appendChild(fragment);
  }

  connectedCallback() {
`;

      // Attach event listeners
      methods.forEach(method => {
        // Find event listeners in the DOM (e.g., onclick="methodName")
        // This implementation assumes that event listeners are specified with standard DOM event attributes
        // e.g., onclick="handleClick"
        // Extract all elements with event attributes and attach listeners
      });

      // Attach event listeners based on methods
      // For simplicity, we'll parse the methods and assume that event listeners are defined in the HTML with attributes like onclick="methodName"
      // This requires that during DOM generation, we store references to elements that have event listeners

      // Assuming that _eventListeners is an array of { element: reference, event: 'click', handler: 'methodName' }
      // We'll have to implement this during DOM generation

      // For this implementation, let's assume that event listeners are already attached during _buildDOM
      // Alternatively, you can enhance the DOM generation to collect event listeners and attach them here

      classCode += `  }

  disconnectedCallback() {
    // Clean up event listeners if necessary
  }

`;

      // Generate getters and setters for properties
      properties.forEach(prop => {
        // Getter
        classCode += `  get ${prop.name}() {
    return this._${prop.name};
  }

  set ${prop.name}(value) {
    if (this._${prop.name} !== value) {
      this._${prop.name} = value;
      this._update${toPascalCase(prop.name)}();
    }
  }

`;

        // Update method
        classCode += `  _update${toPascalCase(prop.name)}() {
    if (this._${prop.name}Node) {
      this._${prop.name}Node.textContent = \`${escapeBackticks('{')}${prop.name}${escapeBackticks('}')}\`.replace('{${prop.name}}', this._${prop.name});
    }
    if (this._${prop.name}Attr) {
      this._${prop.name}Attr.value = this._${prop.name};
    }
  }

`;
      });

      // Add methods
      methods.forEach(method => {
        classCode += `  ${method.name}(${method.params}) {
${method.body.split('\n').map(line => '    ' + line).join('\n')}
  }

`;
      });

      classCode += `}\n`;

      // Store the compiled class code
      componentMap.set(tagName, classCode);
      parsedComponents.add(tagName);

      console.log(`🔍 Parsed and compiled component: <${tagName}> from ${path.relative(projectRoot, file)}`);
    } catch (error) {
      console.error(`❌ Failed to parse ${file}: ${error.message}`);
    }
  }

  if (componentMap.size === 0) {
    console.warn('⚠️  No components found to compile.');
  }

  // -----------------------
  // Step 2: Detect Used Components
  // -----------------------

  // Find all .html files in the project directory for usage detection
  const htmlFiles = glob.sync('**/*.html', {
    cwd: projectRoot,
    absolute: true,
    ignore: ['node_modules/**', 'cat/**']
  });

  const htmlContents = [];
  for (const file of htmlFiles) {
    try {
      const content = await fs.readFile(file, 'utf-8');
      htmlContents.push(content);
    } catch (error) {
      console.error(`❌ Failed to read HTML file ${file}: ${error.message}`);
    }
  }

  // Detect which components are used across all HTML files
  const availableComponents = new Set(componentMap.keys());
  const usedComponents = detectUsedComponents(htmlContents, availableComponents);

  if (usedComponents.size === 0) {
    console.warn('⚠️  No used components found in HTML files.');
  } else {
    console.log(`📦 Used components: ${[...usedComponents].join(', ')}`);
  }

  // -----------------------
  // Step 3: Generate Registration Script
  // -----------------------

  const registrationScript = generateRegistrationScript(
    Array.from(usedComponents).reduce((map, tag) => map.set(tag, componentMap.get(tag)), new Map())
  );

  // -----------------------
  // Step 4: Inject Registration Script
  // -----------------------

  if (registrationScript.trim()) {
    // Inject the registration script before the closing </body> tag
    const scriptTag = `<script>
${registrationScript}
</script>
`;

    if (indexContent.includes('</body>')) {
      indexContent = indexContent.replace('</body>', `${scriptTag}</body>`);
    } else {
      // If no </body> tag, append the script at the end
      indexContent += `\n${scriptTag}`;
    }
    console.log('📝 Injected component registration scripts.');
  }

  // -----------------------
  // Step 5: Handle <load> Tags and Inject Page Content
  // -----------------------

  // Find and replace <load route="..."> tags with corresponding page content
  const loadTagRegex = /<load\s+[^>]*route=["']([^"']+)["'][^>]*>/gi;

  // Process <load> tags in the index.html
  indexContent = indexContent.replace(loadTagRegex, (match, route) => {
    // Path to the page content
    const pageFilePath = path.join(projectRoot, `${route}.page.html`);
    if (fs.existsSync(pageFilePath)) {
      const pageContent = fs.readFileSync(pageFilePath, 'utf-8');
      console.log(`🔄 Injected content for route "${route}"`);
      return pageContent;
    } else {
      console.error(`❌ No page found for route "${route}".`);
      return match; // Keep the <load> tag if no content is found
    }
  });

  // -----------------------
  // Step 6: Return Compiled Content
  // -----------------------

  return indexContent;
}

/**
 * Detect used components in the provided HTML contents by parsing the HTML tags.
 * @param {Array<string>} htmlContents - Array of HTML file contents.
 * @param {Set<string>} availableComponents - Set of all available component tag names.
 * @returns {Set<string>} - Set of used component tag names.
 */
function detectUsedComponents(htmlContents, availableComponents) {
  const usedComponents = new Set();

  // Regex to match all tags in the HTML
  const tagRegex = /<\s*([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*>/g;

  htmlContents.forEach(content => {
    let match;
    while ((match = tagRegex.exec(content)) !== null) {
      let tag = match[1];
      // If tag doesn't contain '-', append '-cat'
      if (!tag.includes('-')) {
        tag = `${tag}-cat`;
      }
      if (availableComponents.has(tag)) {
        usedComponents.add(tag);
      }
    }
  });

  return usedComponents;
}

/**
 * Generate DOM creation code from HTML.
 * This is a simplistic parser and may need enhancements for complex HTML structures.
 * @param {string} html - The inner HTML string of the component.
 * @returns {string} - JavaScript code for building the DOM.
 */
function generateDOMCode(html) {
  // This function parses the HTML and generates JavaScript code using document.createElement
  // For demonstration purposes, this is a rudimentary parser and may not handle all HTML features.
  // For a more robust solution, consider using an HTML parser library.

  // Split the HTML into lines and parse manually
  const lines = html.split('\n').map(line => line.trim()).filter(line => line);
  let code = '';

  // Stack to manage element hierarchy
  const stack = [];

  lines.forEach(line => {
    const openingTagMatch = line.match(/^<([a-zA-Z][\w-]*)([^>]*)>/);
    const closingTagMatch = line.match(/^<\/([a-zA-Z][\w-]*)>/);
    const selfClosingTagMatch = line.match(/^<([a-zA-Z][\w-]*)([^>]*)\/>/);

    if (selfClosingTagMatch) {
      const [ , tagName, attrs ] = selfClosingTagMatch;
      const varName = generateVarName(tagName);
      code += `    const ${varName} = document.createElement('${tagName}');\n`;

      // Handle attributes
      const attrRegex = /([a-zA-Z-:]+)="([^"]+)"/g;
      let attrMatch;
      while ((attrMatch = attrRegex.exec(attrs)) !== null) {
        const [ , attrName, attrValue ] = attrMatch;
        if (attrName.startsWith('on')) {
          // Event listener
          const event = attrName.substring(2);
          const handlerMatch = attrValue.match(/^([a-zA-Z][\w$]*)\((.*)\)$/);
          if (handlerMatch) {
            const handlerName = handlerMatch[1];
            const handlerArgs = handlerMatch[2];
            code += `    ${varName}.addEventListener('${event}', this.${handlerName}.bind(this));\n`;
          } else {
            code += `    ${varName}.addEventListener('${event}', this.${attrValue}.bind(this));\n`;
          }
        } else {
          // Check for reactive expressions in attributes
          const exprMatch = attrValue.match(/^\{([\w$]+)\}$/);
          if (exprMatch) {
            const exprName = exprMatch[1];
            code += `    this._${exprName}Attr = ${varName}.setAttribute('${attrName}', \`\${this.${exprName}}\`);\n`;
          } else {
            code += `    ${varName}.setAttribute('${attrName}', \`${escapeBackticks(attrValue)}\`);\n`;
          }
        }
      }

      stack[stack.length - 1].appendChild(varName);
      stack.push({ tagName, varName });
      stack.pop(); // Self-closing tag, no need to keep in stack
      code += `    ${stack[stack.length - 1].varName}.appendChild(${varName});\n`;
    } else if (openingTagMatch) {
      const [ , tagName, attrs ] = openingTagMatch;
      const varName = generateVarName(tagName);
      code += `    const ${varName} = document.createElement('${tagName}');\n`;

      // Handle attributes
      const attrRegex = /([a-zA-Z-:]+)="([^"]+)"/g;
      let attrMatch;
      while ((attrMatch = attrRegex.exec(attrs)) !== null) {
        const [ , attrName, attrValue ] = attrMatch;
        if (attrName.startsWith('on')) {
          // Event listener
          const event = attrName.substring(2);
          const handlerMatch = attrValue.match(/^([a-zA-Z][\w$]*)\((.*)\)$/);
          if (handlerMatch) {
            const handlerName = handlerMatch[1];
            const handlerArgs = handlerMatch[2];
            code += `    ${varName}.addEventListener('${event}', this.${handlerName}.bind(this));\n`;
          } else {
            code += `    ${varName}.addEventListener('${event}', this.${attrValue}.bind(this));\n`;
          }
        } else {
          // Check for reactive expressions in attributes
          const exprMatch = attrValue.match(/^\{([\w$]+)\}$/);
          if (exprMatch) {
            const exprName = exprMatch[1];
            code += `    this._${exprName}Attr = ${varName}.setAttribute('${attrName}', \`\${this.${exprName}}\`);\n`;
          } else {
            code += `    ${varName}.setAttribute('${attrName}', \`${escapeBackticks(attrValue)}\`);\n`;
          }
        }
      }

      // Handle text content or child elements
      // This simplistic parser assumes that text nodes come immediately after the opening tag
      // For complex HTML structures, consider using a proper HTML parser
      const textMatch = line.match(/^<[\w-]+[^>]*>\s*(.*?)\s*$/);
      if (textMatch && textMatch[1]) {
        const textContent = textMatch[1];
        // Check for reactive expressions in text
        const exprMatch = textContent.match(/^\{([\w$]+)\}$/);
        if (exprMatch) {
          const exprName = exprMatch[1];
          code += `    this._${exprName}Node = document.createTextNode(\`\${this.${exprName}}\`);\n`;
          code += `    ${varName}.appendChild(this._${exprName}Node);\n`;
        } else {
          code += `    ${varName}.textContent = \`${escapeBackticks(textContent)}\`;\n`;
        }
      }

      // Append to parent
      if (stack.length === 0) {
        code += `    fragment.appendChild(${varName});\n`;
      } else {
        code += `    ${stack[stack.length - 1].varName}.appendChild(${varName});\n`;
      }

      // Push to stack
      stack.push({ tagName, varName });
    } else if (closingTagMatch) {
      // Pop the last element from the stack
      stack.pop();
    } else {
      // Handle text nodes outside of tags
      const textContent = line;
      // Check for reactive expressions in text
      const exprMatch = textContent.match(/^\{([\w$]+)\}$/);
      if (exprMatch) {
        const exprName = exprMatch[1];
        const varName = generateVarName('text');
        code += `    const ${varName} = document.createTextNode(\`\${this.${exprName}}\`);\n`;
        code += `    this._${exprName}Node = ${varName};\n`;
        if (stack.length === 0) {
          code += `    fragment.appendChild(${varName});\n`;
        } else {
          code += `    ${stack[stack.length - 1].varName}.appendChild(${varName});\n`;
        }
      } else {
        code += `    const textNode = document.createTextNode(\`${escapeBackticks(textContent)}\`);\n`;
        if (stack.length === 0) {
          code += `    fragment.appendChild(textNode);\n`;
        } else {
          code += `    ${stack[stack.length - 1].varName}.appendChild(textNode);\n`;
        }
      }
    }
  });

  return code;
}

/**
 * Generate a unique variable name based on the tag name.
 * @param {string} tagName - The tag name of the element.
 * @returns {string} - A unique variable name.
 */
function generateVarName(tagName) {
  // Simple counter-based variable name generation
  if (!generateVarName.counter) {
    generateVarName.counter = {};
  }
  if (!generateVarName.counter[tagName]) {
    generateVarName.counter[tagName] = 1;
  } else {
    generateVarName.counter[tagName]++;
  }
  return `${tagName.replace(/-/g, '_')}_${generateVarName.counter[tagName]}`;
}

module.exports = { compile };
```

## Update Task

- **Parse Component Files**
    
    - Implement a parser to read `.comp.html` files and extract HTML, script, and style sections.
    - Identify expressions within attributes and text nodes using a pattern like `{expression}`.
    
- **Generate Class Structure**
    
    - Create a class for each component with a constructor that initializes default values.
    - Attach a shadow DOM to encapsulate styles and markup.
    
- **Build DOM Programmatically**
    
    - Convert the extracted HTML into JavaScript code that creates DOM elements using `document.createElement`.
    - Use `document.createDocumentFragment` for efficient initial rendering.
    - Cache node references for elements containing reactive expressions.
    
- **Create Getters and Setters**
    
    - For each reactive expression, generate a getter and setter in the class.
    - Ensure setters update the DOM directly using cached node references.
    - Use template literals for updating attributes and text content dynamically.
    
- **Implement Event Listeners**
    
    - Parse event listener definitions from the script section.
    - Attach event listeners to the appropriate DOM elements in the `connected and disconnect Callback`.

Compilation format sample
Component definition
```
<!-- greeting.comp.html -->
<greeting-card>
  <div class="card">
    <h1>Hello, {name}!</h1>
    <p>Today is {dayOfWeek}.</p>
    <button onclick="changeGreeting()">Change Greeting</button>
    <img src="{imageUrl}" alt="Greeting Image">
  </div>
</greeting-card>

<script>
let name = 'Alice';
let dayOfWeek = 'Monday';
let imageUrl = 'https://example.com/image.jpg';

function changeGreeting() {
  name = 'Bob';
}
</script>

<style>
.card {
  border: 1px solid #ccc;
  padding: 10px;
}
</style>
```

Compiled version
```
class GreetingCard extends HTMLElement {
  constructor() {
    super();
    this._name = 'Alice';
    this._dayOfWeek = 'Monday';
    this._imageUrl = 'https://example.com/image.jpg';

    this.attachShadow({ mode: 'open' });
    this._buildDOM();
  }

  _buildDOM() {
    const fragment = document.createDocumentFragment();
    const style = document.createElement('style');
    style.textContent = `
      .card {
        border: 1px solid #ccc;
        padding: 10px;
      }
    `;
    fragment.appendChild(style);

    const card = document.createElement('div');
    card.className = 'card';

    this._h1Name = document.createElement('h1');
    this._h1Name.textContent = `Hello, ${this._name}!`;
    card.appendChild(this._h1Name);

    this._pDayOfWeek = document.createElement('p');
    this._pDayOfWeek.textContent = `Today is ${this._dayOfWeek}.`;
    card.appendChild(this._pDayOfWeek);

    this._button = document.createElement('button');
    this._button.textContent = 'Change Greeting';
    card.appendChild(this._button);

    this._img = document.createElement('img');
    this._img.setAttribute('src', `${this._imageUrl}`);
    this._img.alt = 'Greeting Image';
    card.appendChild(this._img);

    fragment.appendChild(card);
    this.shadowRoot.appendChild(fragment);
  }

  connectedCallback() {
    this._button.addEventListener('click', this.changeGreeting.bind(this));
  }

  disconnectedCallback() {
    this._button.removeEventListener('click', this.changeGreeting.bind(this));
  }

  get name() { return this._name; }
  set name(value) {
    if (this._name !== value) {
      this._name = value;
      this._h1Name.textContent = `Hello, ${value}!`;
    }
  }

  get dayOfWeek() { return this._dayOfWeek; }
  set dayOfWeek(value) {
    if (this._dayOfWeek !== value) {
      this._dayOfWeek = value;
      this._pDayOfWeek.textContent = `Today is ${value}.`;
    }
  }

  get imageUrl() { return this._imageUrl; }
  set imageUrl(value) {
    if (this._imageUrl !== value) {
      this._imageUrl = value;
      this._img.setAttribute('src', `${value}`);
    }
  }

  changeGreeting() {
    this.name = 'Bob';
  }
}

customElements.define('greeting-card', GreetingCard);

---

Follow the task requirements exactly, the compiled output must be exact, don't change anything and make sure of not breaking the other features of the compiler.