Переглянути джерело

Add search functionality across documentation pages

- Implemented a search button in the header of each documentation page.
- Added a search modal that allows users to input search queries.
- Integrated a JavaScript search feature that fetches and displays results from a new `pages-data.json` file.
- Each documentation page now includes a search overlay for improved navigation.
- Updated the main JavaScript file to handle search logic, including result highlighting and navigation.
- Created a `pages-data.json` file containing metadata for all documentation pages to facilitate search functionality.
yhirose 1 місяць тому
батько
коміт
843ad379c7

+ 145 - 0
docs-gen/defaults/static/css/main.css

@@ -450,3 +450,148 @@ a:hover {
 .theme-toggle:hover {
   opacity: 1;
 }
+
+/* Search button */
+.search-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: none;
+  border: none;
+  color: var(--text);
+  padding: 5px 6px;
+  border-radius: 4px;
+  cursor: pointer;
+  opacity: 0.8;
+}
+
+.search-btn:hover {
+  opacity: 1;
+}
+
+/* Search overlay & modal */
+.search-overlay {
+  display: none;
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 300;
+  justify-content: center;
+  align-items: flex-start;
+  padding-top: 12vh;
+}
+
+.search-overlay.open {
+  display: flex;
+}
+
+.search-modal {
+  background: var(--bg-secondary);
+  border: 1px solid var(--border);
+  border-radius: 8px;
+  width: 90%;
+  max-width: 560px;
+  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
+  overflow: hidden;
+}
+
+.search-input-wrap {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 16px;
+  border-bottom: 1px solid var(--border);
+}
+
+.search-input-wrap svg {
+  flex-shrink: 0;
+  opacity: 0.5;
+}
+
+#search-input {
+  flex: 1;
+  background: none;
+  border: none;
+  outline: none;
+  color: var(--text-bright);
+  font-size: 1rem;
+  font-family: inherit;
+}
+
+#search-input::placeholder {
+  color: var(--text-muted);
+}
+
+.search-esc {
+  background: var(--bg);
+  color: var(--text-muted);
+  padding: 2px 6px;
+  border-radius: 3px;
+  font-size: 0.7rem;
+  border: 1px solid var(--border);
+}
+
+.search-results {
+  list-style: none;
+  max-height: 50vh;
+  overflow-y: auto;
+  padding: 0;
+  margin: 0;
+}
+
+.search-results:empty::after {
+  content: "";
+  display: none;
+}
+
+.search-results li {
+  padding: 10px 16px;
+  cursor: pointer;
+  border-bottom: 1px solid var(--border);
+}
+
+.search-results li:last-child {
+  border-bottom: none;
+}
+
+.search-results li:hover,
+.search-results li.active {
+  background: var(--bg);
+}
+
+.search-results li .search-result-title {
+  color: var(--text-bright);
+  font-weight: 600;
+  font-size: 0.9rem;
+  margin-bottom: 2px;
+}
+
+.search-results li .search-result-snippet {
+  color: var(--text-muted);
+  font-size: 0.8rem;
+  line-height: 1.4;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.search-results li .search-result-snippet mark {
+  background: rgba(255, 215, 0, 0.3);
+  color: var(--text-bright);
+  border-radius: 2px;
+  padding: 0 1px;
+}
+
+[data-theme="light"] .search-results li .search-result-snippet mark {
+  background: rgba(255, 200, 0, 0.4);
+  color: var(--text);
+}
+
+.search-no-results {
+  padding: 24px 16px;
+  text-align: center;
+  color: var(--text-muted);
+  font-size: 0.9rem;
+}

+ 217 - 0
docs-gen/defaults/static/js/main.js

@@ -78,3 +78,220 @@
     }
   });
 })();
+
+// Site search (⌘K / Ctrl+K)
+(function () {
+  var overlay = document.getElementById('search-overlay');
+  var input = document.getElementById('search-input');
+  var resultsList = document.getElementById('search-results');
+  if (!overlay || !input || !resultsList) return;
+
+  var searchBtn = document.querySelector('.search-btn');
+  var pagesData = null; // cached pages-data.json
+  var activeIndex = -1;
+
+  function getCurrentLang() {
+    return document.documentElement.getAttribute('lang') || 'en';
+  }
+
+  function getBasePath() {
+    return document.documentElement.getAttribute('data-base-path') || '';
+  }
+
+  function openSearch() {
+    overlay.classList.add('open');
+    input.value = '';
+    resultsList.innerHTML = '';
+    activeIndex = -1;
+    input.focus();
+    loadPagesData();
+  }
+
+  function closeSearch() {
+    overlay.classList.remove('open');
+    input.value = '';
+    resultsList.innerHTML = '';
+    activeIndex = -1;
+  }
+
+  function loadPagesData() {
+    if (pagesData) return;
+    var basePath = getBasePath();
+    fetch(basePath + '/pages-data.json')
+      .then(function (res) { return res.json(); })
+      .then(function (data) { pagesData = data; })
+      .catch(function () { pagesData = []; });
+  }
+
+  function escapeRegExp(s) {
+    return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+  }
+
+  function highlightText(text, query) {
+    if (!query) return text;
+    var escaped = escapeRegExp(query);
+    var re = new RegExp('(' + escaped + ')', 'gi');
+    return text.replace(re, '<mark>$1</mark>');
+  }
+
+  function buildSnippet(body, query) {
+    if (!query || !body) return '';
+    var lower = body.toLowerCase();
+    var idx = lower.indexOf(query.toLowerCase());
+    var start, end, snippet;
+    if (idx === -1) {
+      snippet = body.substring(0, 120);
+    } else {
+      start = Math.max(0, idx - 40);
+      end = Math.min(body.length, idx + query.length + 80);
+      snippet = (start > 0 ? '...' : '') + body.substring(start, end) + (end < body.length ? '...' : '');
+    }
+    return highlightText(snippet, query);
+  }
+
+  function search(query) {
+    if (!pagesData || !query) {
+      resultsList.innerHTML = '';
+      activeIndex = -1;
+      return;
+    }
+
+    var lang = getCurrentLang();
+    var q = query.toLowerCase();
+
+    // Score and filter
+    var scored = [];
+    for (var i = 0; i < pagesData.length; i++) {
+      var page = pagesData[i];
+      if (page.lang !== lang) continue;
+
+      var score = 0;
+      var titleLower = page.title.toLowerCase();
+      var bodyLower = (page.body || '').toLowerCase();
+
+      if (titleLower.indexOf(q) !== -1) {
+        score += 10;
+        // Bonus for exact title match
+        if (titleLower === q) score += 5;
+      }
+      if (bodyLower.indexOf(q) !== -1) {
+        score += 3;
+      }
+      if (page.section.toLowerCase().indexOf(q) !== -1) {
+        score += 1;
+      }
+
+      if (score > 0) {
+        scored.push({ page: page, score: score });
+      }
+    }
+
+    // Sort by score descending
+    scored.sort(function (a, b) { return b.score - a.score; });
+
+    // Limit results
+    var results = scored.slice(0, 20);
+
+    if (results.length === 0) {
+      resultsList.innerHTML = '<li class="search-no-results">No results found.</li>';
+      activeIndex = -1;
+      return;
+    }
+
+    var html = '';
+    for (var j = 0; j < results.length; j++) {
+      var r = results[j];
+      var snippet = buildSnippet(r.page.body, query);
+      html += '<li data-url="' + r.page.url + '">'
+        + '<div class="search-result-title">' + highlightText(r.page.title, query) + '</div>'
+        + (snippet ? '<div class="search-result-snippet">' + snippet + '</div>' : '')
+        + '</li>';
+    }
+    resultsList.innerHTML = html;
+    activeIndex = -1;
+  }
+
+  function setActive(index) {
+    var items = resultsList.querySelectorAll('li[data-url]');
+    if (items.length === 0) return;
+    // Remove previous active
+    for (var i = 0; i < items.length; i++) {
+      items[i].classList.remove('active');
+    }
+    if (index < 0) index = items.length - 1;
+    if (index >= items.length) index = 0;
+    activeIndex = index;
+    items[activeIndex].classList.add('active');
+    items[activeIndex].scrollIntoView({ block: 'nearest' });
+  }
+
+  function navigateToActive() {
+    var items = resultsList.querySelectorAll('li[data-url]');
+    if (activeIndex >= 0 && activeIndex < items.length) {
+      var url = items[activeIndex].getAttribute('data-url');
+      if (url) {
+        closeSearch();
+        window.location.href = url;
+      }
+    }
+  }
+
+  // Event: search button
+  if (searchBtn) {
+    searchBtn.addEventListener('click', function (e) {
+      e.stopPropagation();
+      openSearch();
+    });
+  }
+
+  // Use capture phase to intercept keys before browser default behavior
+  // (e.g. ESC clearing input text in some browsers)
+  document.addEventListener('keydown', function (e) {
+    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
+      e.preventDefault();
+      overlay.classList.contains('open') ? closeSearch() : openSearch();
+      return;
+    }
+    if (!overlay.classList.contains('open')) return;
+    if (e.key === 'Escape') {
+      e.preventDefault();
+      closeSearch();
+    } else if (e.key === 'ArrowDown') {
+      e.preventDefault();
+      setActive(activeIndex + 1);
+    } else if (e.key === 'ArrowUp') {
+      e.preventDefault();
+      setActive(activeIndex - 1);
+    } else if (e.key === 'Enter') {
+      e.preventDefault();
+      navigateToActive();
+    }
+  }, true); // capture phase
+
+  // Event: click overlay background to close
+  overlay.addEventListener('click', function (e) {
+    if (e.target === overlay) {
+      closeSearch();
+    }
+  });
+
+  // Event: click result item
+  resultsList.addEventListener('click', function (e) {
+    var li = e.target.closest('li[data-url]');
+    if (!li) return;
+    var url = li.getAttribute('data-url');
+    if (url) {
+      closeSearch();
+      window.location.href = url;
+    }
+  });
+
+  // Event: input for live search
+  var debounceTimer = null;
+  input.addEventListener('input', function () {
+    clearTimeout(debounceTimer);
+    debounceTimer = setTimeout(function () {
+      search(input.value.trim());
+    }, 150);
+  });
+})();

+ 15 - 0
docs-gen/defaults/templates/base.html

@@ -39,6 +39,9 @@
         {% endfor %}
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -68,6 +71,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="{{ site.base_path }}/js/main.js"></script>
 </body>
 </html>

+ 54 - 0
docs-gen/src/builder.rs

@@ -15,6 +15,17 @@ struct PageContext {
     status: Option<String>,
 }
 
+/// Entry for pages-data.json used by client-side search.
+#[derive(Debug, Serialize)]
+struct PageDataEntry {
+    title: String,
+    url: String,
+    lang: String,
+    section: String,
+    /// Plain-text body with HTML tags stripped (truncated to ~500 chars).
+    body: String,
+}
+
 #[derive(Debug, Serialize, Clone)]
 struct NavItem {
     title: String,
@@ -62,6 +73,9 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
         copy_dir_recursive(&static_dir, out)?;
     }
 
+    // Collect page data entries for pages-data.json across all languages
+    let mut page_data_entries: Vec<PageDataEntry> = Vec::new();
+
     // Build each language
     for lang in &config.i18n.langs {
         let pages_dir = src.join("pages").join(lang);
@@ -74,6 +88,17 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
         let nav = build_nav(&pages);
 
         for page in &pages {
+            // Collect search data for pages-data.json
+            let plain_body = strip_html_tags(&page.html_content);
+            let truncated_body: String = plain_body.chars().take(500).collect();
+            page_data_entries.push(PageDataEntry {
+                title: page.frontmatter.title.clone(),
+                url: page.url.clone(),
+                lang: lang.clone(),
+                section: page.section.clone(),
+                body: truncated_body,
+            });
+
             let template_name = if page.section.is_empty() {
                 "portal.html"
             } else {
@@ -136,6 +161,11 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
         }
     }
 
+    // Generate pages-data.json for client-side search
+    let pages_json = serde_json::to_string(&page_data_entries)
+        .context("Failed to serialize pages-data.json")?;
+    fs::write(out.join("pages-data.json"), pages_json)?;
+
     // Generate root redirect
     generate_root_redirect(out, &config)?;
 
@@ -392,3 +422,27 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
     }
     Ok(())
 }
+
+/// Strip HTML tags from a string and collapse whitespace into a single space,
+/// producing a plain-text representation suitable for search indexing.
+fn strip_html_tags(html: &str) -> String {
+    let mut result = String::with_capacity(html.len());
+    let mut in_tag = false;
+
+    for ch in html.chars() {
+        match ch {
+            '<' => in_tag = true,
+            '>' => {
+                in_tag = false;
+                // Insert a space to avoid words being glued across tags
+                result.push(' ');
+            }
+            _ if !in_tag => result.push(ch),
+            _ => {}
+        }
+    }
+
+    // Collapse whitespace
+    let collapsed: String = result.split_whitespace().collect::<Vec<_>>().join(" ");
+    collapsed
+}

+ 145 - 0
docs/css/main.css

@@ -450,3 +450,148 @@ a:hover {
 .theme-toggle:hover {
   opacity: 1;
 }
+
+/* Search button */
+.search-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: none;
+  border: none;
+  color: var(--text);
+  padding: 5px 6px;
+  border-radius: 4px;
+  cursor: pointer;
+  opacity: 0.8;
+}
+
+.search-btn:hover {
+  opacity: 1;
+}
+
+/* Search overlay & modal */
+.search-overlay {
+  display: none;
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 300;
+  justify-content: center;
+  align-items: flex-start;
+  padding-top: 12vh;
+}
+
+.search-overlay.open {
+  display: flex;
+}
+
+.search-modal {
+  background: var(--bg-secondary);
+  border: 1px solid var(--border);
+  border-radius: 8px;
+  width: 90%;
+  max-width: 560px;
+  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
+  overflow: hidden;
+}
+
+.search-input-wrap {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 16px;
+  border-bottom: 1px solid var(--border);
+}
+
+.search-input-wrap svg {
+  flex-shrink: 0;
+  opacity: 0.5;
+}
+
+#search-input {
+  flex: 1;
+  background: none;
+  border: none;
+  outline: none;
+  color: var(--text-bright);
+  font-size: 1rem;
+  font-family: inherit;
+}
+
+#search-input::placeholder {
+  color: var(--text-muted);
+}
+
+.search-esc {
+  background: var(--bg);
+  color: var(--text-muted);
+  padding: 2px 6px;
+  border-radius: 3px;
+  font-size: 0.7rem;
+  border: 1px solid var(--border);
+}
+
+.search-results {
+  list-style: none;
+  max-height: 50vh;
+  overflow-y: auto;
+  padding: 0;
+  margin: 0;
+}
+
+.search-results:empty::after {
+  content: "";
+  display: none;
+}
+
+.search-results li {
+  padding: 10px 16px;
+  cursor: pointer;
+  border-bottom: 1px solid var(--border);
+}
+
+.search-results li:last-child {
+  border-bottom: none;
+}
+
+.search-results li:hover,
+.search-results li.active {
+  background: var(--bg);
+}
+
+.search-results li .search-result-title {
+  color: var(--text-bright);
+  font-weight: 600;
+  font-size: 0.9rem;
+  margin-bottom: 2px;
+}
+
+.search-results li .search-result-snippet {
+  color: var(--text-muted);
+  font-size: 0.8rem;
+  line-height: 1.4;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.search-results li .search-result-snippet mark {
+  background: rgba(255, 215, 0, 0.3);
+  color: var(--text-bright);
+  border-radius: 2px;
+  padding: 0 1px;
+}
+
+[data-theme="light"] .search-results li .search-result-snippet mark {
+  background: rgba(255, 200, 0, 0.4);
+  color: var(--text);
+}
+
+.search-no-results {
+  padding: 24px 16px;
+  text-align: center;
+  color: var(--text-muted);
+  font-size: 0.9rem;
+}

+ 15 - 0
docs/en/cookbook/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -89,6 +92,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -88,6 +91,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/01-getting-started/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -214,6 +217,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/02-basic-client/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -507,6 +510,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/03-basic-server/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -462,6 +465,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/04-static-file-server/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -285,6 +288,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/05-tls-setup/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -209,6 +212,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/06-https-client/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -267,6 +270,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/07-https-server/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -256,6 +259,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/08-websocket/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -308,6 +311,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/09-whats-next/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -440,6 +443,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/en/tour/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -121,6 +124,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/cookbook/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -89,6 +92,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -88,6 +91,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/01-getting-started/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -214,6 +217,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/02-basic-client/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -507,6 +510,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/03-basic-server/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -462,6 +465,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/04-static-file-server/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -285,6 +288,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/05-tls-setup/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -209,6 +212,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/06-https-client/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -267,6 +270,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/07-https-server/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -256,6 +259,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/08-websocket/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -308,6 +311,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/09-whats-next/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -440,6 +443,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 15 - 0
docs/ja/tour/index.html

@@ -41,6 +41,9 @@
         
       </nav>
       <div class="header-tools">
+        <button class="search-btn" aria-label="Search (⌘K)">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        </button>
         <button class="theme-toggle" aria-label="Toggle theme"></button>
         <div class="lang-selector">
           <button class="lang-btn" aria-label="Language">
@@ -121,6 +124,18 @@
     &copy; 2026 yhirose. All rights reserved.
   </footer>
 
+  <!-- Search modal -->
+  <div class="search-overlay" id="search-overlay">
+    <div class="search-modal">
+      <div class="search-input-wrap">
+        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
+        <input type="text" id="search-input" placeholder="Search..." autocomplete="off" spellcheck="false">
+        <kbd class="search-esc">ESC</kbd>
+      </div>
+      <ul class="search-results" id="search-results"></ul>
+    </div>
+  </div>
+
   <script src="&#x2F;cpp-httplib/js/main.js"></script>
 </body>
 </html>

+ 217 - 0
docs/js/main.js

@@ -78,3 +78,220 @@
     }
   });
 })();
+
+// Site search (⌘K / Ctrl+K)
+(function () {
+  var overlay = document.getElementById('search-overlay');
+  var input = document.getElementById('search-input');
+  var resultsList = document.getElementById('search-results');
+  if (!overlay || !input || !resultsList) return;
+
+  var searchBtn = document.querySelector('.search-btn');
+  var pagesData = null; // cached pages-data.json
+  var activeIndex = -1;
+
+  function getCurrentLang() {
+    return document.documentElement.getAttribute('lang') || 'en';
+  }
+
+  function getBasePath() {
+    return document.documentElement.getAttribute('data-base-path') || '';
+  }
+
+  function openSearch() {
+    overlay.classList.add('open');
+    input.value = '';
+    resultsList.innerHTML = '';
+    activeIndex = -1;
+    input.focus();
+    loadPagesData();
+  }
+
+  function closeSearch() {
+    overlay.classList.remove('open');
+    input.value = '';
+    resultsList.innerHTML = '';
+    activeIndex = -1;
+  }
+
+  function loadPagesData() {
+    if (pagesData) return;
+    var basePath = getBasePath();
+    fetch(basePath + '/pages-data.json')
+      .then(function (res) { return res.json(); })
+      .then(function (data) { pagesData = data; })
+      .catch(function () { pagesData = []; });
+  }
+
+  function escapeRegExp(s) {
+    return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+  }
+
+  function highlightText(text, query) {
+    if (!query) return text;
+    var escaped = escapeRegExp(query);
+    var re = new RegExp('(' + escaped + ')', 'gi');
+    return text.replace(re, '<mark>$1</mark>');
+  }
+
+  function buildSnippet(body, query) {
+    if (!query || !body) return '';
+    var lower = body.toLowerCase();
+    var idx = lower.indexOf(query.toLowerCase());
+    var start, end, snippet;
+    if (idx === -1) {
+      snippet = body.substring(0, 120);
+    } else {
+      start = Math.max(0, idx - 40);
+      end = Math.min(body.length, idx + query.length + 80);
+      snippet = (start > 0 ? '...' : '') + body.substring(start, end) + (end < body.length ? '...' : '');
+    }
+    return highlightText(snippet, query);
+  }
+
+  function search(query) {
+    if (!pagesData || !query) {
+      resultsList.innerHTML = '';
+      activeIndex = -1;
+      return;
+    }
+
+    var lang = getCurrentLang();
+    var q = query.toLowerCase();
+
+    // Score and filter
+    var scored = [];
+    for (var i = 0; i < pagesData.length; i++) {
+      var page = pagesData[i];
+      if (page.lang !== lang) continue;
+
+      var score = 0;
+      var titleLower = page.title.toLowerCase();
+      var bodyLower = (page.body || '').toLowerCase();
+
+      if (titleLower.indexOf(q) !== -1) {
+        score += 10;
+        // Bonus for exact title match
+        if (titleLower === q) score += 5;
+      }
+      if (bodyLower.indexOf(q) !== -1) {
+        score += 3;
+      }
+      if (page.section.toLowerCase().indexOf(q) !== -1) {
+        score += 1;
+      }
+
+      if (score > 0) {
+        scored.push({ page: page, score: score });
+      }
+    }
+
+    // Sort by score descending
+    scored.sort(function (a, b) { return b.score - a.score; });
+
+    // Limit results
+    var results = scored.slice(0, 20);
+
+    if (results.length === 0) {
+      resultsList.innerHTML = '<li class="search-no-results">No results found.</li>';
+      activeIndex = -1;
+      return;
+    }
+
+    var html = '';
+    for (var j = 0; j < results.length; j++) {
+      var r = results[j];
+      var snippet = buildSnippet(r.page.body, query);
+      html += '<li data-url="' + r.page.url + '">'
+        + '<div class="search-result-title">' + highlightText(r.page.title, query) + '</div>'
+        + (snippet ? '<div class="search-result-snippet">' + snippet + '</div>' : '')
+        + '</li>';
+    }
+    resultsList.innerHTML = html;
+    activeIndex = -1;
+  }
+
+  function setActive(index) {
+    var items = resultsList.querySelectorAll('li[data-url]');
+    if (items.length === 0) return;
+    // Remove previous active
+    for (var i = 0; i < items.length; i++) {
+      items[i].classList.remove('active');
+    }
+    if (index < 0) index = items.length - 1;
+    if (index >= items.length) index = 0;
+    activeIndex = index;
+    items[activeIndex].classList.add('active');
+    items[activeIndex].scrollIntoView({ block: 'nearest' });
+  }
+
+  function navigateToActive() {
+    var items = resultsList.querySelectorAll('li[data-url]');
+    if (activeIndex >= 0 && activeIndex < items.length) {
+      var url = items[activeIndex].getAttribute('data-url');
+      if (url) {
+        closeSearch();
+        window.location.href = url;
+      }
+    }
+  }
+
+  // Event: search button
+  if (searchBtn) {
+    searchBtn.addEventListener('click', function (e) {
+      e.stopPropagation();
+      openSearch();
+    });
+  }
+
+  // Use capture phase to intercept keys before browser default behavior
+  // (e.g. ESC clearing input text in some browsers)
+  document.addEventListener('keydown', function (e) {
+    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
+      e.preventDefault();
+      overlay.classList.contains('open') ? closeSearch() : openSearch();
+      return;
+    }
+    if (!overlay.classList.contains('open')) return;
+    if (e.key === 'Escape') {
+      e.preventDefault();
+      closeSearch();
+    } else if (e.key === 'ArrowDown') {
+      e.preventDefault();
+      setActive(activeIndex + 1);
+    } else if (e.key === 'ArrowUp') {
+      e.preventDefault();
+      setActive(activeIndex - 1);
+    } else if (e.key === 'Enter') {
+      e.preventDefault();
+      navigateToActive();
+    }
+  }, true); // capture phase
+
+  // Event: click overlay background to close
+  overlay.addEventListener('click', function (e) {
+    if (e.target === overlay) {
+      closeSearch();
+    }
+  });
+
+  // Event: click result item
+  resultsList.addEventListener('click', function (e) {
+    var li = e.target.closest('li[data-url]');
+    if (!li) return;
+    var url = li.getAttribute('data-url');
+    if (url) {
+      closeSearch();
+      window.location.href = url;
+    }
+  });
+
+  // Event: input for live search
+  var debounceTimer = null;
+  input.addEventListener('input', function () {
+    clearTimeout(debounceTimer);
+    debounceTimer = setTimeout(function () {
+      search(input.value.trim());
+    }, 150);
+  });
+})();

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
docs/pages-data.json


Деякі файли не було показано, через те що забагато файлів було змінено