draft
This commit is contained in:
116
frontend/templates/index.html
Normal file
116
frontend/templates/index.html
Normal file
@@ -0,0 +1,116 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{ .PageTitle }}</title>
|
||||
<link rel="stylesheet" type="text/css" href="assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<form id="search-form" class="search-grid" action="{{ .SearchFormAction }}">
|
||||
<div class="search-logo">
|
||||
<img als="girl_juice" src="assets/girl_juice.png" />
|
||||
<h2 class="phrases"></h2>
|
||||
<img als="girl_juice" src="assets/girl_juice.png" />
|
||||
</div>
|
||||
|
||||
<input id="search-input" name="{{ .SearchInputName }}" type="text" class="grid-item" class="search" placeholder="{{ .SearchPlaceholder }}" autocomplete="off" />
|
||||
|
||||
{{ if eq .ActiveCard "stores" }}
|
||||
<div class="cards" id="stores">
|
||||
{{ $T := .DiyHrtTarget }}
|
||||
{{range $Store := .Stores }}
|
||||
<a target="{{ $T }}" href="{{ $Store.Url }}" class="card">
|
||||
<h3>{{ $Store.Name }}</h3>
|
||||
</a>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .ActiveCard "listings" }}
|
||||
<div class="cards" id="listings">
|
||||
{{ $T := .DiyHrtTarget }}
|
||||
{{range $Listing := .Listings }}
|
||||
<a target="{{ $T }}" href="{{ $Listing.Url }}" class="card {{ if $Listing.InStock }}in-stock{{ end }}">
|
||||
<h3>{{ $Listing.ProductName }}</h3>
|
||||
<p>{{ $Listing.StoreName }} - {{ $Listing.Price }} {{ $Listing.PriceCurrency }}</p>
|
||||
</a>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .ActiveCard "websites" }}
|
||||
<div class="cards" id="websites">
|
||||
{{ $T := .WebsiteTarget }}
|
||||
{{range $Website := .Websites }}
|
||||
<a href="{{ $Website.Url }}" class="card" target="{{ $T }}">
|
||||
<h3>{{ $Website.Name }}</h3>
|
||||
<img class="card-image" src="{{ $Website.ImageUrl }}" alt="{{ $Website.Name }} picture">
|
||||
</a>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const phrases = [
|
||||
{{range $Phrase := .HeaderPhrases }}
|
||||
"{{ $Phrase }}",
|
||||
{{- end }}
|
||||
]
|
||||
|
||||
function setTitle(element, s, i) {
|
||||
i++
|
||||
element.textContent = s.substring(0, i)
|
||||
|
||||
if (i >=s.length) return;
|
||||
setTimeout(() => setTitle(element, s, i), 100);
|
||||
}
|
||||
|
||||
function titleChanger(element) {
|
||||
setTitle(element, phrases[Math.floor(Math.random()*phrases.length)], 0);
|
||||
setTimeout(() => titleChanger(element), 10000);
|
||||
}
|
||||
|
||||
Array.from(document.querySelectorAll(".phrases")).forEach(element => {
|
||||
titleChanger(element);
|
||||
})
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
Array.from(document.querySelectorAll('input')).forEach(element => {
|
||||
element.focus();
|
||||
})
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const marqueeElement = document.body;
|
||||
let xPosition = 0;
|
||||
let yPosition = 0;
|
||||
const xSpeed = {{ .BackgroundScrollX }}; // Adjust speed here (lower is slower)
|
||||
const ySpeed = {{ .BackgroundScrollY }};
|
||||
|
||||
function animateMarquee() {
|
||||
xPosition -= xSpeed;
|
||||
yPosition -= ySpeed;
|
||||
|
||||
// Reset position when the image has scrolled completely
|
||||
if (Math.abs(xPosition) >= marqueeElement.offsetWidth) {
|
||||
xPosition = 0;
|
||||
}
|
||||
if (Math.abs(yPosition) >= marqueeElement.offsetHeight) {
|
||||
yPosition = 0;
|
||||
}
|
||||
|
||||
marqueeElement.style.backgroundPosition = `${xPosition}px ${yPosition}px`;
|
||||
requestAnimationFrame(animateMarquee);
|
||||
}
|
||||
|
||||
// Start the animation
|
||||
animateMarquee();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="scripts/search.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
97
frontend/templates/search.js
Normal file
97
frontend/templates/search.js
Normal file
@@ -0,0 +1,97 @@
|
||||
console.log("adding features to search...");
|
||||
|
||||
const form = document.getElementById("search-form");
|
||||
const input = document.getElementById("search-input");
|
||||
|
||||
// https://stackoverflow.com/a/3809435/16804841
|
||||
const expression =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
|
||||
const urlRegex = new RegExp(expression);
|
||||
|
||||
const searchEngines = {
|
||||
g: {
|
||||
action: "https://www.google.com/search",
|
||||
name: "q",
|
||||
},
|
||||
d: {
|
||||
action: "https://duckduckgo.com/",
|
||||
name: "q",
|
||||
},
|
||||
y: {
|
||||
action: "https://www.youtube.com/results",
|
||||
name: "search_query",
|
||||
},
|
||||
ya: {
|
||||
action: "https://yandex.com/search/",
|
||||
name: "text",
|
||||
},
|
||||
lure: {
|
||||
action: "https://lure.sh/pkgs",
|
||||
name: "q",
|
||||
},
|
||||
};
|
||||
|
||||
const translationPrefixes = ["t", "translation"];
|
||||
|
||||
function getDeepLUrl(s) {
|
||||
const parts = s.split("-");
|
||||
if (parts.length != 3) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `https://www.deepl.com/en/translator?/#${encodeURIComponent(
|
||||
parts[0].trim()
|
||||
)}/${encodeURIComponent(parts[1].trim())}/${encodeURIComponent(
|
||||
parts[2].trim()
|
||||
)}`;
|
||||
}
|
||||
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
s = input.value;
|
||||
|
||||
// check if url
|
||||
if (s.match(urlRegex)) {
|
||||
window.open(s, "_self");
|
||||
return;
|
||||
}
|
||||
|
||||
// deepl translations
|
||||
let doTranslation = false;
|
||||
for (const value of translationPrefixes) {
|
||||
const prefix = `!${value} `;
|
||||
if (s.startsWith(prefix)) {
|
||||
doTranslation = true;
|
||||
s = s.slice(prefix.length); // Remove the !{key} prefix
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (doTranslation) {
|
||||
const url = getDeepLUrl(s);
|
||||
if (url) {
|
||||
window.open(url.toString(), "_self");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the string starts with ! followed by a key from searchEngines
|
||||
let selectedEngine = {
|
||||
action: form.getAttribute("action"),
|
||||
name: input.getAttribute("name"),
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(searchEngines)) {
|
||||
const prefix = `!${key} `;
|
||||
if (s.startsWith(prefix)) {
|
||||
selectedEngine = value;
|
||||
s = s.slice(prefix.length); // Remove the !{key} prefix
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const url = new URL(selectedEngine.action);
|
||||
url.searchParams.set(selectedEngine.name, s.trim());
|
||||
window.open(url.toString(), "_self");
|
||||
});
|
||||
115
frontend/templates/style.css
Normal file
115
frontend/templates/style.css
Normal file
@@ -0,0 +1,115 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
|
||||
background-color: pink;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background:
|
||||
url("bg.svg") center center/auto repeat,
|
||||
linear-gradient(to bottom, transparent, pink);
|
||||
}
|
||||
|
||||
.search-grid {
|
||||
width: 100%;
|
||||
|
||||
margin-left: 10em;
|
||||
margin-right: 10em;
|
||||
|
||||
display: grid;
|
||||
gap: 5em;
|
||||
|
||||
grid-template-rows: 10em 4em 17em;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 100%;
|
||||
|
||||
grid-row: 2;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.search-logo {
|
||||
grid-row: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (max-height: 300px) {
|
||||
.search-grid {
|
||||
grid-template-rows: 4em;
|
||||
}
|
||||
|
||||
.search-logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cards {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
width: 10em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
color: black;
|
||||
gap: 1em;
|
||||
padding: 1em;
|
||||
|
||||
height: 15em;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
#listings .card {
|
||||
background-color: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#listings .in-stock {
|
||||
background-color: rgba(125, 255, 125, 0.5);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.card-image {
|
||||
height: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
.search-logo img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
background-color: lightblue;
|
||||
|
||||
padding: 1em;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
Reference in New Issue
Block a user