From 9b92632cd6b4b542c9c5e0d7329d209f7cd176d9 Mon Sep 17 00:00:00 2001 From: partisan Date: Mon, 21 Oct 2024 07:30:48 +0200 Subject: [PATCH] Logo update WIP --- lang/en/LC_MESSAGES/default.po | 2 +- lang/pl/LC_MESSAGES/default.po | 2 +- open-search.go | 6 +- static/css/style-search.css | 353 ++++++++++++++++++++++++++++++--- static/css/style.css | 10 + static/images/icon.svg | 185 +++++++++++++++++ static/images/icon2.svg | 54 +++++ static/images/logo.svg | 94 +++++++++ static/js/autocomplete.js | 282 +++++++++++--------------- templates/search.html | 227 +++++++++++++-------- templates/settings.html | 2 +- 11 files changed, 932 insertions(+), 285 deletions(-) create mode 100644 static/images/icon.svg create mode 100644 static/images/icon2.svg create mode 100644 static/images/logo.svg diff --git a/lang/en/LC_MESSAGES/default.po b/lang/en/LC_MESSAGES/default.po index cae96aa..ee954aa 100644 --- a/lang/en/LC_MESSAGES/default.po +++ b/lang/en/LC_MESSAGES/default.po @@ -59,7 +59,7 @@ msgid "all_settings" msgstr "All settings" msgid "site_name" -msgstr "Ocásek" +msgstr "QGato" msgid "search" msgstr "Search" diff --git a/lang/pl/LC_MESSAGES/default.po b/lang/pl/LC_MESSAGES/default.po index a424815..99436a1 100644 --- a/lang/pl/LC_MESSAGES/default.po +++ b/lang/pl/LC_MESSAGES/default.po @@ -59,7 +59,7 @@ msgid "all_settings" msgstr "Wszystkie ustawienia" msgid "site_name" -msgstr "Ocásek" +msgstr "QGato" msgid "search" msgstr "Szukaj" diff --git a/open-search.go b/open-search.go index 8ae97e1..0cdb72a 100644 --- a/open-search.go +++ b/open-search.go @@ -25,9 +25,9 @@ func generateOpenSearchXML(config Config) { opensearch := OpenSearchDescription{ Xmlns: "http://a9.com/-/spec/opensearch/1.1/", - ShortName: "Warp", - Description: "Warp search engine", - Tags: "search, engine, warp", + ShortName: "QGato", + Description: "QGato search engine", + Tags: "search, spitfire, qgato", URLs: []URL{ { Type: "text/html", diff --git a/static/css/style-search.css b/static/css/style-search.css index adda3d2..27f516f 100644 --- a/static/css/style-search.css +++ b/static/css/style-search.css @@ -1,61 +1,358 @@ +/* Font Definitions */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 300; + src: local(''), + url('/static/fonts/inter-v12-latin-300.woff2') format('woff2'), + url('/static/fonts/inter-v12-latin-300.woff') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + src: local(''), + url('/static/fonts/inter-v12-latin-regular.woff2') format('woff2'), + url('/static/fonts/inter-v12-latin-regular.woff') format('woff'); +} +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + src: local(''), + url('/static/fonts/inter-v12-latin-700.woff2') format('woff2'), + url('/static/fonts/inter-v12-latin-700.woff') format('woff'); +} + +@font-face { + font-display: swap; + font-family: 'Material Icons Round'; + font-style: normal; + font-weight: 400; + src: url('/static/fonts/material-icons-round-v108-latin-regular.woff2') format('woff2'); +} + +/* General Styles */ +body, html { + margin: 0; + padding: 0; + background-color: var(--html-bg); + font-family: 'Inter', Arial, Helvetica, sans-serif; + font-size: 16px; + color: var(--text-color); +} + +body.menu-open { + overflow: hidden; +} + +.highlight { + color: var(--highlight); + font-weight: bold; + font-style: normal; +} + +.material-icons-round { + font-family: 'Material Icons Round' !important; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + font-feature-settings: 'liga'; +} + +#search-wrapper-ico { + background: none; + border: none; + color: var(--fg); + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + cursor: pointer; + font-size: 24px; +} + +#search-wrapper-ico:hover { + transition: color 0.3s ease; + color: var(--blue); +} + +.wrapper { + margin: 0 auto; + background: var(--search-bg-input); + border-radius: 22px; + position: relative; + width: 100%; + max-width: 600px; + overflow: visible; + border: 1px solid var(--search-bg-input-border); +} + +.wrapper input { + width: 100%; + padding: 10px 50px 10px 20px; + font-size: 16px; + color: var(--font-fg); + background-color: var(--search-bg-input); + background: none; + border: none; + outline: none; +} + +.wrapper input::placeholder { + color: var(--fg); +} + +/* Autocomplete Styles */ +.autocomplete { + text-align: left; + width: 100%; /* Ensure it matches the input width */ +} + +.wrapper.show .autocomplete { + display: block; +} + +.autocomplete ul { + margin: 0; + padding: 0; +} + +.autocomplete ul li { + list-style: none; + padding: 8px 12px; + cursor: pointer; + color: var(--font-fg); +} + +.autocomplete ul li.selected, +.autocomplete ul li:hover { + background: var(--search-select); +} + +.autocomplete ul li:last-child { + border-bottom-left-radius: 22px; + border-bottom-right-radius: 22px; +} + +/* Logo Styles */ +.logo-container { + margin: 0 auto 40px auto; + color: var(--font-fg); + text-align: center; +} + +.logo-container svg { + max-width: 300px; + height: auto; + vertical-align: middle; + margin: 0 auto; +} + +/* Centering Content */ .search-page-content { + max-width: 800px; + margin: 0 auto; + text-align: center; display: flex; flex-direction: column; - align-items: center; -} - -.search-page-content h1 { - text-align: center; - margin-bottom: 20px; -} - -#search-input { - width: 100%; - padding: 12px; - font-size: 16px; + justify-content: center; /* Center vertically */ + height: 100vh; /* Make it full viewport height */ } +/* Search Type Icons */ .search-type-icons { display: flex; justify-content: center; flex-wrap: wrap; - gap: 30px; margin-top: 30px; + gap: 20px; } .icon-button { display: flex; flex-direction: column; align-items: center; - background: none; - border: none; - cursor: pointer; text-align: center; } -.icon-button .material-icons-round { - font-size: 48px; - color: var(--sub-search-wrapper-ico); +.icon-button button { + background: none; + border: none; + color: var(--search-button); + cursor: pointer; + font-size: 24px; +} + +.icon-button button:hover { + color: var(--blue); + transition: color 0.3s ease; } .icon-button p { - margin-top: 8px; + margin-top: 5px; font-size: 14px; - color: var(--sub-search-wrapper-ico); + color: var(--font-fg); } -.icon-button:hover .material-icons-round { - transition: all .3s ease; +/* Menu Button */ +.settings-icon-link-search { + position: fixed; + top: 20px; + right: 20px; + background: none; + border: none; + color: var(--fg); + cursor: pointer; + font-size: 36px; + z-index: 999; +} + +.settings-icon-link-search:hover { + color: var(--highlight); +} + +/* Style for Close Button in Menu */ +.closebtn { + background: none; + border: none; + color: var(--fg); + cursor: pointer; + font-size: 36px; + position: absolute; + top: 20px; + right: 20px; +} + +.side-nav .closebtn:hover { + color: var(--highlight); + background: none; +} + +/* Side Navigation Menu */ +.side-nav { + height: 100%; + width: 0; + position: fixed; + top: 0; + right: 0; + background-color: var(--html-bg); + overflow-x: hidden; + transition: 0.5s; + z-index: 1000; + box-shadow: -2px 0 5px rgba(0,0,0,0.5); +} + +/* Menu Content */ +.side-nav a, +.side-nav button, +.side-nav select { + padding: 8px 32px; + text-decoration: none; + font-size: 18px; + color: var(--font-fg); + display: block; + transition: background-color 0.3s ease; + border: none; + background: none; + width: 100%; + text-align: left; +} + +.side-nav a:hover, +.side-nav button:hover, +.side-nav select:hover { + background-color: var(--search-select); color: var(--blue); + cursor: pointer; } -.icon-button:hover p { - transition: all .3s ease; - color: var(--blue); +/* Style for buttons in the menu */ +.side-nav button { + background: none; + border: none; + font-size: 18px; + font-family: inherit; + padding: 8px 32px; + width: 100%; + text-align: left; + color: var(--font-fg); } -.icon-button button:focus { +.side-nav .closebtn:hover { + color: var(--highlight); +} + +/* Overlay effect when menu is open */ +body.menu-open::before { + content: ''; + display: block; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + z-index: 998; +} + +/* Responsive Design */ +@media only screen and (max-width: 450px) { + .wrapper { + width: 90%; + } + .logo-container svg { + width: 75%; + max-width: 90%; + min-width: 25%; + } +} + +/* Additional Global Styles */ +a { + text-decoration: none; + color: var(--link); +} + +a:hover { + text-decoration: underline; +} + +h1 { + font-size: 70px; + color: var(--font-fg); + font-family: 'Inter'; +} + +p { + color: var(--fg); + font-size: 14px; + line-height: 1.58; +} + +input, button { outline: none; -} \ No newline at end of file +} + +/* + Remove default focus outline and add custom focus style +.wrapper input[type="text"]:focus { + outline: none; + border: 1px solid var(--blue); + box-shadow: none; +} */ + + diff --git a/static/css/style.css b/static/css/style.css index 65d0346..34dacec 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1057,6 +1057,16 @@ p { color: #b0316e; } +.logo-container { + color: var(--font-fg); + line-height: 0; +} + +.logo-container svg { + vertical-align: middle; + margin: 0 auto; +} + .results-search-container { background-color: var(--search-bg); width: 100%; diff --git a/static/images/icon.svg b/static/images/icon.svg new file mode 100644 index 0000000..5ce7113 --- /dev/null +++ b/static/images/icon.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Star + + + Star + + + Star + + + Star + + + Star + + + Star + + + Star + + + Star + + + diff --git a/static/images/icon2.svg b/static/images/icon2.svg new file mode 100644 index 0000000..902a961 --- /dev/null +++ b/static/images/icon2.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + Star + + + Star + + + Star + + + Star + + + Star + + + Star + + + Star + + + Star + + + + Kitty + + + + + + Kitty + + + + + + Star + + + Star + + \ No newline at end of file diff --git a/static/images/logo.svg b/static/images/logo.svg new file mode 100644 index 0000000..952d6ab --- /dev/null +++ b/static/images/logo.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/js/autocomplete.js b/static/js/autocomplete.js index 354f254..9ac63f3 100644 --- a/static/js/autocomplete.js +++ b/static/js/autocomplete.js @@ -1,180 +1,120 @@ -/** - * @source: ./script.js (originally from araa-search on Github) - * - * @licstart The following is the entire license notice for the - * JavaScript code in this page. - * - * Copyright (C) 2023 Extravi - * - * The JavaScript code in this page is free software: you can - * redistribute it and/or modify it under the terms of the GNU Affero - * General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * The code is distributed WITHOUT ANY WARRANTY; without even the - * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * As additional permission under GNU Affero General Public License - * section 7, you may distribute non-source (e.g., minimized or compacted) - * forms of that code without the copy of the GNU Affero General Public - * License normally required by section 4, provided you include this - * license notice and a URL through which recipients can access the - * Corresponding Source. - * - * @licend The above is the entire license notice - * for the JavaScript code in this page. - */ +// Wait for the DOM to load +document.addEventListener('DOMContentLoaded', () => { + const searchInput = document.getElementById('search-input'); + const searchWrapper = document.querySelector('.wrapper'); + const resultsWrapper = document.querySelector('.autocomplete'); -// Removes the 'Apply Settings' button for Javascript users, -// since changing any of the elements causes the settings to apply -// automatically. -let resultsSave = document.querySelector(".results-save"); -if (resultsSave != null) { - resultsSave.style.display = "none"; -} + let suggestions = []; + let currentIndex = -1; // Keep track of the currently selected suggestion -const searchInput = document.getElementById('search-input'); -const searchWrapper = document.querySelectorAll('.wrapper, .wrapper-results')[0]; -const resultsWrapper = document.querySelector('.autocomplete'); -// const clearSearch = document.querySelector("#clearSearch"); - -async function getSuggestions(query) { - try { - const params = new URLSearchParams({ "q": query }).toString(); - const response = await fetch(`/suggestions?${params}`); - const data = await response.json(); - return data[1]; // Return only the array of suggestion strings - } catch (error) { - console.error(error); - } - } - -let currentIndex = -1; // Keep track of the currently selected suggestion - -// Handle click events on the type buttons -let results = []; -searchInput.addEventListener('input', async () => { - let input = searchInput.value; - if (input.length) { - results = await getSuggestions(input); - } - renderResults(results); - currentIndex = -1; // Reset index when we return new results -}); - -searchInput.addEventListener("focus", async () => { - let input = searchInput.value; - if (results.length === 0 && input.length != 0) { - results = await getSuggestions(input); - } - renderResults(results); -}) - -// clearSearch.style.visibility = "visible"; // Only show the clear search button for JS users. -// clearSearch.addEventListener("click", () => { -// searchInput.value = ""; -// searchInput.focus(); -// }) - -searchInput.addEventListener('keydown', (event) => { - if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Tab') { - event.preventDefault(); // Prevent the default behavior, such as moving the cursor - - // Find the currently selected suggestion element - const selectedSuggestion = resultsWrapper.querySelector('.selected'); - if (selectedSuggestion) { - selectedSuggestion.classList.remove('selected'); // Deselect the currently selected suggestion + // Fetch suggestions from the server + async function getSuggestions(query) { + try { + const response = await fetch(`/suggestions?q=${encodeURIComponent(query)}`); + const data = await response.json(); + return data[1]; // Return only the array of suggestion strings + } catch (error) { + console.error('Error fetching suggestions:', error); + return []; + } } - // Increment current index when ArrowUp is pressed otherwise hen Tab OR ArrowDown decrement - if (event.key === 'ArrowUp') { - currentIndex--; - } else { - currentIndex++; + // Function to render results + function renderSuggestions(results) { + if (!results || !results.length) { + searchWrapper.classList.remove('show'); + resultsWrapper.innerHTML = ''; + return; + } + + let content = ''; + results.forEach((item, index) => { + content += `
  • ${item}
  • `; + }); + + resultsWrapper.innerHTML = ``; + searchWrapper.classList.add('show'); } - // Wrap around the index if it goes out of bounds - if (currentIndex < 0) { - currentIndex = resultsWrapper.querySelectorAll('li').length - 1; - } else if (currentIndex >= resultsWrapper.querySelectorAll('li').length) { - currentIndex = 0; + // Fetch suggestions when input is focused + searchInput.addEventListener('focus', async () => { + const query = searchInput.value.trim(); + suggestions = await getSuggestions(query); + renderSuggestions(suggestions); + currentIndex = -1; + }); + + // Handle input event + searchInput.addEventListener('input', async () => { + const query = searchInput.value.trim(); + if (query.length > 0) { + suggestions = await getSuggestions(query); + } else { + suggestions = []; + } + currentIndex = -1; // Reset index when new results come in + renderSuggestions(suggestions); + }); + + // Handle keydown events for navigation + searchInput.addEventListener('keydown', (event) => { + const items = resultsWrapper.querySelectorAll('li'); + if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Tab') { + event.preventDefault(); + + // Remove 'selected' class from the current item + if (items[currentIndex]) { + items[currentIndex].classList.remove('selected'); + } + + if (event.key === 'ArrowUp') { + currentIndex = (currentIndex > 0) ? currentIndex - 1 : items.length - 1; + } else { + currentIndex = (currentIndex + 1) % items.length; + } + + // Add 'selected' class to the new item and update the input value + if (items[currentIndex]) { + items[currentIndex].classList.add('selected'); + searchInput.value = items[currentIndex].textContent; + } + } else if (event.key === 'Enter') { + if (currentIndex > -1 && items[currentIndex]) { + event.preventDefault(); + selectSuggestion(items[currentIndex]); + } + } + }); + + // Function to handle suggestion selection + function selectSuggestion(item) { + const query = item.textContent; + searchInput.value = query; + resultsWrapper.innerHTML = ''; + searchWrapper.classList.remove('show'); + + // Submit the form or navigate to search results + const form = searchInput.closest('form'); + if (form) { + form.submit(); + } else { + window.location.href = `/search?q=${encodeURIComponent(query)}&t=web`; + } } - // Select the new suggestion - resultsWrapper.querySelectorAll('li')[currentIndex].classList.add('selected'); - // Update the value of the search input - searchInput.value = resultsWrapper.querySelectorAll('li')[currentIndex].textContent; - } + // Handle clicks on search suggestions + resultsWrapper.addEventListener('click', (event) => { + if (event.target.tagName === 'LI') { + selectSuggestion(event.target); + } + }); + + // Close the suggestions when clicking outside + document.addEventListener('click', (event) => { + if (!searchWrapper.contains(event.target)) { + searchWrapper.classList.remove('show'); + resultsWrapper.innerHTML = ''; + currentIndex = -1; + } + }); }); - -// Default to the currently selected type or fallback to 'text' -let selectedType = document.querySelector('.search-active')?.value || 'text'; - -// Function to render results -function renderResults(results) { - if (!results || !results.length || !searchInput.value) { - searchWrapper.classList.remove('show'); - return; - } - - let content = ''; - results.forEach((item) => { - content += `
  • ${item}
  • `; - }); - - if (searchInput.value) { - searchWrapper.classList.add('show'); - } - resultsWrapper.innerHTML = ``; -} - -// Handle click events on the type buttons -const typeButtons = document.querySelectorAll('[name="t"]'); -typeButtons.forEach(button => { - button.addEventListener('click', function() { - selectedType = this.value; - typeButtons.forEach(btn => btn.classList.remove('search-active')); - this.classList.add('search-active'); - }); -}); - -// Handle clicks on search results -resultsWrapper.addEventListener('click', (event) => { - if (event.target.tagName === 'LI') { - const query = event.target.textContent; - window.location.href = `/search?q=${encodeURIComponent(query)}&t=${encodeURIComponent(selectedType)}`; - } -}); - -document.addEventListener("keypress", (event) => { - if (document.activeElement == searchInput) { - // Allow the '/' character to be pressed when searchInput is active - } else if (document.querySelector(".calc") != null) { - // Do nothing if the calculator is available, so the division keybinding - // will still work - } - else if (event.key == "/") { - event.preventDefault(); - searchInput.focus(); - searchInput.selectionStart = searchInput.selectionEnd = searchInput.value.length; - } -}) - -// Add event listener to hide autocomplete suggestions when clicking outside of search-input or wrapper -document.addEventListener('click', (event) => { - // Check if the target of the event is the search-input or any of its ancestors - if (!searchInput.contains(event.target) && !searchWrapper.contains(event.target)) { - // Remove the show class from the search wrapper - searchWrapper.classList.remove('show'); - } -}); - -// // Update visual feedback for selected type on page load -// document.addEventListener("DOMContentLoaded", () => { -// const activeButton = document.querySelector(`[name="t"][value="${selectedType}"]`); -// if (activeButton) { -// typeButtons.forEach(btn => btn.classList.remove('search-active')); -// activeButton.classList.add('search-active'); -// } -// }); diff --git a/templates/search.html b/templates/search.html index 1e644af..aa8966a 100755 --- a/templates/search.html +++ b/templates/search.html @@ -7,95 +7,127 @@ {{ end }} {{ translate "site_name" }} - - - -