# Spine Runtimes Guide

This guide will teach you how to load, render, and manipulate skeletons in your applications using the Spine Runtimes.

<div
  style="
    display: flex;
    flex-direction: column;
    border-top: 1px solid #eee;
    border-bottom: 1px solid #eee;
    padding: 16px 0;
    margin-bottom: 16px;
  "
>
  <div style="font-weight: bold">Runtimes Guide Search</div>
  <div class="btn-group" style="display: flex; align-items: center; margin: 16px 0">
    <input
      type="text"
      id="query"
      maxlength="128"
      title="Search the Spine Runtimes Guide"
      class="input-search"
      style="height: 25px; margin: 0"
    />
    <button id="search" class="btn btn-round" style="margin: 0">
      <span class="iconfont-search"></span>
    </button>
  </div>
  <div id="queryResults" style="display: flex; flex-direction: column; gap: 16px; width: 100%; height: 100%">
  </div>
</div>

<script>
const doxieUrl = "https://doxietwo.mariozechner.at/api/search";
const sourceId = "1761161265326-jwjztvn";
// Public key scoped to Doxietwo search endpoints.
const apiKey = "3a3bbcf1752b803f8c2c3cc1bd5e1c49f9bd7a1c2b05797ba5ee0e177a771810";

const queryUi = document.querySelector("#query");
queryUi.addEventListener("keydown", (event) => {
   if (event.key === "Enter" && !event.shiftKey) {
      event.preventDefault();
      search();
   }
});
const searchButtonUi = document.querySelector("#search");
searchButtonUi.addEventListener("click", () => search());
const resultsUi = document.querySelector("#queryResults");

async function search() {
   resultsUi.innerHTML = "<div>Searching ...</div>";
   try {
      const response = await fetch(doxieUrl, {
         method: "POST",
         headers: {
            "Authorization": `Bearer ${apiKey}`,
            "Content-Type": "application/json",
         },
         body: JSON.stringify({ sourceId, query: queryUi.value, limit: 500 }),
      });
      if (!response.ok) throw new Error(await response.text());
      const results = (await response.json()).results;
      resultsUi.innerHTML = "";
      const seenUris = new Set();
      let i = 0;
      for (const result of results) {
         const path = new URL(result.docUri, location.origin).pathname.replace(/\/$/, "");
         if (path.startsWith("/spine-api-reference") || path === "/spine-runtimes-guide") continue;
         if (seenUris.has(result.docUri)) continue;
         seenUris.add(result.docUri);
         const row = renderResult(result);
         if (row) {
            resultsUi.append(row);
            i++;
            if (i == 5) break;
         }
      }
   } catch (e) {
      console.log("Search failed", e);
      resultsUi.innerHTML = "<div>Sorry, could not find any results.</div>"
   }
}

function renderResult(result) {
   const lines = result.text.split("\n");
   lines.shift();
   const text = stripMarkup(lines.join("\n"));
   if (text.length == 0) return;

   const resultUi = document.createElement("div");
   resultUi.style.display = "flex";
   resultUi.style.flexDirection = "column";

   const linkUi = document.createElement("a");
   linkUi.href = result.docUri;
   linkUi.style.fontSize = "18px";
   linkUi.textContent = result.docTitle;

   const textUi = document.createElement("p");
   textUi.style.marginBottom = "0";
   textUi.textContent = text.length > 400 ? `${text.substring(0, 400).trim()} ...` : text;

   resultUi.append(linkUi, textUi);
   return resultUi;
}

function stripMarkup(text) {
   const htmlText = text
      .replace(/#content\s+#ref\s+table,\s*#content\s+#ref\s+td\s*\{\s*font-size:\s*100%;\s*\}/gi, "")
      .replace(/(\*\*|__)(.*?)\1/g, "$2")
      .replace(/(\*|_)(.*?)\1/g, "$2")
      .replace(/~~(.*?)~~/g, "$1")
      .replace(/!\[[^\]]*\]\([^)]*\)/g, "")
      .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
      .replace(/^#+\s*/gm, "")
      .replace(/^\s*[-*>]\s*/gm, "")
      .replace(/^\s*\d+\.\s*/gm, "")
      .replace(/`{3}[\s\S]*?`{3}/g, "")
      .replace(/`([^`]+)`/g, "$1");
   const parsed = new DOMParser().parseFromString(htmlText, "text/html");
   return (parsed.body.textContent || "").replace(/\s+/g, " ").trim();
}
</script>

<!--<form method="get" action="//www.google.com/search" class="support-form forms" id="runtimes-guide-form" style="margin-bottom:-0.5em">
<fieldset>
	<legend>Runtimes Guide Search</legend>
	<table class="layout layout-form"><tr>
	<td>Keywords:</td>
	<td class="btn-group">
		<input type="text" id="runtimes-guide-q" maxlength="128" title="Search the Spine Runtimes Guide using Google" class="input-search">
		<button class="btn btn-round" onclick="googleRuntimesGuide(); return false"><span class="iconfont-search"></span></button>
	</td>
	</tr></table>
</fieldset>
</form>-->

!!* [Runtime Architecture](/spine-runtime-architecture)
* [Loading Skeleton Data](/spine-loading-skeleton-data)
* [Applying Animations](/spine-applying-animations)
* [Runtime Skeletons](/spine-runtime-skeletons)
* [Runtime Skins](/spine-runtime-skins)
* [API Reference](/spine-api-reference)

<form method="get" action="//www.google.com/search" id="google" style="display:none">
<input type="hidden" name="q" id="google-q">
</form>

<script>
$("#runtimes-guide-form").submit(function() {
	if (e.which == 13) {
		googleRuntimesGuide();
		return false;
	}
});
function googleRuntimesGuide () {
	$("#google-q").val('site:' + (langs[0] == "en" ? "" : (langs[0] + ".")) + 'esotericsoftware.com "Spine Runtimes Guide" ' + $("#runtimes-guide-q").val());
	$("#google").submit();
}
</script>