github.com/timo-reymann/yal@v0.0.0-20240419173834-5d47db58f9d1/pkg/templating/index.gohtml (about)

     1  <!DOCTYPE html>
     2  <html lang="en">
     3  <head>
     4      <title>{{ .PageTitle }}</title>
     5  
     6      <meta charset="UTF-8">
     7      <meta name='generator' content='YAL {{ Version }}'>
     8      <meta name='robots' content='noindex,nofollow'>
     9      <meta name='apple-mobile-web-app-status-bar-style' content='black'>
    10  
    11      <link rel="icon" href="{{ .Favicon }}">
    12      <style>
    13          * {
    14              margin: 0;
    15              padding: 0;
    16          }
    17  
    18          html, body {
    19              min-height: 100vh;
    20              position: relative;
    21          }
    22  
    23          html {
    24              color: white;
    25              background: black;
    26              background-image: url("{{.Background}}");
    27              background-repeat: no-repeat;
    28              background-attachment: fixed;
    29              background-position: center;
    30              background-size: cover;
    31          }
    32  
    33          body {
    34              font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
    35              backdrop-filter: {{.Assets.BackgroundFilter}};
    36          }
    37  
    38          header {
    39              position: relative;
    40              padding-top: 30px;
    41          }
    42  
    43          main {
    44              width: 50vw;
    45              margin: auto;
    46              padding-bottom: 100px;
    47          }
    48  
    49          .overlay--left,
    50          .overlay--right {
    51              position: absolute;
    52              top: 0;
    53              height: 100%;
    54              z-index: -1;
    55          }
    56  
    57          .overlay--left {
    58              left: 0;
    59          }
    60  
    61          .overlay--right {
    62              right: 0;
    63          }
    64  
    65          .search {
    66              width: 50vw;
    67              margin: auto;
    68              position: relative;
    69          }
    70  
    71          .search--icon-overlay {
    72              position: absolute;
    73              width: 32px;
    74              height: 32px;
    75              left: 20px;
    76              top: 0;
    77              bottom: 0;
    78              margin: auto 0;
    79              vertical-align: middle;
    80          }
    81  
    82          .search--search-field {
    83              border: .2em solid rgb(63, 68, 70);
    84              background-color: rgba(25, 26, 27,.6);
    85              color: white;
    86              border-radius: 20px;
    87              box-shadow: 0 1px 5px rgba(0, 0, 0, .3);
    88              background-repeat: no-repeat;
    89              background-attachment: fixed;
    90              background-size: 24px;
    91              border-bottom-width: 3px;
    92              text-align: left;
    93              font-size: 1.414rem;
    94              outline: none;
    95              width: 100%;
    96              padding: 15px 15px 15px 60px;
    97          }
    98  
    99          .search-suggestions {
   100              border: .2em solid rgb(63, 68, 70);
   101              box-shadow: 0 1px 5px rgba(0, 0, 0, .3);
   102              cursor: auto;
   103              background-color: rgb(25, 26, 27,1);
   104              backdrop-filter: blur(10px);
   105              border-top: 1px solid rgb(63, 68, 70);
   106              display: block;
   107              font-size: 15px;
   108              text-align: left;
   109              border-radius: 0 0 20px 20px;
   110              margin-top: -5px;
   111              margin-left: 10px;
   112              margin-right: 10px;
   113              position: absolute;
   114              left: 0;
   115              right: 0;
   116          }
   117  
   118          .search-suggestion:first-child {
   119              padding: 5px;
   120          }
   121  
   122          .search-suggestion--item {
   123              color: inherit;
   124              padding: 10px 5px;
   125              transition: all .1s;
   126              display: flex;
   127              align-items: center;
   128              text-decoration: none;
   129          }
   130  
   131          .search-suggestion--item-title {
   132              font-weight: bold;
   133          }
   134  
   135          .search-suggestion--item-description {
   136              text-align: right;
   137              font-style: italic;
   138          }
   139  
   140          .search-suggestion--item-icon {
   141              display: inline-block;
   142              vertical-align: middle;
   143              width: 32px;
   144              height: 32px;
   145              padding: 4px 10px 4px 4px;
   146          }
   147  
   148          .search-suggestion--item:last-child:hover {
   149              border-bottom-left-radius: 17px;
   150              border-bottom-right-radius: 17px;
   151          }
   152  
   153          .search-suggestion--item:hover,
   154          .search-suggestion--item[data-active] {
   155              background-color: rgba(51, 118, 184,.5);
   156              color: white;
   157          }
   158  
   159          .search-suggestion--item-content {
   160              display: flex;
   161              flex-grow: 1;
   162              gap: 10px;
   163          }
   164  
   165          .search-suggestion--item-seperator {
   166              flex: 1;
   167          }
   168  
   169          .search-suggestion--item-description {
   170              padding-right: 20px;
   171              text-align: left;
   172              min-width: 50%;
   173          }
   174  
   175          .item-section {
   176              display: flex;
   177              flex-direction: column;
   178              justify-content: start;
   179              align-items: center;
   180          }
   181  
   182          .items-container--no-results {
   183              text-align: center;
   184              font-size: 20px;
   185              margin: 20px;
   186          }
   187  
   188          .item-section--items {
   189              display: flex;
   190              align-items: flex-start;
   191              flex-wrap: wrap;
   192              justify-content: center;
   193              gap: 25px;
   194          }
   195  
   196          .item-section--item {
   197              width: 180px;
   198              height: 180px;
   199              color: white;
   200              display: flex;
   201              flex-direction: column;
   202              justify-content: center;
   203              align-items: center;
   204              text-decoration: none;
   205              border-radius: 10%;
   206              padding: 10px;
   207          }
   208  
   209          .item-section--item:hover {
   210              background-color: hsla(0, 0%, 100%, .082);
   211              transition: background-color .1s;
   212          }
   213  
   214          .item-section--title {
   215              margin-top: 60px;
   216              margin-bottom: 20px;
   217              font-size: 2rem;
   218          }
   219  
   220          .item-section--item-icon {
   221              display: inline-block;
   222              background-color: rgba(25, 26, 27, 0.6);
   223              padding: 20px;
   224              max-width: 100%;
   225              min-height: 100px;
   226              max-height: 60%;
   227              box-shadow: 0 1px 5px rgba(0, 0, 0, .3);
   228              border-radius: 20%;
   229          }
   230  
   231          .item-section--item-title {
   232              font-weight: bold;
   233              margin-top: 10px;
   234              text-align: center;
   235              min-height: 30px;
   236          }
   237  
   238          .overlay--logo,
   239          .overlay--mascot {
   240              position: sticky;
   241              z-index: -1;
   242              bottom: 0;
   243          }
   244  
   245          .overlay--logo {
   246              top: 80vh;
   247              margin-right: 60px;
   248              max-width: 200px;
   249              max-height: 130px;
   250  }
   251  
   252          .overlay--mascot {
   253              top: 30vh;
   254              padding-left: 20px;
   255              max-width: 200px;
   256          }
   257  
   258          .hidden {
   259              display: none;
   260          }
   261  
   262          @media(max-width: 530px) {
   263              .overlay--mascot,
   264              .overlay--logo {
   265                  display: none;
   266              }
   267          }
   268  
   269          @media(max-width: 900px) {
   270              .overlay--mascot,
   271              .overlay--logo {
   272                  top: 5%;
   273                  max-width: 8vw;
   274              }
   275  
   276              main {
   277                  width: 80vw;
   278              }
   279          }
   280  
   281          @media(min-width: 2000px) {
   282              main {
   283                  width: 80vw;
   284              }
   285          }
   286      </style>
   287      <script>
   288          const searchEngines = [
   289              {{ range .SearchEngines }}
   290              {
   291                  title: "{{ .Title }}",
   292                  target_prefix: "{{ .UrlPrefix }}",
   293              },
   294              {{ end }}
   295          ];
   296  
   297          document.addEventListener("DOMContentLoaded", () => {
   298              const searchContainer = document.querySelector(".search");
   299              const searchField = document.querySelector(".search--search-field");
   300              const searchResults = document.querySelector(".search-suggestions");
   301              const itemsContainer = document.querySelector(".items-container");
   302              const noItemsContainer = document.querySelector(".items-container--no-results");
   303  
   304              let activeItem;
   305  
   306              const createSearchSuggestionItem = (title, description, target, icon, active = false) => {
   307                  return `<a href="${target}" role="menuitem"
   308                      ${active ? 'data-active' : ''}
   309                      class="search-suggestion--item">
   310                      <img class="search-suggestion--item-icon" alt="${title}" src="${icon}" />
   311                      <div class="search-suggestion--item-content">
   312                          <span class="search-suggestion--item-title">${title}</span>
   313                          <span class="search-suggestion--item-seperator"></span>
   314                          <span class="search-suggestion--item-description">${description}</span>
   315                      </div>
   316                  </a>`
   317              }
   318  
   319              const renderSearchSuggestions = (itemsToDisplay) => {
   320                  searchResults.innerHTML = "";
   321                  for (let i = 0; i < itemsToDisplay.length; i++) {
   322                      const itemToDisplay = itemsToDisplay[i];
   323                      searchResults.innerHTML += createSearchSuggestionItem(
   324                          itemToDisplay.title,
   325                          itemToDisplay.description,
   326                          itemToDisplay.target,
   327                          itemToDisplay.icon,
   328                          i === 0,
   329                      )
   330                  }
   331              }
   332  
   333              const setSearchResultsVisible = (state) => {
   334                  if (state) {
   335                      searchResults.classList.remove("hidden");
   336                  } else {
   337                      searchResults.classList.add("hidden");
   338                      setActiveNode(searchResults.children[0])
   339                  }
   340              };
   341  
   342              const setActiveNode = (nodeToActivate) => {
   343                  if (activeItem) {
   344                      delete activeItem.dataset.active;
   345                  }
   346                  if (nodeToActivate) {
   347                      nodeToActivate.dataset.active = "";
   348                  }
   349              };
   350  
   351              searchField.addEventListener("blur", () => {
   352                  setTimeout(() => setSearchResultsVisible(false), 100);
   353              })
   354              searchField.addEventListener("focus", () => setSearchResultsVisible(true));
   355  
   356              searchField.addEventListener("input", () => {
   357                  const searchResults = []
   358  
   359                  for (const item of Array.from(itemsContainer.querySelectorAll("[data-search-text]"))) {
   360                      const matchesSearch = item.dataset.searchText.toLowerCase().includes(searchField.value.toLowerCase());
   361  
   362                      if (matchesSearch) {
   363                          searchResults.push({
   364                              title: item.text,
   365                              description: item.dataset.description,
   366                              target: item.href,
   367                              icon: item.querySelector(".item-section--item-icon").src,
   368                          });
   369                          item.classList.remove("hidden");
   370                      } else {
   371                          item.classList.add("hidden");
   372                      }
   373                  }
   374  
   375                  let anyVisibleSection = false;
   376                  for(const section of Array.from(itemsContainer.querySelectorAll(".item-section"))) {
   377                      const sectionHasVisibleItems = Array.from(section.querySelectorAll(".item-section--item"))
   378                          .filter(el => !el.classList.contains("hidden"))
   379                          .length > 0;
   380                      if(sectionHasVisibleItems) {
   381                          section.classList.remove("hidden");
   382                          anyVisibleSection = true;
   383                      } else {
   384                          section.classList.add("hidden");
   385                      }
   386                  }
   387  
   388                  if(anyVisibleSection) {
   389                      noItemsContainer.classList.add("hidden");
   390                  } else {
   391                      noItemsContainer.classList.remove("hidden");
   392                  }
   393  
   394                  searchResults.sort((a, b) => a.title.localeCompare(b.title))
   395  
   396                  const displayResults = searchResults.slice(0, 5)
   397  
   398                  for (const searchEngine of searchEngines) {
   399                      displayResults.push({
   400                          title: searchEngine.title,
   401                          description: `Search in ${searchEngine.title}`,
   402                          target: searchEngine.target_prefix + searchField.value,
   403                          icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBmaWxsPSIjZmZmIiBoZWlnaHQ9IjgwMHB4IiB3aWR0aD0iODAwcHgiIHZlcnNpb249IjEuMSIgaWQ9IkNhcGFfMSIgdmlld0JveD0iMCAwIDQ4OC40IDQ4OC40IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMCwyMDMuMjVjMCwxMTIuMSw5MS4yLDIwMy4yLDIwMy4yLDIwMy4yYzUxLjYsMCw5OC44LTE5LjQsMTM0LjctNTEuMmwxMjkuNSwxMjkuNWMyLjQsMi40LDUuNSwzLjYsOC43LDMuNiAgICBzNi4zLTEuMiw4LjctMy42YzQuOC00LjgsNC44LTEyLjUsMC0xNy4zbC0xMjkuNi0xMjkuNWMzMS44LTM1LjksNTEuMi04Myw1MS4yLTEzNC43YzAtMTEyLjEtOTEuMi0yMDMuMi0yMDMuMi0yMDMuMiAgICBTMCw5MS4xNSwwLDIwMy4yNXogTTM4MS45LDIwMy4yNWMwLDk4LjUtODAuMiwxNzguNy0xNzguNywxNzguN3MtMTc4LjctODAuMi0xNzguNy0xNzguN3M4MC4yLTE3OC43LDE3OC43LTE3OC43ICAgIFMzODEuOSwxMDQuNjUsMzgxLjksMjAzLjI1eiIvPgoJPC9nPgo8L2c+Cjwvc3ZnPgo=",
   404                      });
   405                  }
   406  
   407                  renderSearchSuggestions(displayResults);
   408                  setSearchResultsVisible(searchField.value.trim() !== "");
   409              });
   410  
   411              searchContainer.addEventListener("keyup", e => {
   412                  activeItem = document.querySelector("[data-active]");
   413                  switch (e.key) {
   414                      case "ArrowDown":
   415                          setActiveNode(activeItem.nextElementSibling || searchResults.children[0]);
   416                          break;
   417  
   418                      case "ArrowUp":
   419                          setActiveNode(activeItem.previousElementSibling || searchResults.children[searchResults.children.length - 1]);
   420                          break;
   421  
   422                      case "Enter":
   423                          setSearchResultsVisible(false);
   424                          activeItem.click();
   425                          break;
   426                  }
   427              });
   428          });
   429  
   430      </script>
   431      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
   432  </head>
   433  <body>
   434  
   435  <header role="presentation">
   436      <div class="search" role="presentation" >
   437          <img aria-hidden="true" alt="Search" class="search--icon-overlay"
   438               src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBmaWxsPSIjZmZmIiBoZWlnaHQ9IjgwMHB4IiB3aWR0aD0iODAwcHgiIHZlcnNpb249IjEuMSIgaWQ9IkNhcGFfMSIgdmlld0JveD0iMCAwIDQ4OC40IDQ4OC40IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMCwyMDMuMjVjMCwxMTIuMSw5MS4yLDIwMy4yLDIwMy4yLDIwMy4yYzUxLjYsMCw5OC44LTE5LjQsMTM0LjctNTEuMmwxMjkuNSwxMjkuNWMyLjQsMi40LDUuNSwzLjYsOC43LDMuNiAgICBzNi4zLTEuMiw4LjctMy42YzQuOC00LjgsNC44LTEyLjUsMC0xNy4zbC0xMjkuNi0xMjkuNWMzMS44LTM1LjksNTEuMi04Myw1MS4yLTEzNC43YzAtMTEyLjEtOTEuMi0yMDMuMi0yMDMuMi0yMDMuMiAgICBTMCw5MS4xNSwwLDIwMy4yNXogTTM4MS45LDIwMy4yNWMwLDk4LjUtODAuMiwxNzguNy0xNzguNywxNzguN3MtMTc4LjctODAuMi0xNzguNy0xNzguN3M4MC4yLTE3OC43LDE3OC43LTE3OC43ICAgIFMzODEuOSwxMDQuNjUsMzgxLjksMjAzLjI1eiIvPgoJPC9nPgo8L2c+Cjwvc3ZnPgo="/>
   439  
   440          <input type="search" placeholder="Search ..." class="search--search-field"/>
   441          <div role="menu" aria-label="Search results" class="search-suggestions hidden">
   442          </div>
   443      </div>
   444  </header>
   445  <aside class="overlay--left" role="presentation" aria-description="Mascot">
   446      <img class="overlay--mascot" src="{{ .Mascot }}" alt="Mascot"/>
   447  </aside>
   448  <aside class="overlay--right" role="presentation" aria-description="Logo">
   449      <img class="overlay--logo" src="{{ .Logo }}" alt="Logo"/>
   450  </aside>
   451  <main class="items-container">
   452      <p class="items-container--no-results hidden">
   453          No items to display.
   454      </p>
   455      {{ range .Sections }}
   456          <section role="group" class="item-section" aria-label="{{ .Title }}">
   457              <h2 aria-hidden="true" role="heading" class="item-section--title">{{ .Title }}</h2>
   458              <div class="item-section--items">
   459                  {{ range .Entries }}
   460                      <a class="item-section--item" href="{{ .Link }}" data-search-text="{{ .Text }}" data-description="{{ .Description }}" title="{{ .Description }}" aria-label="{{ .Text }}" aria-description="{{ .Description }}" role="listitem">
   461                          <img alt="icon"
   462                               aria-hidden="true"
   463                               src="{{ .InlineIcon }}"
   464                               class="item-section--item-icon"></img>
   465                          <div class="item-section--item-title">{{ .Text }}</div>
   466                      </a>
   467                  {{end}}
   468              </div>
   469          </section>
   470      {{ end }}
   471  </main>
   472  </body>
   473  </html>