diff --git a/src/css/main.css b/src/css/main.css
index 1006997..6eff091 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -1,50 +1,41 @@
+/* Root Variables */
:root {
--bg-color: rgb(15, 15, 15);
--primary: rgb(240, 240, 240);
-
- --secondary-color: hsl(210, 80%, 60%);
- --secondary-color2: hsl(210, 20%, 20%);
-
- --font-weight: 300;
+ --secondary-color: hsl(210, 80%, 80%);
+ --secondary-color2: hsl(210, 60%, 40%);
+ --secondary-color-bg: hsl(210, 40%, 5%);
+ --font-weight: 350;
--font-family: "Lucida Console";
-
- --link-font-weight: 700;
+ --bigger-font-weight: 700;
--link-border-radius: 1000px;
-
- --page-max-width: 600px;
+ --page-max-width: 700px;
}
-/* General */
+/* General Styles */
body {
- background-color: var(--bg-color);
+ background-color: var(--secondary-color-bg);
color: var(--primary);
+ /*color: var(--primary);*/
font-family: var(--font-family), monospace;
font-weight: var(--font-weight);
overflow-x: hidden;
-
-
- /* if slim */
- @media only screen and (max-width: 480px) {
- margin: 5px;
+ margin: 0;
+ position: relative;
+ height: 100%;
}
- /* if wide */
- @media only screen and (min-width: 480px) {
- margin: 20px;
- }
-
- & h1, p {
- margin: 0;
- }
+h1, p {
+ margin: 0;
}
main {
+ position: relative;
+ z-index: 1;
max-width: var(--page-max-width);
margin: 0 auto;
- /*border: 1px solid var(--secondary-color);*/
- /*border-radius: 5px;*/
padding: 20px;
- filter: drop-shadow(0px 0px 1px var(--secondary-color)) brightness(1);
+ filter: drop-shadow(0 0 2px var(--secondary-color)) brightness(1);
}
::selection {
@@ -52,148 +43,209 @@ main {
background: var(--secondary-color);
}
-/* Header*/
+/* Header Styles */
header {
text-align: center;
width: max-content;
margin: 0 auto;
-
- & .names {
- /*margin: 0 auto;*/
- width: min-content;
- white-space: nowrap;
-
- & h1, p {
-
- text-align: left;
- color: var(--secondary-color);
- }
- }
-
- & img {
- border-radius: 50%;
- max-width: 300px;
- padding: 0;
- }
}
-/* Links */
+header .names {
+ width: min-content;
+ white-space: nowrap;
+}
+
+header h1, header p {
+ text-align: left;
+ color: var(--secondary-color);
+}
+
+header img {
+ border-radius: 50%;
+ max-width: 300px;
+ padding: 0;
+}
+
+/* Links Styles */
h2 {
width: 100%;
margin: 0;
- /* if slim */
@media only screen and (max-width: 480px) {
- text-align: center;
+ text-align: center; /* slim */
margin: 10px auto;
}
+
+ @media only screen and (min-width: 480px) {
+ text-align: left; /* wide */
+ margin: 0;
+ }
}
.text-link {
color: var(--secondary-color);
text-decoration: underline solid var(--secondary-color2) 1px;
- top: 0;
position: relative;
padding: 0 2px;
}
.text-link:hover {
text-decoration: underline solid var(--secondary-color) 1px;
- filter: drop-shadow(0px 0px 5px var(--secondary-color2)) brightness(1.02);
+ filter: drop-shadow(0 0 5px var(--secondary-color2)) brightness(1.02);
top: -2px;
}
+/* Links Container */
.links {
padding: 10px;
- border: 1px solid var(--secondary-color);
- border-radius: 5px;
}
+/* Link Group */
.linkGroup {
margin: auto;
- /* if wide */
@media only screen and (min-width: 480px) {
display: flex;
- flex-wrap: wrap;
- }
-
- & .link {
- height: fit-content;
- position: relative;
- top: 0;
- transition: top ease 100ms;
-
- /* if slim */
- @media only screen and (max-width: 480px) {
- margin-bottom: 10px;
- margin-left: 5%;
- margin-right: 5%;
- }
-
- /* if wide */
- @media only screen and (min-width: 480px) {
- padding: 5px;
- }
+ flex-wrap: wrap; /* wide */
}
}
+/* Link Styles */
+.link {
+ height: fit-content;
+ position: relative;
+ transition: top 100ms ease, filter 100ms ease; /* Added filter transition */
+
+ @media only screen and (max-width: 480px) {
+ margin: 10px 5%; /* slim */
+ }
+
+ @media only screen and (min-width: 480px) {
+ padding: 5px; /* wide */
+ }
+}
+
+/* Button Styles */
.linkButton {
- font-weight: var(--link-font-weight);
+ font-weight: var(--bigger-font-weight);
font-size: 25px;
line-height: 25px;
text-align: center;
text-decoration: none;
text-transform: lowercase;
-
color: var(--bg-color);
background-color: var(--secondary-color);
-
display: block;
box-sizing: border-box;
padding: 10px 20px;
border-radius: var(--link-border-radius);
+ width: 100%; /* slim */
- /* if slim */
- @media only screen and (max-width: 480px) {
- width: 100%;
- }
-
- & .icon {
- width: 25px;
- height: 25px;
- padding-right: 10px;
- padding-bottom: 2px;
- vertical-align: middle;
- }
-}
-
-.link:hover {
- filter: drop-shadow(0px 0px 5px var(--secondary-color)) brightness(1.05);
- top: -2px;
-}
-
-.links:hover .link:not(:hover) a {
- /* if wide */
@media only screen and (min-width: 480px) {
- filter: brightness(0.8);
+ width: auto; /* wide */
}
+}
+.linkButton .icon {
+ width: 25px;
+ height: 25px;
+ padding-right: 10px;
+ padding-bottom: 2px;
+ vertical-align: middle;
+}
+
+/* Default Hover Effect */
+.hoverHighlight:hover {
+ @media only screen and (min-width: 480px) {
+ filter: drop-shadow(0 0 8px var(--secondary-color)) brightness(1.1);
+ top: -2px;
+ }
+}
+.links:hover .hoverHighlight:not(:hover) {
+ @media only screen and (min-width: 480px) {
+ filter: brightness(0.5);
+ }
+}
+
+/* Search Highlighting */
+.highlightSearch {
+ filter: drop-shadow(0 0 8px var(--secondary-color)) brightness(1.1);
+ z-index: 1;
+ @media only screen and (min-width: 480px) {
+ top: -2px;
+ }
+}
+
+.notHighlightSearch {
+ filter: brightness(0.5);
+ @media only screen and (max-width: 480px) {
+ display: none;
+ }
}
.linkButton:active {
transform: scale(0.95);
- top: +2px;
+ top: 2px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
+/* Search Input Field Styles */
+.fuzzysearch {
+ margin: 0 auto;
+ text-align: center;
+ max-width: var(--page-max-width);
+}
+#link-search {
+ background-color: var(--secondary-color2);
+ color: var(--secondary-color);
+ border: 2px solid var(--secondary-color2);
+ border-radius: 50px;
+ padding: 10px 15px;
+ font-size: 16px;
+ text-align: center;
+ font-weight: var(--bigger-font-weight);
+ width: 100%;
+ max-width: 400px; /* Adjust width as needed */
+ /*box-shadow: 0 0 5px var(--secondary-color2);*/
+ transition: background-color 100ms ease, border-color 100ms ease, box-shadow 100ms ease;
+}
-/* Timers */
+#link-search::placeholder {
+ color: var(--secondary-color);
+ opacity: 0.9; /* Placeholder text opacity */
+}
+
+#link-search:focus::placeholder {
+ color: var(--secondary-color);
+ opacity: 0.9; /* Placeholder text opacity */
+}
+
+#link-search:focus {
+ outline: none;
+ background-color: var(--secondary-color-bg);
+ box-shadow: 0 0 10px var(--secondary-color);
+ border-color: var(--secondary-color2);
+ color: var(--secondary-color);
+}
+
+@media only screen and (max-width: 480px) {
+ #link-search {
+ padding: 8px 12px;
+ max-width: 90%; /* Responsive width for small screens */
+ }
+}
+
+@media only screen and (min-width: 480px) {
+ #link-search {
+ padding: 10px 15px;
+ }
+}
+
+/* Timers Styles */
.timer p {
- /* if slim */
@media only screen and (max-width: 480px) {
- margin-bottom: 20px;
+ margin-bottom: 20px; /* slim */
}
}
@@ -202,7 +254,7 @@ h2 {
text-decoration: none;
}
-/* Footer */
+/* Footer Styles */
footer {
text-align: center;
-}
\ No newline at end of file
+}
diff --git a/src/index.html b/src/index.html
index 6e96b8f..6d36b59 100644
--- a/src/index.html
+++ b/src/index.html
@@ -3,10 +3,12 @@
- julian brammer
+ Julian Brammer
+
+
@@ -14,12 +16,13 @@
-
julian brammer
+
Julian Brammer
@brulijam
+ css is pain and I used way too much math to implement that fuzzy search.
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
@@ -30,8 +33,10 @@
takimata sanctus est Lorem ipsum dolor sit
amet.
-
-
+
+
javascript is not available. You can still see a list of my links here .
diff --git a/src/js/distance.js b/src/js/distance.js
new file mode 100644
index 0000000..ff72721
--- /dev/null
+++ b/src/js/distance.js
@@ -0,0 +1,70 @@
+function levenshtein(a, b) {
+ const m = a.length;
+ const n = b.length;
+ const d = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
+
+ for (let i = 0; i <= m; i++) d[i][0] = i;
+ for (let j = 0; j <= n; j++) d[0][j] = j;
+
+ for (let i = 1; i <= m; i++) {
+ for (let j = 1; j <= n; j++) {
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
+ d[i][j] = Math.min(
+ d[i - 1][j] + 1,
+ d[i][j - 1] + 1,
+ d[i - 1][j - 1] + cost
+ );
+ }
+ }
+ return d[m][n];
+}
+
+function jaroWinkler(s1, s2) {
+ if (s1 === s2) return 1;
+
+ const len1 = s1.length;
+ const len2 = s2.length;
+ const maxDist = Math.floor(Math.max(len1, len2) / 2) - 1;
+
+ const s1Matches = Array(len1).fill(false);
+ const s2Matches = Array(len2).fill(false);
+
+ let matches = 0;
+ let transpositions = 0;
+
+ for (let i = 0; i < len1; i++) {
+ const start = Math.max(0, i - maxDist);
+ const end = Math.min(len2, i + maxDist + 1);
+
+ for (let j = start; j < end; j++) {
+ if (s2Matches[j]) continue;
+ if (s1[i] !== s2[j]) continue;
+
+ s1Matches[i] = true;
+ s2Matches[j] = true;
+ matches++;
+ break;
+ }
+ }
+
+ if (matches === 0) return 0;
+
+ let k = 0;
+ for (let i = 0; i < len1; i++) {
+ if (!s1Matches[i]) continue;
+ while (!s2Matches[k]) k++;
+ if (s1[i] !== s2[k]) transpositions++;
+ k++;
+ }
+
+ transpositions /= 2;
+
+ return (matches / len1 + matches / len2 + (matches - transpositions) / matches) / 3;
+}
+
+export function calculateSimilarityScore(query, linkName) {
+ const levenshteinDistance = levenshtein(query.toLowerCase(), linkName.toLowerCase());
+ const jaroWinklerScore = jaroWinkler(query.toLowerCase(), linkName.toLowerCase());
+
+ return jaroWinklerScore - levenshteinDistance / Math.max(query.length, linkName.length);
+}
diff --git a/src/js/fuzzysearch.js b/src/js/fuzzysearch.js
new file mode 100644
index 0000000..de9036f
--- /dev/null
+++ b/src/js/fuzzysearch.js
@@ -0,0 +1,126 @@
+import { calculateSimilarityScore } from './distance.js';
+
+function applyFuzzySearch(linksData) {
+ const searchInput = document.getElementById("link-search");
+
+ searchInput.addEventListener("input", function () {
+ const query = searchInput.value.toLowerCase().trim();
+
+ if (query === "") {
+ clearHighlights();
+ return;
+ }
+
+ const allLinks = linksData.links;
+ const scores = allLinks.map(link => ({
+ link: link,
+ score: calculateSimilarityScore(query, link.name)
+ }));
+
+ // Sort scores in descending order
+ scores.sort((a, b) => b.score - a.score);
+
+ const highestScore = scores[0].score;
+ const threshold = Math.max(0.01, highestScore * 0.5) - 0.1; // Adjust as needed
+
+ const matchingLinks = scores
+ .filter(entry => entry.score >= threshold)
+ .map(entry => entry.link);
+
+ highlightLinks(matchingLinks);
+ });
+
+ searchInput.addEventListener("keydown", function (event) {
+ if (event.key === "Enter") {
+ const matchingLinksCount = document.querySelectorAll(".links .link.highlightSearch").length;
+ if (matchingLinksCount === 1) {
+ const bestMatch = document.querySelector(".links .link.highlightSearch .linkButton");
+ if (bestMatch) {
+ bestMatch.click();
+ }
+ }
+ } else if (event.key === "Escape") {
+ searchInput.value = '';
+ clearHighlights();
+ }
+ });
+}
+
+function highlightLinks(matchingLinks) {
+ clearHighlights();
+ const allLinks = document.querySelectorAll(".links .link");
+ allLinks.forEach(linkDiv => {
+ const link = linkDiv.querySelector('a');
+ const linkHref = link.getAttribute('href');
+ const isMatching = matchingLinks.some(matchingLink => matchingLink.href === linkHref);
+
+ if (isMatching) {
+ linkDiv.classList.add('highlightSearch');
+ linkDiv.classList.remove('notHighlightSearch');
+ linkDiv.classList.remove('hoverHighlight');
+ } else {
+ linkDiv.classList.add('notHighlightSearch');
+ linkDiv.classList.remove('highlightSearch');
+ linkDiv.classList.remove('hoverHighlight');
+ }
+ });
+
+ // Add or remove notHighlightSearch
+ const groupContainers = document.querySelectorAll('.linkGroup');
+ groupContainers.forEach(groupContainer => {
+ const hasHighlightedLink = groupContainer.querySelector('.link.highlightSearch');
+ const groupHeader = groupContainer.querySelector('h2');
+
+ if (hasHighlightedLink) {
+ groupHeader.classList.remove('notHighlightSearch');
+ } else {
+ groupHeader.classList.add('notHighlightSearch');
+ }
+
+ });
+
+ document.querySelector('.links').classList.remove('hoverHighlight');
+}
+
+function clearHighlights() {
+ const linkDivs = document.querySelectorAll(".links .link");
+ linkDivs.forEach(linkDiv => {
+ linkDiv.classList.remove('highlightSearch');
+ linkDiv.classList.remove('notHighlightSearch');
+ linkDiv.classList.add('hoverHighlight');
+ });
+
+ const groupHeaders = document.querySelectorAll('.linkGroup h2');
+ groupHeaders.forEach(header => {
+ header.classList.remove('notHighlightSearch');
+ });
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ const searchInput = document.getElementById('link-search');
+ if (searchInput) {
+ searchInput.value = '';
+ }
+});
+
+document.addEventListener('DOMContentLoaded', function() {
+ const searchInput = document.getElementById("link-search");
+
+ if (searchInput) {
+ searchInput.value = '';
+
+ document.addEventListener('keydown', function (event) {
+ const key = event.key;
+ if (key.length === 1) {
+ searchInput.focus();
+ }
+ });
+ }
+});
+
+fetch('content/links.json')
+ .then(response => response.json())
+ .then(data => {
+ applyFuzzySearch(data);
+ })
+ .catch(error => console.error('Error fetching links:', error));
diff --git a/src/js/links.js b/src/js/links.js
index 3a0290b..823739a 100644
--- a/src/js/links.js
+++ b/src/js/links.js
@@ -1,6 +1,7 @@
function createLinkDiv(link) {
const linkDiv = document.createElement('div');
linkDiv.className = 'link';
+ linkDiv.classList.add('hoverHighlight');
const anchor = document.createElement('a');
anchor.className = 'linkButton';
diff --git a/src/js/main.js b/src/js/main.js
index 52ee96b..aca7132 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -1,34 +1,12 @@
-// const secondaryColors = [
-// // "rgb(240, 240, 240)",
-// // "rgb(136, 139, 141)",
-// "rgb(224, 231, 34)",
-// "rgb(255, 173, 0)",
-// "rgb(244,54,76)",
-// // "rgb(219, 62, 177)",
-// "rgb(250, 150, 250)",
-// "rgb(250, 150, 150)",
-// "rgb(242, 172, 185)",
-// "rgb(0, 138, 216)",
-// "rgb(0, 178, 169)",
-// "rgb(88, 188, 64)",
-// "rgb(173, 223, 179)"
-// ];
-//
-// function setRandomSecondaryColor() {
-// const randomColor = secondaryColors[Math.floor(Math.random() * secondaryColors.length)];
-// document.documentElement.style.setProperty('--secondary-color', randomColor);
-// }
-//
-// setRandomSecondaryColor();
+let hue = Math.floor(Math.random() * 360);
-
-let hue = 210;
-const colorChangeInterval = 50;
+const colorChangeInterval = 100;
function setRainbowColor() {
const color = `hsl(${hue}, 80%, 80%)`;
document.documentElement.style.setProperty('--secondary-color', color);
document.documentElement.style.setProperty('--secondary-color2', `hsl(${hue}, 40%, 40%)`);
+ document.documentElement.style.setProperty('--secondary-color-bg', `hsl(${hue}, 40%, 5%)`);
hue = (hue + 1) % 360;
}
diff --git a/src/js/redirect.js b/src/js/redirect.js
new file mode 100644
index 0000000..a2c7664
--- /dev/null
+++ b/src/js/redirect.js
@@ -0,0 +1,60 @@
+import {calculateSimilarityScore} from './distance.js';
+
+document.addEventListener("DOMContentLoaded", function() {
+ handleRedirection();
+});
+
+function handleRedirection() {
+ let path = window.location.pathname.substring(1).toLowerCase();
+
+ if (path === "") {
+ window.location.href = "/";
+ return;
+ }
+
+ fetch('/content/links.json')
+ .then(response => response.json())
+ .then(data => {
+ const bestMatch = findBestMatch(path, data.links);
+ if (bestMatch) {
+ window.location.href = bestMatch.href;
+ } else {
+ displayNotFound();
+ }
+ })
+ .catch(error => {
+ console.error('Error fetching links.json:', error);
+ displayError();
+ });
+}
+
+function findBestMatch(path, links) {
+ let bestMatch = null;
+ let highestScore = -Infinity;
+ const threshold = 0.5;
+
+ for (let item of links) {
+ const itemName = item.name.toLowerCase();
+ const score = calculateSimilarityScore(path, itemName);
+
+ if (score > highestScore) {
+ highestScore = score;
+ bestMatch = item;
+ }
+ }
+
+ if (highestScore < threshold) {
+ window.location.href = "/";
+ return null;
+ }
+
+ return bestMatch;
+}
+
+function displayNotFound() {
+ document.body.innerHTML = 'Not Found The requested page could not be found.
';
+}
+
+function displayError() {
+ document.body.innerHTML = 'Error There was an error retrieving the redirection data.
';
+}
diff --git a/src/js/timer.js b/src/js/timer.js
index c51a123..d2371c0 100644
--- a/src/js/timer.js
+++ b/src/js/timer.js
@@ -34,7 +34,7 @@ function calculateTime() {
employedSinceTenthOfASecond = employedSinceTenthOfASecond.toString().padStart(2, "0");
const employedSinceS = Math.floor(employedSinceMs / 1000);
- // Update Document
+ // update Document
updateDocument("timer-alive-since-s", `${formatNumber(aliveSinceS)}`)
updateDocument("timer-alive-since-ms", `.${formatNumber(aliveSinceTenthOfASecond)}`)
updateDocument("timer-until-birthday-s", `${formatNumber(nextBirthdayS)}`)
diff --git a/src/redirect.html b/src/redirect.html
new file mode 100644
index 0000000..cc86dfe
--- /dev/null
+++ b/src/redirect.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Redirecting...
+
+
+
+
+ Redirecting...
+
+
\ No newline at end of file