Skip to content

Instantly share code, notes, and snippets.

@Apeiros-46B
Created October 4, 2024 19:24
Show Gist options
  • Select an option

  • Save Apeiros-46B/54266f6673e47acdf4f3d2b2f167dd0d to your computer and use it in GitHub Desktop.

Select an option

Save Apeiros-46B/54266f6673e47acdf4f3d2b2f167dd0d to your computer and use it in GitHub Desktop.
startpage.html
<!-- SCROLL TO THE BOTTOM OF THE GIST TO SEE README -->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta http-equiv='X-UA-Compatible' content='ie-edge' />
<title>startpage</title>
<!-- {{{ user config -->
<script>
const autojump = true;
const links = [
{
title: 'school',
links: [
{ name: 'gcal', url: 'https://calendar.google.com/calendar/u/0/r/week' },
{ name: 'gmail', url: 'https://gmail.com/' },
{ name: 'outlook', url: 'https://webmail.student.ubc.ca/owa/' },
{ name: 'canvas', url: 'https://canvas.ubc.ca/' },
],
},
{
title: 'util',
links: [
{ name: 'github inbox', url: 'https://github.com/notifications' },
{ name: 'nix search', url: 'https://search.nixos.org/packages' },
{ name: 'syncthing', url: 'http://127.0.0.1:8384/' },
{ name: 'org-roam', url: 'http://localhost:35901/' },
],
},
{
title: 'social',
links: [
{ name: 'discord', url: 'https://discord.com/app' },
{ name: 'twitter', url: 'https://x.com/' },
{ name: 'youtube', url: 'https://www.youtube.com/' },
{ name: 'instagram', url: 'https://www.instagram.com/direct/inbox/' },
],
},
{
title: 'other',
links: [
{ name: 'monkeytype', url: 'https://monkeytype.com/' },
{ name: 'wikispeedrun', url: 'https://wikispeedrun.org' },
{ name: 'mynoise', url: 'https://mynoise.net/noiseMachines.php' },
{ name: 'rain sounds', url: 'https://mynoise.net/NoiseMachines/whiteRainNoiseGenerator.php?l=53474337374040454849&a=1&am=s&title=White%20Rain' },
],
},
];
</script>
<!-- }}} -->
<!-- {{{ css -->
<style>
:root {
--font: 'IBM Plex Sans', monospace;
--bg1: #2b3339;
--bg2: #323c41;
--fg1: #d3c6aa;
--fg2: #859289;
--dim: #607279;
--accent1: #a7c080;
--accent2: #e67e80;
--accent3: #7fbbb3;
}
body {
background-color: var(--bg1);
margin: 0px;
}
.container {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
#clock-time {
text-align: center;
font-family: sans-serif;
font-size: 3.5rem;
font-weight: 600;
font-family: var(--font);
color: var(--fg1);
margin-bottom: 0.1em;
}
#clock-date {
text-align: center;
font-family: sans-serif;
font-size: 1.2rem;
font-weight: 600;
font-family: var(--font);
color: var(--fg2);
margin-bottom: -0.1em;
}
.inline {
color: var(--accent2);
display: inline-block;
}
#bookmark-container {
display: flex;
flex-wrap: wrap;
flex-direction: grid;
justify-content: center;
width: 50%;
margin: 1em 1em;
}
@media only screen and (max-width: 960px) {
.container {
height: auto;
}
#clock {
margin-top: 1em;
}
.container > .bookmark-container {
flex-direction: column;
width: 60%;
}
.bookmark-container > .bookmark-set {
width: auto;
margin: 1em 0em;
}
}
.bookmark-set {
padding: 1em;
background-color: var(--bg2);
border-radius: 3px;
font-family: var(--font);
font-size: 0.85rem;
width: 14em;
height: 12em;
margin: 0.5em;
box-sizing: border-box;
}
.bookmark-inner-container {
overflow-y: scroll;
height: 80%;
vertical-align: top;
padding-right: 6px;
box-sizing: border-box;
scrollbar-width: thin;
scrollbar-color: var(--dim) #ffffff00;
}
.bookmark-inner-container::-webkit-scrollbar {
width: 6px;
}
.bookmark-inner-container::-webkit-scrollbar-track {
background: #ffffff00;
}
.bookmark-inner-container::-webkit-scrollbar-thumb {
background-color: var(--dim);
border-radius: 6px;
border: 3px solid #ffffff00;
}
.bookmark-title {
color: var(--accent1) !important;
font-size: 1.1rem;
font-weight: 600;
color: var(--fg1);
margin: 0em 0em 0.35em 0em;
}
.bm-highlight {
color: var(--accent3) !important;
font-weight: bold !important;
}
.bm-bright {
color: var(--fg1) !important;
}
.bm-selected {
font-weight: bold;
}
.bookmark {
text-decoration: none;
color: var(--fg2);
display: block;
margin: 0.5em 0em;
transition: 0.3s color;
transition: 0.3s font-weight;
}
.bookmark:hover {
color: var(--accent3);
font-weight: 600;
transition: 0.3s color;
transition: 0.3s font-weight;
}
@media only screen and (max-width: 446px) {
#clock-time,
#clock-date {
text-align: center;
font-family: sans-serif;
font-size: 1.5rem;
font-weight: 600;
font-family: var(--font);
color: var(--fg1);
margin-bottom: 0.25em;
}
}
</style>
<!-- }}} -->
</head>
<body>
<div class='container'>
<input type='text' id='search' style='opacity: 0; position: absolute' autocomplete='off' />
<div id='clock-time'></div>
<div id='clock-date'></div>
<div id='bookmark-container'></div>
</div>
<!-- {{{ setup -->
<script>
const clockTime = document.getElementById('clock-time');
const clockDate = document.getElementById('clock-date');
const bookmarkC = document.getElementById('bookmark-container');
const search = document.getElementById('search');
// {{{ update time elements
function time() {
let date = new Date(),
M = date.getMinutes(),
H = date.getHours(),
d = date.getDate(),
m = date.getMonth() + 1,
Y = date.getFullYear();
clockTime.innerHTML = `${H < 10 ? '0' : ''}${H}:${M < 10 ? '0' : ''}${M}`;
clockDate.innerHTML = `${Y}.${m < 10 ? '0' : ''}${m}.${d < 10 ? '0' : ''}${d}`;
}
// }}}
// {{{ set up bookmark containers
function setupBookmarks() {
bookmarkC.innerHTML = links
.map(b => {
const html = ["<div class='bookmark-set'>"];
html.push(`<div class='bookmark-title'>${b.title}</div>`);
html.push("<div class='bookmark-inner-container'>");
html.push(...b.links.map(l => `<a class='bookmark' href='${l.url}'>${l.name}</a>`));
html.push('</div></div>');
return html.join('');
})
.join('');
};
// }}}
window.onload = () => {
// set up bookmarks
setupBookmarks();
// update time every 0.1s
time(); setInterval(time, 100);
// {{{ handle bookmark filtering
const bookmarks = Array.from(document.getElementsByClassName('bookmark'))
let filtered = [];
let sel = 0;
function filterBookmarks() {
let matched = false;
// clear filtered links
filtered = [];
sel = 0;
bookmarks.forEach(l => {
let name = l.innerText || l.innerHTML.replace(/<\/?span( class='bm-highlight')?>/g, '');
// check match
if (search.value && name.toLowerCase().startsWith(search.value.toLowerCase())) {
// {{{ matched
// highlight
l.classList.add('bm-bright')
matched || l.classList.add('bm-selected')
filtered.push({ el: l, name: name });
matched = true;
// remove match prefix
name = name.replace(new RegExp(`^${search.value}`), '');
l.innerText = '';
l.innerHTML = `<span class='bm-highlight'>${search.value}</span>${name}`;
// }}}
} else {
// {{{ no match
// unhighlight
l.classList.remove('bm-bright')
l.classList.remove('bm-selected')
// reset
l.innerHTML = name;
// }}}
}
});
// clear search field if no matches
if (!matched) { search.value = ''; return; }
// highlight selected bookmark
filtered[sel]?.el.classList.add('bm-selected')
// autojump to bookmark (if enabled)
if (autojump && filtered.length == 1) { window.location.href = filtered[0].el.href; return }
}
// }}}
// set up search field
search.focus()
search.onblur = search.focus // always focused
search.addEventListener('input', filterBookmarks);
// {{{ handle keybinds
document.addEventListener('keydown', evt => {
let prevent = false;
if (evt.keyCode == 27) {
// ESC: clear search field
search.value = '';
filterBookmarks();
prevent = true;
} else if (evt.keyCode == 9) {
// {{{ TAB/SHIFT-TAB: navigate up/down bookmarks
// unhighlight previous
filtered[sel]?.el.classList.remove('bm-selected')
// navigate up/down
sel += evt.shiftKey ? -1 : +1;
if (sel < 0) {
sel = filtered.length - 1;
} else if (sel >= filtered.length) {
sel = 0;
}
// highlight selected bookmark
filtered[sel]?.el.classList.add('bm-selected')
// }}}
prevent = true;
} else if (evt.keyCode == 13) {
// ENTER: jump to link
if (filtered[sel] !== undefined) {
window.location.href = filtered[sel]?.el.href;
prevent = true;
}
}
if (prevent) {
evt.preventDefault();
evt.stopPropagation();
}
})
// }}}
}
</script>
<!-- }}} -->
</body>
</html>

minimal single-file browser startpage with quick keyboard navigation

default colorscheme: Everforest before c7d3ffe

setup

  • edit const autojump value (line 12) (see usage section for what this does)
  • edit const links object (line 13) to define links
  • edit :root selector (line 56) to define font and colorsk
  • set it as the startpage for your browser (procedure depends on browser)

usage

  • Begin typing link name to search links
    • When an ambiguous prefix (could match multiple links) is entered, they will be highlighted. Press tab to cycle selection and enter to jump
    • When an unambiguous prefix (matches only one link) is entered, the behaviour depends on autojump:
      • autojump = true: Automatically jumps to matched link
      • autojump = false: Still need to press enter to manually jump to a link
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment