changed and implemented a lot
This commit is contained in:
parent
4893ac5a2f
commit
5dbd5fbcdf
262
src/css/main.css
262
src/css/main.css
@ -1,50 +1,41 @@
|
|||||||
|
/* Root Variables */
|
||||||
:root {
|
:root {
|
||||||
--bg-color: rgb(15, 15, 15);
|
--bg-color: rgb(15, 15, 15);
|
||||||
--primary: rgb(240, 240, 240);
|
--primary: rgb(240, 240, 240);
|
||||||
|
--secondary-color: hsl(210, 80%, 80%);
|
||||||
--secondary-color: hsl(210, 80%, 60%);
|
--secondary-color2: hsl(210, 60%, 40%);
|
||||||
--secondary-color2: hsl(210, 20%, 20%);
|
--secondary-color-bg: hsl(210, 40%, 5%);
|
||||||
|
--font-weight: 350;
|
||||||
--font-weight: 300;
|
|
||||||
--font-family: "Lucida Console";
|
--font-family: "Lucida Console";
|
||||||
|
--bigger-font-weight: 700;
|
||||||
--link-font-weight: 700;
|
|
||||||
--link-border-radius: 1000px;
|
--link-border-radius: 1000px;
|
||||||
|
--page-max-width: 700px;
|
||||||
--page-max-width: 600px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* General */
|
/* General Styles */
|
||||||
body {
|
body {
|
||||||
background-color: var(--bg-color);
|
background-color: var(--secondary-color-bg);
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
|
/*color: var(--primary);*/
|
||||||
font-family: var(--font-family), monospace;
|
font-family: var(--font-family), monospace;
|
||||||
font-weight: var(--font-weight);
|
font-weight: var(--font-weight);
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
/* if slim */
|
height: 100%;
|
||||||
@media only screen and (max-width: 480px) {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if wide */
|
h1, p {
|
||||||
@media only screen and (min-width: 480px) {
|
margin: 0;
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& h1, p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
max-width: var(--page-max-width);
|
max-width: var(--page-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
/*border: 1px solid var(--secondary-color);*/
|
|
||||||
/*border-radius: 5px;*/
|
|
||||||
padding: 20px;
|
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 {
|
::selection {
|
||||||
@ -52,148 +43,209 @@ main {
|
|||||||
background: var(--secondary-color);
|
background: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header*/
|
/* Header Styles */
|
||||||
header {
|
header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
margin: 0 auto;
|
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 {
|
h2 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
/* if slim */
|
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
text-align: center;
|
text-align: center; /* slim */
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 480px) {
|
||||||
|
text-align: left; /* wide */
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-link {
|
.text-link {
|
||||||
color: var(--secondary-color);
|
color: var(--secondary-color);
|
||||||
text-decoration: underline solid var(--secondary-color2) 1px;
|
text-decoration: underline solid var(--secondary-color2) 1px;
|
||||||
top: 0;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-link:hover {
|
.text-link:hover {
|
||||||
text-decoration: underline solid var(--secondary-color) 1px;
|
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;
|
top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Links Container */
|
||||||
.links {
|
.links {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid var(--secondary-color);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Link Group */
|
||||||
.linkGroup {
|
.linkGroup {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
/* if wide */
|
|
||||||
@media only screen and (min-width: 480px) {
|
@media only screen and (min-width: 480px) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap; /* wide */
|
||||||
}
|
|
||||||
|
|
||||||
& .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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
.linkButton {
|
||||||
font-weight: var(--link-font-weight);
|
font-weight: var(--bigger-font-weight);
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
|
|
||||||
color: var(--bg-color);
|
color: var(--bg-color);
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
border-radius: var(--link-border-radius);
|
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) {
|
@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 {
|
.linkButton:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
top: +2px;
|
top: 2px;
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
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 {
|
.timer p {
|
||||||
/* if slim */
|
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px; /* slim */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +254,7 @@ h2 {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer Styles */
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>julian brammer</title>
|
<title>Julian Brammer</title>
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="css/main.css">
|
||||||
<link rel="me" href="https://social.brulijam.com/@brulijam">
|
<link rel="me" href="https://social.brulijam.com/@brulijam">
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
|
<script src="js/fuzzysearch.js" type="module"></script>
|
||||||
|
<script src="js/links.js"></script>
|
||||||
<script src="js/timer.js"></script>
|
<script src="js/timer.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -14,12 +16,13 @@
|
|||||||
<header>
|
<header>
|
||||||
<!-- <img src="https://brulijam.com/assets/img/1_1713277032.jpga" alt="<profile picture>"></img>-->
|
<!-- <img src="https://brulijam.com/assets/img/1_1713277032.jpga" alt="<profile picture>"></img>-->
|
||||||
<div class="names">
|
<div class="names">
|
||||||
<h1>julian brammer</h1>
|
<h1>Julian Brammer</h1>
|
||||||
<p>@brulijam</p>
|
<p>@brulijam</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<br>
|
<br>
|
||||||
<section class="description">
|
<section class="description">
|
||||||
|
<p>css is pain and I used way too much math to implement that fuzzy search.</p>
|
||||||
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
||||||
dolore magna aliquyam erat, sed diam
|
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
|
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
|
takimata sanctus est Lorem ipsum dolor sit
|
||||||
amet.</p>
|
amet.</p>
|
||||||
</section>
|
</section>
|
||||||
<br>
|
<br><br>
|
||||||
<script src="js/links.js"></script>
|
<section class="fuzzysearch">
|
||||||
|
<input type="text" id="link-search" placeholder="fuzzy search">
|
||||||
|
</section>
|
||||||
<section class="links">
|
<section class="links">
|
||||||
<noscript>
|
<noscript>
|
||||||
<p>javascript is not available. You can still see a list of my links <a class="text-link" href="content/links.json">here</a>.</p>
|
<p>javascript is not available. You can still see a list of my links <a class="text-link" href="content/links.json">here</a>.</p>
|
||||||
|
70
src/js/distance.js
Normal file
70
src/js/distance.js
Normal file
@ -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);
|
||||||
|
}
|
126
src/js/fuzzysearch.js
Normal file
126
src/js/fuzzysearch.js
Normal file
@ -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));
|
@ -1,6 +1,7 @@
|
|||||||
function createLinkDiv(link) {
|
function createLinkDiv(link) {
|
||||||
const linkDiv = document.createElement('div');
|
const linkDiv = document.createElement('div');
|
||||||
linkDiv.className = 'link';
|
linkDiv.className = 'link';
|
||||||
|
linkDiv.classList.add('hoverHighlight');
|
||||||
|
|
||||||
const anchor = document.createElement('a');
|
const anchor = document.createElement('a');
|
||||||
anchor.className = 'linkButton';
|
anchor.className = 'linkButton';
|
||||||
|
@ -1,34 +1,12 @@
|
|||||||
// const secondaryColors = [
|
let hue = Math.floor(Math.random() * 360);
|
||||||
// // "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();
|
|
||||||
|
|
||||||
|
const colorChangeInterval = 100;
|
||||||
let hue = 210;
|
|
||||||
const colorChangeInterval = 50;
|
|
||||||
|
|
||||||
function setRainbowColor() {
|
function setRainbowColor() {
|
||||||
const color = `hsl(${hue}, 80%, 80%)`;
|
const color = `hsl(${hue}, 80%, 80%)`;
|
||||||
document.documentElement.style.setProperty('--secondary-color', color);
|
document.documentElement.style.setProperty('--secondary-color', color);
|
||||||
document.documentElement.style.setProperty('--secondary-color2', `hsl(${hue}, 40%, 40%)`);
|
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;
|
hue = (hue + 1) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
src/js/redirect.js
Normal file
60
src/js/redirect.js
Normal file
@ -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 = '<h1>Not Found</h1><p>The requested page could not be found.</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayError() {
|
||||||
|
document.body.innerHTML = '<h1>Error</h1><p>There was an error retrieving the redirection data.</p>';
|
||||||
|
}
|
@ -34,7 +34,7 @@ function calculateTime() {
|
|||||||
employedSinceTenthOfASecond = employedSinceTenthOfASecond.toString().padStart(2, "0");
|
employedSinceTenthOfASecond = employedSinceTenthOfASecond.toString().padStart(2, "0");
|
||||||
const employedSinceS = Math.floor(employedSinceMs / 1000);
|
const employedSinceS = Math.floor(employedSinceMs / 1000);
|
||||||
|
|
||||||
// Update Document
|
// update Document
|
||||||
updateDocument("timer-alive-since-s", `${formatNumber(aliveSinceS)}`)
|
updateDocument("timer-alive-since-s", `${formatNumber(aliveSinceS)}`)
|
||||||
updateDocument("timer-alive-since-ms", `.${formatNumber(aliveSinceTenthOfASecond)}`)
|
updateDocument("timer-alive-since-ms", `.${formatNumber(aliveSinceTenthOfASecond)}`)
|
||||||
updateDocument("timer-until-birthday-s", `${formatNumber(nextBirthdayS)}`)
|
updateDocument("timer-until-birthday-s", `${formatNumber(nextBirthdayS)}`)
|
||||||
|
13
src/redirect.html
Normal file
13
src/redirect.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Redirecting...</title>
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
<script src="js/redirect.js" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Redirecting...</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user