<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Skillup Bot — Instant Skills for Your AI Agent</title>
  <meta name="description" content="Open directory of agentic skills for AI agents. Give your agent the Skillup Bot URL and it can query any skill instantly. Free, no auth, no rate limits.">
  <meta name="robots" content="index, follow, max-image-preview:large">
  <meta name="theme-color" content="#09090b" media="(prefers-color-scheme: dark)">
  <meta name="theme-color" content="#fafaf7" media="(prefers-color-scheme: light)">
  <link rel="canonical" href="https://skillup.bot/">
  <link rel="alternate" type="text/markdown" href="https://skillup.bot/AgentWelcome.md" title="agent-context" />

  <!-- Favicons -->
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E🤖%3C/text%3E%3C/svg%3E">

  <!-- Open Graph -->
  <meta property="og:title" content="Skillup Bot — Instant Skills for Your AI Agent">
  <meta property="og:description" content="Open directory of agentic skills for AI agents. Query any skill instantly. Free, no auth, no rate limits.">
  <meta property="og:url" content="https://skillup.bot/">
  <meta property="og:type" content="website">
  <meta property="og:site_name" content="Skillup Bot">
  <meta property="og:locale" content="en_US">

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="Skillup Bot — Instant Skills for Your AI Agent">
  <meta name="twitter:description" content="Open directory of agentic skills for AI agents. Query any skill instantly. Free, no auth, no rate limits.">

  <!-- JSON-LD Structured Data -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "WebSite",
    "name": "Skillup Bot",
    "url": "https://skillup.bot/",
    "description": "Open directory of agentic skills for AI agents. Give your agent the Skillup Bot URL and it can query any skill instantly.",
    "potentialAction": {
      "@type": "SearchAction",
      "target": {
        "@type": "EntryPoint",
        "urlTemplate": "https://skillup.bot/search?q={search_term_string}"
      },
      "query-input": "required name=search_term_string"
    }
  }
  </script>
  <script src="https://cdn.tailwindcss.com"></script>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
  <script>
    tailwind.config = {
      theme: {
        extend: {
          fontFamily: {
            serif: ['"Instrument Serif"', 'Georgia', 'serif'],
            sans: ['"Inter"', 'system-ui', 'sans-serif'],
            mono: ['"JetBrains Mono"', 'ui-monospace', 'monospace'],
          },
        }
      }
    }
  </script>
  <style>
    :root {
      --bg: #fafaf7;
      --bg-subtle: #f0efe9;
      --card-bg: #ffffff;
      --card-border: #e4e2da;
      --card-border-hover: #c9c5b8;
      --card-shadow: 0 1px 3px rgba(0,0,0,0.04);
      --card-shadow-hover: 0 4px 16px rgba(0,0,0,0.07);
      --card-accent: #2d6a4f;
      --text-primary: #1a1915;
      --text-secondary: #6b6860;
      --text-tertiary: #9e9a90;
      --accent: #2d6a4f;
      --accent-soft: rgba(45,106,79,0.07);
      --hashtag-color: #8a7e6b;
      --chip-bg: transparent;
      --chip-border: #e4e2da;
      --chip-text: #9e9a90;
      --chip-hover-border: #2d6a4f;
      --chip-hover-text: #2d6a4f;
      --chip-copied-border: #2d6a4f;
      --chip-copied-text: #2d6a4f;
      --search-bg: #ffffff;
      --search-border: #e4e2da;
      --search-focus: #2d6a4f;
      --search-shadow: rgba(45,106,79,0.08);
      --divider: #e4e2da;
      --toggle-color: #9e9a90;
      --modal-bg: #ffffff;
      --modal-border: #e4e2da;
      --modal-overlay: rgba(250,250,247,0.85);
      --line-number: #c4c0b6;
      --line-rule: #edeae3;
      --code-text: #3a3830;
      --code-heading: #1a1915;
      --code-bold: #1a1915;
      --code-link: #2d6a4f;
      --eye-color: #9e9a90;
      --eye-hover-color: #2d6a4f;
    }

    [data-theme="dark"] {
      --bg: #09090b;
      --bg-subtle: #111113;
      --card-bg: #131315;
      --card-border: #1f1f23;
      --card-border-hover: #33333a;
      --card-shadow: 0 1px 3px rgba(0,0,0,0.2);
      --card-shadow-hover: 0 4px 20px rgba(0,0,0,0.35);
      --card-accent: #52b788;
      --text-primary: #e8e6df;
      --text-secondary: #8a8680;
      --text-tertiary: #555250;
      --accent: #52b788;
      --accent-soft: rgba(82,183,136,0.06);
      --hashtag-color: #7a7568;
      --chip-bg: transparent;
      --chip-border: #1f1f23;
      --chip-text: #555250;
      --chip-hover-border: #52b788;
      --chip-hover-text: #52b788;
      --chip-copied-border: #52b788;
      --chip-copied-text: #52b788;
      --search-bg: #111113;
      --search-border: #1f1f23;
      --search-focus: #52b788;
      --search-shadow: rgba(82,183,136,0.06);
      --divider: #1a1a1e;
      --toggle-color: #555250;
      --modal-bg: #0e0e10;
      --modal-border: #1f1f23;
      --modal-overlay: rgba(9,9,11,0.88);
      --line-number: #3a3a40;
      --line-rule: #1a1a1e;
      --code-text: #b0ada5;
      --code-heading: #e8e6df;
      --code-bold: #e8e6df;
      --code-link: #52b788;
      --eye-color: #555250;
      --eye-hover-color: #52b788;
    }

    body {
      background: var(--bg);
      color: var(--text-primary);
      font-family: 'Inter', system-ui, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }

    body, .skill-card, .url-chip, #search, #theme-toggle svg {
      transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
    }

    .skill-card {
      background: var(--card-bg);
      border: 1px solid var(--card-border);
      box-shadow: var(--card-shadow);
      transition: border-color 0.25s ease, box-shadow 0.25s ease, background-color 0.25s ease, transform 0.25s ease;
      position: relative;
      overflow: hidden;
    }
    .skill-card::before {
      content: '';
      position: absolute;
      top: 0; left: 0; right: 0;
      height: 2px;
      background: var(--card-accent);
      opacity: 0;
      transition: opacity 0.25s ease;
    }
    .skill-card:hover {
      border-color: var(--card-border-hover);
      box-shadow: var(--card-shadow-hover);
      transform: translateY(-2px);
    }
    .skill-card:hover::before { opacity: 1; }

    .eye-icon {
      position: absolute;
      top: 20px;
      right: 20px;
      color: var(--eye-color);
      cursor: pointer;
      transition: color 0.2s ease;
      z-index: 2;
    }
    .eye-icon:hover {
      color: var(--eye-hover-color);
    }

    .hashtag {
      color: var(--hashtag-color);
      transition: color 0.15s ease;
    }
    .hashtag:hover { color: var(--accent); }

    .url-chip {
      background: var(--chip-bg);
      border: 1px solid var(--chip-border);
      color: var(--chip-text);
      cursor: pointer;
      transition: all 0.2s ease;
    }
    .url-chip:hover {
      border-color: var(--chip-hover-border);
      color: var(--chip-hover-text);
    }
    .url-chip .copy-icon {
      opacity: 0;
      transition: opacity 0.15s ease;
    }
    .url-chip:hover .copy-icon { opacity: 0.5; }
    .url-chip.copied {
      border-color: var(--chip-copied-border) !important;
      color: var(--chip-copied-text) !important;
    }
    .url-chip.copied .copy-icon { opacity: 1 !important; }

    #search {
      background: var(--search-bg);
      border: 1px solid var(--search-border);
      color: var(--text-primary);
      transition: border-color 0.2s ease, box-shadow 0.2s ease;
    }
    #search::placeholder { color: var(--text-tertiary); }
    #search:focus {
      border-color: var(--search-focus);
      box-shadow: 0 0 0 3px var(--search-shadow);
      outline: none;
    }

    #theme-toggle {
      color: var(--toggle-color);
      transition: color 0.2s ease;
    }
    #theme-toggle:hover { color: var(--text-secondary); }

    .no-results { display: none; }

    .skill-link {
      color: var(--text-primary);
      text-decoration: none;
      transition: color 0.15s ease;
      cursor: pointer;
    }
    .skill-link:hover { color: var(--accent); }

    .editorial-rule {
      border: none;
      border-top: 1px solid var(--divider);
    }

    .ecp-badge { transition: opacity 0.25s ease; }
    .ecp-badge[data-mode="light"] { display: none; }
    .ecp-badge[data-mode="dark"] { display: inline-block; }
    [data-theme="light"] .ecp-badge[data-mode="light"] { display: inline-block; }
    [data-theme="light"] .ecp-badge[data-mode="dark"] { display: none; }
    [data-theme="dark"] .ecp-badge[data-mode="light"] { display: none; }
    [data-theme="dark"] .ecp-badge[data-mode="dark"] { display: inline-block; }

    .instruction-code {
      color: var(--text-secondary);
      font-family: 'JetBrains Mono', ui-monospace, monospace;
      font-size: 12px;
      font-weight: 400;
      letter-spacing: 0.01em;
      background: var(--accent-soft);
      padding: 2px 7px;
      border-radius: 4px;
    }

    /* ── Modal ── */
    .modal-overlay {
      position: fixed;
      inset: 0;
      background: var(--modal-overlay);
      backdrop-filter: blur(12px);
      -webkit-backdrop-filter: blur(12px);
      z-index: 100;
      display: flex;
      align-items: center;
      justify-content: center;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.3s ease;
    }
    .modal-overlay.open {
      opacity: 1;
      pointer-events: auto;
    }
    .modal-container {
      background: var(--modal-bg);
      border: 1px solid var(--modal-border);
      border-radius: 16px;
      width: 90vw;
      max-width: 680px;
      max-height: 82vh;
      display: flex;
      flex-direction: column;
      box-shadow: 0 24px 80px rgba(0,0,0,0.15);
      transform: translateY(8px) scale(0.98);
      transition: transform 0.3s ease;
    }
    .modal-overlay.open .modal-container {
      transform: translateY(0) scale(1);
    }
    .modal-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 20px 24px 16px;
      border-bottom: 1px solid var(--divider);
      flex-shrink: 0;
    }
    .modal-header-title {
      font-family: 'JetBrains Mono', monospace;
      font-size: 13px;
      font-weight: 500;
      color: var(--text-secondary);
      letter-spacing: 0.01em;
    }
    .modal-header-actions {
      display: flex;
      align-items: center;
      gap: 6px;
    }
    .modal-action-btn {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 32px;
      height: 32px;
      border-radius: 8px;
      border: 1px solid var(--chip-border);
      background: transparent;
      color: var(--text-tertiary);
      cursor: pointer;
      transition: all 0.15s ease;
    }
    .modal-action-btn:hover {
      border-color: var(--chip-hover-border);
      color: var(--chip-hover-text);
    }
    .modal-action-btn.copied-btn {
      border-color: var(--accent) !important;
      color: var(--accent) !important;
    }
    .modal-body {
      flex: 1;
      overflow-y: auto;
      padding: 0;
    }
    .modal-body::-webkit-scrollbar { width: 4px; }
    .modal-body::-webkit-scrollbar-track { background: transparent; }
    .modal-body::-webkit-scrollbar-thumb { background: var(--line-number); border-radius: 2px; }

    .md-line {
      display: flex;
      min-height: 26px;
      font-family: 'JetBrains Mono', monospace;
      font-size: 13px;
      line-height: 26px;
      color: var(--code-text);
      padding: 0 24px;
    }
    .md-line:hover { background: var(--accent-soft); }
    .md-line-num {
      width: 44px;
      flex-shrink: 0;
      text-align: right;
      padding-right: 16px;
      color: var(--line-number);
      font-size: 11px;
      user-select: none;
      -webkit-user-select: none;
    }
    .md-line-content {
      flex: 1;
      min-width: 0;
      white-space: pre-wrap;
      word-break: break-word;
    }
    .md-line-content h1 {
      font-size: 18px;
      font-weight: 600;
      color: var(--code-heading);
      line-height: 1.4;
    }
    .md-line-content h2 {
      font-size: 14px;
      font-weight: 600;
      color: var(--code-heading);
      line-height: 1.5;
    }
    .md-line-content strong {
      color: var(--code-bold);
      font-weight: 500;
    }
    .md-line-content em {
      font-style: italic;
    }
    .md-line-content code {
      background: var(--accent-soft);
      padding: 1px 5px;
      border-radius: 3px;
      font-size: 12px;
    }
    .md-line-content a {
      color: var(--code-link);
      text-decoration: none;
    }
    .md-line-content a:hover { text-decoration: underline; }
    .md-line.is-blank { min-height: 14px; }
    .md-line.is-rule {
      min-height: 20px;
    }
    .md-line.is-rule .md-line-content hr {
      border: none;
      border-top: 1px solid var(--line-rule);
      margin-top: 10px;
    }
    .md-line.is-list .md-line-content {
      padding-left: 16px;
      text-indent: -16px;
    }
  </style>
</head>
<body class="min-h-screen flex flex-col">

  <!-- Modal -->
  <div id="modal-overlay" class="modal-overlay" role="dialog" aria-modal="true">
    <div class="modal-container">
      <div class="modal-header">
        <span class="modal-header-title" id="modal-filename"></span>
        <div class="modal-header-actions">
          <button id="modal-copy" class="modal-action-btn" title="Copy skill" aria-label="Copy skill content">
            <svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
            </svg>
          </button>
          <button id="modal-download" class="modal-action-btn" title="Download skill" aria-label="Download skill as file">
            <svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/>
            </svg>
          </button>
          <button id="modal-close" class="modal-action-btn" title="Close" aria-label="Close modal">
            <svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
            </svg>
          </button>
        </div>
      </div>
      <div class="modal-body" id="modal-body"></div>
    </div>
  </div>

  <!-- Page -->
  <div class="max-w-6xl mx-auto px-6 pt-16 pb-16 flex-1 w-full">
    <div class="flex justify-end mb-[-40px]">
      <button id="theme-toggle" class="p-2 rounded-lg" aria-label="Toggle theme">
        <svg id="icon-sun" class="w-[18px] h-[18px] hidden" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"/>
        </svg>
        <svg id="icon-moon" class="w-[18px] h-[18px] hidden" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"/>
        </svg>
      </button>
    </div>

    <!-- Header -->
    <div class="text-center mb-14">
      <h1 class="font-serif text-7xl italic tracking-tight mb-5" style="color: var(--text-primary); line-height: 1;">
        skillup<span style="color: var(--text-tertiary)">.bot</span>
      </h1>
      <p class="text-sm font-light tracking-widest uppercase" style="color: var(--text-secondary); letter-spacing: 0.15em;">
        Instant skills for your agent
      </p>
      <p class="font-serif italic text-sm mt-3" style="color: var(--text-tertiary);">
        "I know kung fu." — Neo
      </p>
      <div class="mt-5">
        <a href="https://endpointcontextprotocol.io" target="_blank" rel="noopener" aria-label="ECP Enabled">
          <img class="ecp-badge inline-block h-[26px] w-auto" data-mode="light" src="https://pub-9c91ce0359bd4288bb8e8802509b5050.r2.dev/endpointcontextprotocol_staticassets/ecp-enabled-light.png" alt="ECP Enabled" />
          <img class="ecp-badge inline-block h-[26px] w-auto" data-mode="dark" src="https://pub-9c91ce0359bd4288bb8e8802509b5050.r2.dev/endpointcontextprotocol_staticassets/ecp-enabled-dark.png" alt="ECP Enabled" />
        </a>
      </div>
      <div class="mt-6" style="max-width: 420px; margin-left: auto; margin-right: auto;">
        <p class="text-[13px] font-light leading-relaxed" style="color: var(--text-secondary);">
          Tell your agent to <span class="instruction-code">curl skillup.bot</span> to browse the directory, or <span class="instruction-code">curl skillup.bot/&lt;skill&gt;</span> to load one directly.
        </p>
        <p class="text-[13px] font-light mt-2" style="color: var(--text-tertiary);">
          Open and free to use.
        </p>
      </div>
    </div>

    <!-- Divider -->
    <hr class="editorial-rule max-w-xs mx-auto mb-12">

    <!-- Search -->
    <div class="max-w-md mx-auto mb-14">
      <input
        id="search"
        type="text"
        placeholder="Search skills…"
        autocomplete="off"
        class="w-full rounded-2xl py-3.5 px-5 text-sm font-light outline-none"
      />
    </div>

    <!-- Skills Grid -->
    <div id="grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
    </div>

    <!-- No Results -->
    <div id="no-results" class="no-results text-center py-20">
      <p class="text-sm font-light" style="color: var(--text-tertiary)">No skills match that query</p>
    </div>
  </div>

  <!-- Footer -->
  <footer class="text-center pb-12 pt-4">
    <hr class="editorial-rule max-w-xs mx-auto mb-6">
    <p class="text-xs font-light" style="color: var(--text-tertiary)">
      Open and free to use.
    </p>
    <p class="text-xs font-light mt-1" style="color: var(--text-tertiary)">
      Sponsored by <a href="https://lucenacoder.com" target="_blank" rel="noopener" class="hover:underline" style="color: var(--text-secondary)">LucenaCoder.com</a>
    </p>
  </footer>


  <script>
    // ── Theme ──
    const toggle = document.getElementById('theme-toggle');
    const iconSun = document.getElementById('icon-sun');
    const iconMoon = document.getElementById('icon-moon');
    const html = document.documentElement;

    function getPreferredTheme() {
      const stored = localStorage.getItem('theme');
      if (stored) return stored;
      return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    }

    function setTheme(theme) {
      html.setAttribute('data-theme', theme);
      localStorage.setItem('theme', theme);
      iconSun.classList.toggle('hidden', theme === 'dark');
      iconMoon.classList.toggle('hidden', theme === 'light');
    }

    setTheme(getPreferredTheme());
    toggle.addEventListener('click', () => {
      setTheme(html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark');
    });

    // ── Skills ──
    const skills = [
      { name: 'ArchitecturalStandards', slug: 'ArchitecturalStandards', tags: ['architecture', 'standards', 'structure'] },
      { name: 'GitWorkflow', slug: 'GitWorkflow', tags: ['git', 'version control', 'branching'] },
      { name: 'CodeReview', slug: 'CodeReview', tags: ['review', 'quality', 'collaboration'] },
      { name: 'TestingStrategy', slug: 'TestingStrategy', tags: ['testing', 'coverage', 'reliability'] },
      { name: 'APIEndpointDesign', slug: 'APIEndpointDesign', tags: ['api', 'rest', 'endpoints'] },
      { name: 'ErrorHandling', slug: 'ErrorHandling', tags: ['errors', 'resilience', 'debugging'] },
      { name: 'DatabaseSchemaDesign', slug: 'DatabaseSchemaDesign', tags: ['database', 'schema', 'modeling'] },
      { name: 'SecurityBasics', slug: 'SecurityBasics', tags: ['security', 'auth', 'vulnerabilities'] },
      { name: 'PerformanceProfiling', slug: 'PerformanceProfiling', tags: ['performance', 'profiling', 'optimization'] },
      { name: 'EnvironmentConfig', slug: 'EnvironmentConfig', tags: ['config', 'env', 'deployment'] },
      { name: 'DocumentationStandards', slug: 'DocumentationStandards', tags: ['docs', 'readme', 'standards'] },
      { name: 'BeforeYouBuild', slug: 'BeforeYouBuild', tags: ['planning', 'scope', 'assumptions'] },
      { name: 'Debugging', slug: 'Debugging', tags: ['debug', 'fix', 'bug'] },
      { name: 'ShippingDone', slug: 'ShippingDone', tags: ['done', 'verify', 'complete'] },
      { name: 'SafeRefactoring', slug: 'SafeRefactoring', tags: ['refactor', 'restructure', 'cleanup'] },
      { name: 'WorkingInCodebases', slug: 'WorkingInCodebases', tags: ['navigate', 'codebase', 'search'] },
      { name: 'TechnicalDecisions', slug: 'TechnicalDecisions', tags: ['decisions', 'tradeoffs', 'architecture'] },
      { name: 'IdeaStressTest', slug: 'IdeaStressTest', tags: ['evaluate', 'critique', 'stress test'] },
      { name: 'PairDiscussion', slug: 'PairDiscussion', tags: ['pair', 'collaborate', 'discuss'] },
      { name: 'IncidentResponse', slug: 'IncidentResponse', tags: ['incident', 'outage', 'production'] },
      { name: 'DeploymentAndRelease', slug: 'DeploymentAndRelease', tags: ['deploy', 'release', 'rollback'] },
      { name: 'Observability', slug: 'Observability', tags: ['logging', 'metrics', 'tracing'] },
      { name: 'CICDPipelines', slug: 'CICDPipelines', tags: ['CI', 'CD', 'pipeline'] },
      { name: 'DataIntegrity', slug: 'DataIntegrity', tags: ['idempotency', 'caching', 'consistency'] },
      { name: 'AuthAndPermissions', slug: 'AuthAndPermissions', tags: ['authentication', 'authorization', 'permissions'] },
      { name: 'SecurityEngineering', slug: 'SecurityEngineering', tags: ['security', 'threat modeling', 'supply chain'] },
      { name: 'VisualDesign', slug: 'VisualDesign', tags: ['design', 'UI', 'visual'] },
      { name: 'FrontendCraft', slug: 'FrontendCraft', tags: ['frontend', 'components', 'state'] },
      { name: 'TechnicalDebt', slug: 'TechnicalDebt', tags: ['debt', 'refactor', 'defer'] },
      { name: 'LegacyCode', slug: 'LegacyCode', tags: ['legacy', 'brownfield', 'old code'] },
      { name: 'AsyncSystems', slug: 'AsyncSystems', tags: ['async', 'events', 'queues'] },
      { name: 'IntegrationDesign', slug: 'IntegrationDesign', tags: ['integration', 'third-party', 'API'] },
      { name: 'PrivacyAndCompliance', slug: 'PrivacyAndCompliance', tags: ['privacy', 'PII', 'GDPR'] },
      { name: 'ConcurrencyAndReliability', slug: 'ConcurrencyAndReliability', tags: ['concurrency', 'reliability', 'SLO'] },
      { name: 'APIEvolution', slug: 'APIEvolution', tags: ['API', 'versioning', 'deprecation'] },
      { name: 'StripePayments', slug: 'StripePayments', tags: ['stripe', 'payments', 'billing'], provider: 'stripe' },
      { name: 'VercelDeployment', slug: 'VercelDeployment', tags: ['vercel', 'deploy', 'serverless'], provider: 'vercel' },
      { name: 'NextJSAppRouter', slug: 'NextJSAppRouter', tags: ['nextjs', 'app router', 'react'], provider: 'vercel' },
      { name: 'SupabaseBackend', slug: 'SupabaseBackend', tags: ['supabase', 'postgres', 'auth'], provider: 'supabase' },
      { name: 'CloudflareWorkers', slug: 'CloudflareWorkers', tags: ['cloudflare', 'workers', 'edge'], provider: 'cloudflare' },
      { name: 'SentryErrorTracking', slug: 'SentryErrorTracking', tags: ['sentry', 'errors', 'monitoring'], provider: 'sentry' },
      { name: 'SecurityAudit', slug: 'SecurityAudit', tags: ['security', 'audit', 'semgrep'], provider: 'trailofbits' },
      { name: 'TerraformIaC', slug: 'TerraformIaC', tags: ['terraform', 'IaC', 'infrastructure'], provider: 'hashicorp' },
      { name: 'ReactNativeExpo', slug: 'ReactNativeExpo', tags: ['expo', 'react native', 'mobile'], provider: 'expo' },
      { name: 'TailwindDesign', slug: 'TailwindDesign', tags: ['tailwind', 'CSS', 'design'] },
      { name: 'FigmaToCode', slug: 'FigmaToCode', tags: ['figma', 'design', 'UI'], provider: 'figma' },
      { name: 'NetlifyDeployment', slug: 'NetlifyDeployment', tags: ['netlify', 'deploy', 'JAMstack'], provider: 'netlify' },
      { name: 'PostgreSQLPatterns', slug: 'PostgreSQLPatterns', tags: ['postgres', 'SQL', 'database'], provider: 'neon' },
      { name: 'GitHubActions', slug: 'GitHubActions', tags: ['github', 'actions', 'CI'], provider: 'github' },
      { name: 'ReactPatterns', slug: 'ReactPatterns', tags: ['react', 'hooks', 'components'], provider: 'vercel' },
      { name: 'Copywriting', slug: 'Copywriting', tags: ['copy', 'marketing', 'headlines'] },
      { name: 'LandingPageStructure', slug: 'LandingPageStructure', tags: ['landing page', 'marketing', 'conversion'] },
      { name: 'SEODiscipline', slug: 'SEODiscipline', tags: ['SEO', 'marketing', 'meta'] },
      { name: 'EmailLifecycle', slug: 'EmailLifecycle', tags: ['email', 'marketing', 'transactional'] },
      { name: 'ProductMessaging', slug: 'ProductMessaging', tags: ['messaging', 'positioning', 'tagline'] },
      { name: 'StripeWebhooks', slug: 'StripeWebhooks', tags: ['stripe', 'webhooks', 'events'], provider: 'stripe' },
      { name: 'StripeBilling', slug: 'StripeBilling', tags: ['stripe', 'billing', 'subscriptions'], provider: 'stripe' },
      { name: 'PostgresPatterns', slug: 'PostgresPatterns', tags: ['postgres', 'supabase', 'SQL'], provider: 'supabase' },
      { name: 'WranglerDeploy', slug: 'WranglerDeploy', tags: ['cloudflare', 'wrangler', 'deploy'], provider: 'cloudflare' },
      { name: 'SentryWorkflow', slug: 'SentryWorkflow', tags: ['sentry', 'workflow', 'incident'], provider: 'sentry' },
      { name: 'NetlifyFunctions', slug: 'NetlifyFunctions', tags: ['netlify', 'functions', 'serverless'], provider: 'netlify' },
      { name: 'ExpoDeployment', slug: 'ExpoDeployment', tags: ['expo', 'deploy', 'EAS'], provider: 'expo' },
      { name: 'StaticAnalysis', slug: 'StaticAnalysis', tags: ['security', 'semgrep', 'static analysis'], provider: 'trailofbits' }
    ];

    const grid = document.getElementById('grid');
    const noResults = document.getElementById('no-results');
    const search = document.getElementById('search');

    function skillPublicPath(skill) {
      if (skill.provider) return '@' + skill.provider + '/' + skill.slug;
      return skill.slug;
    }

    function skillFilePath(skill) {
      if (skill.provider) return '/skill_files/@' + skill.provider + '/' + skill.slug + '.md';
      return '/skill_files/' + skill.slug + '.md';
    }

    function findSkill(slug) {
      for (var i = 0; i < skills.length; i++) {
        if (skills[i].slug === slug) return skills[i];
      }
      return { slug: slug, name: slug };
    }

    // ── Copy URL chip ──
    function copyUrl(skill, chip) {
      const url = 'skillup.bot/' + skillPublicPath(skill);
      navigator.clipboard.writeText(url).then(() => {
        chip.classList.add('copied');
        const label = chip.querySelector('.chip-label');
        const icon = chip.querySelector('.copy-icon');
        if (label) label.textContent = 'Copied';
        if (icon) icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>';
        setTimeout(() => {
          chip.classList.remove('copied');
          if (label) label.innerHTML = '<span style="opacity:0.4">skillup.bot/</span>' + skillPublicPath(skill);
          if (icon) icon.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>';
        }, 1400);
      });
    }

    // ── Render cards ──
    function render(filter) {
      if (filter === undefined) filter = '';
      const q = filter.toLowerCase().trim();
      const filtered = q
        ? skills.filter(function(s) {
            return s.name.toLowerCase().includes(q) ||
              s.tags.some(function(t) { return t.includes(q); });
          })
        : skills;

      grid.innerHTML = '';
      noResults.style.display = filtered.length === 0 ? 'block' : 'none';

      filtered.forEach(function(skill) {
        var card = document.createElement('div');
        card.className = 'skill-card rounded-2xl flex flex-col';

        var tagsHtml = skill.tags.map(function(t) {
          return '<span class="hashtag text-[11px] font-light tracking-wide">#' + t + '</span>';
        }).join('');

        card.innerHTML =
          '<div class="px-6 pt-5 pb-4 relative pr-14">' +
            '<span class="skill-link block">' +
              '<span class="text-[17px] font-medium leading-snug">' + skill.name + '</span>' +
            '</span>' +
            '<div class="mt-3 flex flex-wrap gap-x-3 gap-y-1">' + tagsHtml + '</div>' +
          '</div>' +
          '<div class="mt-auto px-5 pb-5">' +
            '<button class="url-chip w-full rounded-lg px-3 py-2 text-[11px] font-mono text-left flex items-center justify-between gap-2" data-slug="' + skill.slug + '">' +
              '<span class="chip-label truncate"><span style="opacity:0.4">skillup.bot/</span>' + skillPublicPath(skill) + '</span>' +
              '<svg class="copy-icon w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">' +
                '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>' +
              '</svg>' +
            '</button>' +
          '</div>' +
          '<span class="eye-icon" data-slug="' + skill.slug + '">' +
            '<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">' +
              '<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/>' +
              '<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>' +
            '</svg>' +
          '</span>';

        // Wire up skill name click
        card.querySelector('.skill-link').addEventListener('click', function() {
          openModal(skill.slug);
        });

        // Wire up eye icon click
        card.querySelector('.eye-icon').addEventListener('click', function() {
          openModal(skill.slug);
        });

        // Wire up URL chip click
        card.querySelector('.url-chip').addEventListener('click', function(e) {
          e.preventDefault();
          e.stopPropagation();
          copyUrl(skill, this);
        });

        grid.appendChild(card);
      });
    }

    search.addEventListener('input', function(e) { render(e.target.value); });
    render();

    // ── Modal ──
    var modalOverlay = document.getElementById('modal-overlay');
    var modalBody = document.getElementById('modal-body');
    var modalFilename = document.getElementById('modal-filename');
    var modalCopy = document.getElementById('modal-copy');
    var modalDownload = document.getElementById('modal-download');
    var modalClose = document.getElementById('modal-close');

    var currentModalContent = '';
    var currentModalSlug = '';

    function parseMarkdownLine(line) {
      var h = line
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
        .replace(/\*(.+?)\*/g, '<em>$1</em>')
        .replace(/`(.+?)`/g, '<code>$1</code>')
        .replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
      return h;
    }

    function renderMarkdown(text) {
      var lines = text.split('\n');
      var out = '';
      for (var i = 0; i < lines.length; i++) {
        var num = i + 1;
        var line = lines[i];

        if (line.trim() === '') {
          out += '<div class="md-line is-blank"><span class="md-line-num">' + num + '</span><span class="md-line-content"></span></div>';
          continue;
        }

        if (line.trim() === '---' || line.trim() === '***' || line.trim() === '___') {
          out += '<div class="md-line is-rule"><span class="md-line-num">' + num + '</span><span class="md-line-content"><hr></span></div>';
          continue;
        }

        var cls = 'md-line';
        var content = '';

        if (line.indexOf('# ') === 0) {
          content = '<h1>' + parseMarkdownLine(line.slice(2)) + '</h1>';
        } else if (line.indexOf('## ') === 0) {
          content = '<h2>' + parseMarkdownLine(line.slice(3)) + '</h2>';
        } else if (line.indexOf('### ') === 0) {
          content = '<h2>' + parseMarkdownLine(line.slice(4)) + '</h2>';
        } else if (/^\d+\.\s/.test(line)) {
          cls += ' is-list';
          content = parseMarkdownLine(line);
        } else if (line.indexOf('- ') === 0 || line.indexOf('* ') === 0) {
          cls += ' is-list';
          content = parseMarkdownLine(line);
        } else {
          content = parseMarkdownLine(line);
        }

        out += '<div class="' + cls + '"><span class="md-line-num">' + num + '</span><span class="md-line-content">' + content + '</span></div>';
      }
      return out;
    }

    function openModal(slug) {
      currentModalSlug = slug;
      modalFilename.textContent = slug + '.md';
      modalBody.innerHTML = '<div style="padding:40px 24px;text-align:center;color:var(--text-tertiary);font-size:13px;">Loading…</div>';
      modalOverlay.classList.add('open');
      document.body.style.overflow = 'hidden';

      fetch(skillFilePath(findSkill(slug)))
        .then(function(res) {
          if (!res.ok) throw new Error('Not found');
          return res.text();
        })
        .then(function(text) {
          currentModalContent = text;
          modalBody.innerHTML = renderMarkdown(text);
        })
        .catch(function() {
          modalBody.innerHTML = '<div style="padding:40px 24px;text-align:center;color:var(--text-tertiary);font-size:13px;">Could not load skill content</div>';
        });
    }

    function closeModal() {
      modalOverlay.classList.remove('open');
      document.body.style.overflow = '';
    }

    modalClose.addEventListener('click', closeModal);
    modalOverlay.addEventListener('click', function(e) {
      if (e.target === modalOverlay) closeModal();
    });
    document.addEventListener('keydown', function(e) {
      if (e.key === 'Escape') closeModal();
    });

    modalCopy.addEventListener('click', function() {
      navigator.clipboard.writeText(currentModalContent).then(function() {
        modalCopy.classList.add('copied-btn');
        modalCopy.innerHTML = '<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>';
        setTimeout(function() {
          modalCopy.classList.remove('copied-btn');
          modalCopy.innerHTML = '<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>';
        }, 1400);
      });
    });

    modalDownload.addEventListener('click', function() {
      var blob = new Blob([currentModalContent], { type: 'text/markdown' });
      var url = URL.createObjectURL(blob);
      var a = document.createElement('a');
      a.href = url;
      a.download = currentModalSlug + '.md';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    });
  </script>

</body>
</html>