diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/component/About.svelte | 98 | ||||
| -rw-r--r-- | src/lib/component/Blog.svelte | 244 | ||||
| -rw-r--r-- | src/lib/component/CV.svelte | 78 | ||||
| -rw-r--r-- | src/lib/component/Music.svelte | 354 | ||||
| -rw-r--r-- | src/lib/component/MyMusic.svelte | 173 | ||||
| -rw-r--r-- | src/lib/component/Project.svelte | 241 | ||||
| -rw-r--r-- | src/lib/component/Promo.svelte | 66 | ||||
| -rw-r--r-- | src/lib/component/Remarks.svelte | 179 | ||||
| -rw-r--r-- | src/lib/component/Ring.svelte | 312 | ||||
| -rw-r--r-- | src/lib/component/Social.svelte | 112 | ||||
| -rw-r--r-- | src/lib/component/Support.svelte | 70 | ||||
| -rw-r--r-- | src/lib/component/Uses.svelte | 145 |
12 files changed, 2072 insertions, 0 deletions
diff --git a/src/lib/component/About.svelte b/src/lib/component/About.svelte new file mode 100644 index 0000000..c2a076d --- /dev/null +++ b/src/lib/component/About.svelte @@ -0,0 +1,98 @@ +<script lang="ts"> + //// import + import { onMount } from "svelte"; + + //// var + const portraits = [ + "/images/netopwibby/01.jpg", + "/images/netopwibby/02.jpg", + "/images/netopwibby/03.jpg", + "/images/netopwibby/04.jpg", + "/images/netopwibby/05.jpg", + "/images/netopwibby/06.jpg", + "/images/netopwibby/07.jpg", + "/images/netopwibby/08.jpg", + "/images/netopwibby/09.jpg", + "/images/netopwibby/10.jpg", + "/images/netopwibby/11.jpg", + "/images/netopwibby/12.jpg", + "/images/netopwibby/13.jpg" + ]; + + let selfie = ""; + + //// function + function randomPortrait() { + return portraits[Math.floor(Math.random() * portraits.length)]; + } + + onMount(async() => { + selfie = randomPortrait(); + }); +</script> + +<style lang="scss"> + :root { + --about-padding: calc(var(--padding) * 2); + } + + h2 { + margin: 0 0 var(--about-padding); padding: var(--padding) var(--about-padding); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + p { + margin: 0; padding: 0 var(--about-padding) var(--about-padding); + white-space: break-spaces; + } + + hr { + margin: 0 0 calc(var(--line-height) / 2) 0; + } + + figure { + padding: 0 var(--about-padding) var(--about-padding) var(--about-padding); + + img { + margin: 0; + } + + figcaption { + padding-top: var(--about-padding); + text-align: center; + } + } +</style> + +<h2>about.webb.page</h2> + +<p><strong>TL;DR bio:</strong> Architech × Excessively Black × East Coast kid on the West Coast × JU$T × Building a better Internet</p> + +<p>I go by NetOperator Wibby, NetOpWibby, or netop://ウエブ pretty much everywhere online. "NetOperator" is the term given to <del>chronically online people</del> internet enthusiasts in the Megaman Battle Network series, one of my favorite game series of all time and constant source of inspiration.</p> + +<p><strong>TL;DR career bio:</strong> From college dropout to self-taught designer turned developer during the 2008 recession → Stopped working on music to focus on family and career → lots of startup work → Now I work at a major "fruit" company. Not bad, eh? Bless the Internet.</p> + +<p><strong>Short life bio:</strong> Voracious reader, absolutely loved mystery books: Encyclopedia Brown, Hardy Boys, Nancy Drew, Archie Comics, &c. Frequently took things apart and collected scraps to build "inventions." Avid sketcher and jotter of ideas.</p> +<p>Majorly influenced by Pharrell Williams, Chad Hugo, Timbaland, Clipse, N.E.R.D, Nujabes, Shing02, Fat Jon, Five Deez, &c and wanted to make music and beats. Performed exactly once and was recognized on the street afterwards ("your performance was <em>really</em> good"). Developed a perfectionist mindset, which is to blame for my debut album still under wraps (lyrics still hold up though), among other things.</p> +<p>Married the girl of my dreams in 2017 (maiden name? Page). We met on a web page in 9th grade, a little known social network called "Classface." Webb + Page. You can't make this up. Oh, and I lost my job four days after our wedding (IoT company imploded and we were the last to know). You wanna hear God laugh? Make plans. Now we have a loquat tree in our backyard in sunny California…LOL (Love Our Loquats)!</p> +<p>I try to live life according to The Golden Rule, and to stick to my ethics and morals as much as possible. It's bothersome when I find that others do not. But like…that's life? It's annoying though. You all should think like me!! 😤</p> + +<hr/> + +<p>Anyhoo, I love the internet. The internet has empowered me to learn, explore, and get inspired by so many cool people around the world. If you love yourself, you probably love others and aren't bigoted. That makes you cool as fuck. If this isn't you, I hope you find peace.</p> + +{#if selfie === ""} + <p>loading…</p> +{:else} + <figure> + <img alt="selfie" src={selfie}/> + <figcaption>the most fantabulous</figcaption> + </figure> +{/if} diff --git a/src/lib/component/Blog.svelte b/src/lib/component/Blog.svelte new file mode 100644 index 0000000..8469701 --- /dev/null +++ b/src/lib/component/Blog.svelte @@ -0,0 +1,244 @@ +<script lang="ts"> + /*** STATE -------------------------------------------- ***/ + const memos = [ + "WM-088.txt", + "WM-087.txt", + "WM-086.txt", + "WM-085.txt", + "WM-084.txt", + "WM-083.txt", + "WM-082.txt", + "WM-081.txt", + "WM-080.txt", + "WM-079.txt", + "WM-078.txt", + "WM-077.txt", + "WM-076.txt", + "WM-075.txt", + "WM-074.txt", + "WM-073.txt", + "WM-072.txt", + "WM-071.txt", + "WM-070.txt", + "WM-069.txt", + "WM-068.txt", + "WM-067.txt", + "WM-066.txt", + "WM-065.txt", + "WM-064.txt", + "WM-063.txt", + "WM-062.txt", + "WM-061.txt", + "WM-060.txt", + "WM-059.txt", + "WM-058.txt", + "WM-057.txt", + "WM-056.txt", + "WM-055.txt", + "WM-054.txt", + "WM-053.txt", + "WM-052.txt", + "WM-051.txt", + "WM-050.txt", + "WM-049.txt", + "WM-048.txt", + "WM-047.txt", + "WM-046.txt", + "WM-045.txt", + "WM-044.txt", + "WM-043.txt", + "WM-042.txt", + "WM-041.txt", + "WM-040.txt", + "WM-039.txt", + "WM-038.txt", + "WM-037.txt", + "WM-036.txt", + "WM-035.txt", + "WM-034.txt", + "WM-033.txt", + "WM-032.txt", + "WM-031.txt", + "WM-030.txt", + "WM-029.txt", + "WM-028.txt", + "WM-027.txt", + "WM-026.txt", + "WM-025.txt", + "WM-024.txt", + "WM-023.txt", + "WM-022.txt", + "WM-021.txt", + "WM-020.txt", + "WM-019.txt", + "WM-018.txt", + "WM-017.txt", + "WM-016.txt", + "WM-015.txt", + "WM-014.txt", + "WM-013.txt", + "WM-012.txt", + "WM-011.txt", + "WM-010.txt", + "WM-009.txt", + "WM-008.txt", + "WM-007.txt", + "WM-006.txt", + "WM-005.txt", + "WM-004.txt", + "WM-003.txt", + "WM-002.txt", + "WM-001.txt" + ]; + + let selectedMemo: string; + let selectedMemoContent: string; + + /*** HELPER ------------------------------------------- ***/ + function processMemo(filename: string): string { + const extensionRegex = /\.[^.]+$/; /*** file extension ***/ + const specialCharsRegex = /[^a-zA-Z0-9]/g; /*** special characters ***/ + const extensionMatch = filename.match(extensionRegex); + let processedFilename = filename; + let wrappedExtension = ""; + + if (extensionMatch) { + processedFilename = filename.slice(0, extensionMatch.index); + wrappedExtension = extensionMatch[0].replace(extensionRegex, (match) => `<span class="special-char">${match}</span>`); + } + + return processedFilename.replace(specialCharsRegex, (match) => `<span class="special-char">${match}</span>`) + wrappedExtension; + } + + async function showMemo(slug: string) { + if (slug === selectedMemo) { + document.querySelector("li.active")!.classList.remove("active"); + selectedMemo = ""; /*** toggle ***/ + } else { + selectedMemoContent = "\nloading…\n"; + selectedMemo = slug; + + try { + const response = await fetch("/api/blog.json", { + body: JSON.stringify({ filename: slug }), + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + method: "POST" + }); + + const { content } = await response.json(); + selectedMemoContent = content; + } catch(error) { + console.error(error); + } + } + } +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding) * 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) calc(var(--list-indentation) * 4) 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 4); + } + + &::after { + top: calc(var(--list-indentation) * -0.75); + width: 1px; + } + + &:not(.active) { + &::after { + height: var(--list-indentation); + } + } + + &.active::after { + height: 100%; + } + } + } + + button { + cursor: pointer; + + &:hover { + color: var(--uchu-yin-4); + text-decoration: underline var(--uchu-yin-2); + } + } + + .content { + line-height: 1.55; + position: relative; + white-space: pre-wrap; + + text-overflow: ellipsis; + overflow-x: hidden; + + &::before { + width: 1px; height: calc(100% + 0.75rem); + top: -0.75rem; left: calc(var(--list-indentation) * -0.75); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } + + :global(.date) { + color: var(--uchu-yin-3); + } + + :global(.special-char) { + color: var(--uchu-yin-3); + } +</style> + +<h2> + <a href="https://blog.webb.page" target="_blank">blog.webb.page</a> +</h2> + +<ul> + {#each memos as memo} + <li class:active={selectedMemo === memo}> + <button on:click={() => showMemo(memo)}>{@html processMemo(memo)}</button> + + {#if selectedMemo === memo} + <div class="content"> + {@html selectedMemoContent} + </div> + {/if} + </li> + {/each} +</ul> diff --git a/src/lib/component/CV.svelte b/src/lib/component/CV.svelte new file mode 100644 index 0000000..b24d2cf --- /dev/null +++ b/src/lib/component/CV.svelte @@ -0,0 +1,78 @@ +<script lang="ts"> + //// import + import { onMount } from "svelte"; + + //// var + let cv = ""; + + //// function + async function showContent() { + let cvContent = ""; + + try { + const response = await fetch("/api/cv.json", { + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + method: "POST" + }); + + const { content } = await response.json(); + cvContent = content; + } catch(error) { + console.error(error); + } + + return cvContent; + } + + onMount(async() => { + cv = await showContent(); + }); +</script> + +<style lang="scss"> + :root { + --cv-padding: calc(var(--padding) * 2); + } + + h2 { + margin: 0 0 var(--cv-padding); padding: var(--padding) var(--cv-padding); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + .content { + font-family: monospace; + line-height: 1.55; + overflow-x: hidden; + padding-left: var(--cv-padding); + padding-right: var(--cv-padding); + position: relative; + text-overflow: ellipsis; + white-space: pre-line; + + &.loading { + padding-bottom: var(--cv-padding); + } + } +</style> + +<h2> + <a href="https://cv.webb.page" target="_blank">cv.webb.page</a> +</h2> + +{#if cv === ""} + <div class="content loading">loading…</div> +{:else} + <div class="content"> + {cv} + </div> +{/if} diff --git a/src/lib/component/Music.svelte b/src/lib/component/Music.svelte new file mode 100644 index 0000000..01b731a --- /dev/null +++ b/src/lib/component/Music.svelte @@ -0,0 +1,354 @@ +<script lang="ts"> + //// var + const bandcamp = [ + { + by: "ViRiX Dreamcore, DV-i", + id: "3230467359", + title: "UPLINK - CROSSNIQ+ Original Sound", + type: "album" + }, + { + by: "Pizza Hotline", + id: "2804828422", + title: "Polygon Island", + type: "album" + }, + { + by: "leon chang", + id: "3685680305", + title: "re:treat", + type: "album" + }, + { + by: "Sebdoom", + id: "640549190", + title: "Cyberpunk 2077 Fanmade Soundtrack", + type: "album" + }, + { + by: "Teebs", + id: "1931742968", + title: "Ardour", + type: "album" + }, + { + by: "Kamasi Washington", + id: "3603991383", + title: "The Epic", + type: "album" + }, + { + by: "Thundercat", + id: "3794237863", + title: "Drunk", + type: "album" + }, + { + by: "TEEEL", + id: "1898404266", + title: "Overtime", + type: "album" + }, + { + by: "MACROSS 82-99", + id: "2376445182", + title: "It's Own Way [マクロスMACROSS 82-99 Remix]", + type: "single" + }, + { + by: "LAKIM", + id: "635810172", + title: "mi_amigos_", + type: "single" + }, + { + by: "LAKIM", + id: "1488235027", + title: "the dash", + type: "single" + }, + { + by: "LAKIM", + id: "2759787857", + title: "20 Steps", + type: "single" + }, + { + by: "LAKIM", + id: "3667963750", + title: "Forward March", + type: "single" + }, + { + by: "Alexander Brandon", + id: "953490707", + title: "Space Noir (Original Game Soundtrack)", + type: "album" + }, + { + by: "Funk Fiction", + id: "4237561960", + title: "Sonic BeATS", + type: "album" + }, + { + by: "Introverted Dancefloor", + id: "751538854", + title: "When I'm Lonely", + type: "album" + }, + { + by: "C418", + id: "1349219244", + title: "Minecraft - Volume Alpha", + type: "album" + }, + { + by: "Cyberpunk 2077", + id: "1773399509", + title: "Cyberpunk 2077: Radio, Vol. 1 (Original Soundtrack)", + type: "album" + }, + { + by: "Cyberpunk 2077", + id: "1870167199", + title: "Cyberpunk 2077: Radio, Vol. 2 (Original Soundtrack)", + type: "album" + }, + { + by: "Cyberpunk 2077", + id: "4182307863", + title: "Cyberpunk 2077: Radio, Vol. 3 (Original Soundtrack)", + type: "album" + }, + { + by: "Cyberpunk 2077", + id: "3624438326", + title: "Cyberpunk 2077: Radio, Vol. 4 (Original Soundtrack)", + type: "album" + }, + + { + by: "Danny Scott Lane", + id: "422810680", + title: "Shower", + type: "album" + }, + { + by: "Danny Scott Lane", + id: "1584151539", + title: "Home Decor", + type: "album" + }, + { + by: "ALFAXYSM", + id: "803715127", + title: "CRYSTALLOMANIA (Ver. 1.0)", + type: "album" + }, + { + by: "ALFAXYSM", + id: "4001491411", + title: "i-port", + type: "album" + }, + { + by: "Limousine", + id: "2493506758", + title: "Skymall", + type: "album" + }, + { + by: "nuphory", + id: "3332776732", + title: "ditherdream1", + type: "album" + }, + { + by: "AFTA-1", + id: "3211763084", + title: "Silica_01", + type: "track" + }, + { + by: "AFTA-1", + id: "692190634", + title: "103120", + type: "album" + }, + { + by: "bo en", + id: "1387178982", + title: "pale machine 10 year anniversary re-master", + type: "album" + }, + { + by: "Mndsgn.", + id: "3524388493", + title: "Snaxxx", + type: "album" + }, + { + by: "Zai Kowen", + id: "1537466475", + title: "Ocean View", + type: "album" + }, + { + by: "Jungle Fatigue Kru", + id: "3478474368", + title: "Jungle Fatigue Vol. 3", + type: "album" + }, + { + by: "DEVIL KILLER", + id: "3479413388", + title: "Pick A Fucking Car", + type: "album" + }, + { + by: "Pizza Hotline", + id: "71750021", + title: "DELIVERY BOY 2099 // 配達少年2099", + type: "album" + }, + { + by: "Pizza Hotline", + id: "483965118", + title: "Pressing Business", + type: "album" + }, + { + by: "nuphory & Pizza Hotline", + id: "1120176299", + title: "startup tune feat. nuphory", + type: "track" + }, + { + by: "Pizza Hotline", + id: "2629563639", + title: "LEVEL SELECT", + type: "album" + }, + { + by: "Alice Auer", + id: "2689738227", + title: "Baby, Cry EP", + type: "album" + }, + { + by: "DV-i", + id: "435475786", + title: "Perpetual", + type: "track" + }, + ]; + + let activeRelease: string; +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding) * 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) calc(var(--list-indentation) * 4) 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 4); + } + + &::after { + width: 1px; height: var(--list-indentation); + top: calc(var(--list-indentation) * -0.75); + } + + button { + cursor: pointer; + position: relative; + + &.active { + padding-left: calc(var(--baseline) * 2); + + &::before { + width: calc(var(--list-indentation) * 1.25); height: 1px; + top: 50%; left: calc(calc(var(--baseline) * 2) * -1); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } + } + } + } + + .player { + align-items: center; + display: flex; + flex-direction: column; + position: relative; + + iframe { + max-width: 700px; + width: 100%; + } + + &::before { + width: 1px; height: calc(100% + 0.75rem); + top: -0.75rem; left: calc(var(--list-indentation) * -0.75); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } +</style> + +<h2>music.webb.page/bandcamp</h2> + +<ul> + {#each bandcamp as release (release.id)} + <li> + <button + class:active={activeRelease === release.id} + on:click={() => activeRelease === release.id ? activeRelease = "" : activeRelease = release.id}>{release.title} by {release.by}</button> + + {#if activeRelease === release.id} + <div class="player"> + <img alt={release.title + " art"} src={"/images/bandcamp/" + release.id + ".jpg"}/> + <!-- svelte-ignore a11y-missing-attribute --> + {#if release.type === "album"} + <iframe style="height: 300px;" src="https://bandcamp.com/EmbeddedPlayer/album={release.id}/size=large/bgcol=ffffff/linkcol=333333/artwork=none/transparent=true/" seamless></iframe> + {:else} + <iframe style="height: 42px;" src="https://bandcamp.com/EmbeddedPlayer/track={release.id}/size=small/bgcol=ffffff/linkcol=333333/artwork=none/transparent=true/" seamless></iframe> + {/if} + </div> + {/if} + </li> + {/each} +</ul> diff --git a/src/lib/component/MyMusic.svelte b/src/lib/component/MyMusic.svelte new file mode 100644 index 0000000..aed9098 --- /dev/null +++ b/src/lib/component/MyMusic.svelte @@ -0,0 +1,173 @@ +<script lang="ts"> + //// var + const bandcamp = [ + { + by: "Yung Sun", + id: "2006853030", + title: "Past Future Artifacts", + type: "album" + }, + { + by: "Yung Sun", + id: "1972128885", + title: "Don't Leave Me This Way (FRSH×RMX)", + type: "single" + }, + { + by: "Yung Sun", + id: "4219807572", + title: "Music from the Love Lounge, Vol. 04", + type: "album" + }, + { + by: "Yung Sun", + id: "3091487133", + title: "Spaceman Fresh", + type: "album" + }, + { + by: "Yung Sun", + id: "2247255572", + title: "Music from the Love Lounge, Vol. 03", + type: "album" + }, + { + by: "Yung Sun", + id: "955213907", + title: "Music from the Love Lounge, Vol. 02", + type: "album" + }, + { + by: "Yung Sun", + id: "1085756242", + title: "The Aerobyss Files", + type: "album" + }, + { + by: "Yung Sun", + id: "2714432574", + title: "The Construct", + type: "album" + }, + { + by: "Yung Sun", + id: "2723488137", + title: "P.I. Noir - The Disappearance of Madamé Sélour", + type: "album" + }, + { + by: "Yung Sun", + id: "512177084", + title: "Music from the Love Lounge, Vol. 01", + type: "album" + } + ]; + + let activeRelease = "3091487133"; +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding)* 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) calc(var(--list-indentation) * 4) 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 4); + } + + &::after { + width: 1px; height: var(--list-indentation); + top: calc(var(--list-indentation) * -0.75); + } + + button { + cursor: pointer; + position: relative; + + &.active { + padding-left: calc(var(--baseline) * 2); + + &::before { + width: calc(var(--list-indentation) * 1.25); height: 1px; + top: 50%; left: calc(calc(var(--baseline) * 2) * -1); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } + } + } + } + + .player { + align-items: center; + display: flex; + flex-direction: column; + position: relative; + + iframe { + max-width: 700px; + width: 100%; + } + + &::before { + width: 1px; height: calc(100% + 0.75rem); + top: -0.75rem; left: calc(var(--list-indentation) * -0.75); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } +</style> + +<h2>music.webb.page</h2> + +<ul> + {#each bandcamp as release (release.id)} + <li> + <button + class:active={activeRelease === release.id} + on:click={() => activeRelease === release.id ? activeRelease = "" : activeRelease = release.id}>{release.title} by {release.by}</button> + + {#if activeRelease === release.id} + <div class="player"> + <img alt={release.title + " art"} src={"/images/bandcamp/" + release.id + ".jpg"}/> + <!-- svelte-ignore a11y-missing-attribute --> + {#if release.type === "album"} + <iframe style="height: 300px;" src="https://bandcamp.com/EmbeddedPlayer/album={release.id}/size=large/bgcol=ffffff/linkcol=333333/artwork=none/transparent=true/" seamless></iframe> + {:else} + <iframe style="height: 42px;" src="https://bandcamp.com/EmbeddedPlayer/track={release.id}/size=small/bgcol=ffffff/linkcol=333333/artwork=none/transparent=true/" seamless></iframe> + {/if} + </div> + {/if} + </li> + {/each} +</ul> diff --git a/src/lib/component/Project.svelte b/src/lib/component/Project.svelte new file mode 100644 index 0000000..024045e --- /dev/null +++ b/src/lib/component/Project.svelte @@ -0,0 +1,241 @@ +<script lang="ts"> + //// var + const videoRegex = /\.(mp4|mov|avi|wmv|flv|mkv|webm|m4v)$/i; + + const projects = [ + { + content: "<p>This project was a proof-of-concept utilizing node-webkit (circa 2013) to see if it was possible to create a web browser.</p><p>Performance wasn't great but damn it looks good.</p>", + id: "001", + media: [ + "/images/projects/aries_01.jpg", + "/images/projects/aries_02.jpg" + ], + tagline: "web browser", + title: "Aries" + }, + { + content: `<p>This registrar was built upon the Handshake blockchain to sell domain names and provide hosting. I poured everything I've ever wanted in a registrar into this.</p><p>I've stopped working on it due to <a href="https://blog.neuenet.com/post/devlog-014" target="_blank">happenings I didn't agree with</a> happening in the Handshake community. I will come back to beachfront/ when I feel things are better or I <a href="https://dap.sh?ref=webb.page" target="_blank">fork Handshake</a>.</p>`, + id: "002", + media: [ + "/images/projects/beachfront_01.jpg", + "/images/projects/beachfront_02.jpg" + ], + tagline: "next-generation registrar", + title: "beachfront/" + }, + { + content: `<p>In 2019 I was sick of using SemVer so I made something better (for me). Check it out at <a href="https://chronver.org?ref=webb.page">chronver.org</a>!</p>`, + id: "009", + media: [ + "/images/projects/chronver.jpg" + ], + tagline: "chronologic/calendar-based versioning system", + title: "ChronVer" + }, + { + content: `<p>I didn’t like the way things were being run on <a href="https://archive.is/vpZCG?ref=webb.page" target="_blank">the Handshake blockchain</a> but <a href="https://archive.is/TMycF?ref=webb.page" target="_blank">I believe in the vision</a> so I made my own. Dap is the better handshake and it’s entering testnet soon.</p><p>Check it out at <a href="https://dap.sh?ref=webb.page">dap.sh</a>!</p>`, + id: "010", + media: [ + "/images/projects/dap.jpg" + ], + tagline: "a secure foundation for the neue internet", + title: "Dap" + }, + { + content: "<p>In 2014 I had the idea for a “responsive operating system” that could run on any device from a Raspberry Pi to a desktop PC.</p><p>There's no reason why a neat OS GUI shouldn't look as good as anime interfaces.</p>", + id: "003", + media: [ + "https://nickel.video/embed/9Ik01L_lK5n1" + ], + tagline: "responsive operating system", + title: "hikari" + }, + // { + // content: `<p>This registry was built upon the Handshake blockchain to provide infrastructure for my TLDs. I wrote a nameserver in Deno, created a DNSSEC tool to secure said TLDs, and so on. I built and blogged a <strong>lot</strong> about my vision for the internet and freely gave away ideas and a roadmap for others to adopt.</p><p>I've stopped working on it due to <a href="https://archive.is/vpZCG?ref=webb.page" target="_blank">happenings I didn't agree with</a> happening in the Handshake community. I will come back to beachfront/ when I feel things are better or I <a href="https://dap.sh?ref=webb.page" target="_blank">fork Handshake</a>.</p>`, + // id: "004", + // media: [ + // "/images/projects/neuenet_01.jpg", + // "/images/projects/neuenet_02.jpg" + // ], + // tagline: "registry for the Neue Internet", + // title: "Neuenet" + // }, + { + content: `<p>I just want a place to upload my Fortnite clips and <del>fuck</del> <em>I'm not a fan of</em> Google. No other platform out there makes sense either so once again, I gotta build what I want to use.</p><p>Check it out at <a href="https://nickel.video?ref=webb.page">nickel.video</a>!</p>`, + id: "005", + media: [ + "/images/projects/nickel_01.jpg" + ], + tagline: "short-form video platform", + title: "Nickel" + }, + { + content: "<p>There's no point in competing with Google, they've got a 20 year headstart…but what if you could just ask someone who's knowledgeable af? You do realize Google only has data because of us. Right?</p><p>Anyhoo, there's a lot of parts to make this work and I'm busy with side quests at the moment.</p>", + id: "006", + media: [], + tagline: "P2P search", + title: "queree" + }, + { + content: `<p>Around 2017 or so I had the idea of a new kind of social network that brought back the great parts of Myspace while empowering people with comprehensive customization, data, and moderation tools. Bluesky does an excellent job with the latter but being VC-backed means it's only a matter of time before enshittification.</p><p><del>Work in progress.</del> Mastodon and Bluesky are good enough so I decided to stop working on this.</p>`, + id: "007", + media: [ + "/images/projects/socii_01.jpg" + ], + tagline: "the social network", + title: "socii" + }, + { + content: `<p>After I learned about <a href="https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl?ref=webb.page" target="_blank">the OKLCH color space</a> and saw how much more vibrant colors were than HEX, I delved into creating the perfect color palette for me. I spent nearly a year copy/pasting my CSS variables in my projects until I decided to have a unified place for them.</p><p><em>For some reason</em> <a href="https://news.ycombinator.com/item?id=43072338?ref=webb.page" target="_blank">my post on HackerNews</a> about uchū got super popular, which was neat. 600+ points and 900+ Github stars is wild. A handful of weirdos got irate, which was…alarming. Maybe touch grass?</p><p>Check out the site at <a href="https://uchu.style?ref=webb.page" target="_blank">uchu.style</a>!</p>`, + id: "008", + media: [ + "/images/projects/uchu_01.jpg" + ], + tagline: "the color palette for internet lovers", + title: "uchū" + } + ]; + + let activeProject = "005"; +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding) * 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) calc(var(--list-indentation) * 4) 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 4); + } + + &::after { + width: 1px; height: var(--list-indentation); + top: calc(var(--list-indentation) * -0.75); + } + + button { + cursor: pointer; + position: relative; + + &.active { + padding-left: calc(var(--baseline) * 2); + + &::before { + width: calc(var(--list-indentation) * 1.25); height: 1px; + top: 50%; left: calc(calc(var(--baseline) * 2) * -1); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } + } + } + } + + .player { + align-items: center; + display: flex; + flex-direction: column; + position: relative; + + &::before { + width: 1px; height: calc(100% + 0.75rem); + top: -0.75rem; left: calc(var(--list-indentation) * -0.75); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } + + :global(p) { + line-height: var(--line-height); + margin-bottom: 0; + padding-top: calc(var(--padding) * 2); + text-align: left; + white-space: normal; + width: 100%; + + :global(a) { + text-decoration: underline var(--uchu-yin-2); + } + } + + figure { + margin-bottom: 0; + } + + iframe { + aspect-ratio: 16 / 9; + margin-top: calc(var(--padding) * 2); + width: 100%; + } + + video { + padding-top: calc(var(--padding) * 2); + width: 100%; + } +</style> + +<h2>projects.webb.page</h2> + +<ul> + {#each projects as project (project.id)} + <li> + <button + class:active={activeProject === project.id} + on:click={() => activeProject === project.id ? activeProject = "" : activeProject = project.id}>{project.title} · {project.tagline}</button> + + {#if activeProject === project.id} + <div class="player"> + {@html project.content} + + {#each project.media as mediaItem} + {#if videoRegex.test(mediaItem)} + <figure> + <!-- svelte-ignore a11y-media-has-caption --> + <video controls> + <source src={mediaItem}/> + </video> + </figure> + {:else if mediaItem.includes("nickel.video")} + <iframe src={mediaItem} title=""></iframe> + {:else} + <figure> + <img alt="" src={mediaItem}/> + <figcaption></figcaption> + </figure> + {/if} + {/each} + </div> + {/if} + </li> + {/each} +</ul> diff --git a/src/lib/component/Promo.svelte b/src/lib/component/Promo.svelte new file mode 100644 index 0000000..54f57bd --- /dev/null +++ b/src/lib/component/Promo.svelte @@ -0,0 +1,66 @@ +<script lang="ts"></script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding)* 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) 0.25rem 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 4); + } + + &::after { + width: 1px; height: var(--list-indentation); + top: calc(var(--list-indentation) * -0.75); + } + } + + a { + text-decoration: underline var(--uchu-yin-2); + + span { + padding-left: 0.2rem; + position: relative; + text-decoration: underline var(--uchu-gray-2); + } + } + } +</style> + +<h2>promo.webb.page</h2> + +<ul> + <li> + <a href="https://www.eyebuydirect.com/referral/5217807">EyeBuyDirect<span> (prescription eyewear)</span></a> + </li> + + <li> + <a href="https://lnk.rise-ai.com/68SlyIVHzDVybhw">Moment<span> (iPhone photography/videography tools)</span></a> + </li> +</ul> diff --git a/src/lib/component/Remarks.svelte b/src/lib/component/Remarks.svelte new file mode 100644 index 0000000..6633654 --- /dev/null +++ b/src/lib/component/Remarks.svelte @@ -0,0 +1,179 @@ +<script lang="ts"> + /*** STATE -------------------------------------------- ***/ + const remarks = [ + "WR-023.txt", + "WR-022.txt", + "WR-021.txt", + "WR-020.txt", + "WR-019.txt", + "WR-018.txt", + "WR-017.txt", + "WR-016.txt", + "WR-015.txt", + "WR-014.txt", + "WR-013.txt", + "WR-012.txt", + "WR-011.txt", + "WR-010.txt", + "WR-009.txt", + "WR-008.txt", + "WR-007.txt", + "WR-006.txt", + "WR-005.txt", + "WR-004.txt", + "WR-003.txt", + "WR-002.txt", + "WR-001.txt" + ]; + + let selectedRemark: string; + let selectedRemarkContent: string; + + /*** HELPER ------------------------------------------- ***/ + function processNote(filename: string): string { + const extensionRegex = /\.[^.]+$/; /*** file extension ***/ + const specialCharsRegex = /[^a-zA-Z0-9]/g; /*** special characters ***/ + const extensionMatch = filename.match(extensionRegex); + let processedFilename = filename; + let wrappedExtension = ""; + + if (extensionMatch) { + processedFilename = filename.slice(0, extensionMatch.index); + wrappedExtension = extensionMatch[0].replace(extensionRegex, (match) => `<span class="special-char">${match}</span>`); + } + + return processedFilename.replace(specialCharsRegex, (match) => `<span class="special-char">${match}</span>`) + wrappedExtension; + } + + async function showNote(slug: string) { + if (slug === selectedRemark) { + document.querySelector("li.active")!.classList.remove("active"); + selectedRemark = ""; /*** toggle ***/ + } else { + selectedRemarkContent = "\nloading…\n"; + selectedRemark = slug; + + try { + const response = await fetch("/api/remarks.json", { + body: JSON.stringify({ filename: slug }), + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + method: "POST" + }); + + const { content } = await response.json(); + selectedRemarkContent = content; + } catch(error) { + console.error(error); + } + } + } +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding)* 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) calc(var(--list-indentation) / 2) 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 4); + } + + &::after { + top: calc(var(--list-indentation) * -0.75); + width: 1px; + } + + &:not(.active) { + &::after { + height: var(--list-indentation); + } + } + + &.active::after { + height: 100%; + } + } + } + + button { + cursor: pointer; + + &:hover { + color: var(--uchu-yin-4); + text-decoration: underline var(--uchu-yin-2); + } + } + + .content { + line-height: 1.55; + position: relative; + white-space: pre-wrap; + + text-overflow: ellipsis; + overflow-x: hidden; + + &::before { + width: 1px; height: calc(100% + 0.75rem); + top: -0.75rem; left: calc(var(--list-indentation) * -0.75); + + background-color: var(--color-border); + content: ""; + position: absolute; + } + } + + :global(.date) { + color: var(--uchu-yin-3); + } + + :global(.special-char) { + color: var(--uchu-yin-3); + } +</style> + +<h2> + <a href="https://blog.webb.page/remarks" target="_blank">blog.webb.page/remarks</a> +</h2> + +<ul> + {#each remarks as remark} + <li class:active={selectedRemark === remark}> + <button on:click={() => showNote(remark)}>{@html processNote(remark)}</button> + + {#if selectedRemark === remark} + <div class="content"> + {@html selectedRemarkContent} + </div> + {/if} + </li> + {/each} +</ul> diff --git a/src/lib/component/Ring.svelte b/src/lib/component/Ring.svelte new file mode 100644 index 0000000..01e4910 --- /dev/null +++ b/src/lib/component/Ring.svelte @@ -0,0 +1,312 @@ +<script lang="ts"> + //// var + const protocolRegex = /^(https?:\/\/)(www\.)?/; + const referrer = "?ref=webb.page"; + + const friends = [ + { + alias: "", + name: "Anna Maria", + urls: ["https://annamariaperez.com"] + }, + { + alias: "Astro✨Dim", + name: "", + urls: ["https://www.astrodim.com"] + }, + { + alias: "bias", + name: "", + urls: [ + "https://www.mendicantbias.xyz", + "https://www.punk.energy", + "https://www.punkstep.com" + ] + }, + { + alias: "", + name: "Brad Barrish", + urls: ["https://bradbarrish.me"] + }, + { + alias: "", + name: "Cassandra Heart", + urls: ["https://quilibrium.com"] + }, + { + alias: "", + name: "Cornelius Toole", + urls: ["https://micro.corntoole.com"] + }, + { + alias: "", + name: "David Yarde", + urls: ["https://davidyarde.com"] + }, + { + alias: "", + name: "Dewon Hall", + urls: ["https://thatcreativeguy.com"] + }, + { + alias: "riotgoools", + name: "elle", + urls: [ + "https://ellesho.me/page", + "https://riotgoools.com", + "https://veryinter.net/person" + ] + }, + { + alias: "", + name: "EmmoLei Sankofa", + urls: ["https://www.e-sankofa.com"] + }, + { + alias: "eskimo", + name: "", + urls: ["https://eskimo.software"] + }, + { + alias: "", + name: "Iheanyi Ekechukwu", + urls: ["https://iheanyi.com"] + }, + { + alias: "", + name: "Jacky Alciné", + urls: ["https://jacky.wtf"] + }, + { + alias: "", + name: "Jamar Torres", + urls: ["https://www.jamar.dev"] + }, + { + alias: "", + name: "Jesse Youngblood", + urls: ["https://jesseyoungblood.com"] + }, + { + alias: "", + name: "Jonathon Toon", + urls: ["https://jonathontoon.com"] + }, + { + alias: "", + name: "Jordan Greene", + urls: ["https://www.jordankgreen.com"] + }, + { + alias: "", + name: "Josh Sender", + urls: ["https://www.joshsender.com"] + }, + { + alias: "", + name: "Justin Edmund", + urls: ["https://www.jedmund.com"] + }, + { + alias: "", + name: "Kathy Varela", + urls: ["https://www.kathyvarela.com"] + }, + { + alias: "DJ Brisseaux", + name: "Karl Brisseaux", + urls: ["https://kbrissy.com"] + }, + { + alias: "brisseaux", + name: "Kervin Brisseaux", + urls: ["https://www.brisseaux.com"] + }, + { + alias: "", + name: "Kumail Hunaid", + urls: ["https://kumailht.com"] + }, + { + alias: "my wife", + name: "Lakeisha Webb", + urls: ["https://sincerelyshantelle.com"] + }, + { + alias: "", + name: "Lauren Dorman", + urls: ["https://laurendorman.io"] + }, + { + alias: "", + name: "Matt Thomas", + urls: ["https://insidae.com"] + }, + { + alias: "", + name: "Maurice Cherry", + urls: ["https://www.mauricecherry.com"] + }, + { + alias: "Fresh Daily", + name: "Michael Richardson", + urls: ["https://freshdaily.godaddysites.com"] + }, + { + alias: "", + name: "Mike Toney", + urls: ["https://miketoney.com"] + }, + { + alias: "", + name: "Ollie Barker", + urls: ["https://www.olliebarker.co.uk"] + }, + { + alias: "", + name: "Rasmus Andersson", + urls: ["https://rsms.me"] + }, + { + alias: "", + name: "Reginé Gilbert", + urls: ["https://reginegilbert.com"] + }, + { + alias: "", + name: "Samantha", + urls: ["https://humankind.place"] + }, + { + alias: "", + name: "Sang Lee", + urls: ["https://www.sanglee.me"] + }, + { + alias: "SegamanXero", + name: "", + urls: ["https://www.segamanxero.com"] + }, + { + alias: "", + name: "Selena Rox", + urls: ["https://selenarox.com"] + }, + { + alias: "", + name: "Shomi Patwary", + urls: ["https://www.illusivemedia.com"] + }, + { + alias: "Skunkie", + name: "Tara", + urls: ["https://punklabs.com"] + }, + { + alias: "pugson", + name: "Wojtek Witkowski", + urls: ["https://wojtek.im"] + }, + { + alias: "Z", + name: "", + urls: ["https://blacksocialists.us"] + } + ]; + + //// function + function processUrl(url: string) { + const ancillaryParts = url.match(protocolRegex); + + if (!ancillaryParts) + return url; + + const ancillaryPartsMatch = ancillaryParts[0]; + const restOfUrl = url.slice(ancillaryPartsMatch.length); + + return `<span class="special-char">${ancillaryPartsMatch}</span>${restOfUrl}`; + } +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding)* 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) calc(var(--list-indentation) * 4) 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + + line-height: var(--line-height); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 2.5); + } + + &::after { + width: 1px; height: var(--list-indentation); + top: calc(var(--list-indentation) * -0.6); + } + + &:not(:last-of-type) { + &::after { + height: 100%; + } + } + } + + a { + text-decoration: underline var(--uchu-yin-2); + } + } + + :global(.special-char) { + color: var(--uchu-yin-3); + letter-spacing: -0.05rem; + } +</style> + +<h2>ring.webb.page</h2> + +<ul> + {#each friends as friend} + <li> + {#if friend.name.length} + {friend.name} + {#if friend.alias} + ({friend.alias}) + {/if} + {:else} + {friend.alias} + {/if} + + <div> + {#each friend.urls as url} + <a href={url + referrer} target="_blank">{@html processUrl(url)}</a><br/> + {/each} + </div> + </li> + {/each} +</ul> diff --git a/src/lib/component/Social.svelte b/src/lib/component/Social.svelte new file mode 100644 index 0000000..1e469eb --- /dev/null +++ b/src/lib/component/Social.svelte @@ -0,0 +1,112 @@ +<script lang="ts"> + //// import + import { format as formatDate } from "timeago.js"; + import { onMount } from "svelte"; + + //// var + const videoRegex = /\.(mp4|mov|avi|wmv|flv|mkv|webm|m4v)$/i; + let mastodonLoaded = false; + let mastodonPostContent = ""; + let mastodonPostDate = ""; + let mastodonPostLink = ""; + let mastodonPostMedia: string[] = []; + + //// function + async function mastodon() { + const response = await fetch("/api/mastodon.json", { + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + method: "POST" + }); + + const { created, content, link, media } = await response.json(); + + mastodonPostContent = content; + mastodonPostDate = created; + mastodonPostLink = link; + mastodonPostMedia = media; + mastodonLoaded = true; + } + + //// ready + onMount(() => { + mastodon(); + }); +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding)* 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + blockquote { + padding: 0 calc(var(--padding) * 2) calc(var(--padding) * 2); + white-space: normal; + } + + .media { + display: flex; + flex-direction: row; + margin-bottom: var(--padding); + + figure { + margin: 0 var(--padding) var(--padding) 0; + } + + img { + width: 100%; height: 100%; + object-fit: contain; + } + } + + :global(blockquote p) { + margin-bottom: calc(var(--block-spacing-bottom) / 2); + padding-top: 0; + } +</style> + +<h2>social.webb.page/mastodon</h2> + +{#if mastodonLoaded} + <blockquote cite={mastodonPostLink}> + {@html mastodonPostContent} + + {#if mastodonPostMedia.length} + <div class="media"> + {#each mastodonPostMedia as media} + {#if videoRegex.test(media)} + <!-- svelte-ignore a11y-media-has-caption --> + <video controls> + <source src={media}/> + </video> + {:else} + <figure> + <img alt="mastodon media" src={media}/> + <figcaption></figcaption> + </figure> + {/if} + {/each} + </div> + {/if} + + <p>— + <a href={mastodonPostLink} rel="nofollow noopener noreferrer" target="_blank"> + <time datetime={mastodonPostDate}>{formatDate(mastodonPostDate)}</time> + </a> + </p> + </blockquote> +{:else} + <blockquote> + <p>loading…</p> + </blockquote> +{/if} diff --git a/src/lib/component/Support.svelte b/src/lib/component/Support.svelte new file mode 100644 index 0000000..ceff0fd --- /dev/null +++ b/src/lib/component/Support.svelte @@ -0,0 +1,70 @@ +<script lang="ts"> +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding)* 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-bottom: 0; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) 0.25rem 0; + + li { + margin: 0; padding: 0 0 calc(var(--baseline) / 2) calc(var(--baseline) * 2); + + line-height: var(--line-height); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 2.5); + } + + &::after { + width: 1px; height: var(--list-indentation); + top: calc(var(--list-indentation) * -0.6); + } + + code { + line-height: inherit; + white-space: nowrap; + } + } + } + + p { + margin: 0; padding: 0 var(--about-padding) var(--about-padding); + white-space: break-spaces; + } +</style> + +<h2>support.webb.page</h2> + +<ul> + <li>Bitcoin: <code>bc1q7znsdk9eudlctcmhlel3d9vmxz4vmgedndx5qn</code></li> + <li>Cardano: <code>addr1q89wumdmfsjn9hsk57cw2g4wwyv2uz5g97c6pyxux03vpylju9njft3tr69mheqh5k3wh3d5h9f88hkecn4h6h2jll5sdncex7</code></li> + <li>Ethereum: <code>0x9c94aafF71e4A58Cd9fb5bE2005b8Dc952C4c2e3</code></li> + <li>Solana: <code>DbJ7peGAyrNNH8J1yJ82S7ZnK3BJenefjVQtGLM9pMny</code></li> + <li>Tezos: <code>tz1UVwR1MhqfytRbypB1jeFbFKTLu4LSFE9A</code></li> +</ul> + +<p>I have a number of domains for sale. Past projects never embarked on but names I don’t want to let expire just yet; <a href="https://dsgn.io">dsgn.io</a>, <a href="https://hackerspace.wtf">hackerspace.wtf</a>, <a href="https://imagination.industries">imagination.industries</a>, and <a href="https://phoenix.systems">phoenix.systems</a>.</p> diff --git a/src/lib/component/Uses.svelte b/src/lib/component/Uses.svelte new file mode 100644 index 0000000..4c91944 --- /dev/null +++ b/src/lib/component/Uses.svelte @@ -0,0 +1,145 @@ +<script lang="ts"> + //// var + const referrer = "?ref=webb.page"; + + const products = [ + { + comment: "why the hell would I remember a password?", + name: "1Password", + url: "https://1password.com" + }, + { + comment: "Terminal emulator", + name: "Alacritty", + url: "https://alacritty.org" + }, + { + comment: "when songs aren’t on Bandcamp", + name: "Apple Music", + url: "https://music.apple.com" + }, + { + comment: "default browser", + name: "Arc", + url: "https://arc.net" + }, + { + comment: "because not having a backup hurts", + name: "Backblaze", + url: "https://www.backblaze.com" + }, + { + comment: "better than ChatGPT", + name: "Claude", + url: "https://claude.ai" + }, + { + comment: "the best database on the planet", + name: "Gel", + url: "https://www.geldata.com" + }, + { + comment: "Terminal emulator with tab support", + name: "Ghostty", + url: "https://ghostty.org" + }, + { + comment: "best search engine after Neeva sold out", + name: "Kagi", + url: "https://kagi.com" + }, + { + comment: "taking notes on the go", + name: "Lumen", + url: "https://uselumen.com" + }, + { + comment: "one day I’ll graduate to hosting my own IMAP software instead", + name: "Mail-in-a-Box", + url: "https://mailinabox.email" + }, + { + comment: "RSS is alive and well", + name: "NetNewsWire", + url: "https://netnewswire.com" + }, + { + comment: "when Safari is being dumb", + name: "Orion", + url: "https://kagi.com/orion" + }, + { + comment: "taking notes at my computer", + name: "Obsidian", + url: "https://obsidian.md" + }, + { + comment: "code editor", + name: "Sublime Text", + url: "https://www.sublimetext.com" + }, + ]; +</script> + +<style lang="scss"> + h2 { + margin: 0 0 calc(var(--padding)* 2); padding: var(--padding) calc(var(--padding) * 2); + + background-color: var(--color-border); + color: var(--uchu-yin-7); + font-size: 1rem; + line-height: inherit; + position: sticky; + top: 0; + z-index: 1; + } + + ul { + line-height: 1; + margin-left: calc(var(--list-indentation) / 2); + padding: 0 var(--list-indentation) 0.25rem 0; + + li { + margin: 0; padding: 0 0 var(--baseline) calc(var(--baseline) * 2); + position: relative; + + &::before, + &::after { + background-color: var(--color-border); + content: ""; + left: 0; + position: absolute; + } + + &::before { + width: calc(var(--list-indentation) / 2); height: 1px; + top: calc(var(--list-indentation) / 4); + } + + &::after { + width: 1px; height: var(--list-indentation); + top: calc(var(--list-indentation) * -0.75); + } + } + + a { + text-decoration: underline var(--uchu-yin-2); + + span { + padding-left: 0.2rem; + position: relative; + text-decoration: underline var(--uchu-gray-2); + } + } + } +</style> + +<h2>uses.webb.page</h2> + +<ul> + {#each products as product} + <li> + <a href={product.url + referrer}>{product.name}<span> ({product.comment})</span></a> + </li> + {/each} +</ul> |
