Move beta into stable

This commit is contained in:
Clove 2026-06-09 17:04:12 +01:00
parent 9ca073876b
commit ac5747b189
48 changed files with 2028 additions and 234 deletions

20
arch.ascii Normal file
View File

@ -0,0 +1,20 @@
{"match": "\"Arch\"*", "color": "6 6 7 1"}
${c1} -`
.o+`
`ooo/
`+oooo:
`+oooooo:
-+oooooo+:
`/:-:++oooo+:
`/++++/+++++++:
`/++++++++++++++:
`/+++o${c2}oooooooo${c1}oooo/`
${c2} ${c1}./${c2}ooosssso++osssssso${c1}+`
${c2} .oossssso-````/ossssss+`
-osssssso. :ssssssso.
:osssssss/ osssso+++.
/ossssssss/ +ssssooo/-
`/ossssso+/:- -:/+osssso+-
`+sso+:-` `.-/+oso:
`++:. `-/+/
.` `/

BIN
assets/88x31/antifa.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/88x31/blink.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
assets/88x31/estrogen.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
assets/88x31/firefox.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/88x31/girlsnow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/88x31/gitgay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/88x31/killmenow.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

BIN
assets/88x31/linux.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

BIN
assets/88x31/meltice.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/88x31/microslop.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/88x31/nft.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/88x31/noweb32.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
assets/88x31/palestine.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/88x31/skirt.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/88x31/ukraine.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 KiB

After

Width:  |  Height:  |  Size: 240 KiB

BIN
assets/friends/ari.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/friends/camilla.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
assets/friends/fin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
assets/friends/lilly.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/friends/lylla.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
assets/friends/meme.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
assets/friends/n.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

BIN
assets/friends/ria.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/friends/saphie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
assets/friends/simon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
assets/misc/meow.mp4 Normal file

Binary file not shown.

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" class="bi bi-github" viewBox="0 0 16 16">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
</svg>

Before

Width:  |  Height:  |  Size: 703 B

177
cool-people/index.html Normal file
View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cool People</title>
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/themes/mocha.css">
<link rel="stylesheet" href="/css/themes/macchiato.css">
<link rel="stylesheet" href="/css/themes/frappe.css">
<link rel="stylesheet" href="/css/themes/latte.css">
<link rel="stylesheet" href="/api/api.css">
<script>try { var f = localStorage.getItem('ctpFlavor'); document.documentElement.setAttribute('data-flavor', ['mocha', 'macchiato', 'frappe', 'latte'].indexOf(f) >= 0 ? f : 'mocha'); } catch (e) { document.documentElement.setAttribute('data-flavor', 'mocha'); }</script>
<link rel="icon" type="image/svg+xml" href="/assets/favicon/favicon.svg">
<!-- SEO Meta Tags -->
<meta name="description" content="Link Center for Clove Twilight" />
<meta name="keywords" content="Portfolio, Personal, Developer" />
<meta name="author" content="doughmination" />
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<!-- Canonical URL -->
<link rel="canonical" href="https://clove.is-a.dev/cool-people" />
<!-- Alternate for mobile -->
<link rel="alternate" media="only screen and (max-width: 640px)" href="https://clove.is-a.dev" />
<!-- Theme Color -->
<meta name="theme-color" content="#f5c2e7" />
<!-- Open Graph / Discord / Facebook -->
<meta property="og:image" content="https://clove.is-a.dev/assets/favicon/favicon.png" />
<meta property="og:site_name" content="clove.is-a.dev" />
<meta property="og:title" content="Cool People | Clove Twilight" />
<meta property="og:description" content="Cool people Clove knows!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://clove.is-a.dev/cool-people" />
<meta property="og:locale" content="en_GB" />
<!-- Twitter Card -->
<meta name="twitter:image" content="https://clove.is-a.dev/assets/favicon/favicon.png" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Cool People | Clove Twilight" />
<meta name="twitter:description" content="Cool people Clove knows!" />
</head>
<body>
<header class="nav">
<nav class="nav-links">
<a class="nav-link" data-href="/">Link Center</a>
<a class="nav-link selected" data-href="/cool-people">Cool People</a>
<a class="nav-link" data-href="/dev-info">Coding Stats</a>
<a class="nav-link" data-href="/discord-bots">Discord Bots</a>
</nav>
</header>
<div id="now-playing"></div>
<div class="hub">
<header class="hub-header">
<h1>Cool People</h1>
<p class="tagline">This is where people I know can be put up on my site, click their profiles for their pages</p>
</header>
<!-- Girlfriend -->
<section class="section">
<h2 class="section-title">Fiancee</h2>
<div class="friend-grid">
<a class="friend">
<img class="friend-pfp wife" src="/assets/friends/ari.png">
<span class="friend-name wife">Aria</span>
</a>
</div>
</section>
<!-- Close Friends -->
<section class="section">
<h2 class="section-title">Close Friends</h2>
<div class="friend-grid">
<a class="friend">
<img class="friend-pfp close" src="/assets/friends/ria.png">
<span class="friend-name close">Ria</span>
</a>
<a class="friend">
<img class="friend-pfp close"
src="/assets/friends/lilly.png">
<span class="friend-name close">Lilly</span>
</a>
<a class="friend" href="https://cammy-the-cat.com">
<img class="friend-pfp close"
src="/assets/friends/camilla.png">
<span class="friend-name close">Camilla</span>
</a>
<a class="friend">
<img class="friend-pfp close"
src="/assets/friends/saphie.png">
<span class="friend-name close">Saphie</span>
</a>
</div>
</section>
<section class="section">
<h2 class="section-title">Friends</h2>
<div class="friend-grid">
<a class="friend">
<img class="friend-pfp" src="/assets/friends/meme.png">
<span class="friend-name">Memeancia</span>
</a>
<a class="friend">
<img class="friend-pfp" src="/assets/friends/n.png">
<span class="friend-name">N</span>
</a>
<a class="friend">
<img class="friend-pfp" src="/assets/friends/lylla.png">
<span class="friend-name">Lylla</span>
</a>
<a class="friend">
<img class="friend-pfp" src="/assets/friends/simon.png">
<span class="friend-name">Simon</span>
</a>
</div>
</section>
<section class="section">
<h2 class="section-title">Other Peeps</h2>
<p class="section-subtitle">You can request to be added here!</p>
</section>
</div>
<aside class="button-wall" aria-label="88x31 buttons">
<img src="/assets/88x31/linux.gif" alt="Linux" loading="lazy">
<img src="/assets/88x31/microslop.gif" alt="Microslop" loading="lazy">
<img src="/assets/88x31/estrogen.gif" alt="Estrogen" loading="lazy">
<img src="/assets/88x31/girlsnow.png" alt="Girls Network" loading="lazy">
<img src="/assets/88x31/skirt.gif" alt="Skirt" loading="lazy">
<img src="/assets/88x31/gitgay.png" alt="GitGay" loading="lazy">
<img src="/assets/88x31/blink.gif" alt="Blink" loading="lazy">
<img src="/assets/88x31/firefox.gif" alt="Firefox" loading="lazy">
<img src="/assets/88x31/nft.gif" alt="NFT" loading="lazy">
<img src="/assets/88x31/noweb32.gif" alt="No Web 3.2" loading="lazy">
<img src="/assets/88x31/meltice.gif" alt="Melt Ice" loading="lazy">
</aside>
<aside class="badges" aria-label="System badges">
<span class="badge">
<img class="badge-icon" src="/assets/misc/amd.svg" alt="">
AMD Purist
</span>
<span class="badge">
<img class="badge-icon" src="/assets/misc/arch.svg" alt="">
I use arch btw
</span>
<span class="badge">
<img class="badge-icon" src="/assets/misc/debian.svg" alt="">
Debian Professional
</span>
<span class="badge">
<img class="badge-icon" src="/assets/misc/apple.svg" alt="">
Apple Ecosystem Enthusiast
</span>
</aside>
<script src="/js/cat.js" data-cat="/assets/oneko/classics/classic.png"></script>
<script src="/js/nav.js"></script>
<script src="/api/now-playing.js" data-user="1464890289922641993"></script>
<script src="/js/flavors.js"></script>
<script src="/js/dev-mode.js"></script>
<script src="/js/terminal.js"></script>
<script src="/js/site-switcher.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -4,27 +4,22 @@
<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>Clove Twilight - Tech Stack</title> <title>Clove Twilight - Coding Stats</title>
<link rel="stylesheet" href="/css/main.css"> <link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/themes/mocha.css">
<link rel="stylesheet" href="/css/themes/macchiato.css">
<link rel="stylesheet" href="/css/themes/frappe.css">
<link rel="stylesheet" href="/css/themes/latte.css">
<link rel="stylesheet" href="/api/api.css">
<script>try { var f = localStorage.getItem('ctpFlavor'); document.documentElement.setAttribute('data-flavor', ['mocha', 'macchiato', 'frappe', 'latte'].indexOf(f) >= 0 ? f : 'mocha'); } catch (e) { document.documentElement.setAttribute('data-flavor', 'mocha'); }</script> <script>try { var f = localStorage.getItem('ctpFlavor'); document.documentElement.setAttribute('data-flavor', ['mocha', 'macchiato', 'frappe', 'latte'].indexOf(f) >= 0 ? f : 'mocha'); } catch (e) { document.documentElement.setAttribute('data-flavor', 'mocha'); }</script>
<link rel="icon" type="image/svg+xml" href="/assets/favicon/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/assets/favicon/favicon.svg">
<!-- SEO Meta Tags --> <!-- SEO Meta Tags -->
<meta name="description" content="Tech Stack for Clove Twilight" /> <meta name="description" content="What Clove has been coding lately, tracked by dev-info" />
<meta name="keywords" content="Clove Twilight, Portfolio, Personal, Developer" /> <meta name="keywords" content="Portfolio, Personal, Developer, dev-info, Coding Stats" />
<meta name="author" content="doughmination" /> <meta name="author" content="doughmination" />
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> <meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<!-- Canonical URL --> <!-- Canonical URL -->
<link rel="canonical" href="https://clove.is-a.dev/tech-stack" /> <link rel="canonical" href="https://clove.is-a.dev/dev-info" />
<!-- Alternate for mobile --> <!-- Alternate for mobile -->
<link rel="alternate" media="only screen and (max-width: 640px)" href="https://clove.is-a.dev/tech-stack" /> <link rel="alternate" media="only screen and (max-width: 640px)" href="https://clove.is-a.dev/dev-info" />
<!-- Theme Color --> <!-- Theme Color -->
<meta name="theme-color" content="#f5c2e7" /> <meta name="theme-color" content="#f5c2e7" />
@ -32,56 +27,62 @@
<!-- Open Graph / Discord / Facebook --> <!-- Open Graph / Discord / Facebook -->
<meta property="og:image" content="https://clove.is-a.dev/assets/favicon/favicon.png" /> <meta property="og:image" content="https://clove.is-a.dev/assets/favicon/favicon.png" />
<meta property="og:site_name" content="clove.is-a.dev" /> <meta property="og:site_name" content="clove.is-a.dev" />
<meta property="og:title" content="Clove Twilight | Tech Stack" /> <meta property="og:title" content="Clove Twilight | Coding Stats" />
<meta property="og:description" content="Tech Stack for Clove Twilight" /> <meta property="og:description" content="What Clove has been coding lately, tracked by dev-info" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://clove.is-a.dev/tech-stack" /> <meta property="og:url" content="https://clove.is-a.dev/dev-info" />
<meta property="og:locale" content="en_GB" /> <meta property="og:locale" content="en_GB" />
<!-- Twitter Card --> <!-- Twitter Card -->
<meta name="twitter:image" content="https://clove.is-a.dev/assets/favicon/favicon.png" /> <meta name="twitter:image" content="https://clove.is-a.dev/assets/favicon/favicon.png" />
<meta name="twitter:card" content="summary" /> <meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Clove Twilight | Tech Stack" /> <meta name="twitter:title" content="Clove Twilight | Coding Stats" />
<meta name="twitter:description" content="Tech Stack for Clove Twilight" /> <meta name="twitter:description" content="What Clove has been coding lately, tracked by dev-info" />
</head> </head>
<body> <body>
<div id="now-playing"></div>
<header class="nav"> <header class="nav">
<nav class="nav-links"> <nav class="nav-links">
<a class="nav-link" data-href="/">Link Center</a> <a class="nav-link" data-href="/">Link Center</a>
<a class="nav-link selected" data-href="/tech-stack">Tech Stack</a> <a class="nav-link" data-href="/cool-people">Cool People</a>
<a class="nav-link selected" data-href="/dev-info">Coding Stats</a>
<a class="nav-link" data-href="/discord-bots">Discord Bots</a> <a class="nav-link" data-href="/discord-bots">Discord Bots</a>
</nav> </nav>
</header> </header>
<a class="now-playing" id="now-playing" target="_blank" rel="noopener" hidden> <main class="waka">
<img class="np-art" alt="" referrerpolicy="no-referrer" crossorigin="anonymous">
<span class="np-bars" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
<span class="np-text">
<span class="np-head">
<span class="np-status" aria-hidden="true"></span>
<span class="np-status-label"></span>
<span class="np-label">Now playing</span>
</span>
<span class="np-track"></span>
<span class="np-artist"></span>
<span class="np-progress" aria-hidden="true">
<span class="np-bar"><span class="np-fill"></span></span>
<span class="np-times"><span class="np-cur">0:00</span><span class="np-dur">0:00</span></span>
</span>
</span>
</a>
<main class="hub">
<header class="hub-header"> <header class="hub-header">
<img class="pfp" src="/assets/favicon/avatar.png" alt="Clove Twilight avatar"> <img class="pfp" src="/assets/favicon/avatar.png" alt="Clove Twilight avatar">
<h1>Clove Twilight</h1> <h1>Clove Twilight</h1>
<h2 class="pronouns">(fae/faer)</h2> <h2 class="pronouns">(fae/faer)</h2>
<p class="tagline">Tech Stack</p> <p class="tagline">Dev Info</p>
<p class="waka-meta" id="waka-meta" hidden></p>
</header> </header>
<section class="tech-stack" aria-label="Tech stack"> <!-- shown only until the dev-info share URLs are filled in -->
<section class="waka-setup" id="waka-setup" hidden>
<h2 class="section-title">Almost there</h2>
<p class="waka-empty">
This page is wired up and ready — it just needs your dev-info
<strong>embeddable JSON</strong> share URLs.
</p>
<ol class="waka-steps">
<li>Open <a href="https://dev-info.com/share/embed" target="_blank"
rel="noopener">dev-info.com/share/embed</a>.</li>
<li>Create an embed for each of: <em>Coding Activity, Languages, Projects, Editors, Operating
Systems</em>.</li>
<li>Choose the <strong>JSON</strong> format and copy each <code>.json</code> URL.</li>
<li>Paste them into the <code>dev-info</code> block at the top of <code>/js/dev-info.js</code>.</li>
</ol>
<a class="waka-setup-btn" href="https://dev-info.com/share/embed" target="_blank" rel="noopener">Open
dev-info Share</a>
</section>
<section class="dev-info" aria-label="Tech stack">
<span class="tech-icon pink" style="--si:url('https://cdn.simpleicons.org/javascript')" role="img" <span class="tech-icon pink" style="--si:url('https://cdn.simpleicons.org/javascript')" role="img"
aria-label="JavaScript"></span> aria-label="JavaScript"></span>
<span class="tech-icon mauve" style="--si:url('https://cdn.simpleicons.org/openjdk')" role="img" <span class="tech-icon mauve" style="--si:url('https://cdn.simpleicons.org/openjdk')" role="img"
@ -171,8 +172,57 @@
<span class="tech-icon blue" style="--si:url('https://cdn.simpleicons.org/vscodium')" role="img" <span class="tech-icon blue" style="--si:url('https://cdn.simpleicons.org/vscodium')" role="img"
aria-label="VSCodium"></span> aria-label="VSCodium"></span>
</section> </section>
<!-- the real content; revealed once at least one URL is configured -->
<div id="waka-content" hidden>
<section class="waka-section waka-total" id="waka-total">
<div class="waka-total-num" id="waka-total-val"></div>
<div class="waka-total-sub" id="waka-total-sub">total coding time</div>
<div class="waka-week" id="waka-week"></div>
</section>
<section class="waka-section" id="waka-section-languages" hidden>
<h2 class="section-title">Languages</h2>
<div class="waka-bars" id="waka-languages"></div>
</section>
<section class="waka-section" id="waka-section-categories" hidden>
<h2 class="section-title">Categories</h2>
<div class="waka-bars" id="waka-categories"></div>
</section>
<div class="waka-grid2">
<section class="waka-section" id="waka-section-editors" hidden>
<h2 class="section-title">Editors</h2>
<div class="waka-bars" id="waka-editors"></div>
</section>
<section class="waka-section" id="waka-section-os" hidden>
<h2 class="section-title">Operating Systems</h2>
<div class="waka-bars" id="waka-os"></div>
</section>
</div>
<p class="waka-credit">Tracked automatically with <a href="https://wakatime.com" target="_blank"
rel="noopener">wakatime</a>.</p>
</div>
</main> </main>
<aside class="button-wall" aria-label="88x31 buttons">
<img src="/assets/88x31/linux.gif" alt="Linux" loading="lazy">
<img src="/assets/88x31/microslop.gif" alt="Microslop" loading="lazy">
<img src="/assets/88x31/estrogen.gif" alt="Estrogen" loading="lazy">
<img src="/assets/88x31/girlsnow.png" alt="Girls Network" loading="lazy">
<img src="/assets/88x31/skirt.gif" alt="Skirt" loading="lazy">
<img src="/assets/88x31/gitgay.png" alt="GitGay" loading="lazy">
<img src="/assets/88x31/blink.gif" alt="Blink" loading="lazy">
<img src="/assets/88x31/firefox.gif" alt="Firefox" loading="lazy">
<img src="/assets/88x31/nft.gif" alt="NFT" loading="lazy">
<img src="/assets/88x31/noweb32.gif" alt="No Web 3.2" loading="lazy">
<img src="/assets/88x31/meltice.gif" alt="Melt Ice" loading="lazy">
</aside>
<aside class="badges" aria-label="System badges"> <aside class="badges" aria-label="System badges">
<span class="badge"> <span class="badge">
<img class="badge-icon" src="/assets/misc/amd.svg" alt=""> <img class="badge-icon" src="/assets/misc/amd.svg" alt="">
@ -194,9 +244,10 @@
<script src="/js/cat.js" data-cat="/assets/oneko/classics/classic.png"></script> <script src="/js/cat.js" data-cat="/assets/oneko/classics/classic.png"></script>
<script src="/js/nav.js"></script> <script src="/js/nav.js"></script>
<script src="/api/now-playing.js"></script> <script src="/api/now-playing.js" data-user="1464890289922641993"></script>
<script src="/js/flavors.js"></script> <script src="/js/flavors.js"></script>
<script src="/js/dev-mode.js" user-data="1464890289922641993"></script> <script src="/js/dev-mode.js"></script>
<script src="/js/dev-info.js"></script>
<script src="/js/site-switcher.js"></script> <script src="/js/site-switcher.js"></script>
</body> </body>

View File

@ -47,28 +47,13 @@
<body> <body>
<a class="now-playing" id="now-playing" target="_blank" rel="noopener" hidden> div id="now-playing"></div>
<img class="np-art" alt="" referrerpolicy="no-referrer" crossorigin="anonymous">
<span class="np-bars" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
<span class="np-text">
<span class="np-head">
<span class="np-status" aria-hidden="true"></span>
<span class="np-status-label"></span>
<span class="np-label">Now playing</span>
</span>
<span class="np-track"></span>
<span class="np-artist"></span>
<span class="np-progress" aria-hidden="true">
<span class="np-bar"><span class="np-fill"></span></span>
<span class="np-times"><span class="np-cur">0:00</span><span class="np-dur">0:00</span></span>
</span>
</span>
</a>
<header class="nav"> <header class="nav">
<nav class="nav-links"> <nav class="nav-links">
<a class="nav-link" data-href="/">Link Center</a> <a class="nav-link" data-href="/">Link Center</a>
<a class="nav-link" data-href="/tech-stack">Tech Stack</a> <a class="nav-link" data-href="/cool-people">Cool People</a>
<a class="nav-link" data-href="/dev-info">Coding Stats</a>
<a class="nav-link selected" data-href="/discord-bots">Discord Bots</a> <a class="nav-link selected" data-href="/discord-bots">Discord Bots</a>
</nav> </nav>
</header> </header>
@ -185,9 +170,9 @@
<a class="bot" href="https://github.com/doughmination/widget-script"> <a class="bot" href="https://github.com/doughmination/widget-script">
<img class="bot-pfp" src="/assets/favicon/avatar.png" alt="" /> <img class="bot-pfp" src="/assets/favicon/avatar.png" alt="" />
<span class="link-text"> <span class="bot-link-text">
<span class="link-title">Widget-v2 Script</span> <span class="bot-link-title">Widget-v2 Script</span>
<span class="link-sub">Open Source</span> <span class="bot-link-sub">Open Source</span>
</span> </span>
</a> </a>
@ -195,6 +180,20 @@
</section> </section>
</main> </main>
<aside class="button-wall" aria-label="88x31 buttons">
<img src="/assets/88x31/linux.gif" alt="Linux" loading="lazy">
<img src="/assets/88x31/microslop.gif" alt="Microslop" loading="lazy">
<img src="/assets/88x31/estrogen.gif" alt="Estrogen" loading="lazy">
<img src="/assets/88x31/girlsnow.png" alt="Girls Network" loading="lazy">
<img src="/assets/88x31/skirt.gif" alt="Skirt" loading="lazy">
<img src="/assets/88x31/gitgay.png" alt="GitGay" loading="lazy">
<img src="/assets/88x31/blink.gif" alt="Blink" loading="lazy">
<img src="/assets/88x31/firefox.gif" alt="Firefox" loading="lazy">
<img src="/assets/88x31/nft.gif" alt="NFT" loading="lazy">
<img src="/assets/88x31/noweb32.gif" alt="No Web 3.2" loading="lazy">
<img src="/assets/88x31/meltice.gif" alt="Melt Ice" loading="lazy">
</aside>
<aside class="badges" aria-label="System badges"> <aside class="badges" aria-label="System badges">
<span class="badge"> <span class="badge">
<img class="badge-icon" src="/assets/misc/amd.svg" alt=""> <img class="badge-icon" src="/assets/misc/amd.svg" alt="">
@ -219,7 +218,7 @@
<script src="/js/cat.js" data-cat="/assets/oneko/classics/classic.png"></script> <script src="/js/cat.js" data-cat="/assets/oneko/classics/classic.png"></script>
<script src="/js/nav.js"></script> <script src="/js/nav.js"></script>
<script src="/api/now-playing.js" user-data="1464890289922641993"></script> <script src="/api/now-playing.js" data-user="1464890289922641993"></script>
<script src="/js/flavors.js"></script> <script src="/js/flavors.js"></script>
<script src="/js/dev-mode.js"></script> <script src="/js/dev-mode.js"></script>
<script src="/js/site-switcher.js"></script> <script src="/js/site-switcher.js"></script>

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -46,10 +46,11 @@
</head> </head>
<body> <body>
<header class="nav"> <header class="nav">
<nav class="nav-links"> <nav class="nav-links">
<a class="nav-link selected" data-href="/">Link Center</a> <a class="nav-link selected" data-href="/">Link Center</a>
<a class="nav-link" data-href="/tech-stack">Tech Stack</a> <a class="nav-link" data-href="/cool-people">Cool People</a>
<a class="nav-link" data-href="/dev-info">Coding Stats</a>
<a class="nav-link" data-href="/discord-bots">Discord Bots</a> <a class="nav-link" data-href="/discord-bots">Discord Bots</a>
</nav> </nav>
</header> </header>
@ -61,125 +62,24 @@
<img class="pfp" src="/assets/favicon/avatar.png" alt="Clove Twilight avatar"> <img class="pfp" src="/assets/favicon/avatar.png" alt="Clove Twilight avatar">
<h1>Clove Twilight</h1> <h1>Clove Twilight</h1>
<h2 class="pronouns">(fae/faer)</h2> <h2 class="pronouns">(fae/faer)</h2>
<p class="tagline">Link Center</p>
</header> </header>
<div class="terminal" id="terminal"></div>
<nav class="links">
<a class="link-card" href="https://github.com/doughmination">
<img class="icon" src="/assets/socials/github.svg" alt="">
<span class="link-text">
<span class="link-title">GitHub</span>
<span class="link-sub">@doughmination</span>
</span>
</a>
<a class="link-card" href="https://git.gay/doughmination">
<img class="icon" src="/assets/socials/git-gay.svg" alt="">
<span class="link-text">
<span class="link-title">Git.Gay</span>
<span class="link-sub">@doughmination</span>
</span>
</a>
<a class="link-card" href="https://x.com/DoughminCEO">
<img class="icon" src="/assets/socials/twitter.svg" alt="">
<span class="link-text">
<span class="link-title">Twitter</span>
<span class="link-sub">@DoughminCEO</span>
</span>
</a>
<a class="link-card" href="https://bsky.app/profile/doughmination.win">
<img class="icon" src="/assets/socials/bluesky.svg" alt="">
<span class="link-text">
<span class="link-title">Bluesky</span>
<span class="link-sub">@doughmination.win</span>
</span>
</a>
<a class="link-card" href="https://www.linkedin.com/in/estrogen/">
<img class="icon" src="/assets/socials/linkedin.svg" alt="">
<span class="link-text">
<span class="link-title">LinkedIn</span>
<span class="link-sub">Clove Twilight</span>
</span>
</a>
<a class="link-card" href="https://open.spotify.com/user/x060f5w4ftwv8zc8fi9662t70">
<img class="icon" src="/assets/socials/spotify.svg" alt="">
<span class="link-text">
<span class="link-title">Spotify</span>
<span class="link-sub">doughmination</span>
</span>
</a>
<a class="link-card" href="https://discord.gg/TransRights">
<img class="icon" src="/assets/socials/discord.svg" alt="">
<span class="link-text">
<span class="link-title">Discord</span>
<span class="link-sub">Girls Discord Server</span>
</span>
</a>
<a class="link-card" href="https://www.twitch.tv/doughminationgaming">
<img class="icon" src="/assets/socials/twitch.svg" alt="">
<span class="link-text">
<span class="link-title">Twitch</span>
<span class="link-sub">@doughminationgaming</span>
</span>
</a>
<a class="link-card" href="https://www.reddit.com/user/XerinDotZero/">
<img class="icon" src="/assets/socials/reddit.svg" alt="">
<span class="link-text">
<span class="link-title">Reddit</span>
<span class="link-sub">u/XerinDotZero</span>
</span>
</a>
<a class="link-card" href="https://www.youtube.com/@CloveTwiGaming">
<img class="icon" src="/assets/socials/youtube.svg" alt="">
<span class="link-text">
<span class="link-title">YouTube</span>
<span class="link-sub">@CloveTwiGaming</span>
</span>
</a>
<a class="link-card" rel="me" href="https://mastodon.social/@doughmination">
<img class="icon" src="/assets/socials/mastodon.svg" alt="">
<span class="link-text">
<span class="link-title">Mastodon</span>
<span class="link-sub">@doughmination@mastodon.social</span>
</span>
</a>
<a class="link-card" href="mailto:admin@doughmination.win">
<img class="icon" src="/assets/socials/email.svg" alt="">
<span class="link-text">
<span class="link-title">Email</span>
<span class="link-sub">admin@doughmination.win</span>
</span>
</a>
<a class="link-card" href="https://doughmination.win/">
<img class="icon" src="/assets/socials/site.svg" alt="">
<span class="link-text">
<span class="link-title">My Company Website</span>
<span class="link-sub">doughmination.win</span>
</span>
</a>
<a class="link-card" href="https://doughmination.co.uk/">
<img class="icon" src="/assets/socials/site.svg" alt="">
<span class="link-text">
<span class="link-title">My Portfolio</span>
<span class="link-sub">doughmination.co.uk</span>
</span>
</a>
</nav>
</main> </main>
<aside class="button-wall" aria-label="88x31 buttons">
<img src="/assets/88x31/linux.gif" alt="Linux" loading="lazy">
<img src="/assets/88x31/microslop.gif" alt="Microslop" loading="lazy">
<img src="/assets/88x31/estrogen.gif" alt="Estrogen" loading="lazy">
<img src="/assets/88x31/girlsnow.png" alt="Girls Network" loading="lazy">
<img src="/assets/88x31/skirt.gif" alt="Skirt" loading="lazy">
<img src="/assets/88x31/gitgay.png" alt="GitGay" loading="lazy">
<img src="/assets/88x31/blink.gif" alt="Blink" loading="lazy">
<img src="/assets/88x31/firefox.gif" alt="Firefox" loading="lazy">
<img src="/assets/88x31/nft.gif" alt="NFT" loading="lazy">
<img src="/assets/88x31/noweb32.gif" alt="No Web 3.2" loading="lazy">
<img src="/assets/88x31/meltice.gif" alt="Melt Ice" loading="lazy">
</aside>
<aside class="badges" aria-label="System badges"> <aside class="badges" aria-label="System badges">
<span class="badge"> <span class="badge">
<img class="badge-icon" src="/assets/misc/amd.svg" alt=""> <img class="badge-icon" src="/assets/misc/amd.svg" alt="">
@ -204,6 +104,7 @@
<script src="/api/now-playing.js" data-user="1464890289922641993"></script> <script src="/api/now-playing.js" data-user="1464890289922641993"></script>
<script src="/js/flavors.js"></script> <script src="/js/flavors.js"></script>
<script src="/js/dev-mode.js"></script> <script src="/js/dev-mode.js"></script>
<script src="/js/terminal.js"></script>
<script src="/js/site-switcher.js"></script> <script src="/js/site-switcher.js"></script>
</body> </body>

294
js/dev-info.js Normal file
View File

@ -0,0 +1,294 @@
(function () {
"use strict";
// ====== PASTE YOUR EMBEDDABLE JSON SHARE URLS HERE ===================
var WAKATIME = {
codingActivity: "https://wakatime.com/share/@doughmination/9dcc5b5c-ed3d-4896-bfa3-87737fa70930.json",
languages: "https://wakatime.com/share/@doughmination/8354e3f8-b458-452b-aa06-839f303d4904.json",
categories: "https://wakatime.com/share/@doughmination/c54fcd4e-91b3-46ab-8e7e-82226491ec0f.json",
editors: "https://wakatime.com/share/@doughmination/38dba24b-d2de-4d50-9b09-83642c01c33e.json",
operatingSystems: "https://wakatime.com/share/@doughmination/a69f00cb-e38e-4de1-aa42-eec71dc6d658.json"
};
// How many rows to show in each ranked list.
var MAX_ROWS = 8;
// =====================================================================
// ---- JSONP loader ---------------------------------------------------
var seq = 0;
function jsonp(url, timeoutMs) {
return new Promise(function (resolve, reject) {
var cb = "__wakatime_cb_" + (++seq);
var script = document.createElement("script");
var timer = setTimeout(function () { cleanup(); reject(new Error("timed out")); }, timeoutMs || 12000);
function cleanup() {
clearTimeout(timer);
try { delete window[cb]; } catch (e) { window[cb] = undefined; }
if (script.parentNode) script.parentNode.removeChild(script);
}
window[cb] = function (data) { cleanup(); resolve(data); };
script.onerror = function () { cleanup(); reject(new Error("failed to load")); };
var sep = url.indexOf("?") === -1 ? "?" : "&";
script.src = url + sep + "callback=" + cb;
document.head.appendChild(script);
});
}
// ---- helpers --------------------------------------------------------
function fmt(seconds) {
seconds = Math.max(0, Math.round(seconds || 0));
var h = Math.floor(seconds / 3600);
var m = Math.round((seconds % 3600) / 60);
if (h && m) return h + " hr" + (h > 1 ? "s" : "") + " " + m + " min" + (m > 1 ? "s" : "");
if (h) return h + " hr" + (h > 1 ? "s" : "");
return m + " min" + (m === 1 ? "" : "s");
}
function pctLabel(p) {
return (p < 10 ? Math.round(p * 10) / 10 : Math.round(p)) + "%";
}
// Value shown at the end of a bar: real time when the embed provides it,
// otherwise the percentage share.
function valueLabel(d, hasSeconds) {
if (hasSeconds && (d.total_seconds || 0) > 0) return d.text || fmt(d.total_seconds);
if (typeof d.percent === "number") return pctLabel(d.percent);
if (d.text) return d.text;
return fmt(d.total_seconds);
}
function el(id) { return document.getElementById(id); }
function setMeta(text) {
var m = el("waka-meta");
if (m && text) { m.textContent = text; m.hidden = false; }
}
// Render a ranked list of horizontal bars into a container.
function renderBars(containerId, items) {
var box = el(containerId);
if (!box) return;
if (!items || !items.length) { failSection(box, "No data yet."); return; }
// Share embeds for languages/categories/editors/OS often return only
// {name, percent, color} with no seconds — so fall back to percent for
// both the bar width and the value label when time isn't provided.
var hasSeconds = items.some(function (d) { return d && (d.total_seconds || 0) > 0; });
var rows = items
.filter(function (d) { return d && ((d.total_seconds || 0) > 0 || (d.percent || 0) > 0); })
.sort(function (a, b) {
return hasSeconds
? (b.total_seconds || 0) - (a.total_seconds || 0)
: (b.percent || 0) - (a.percent || 0);
})
.slice(0, MAX_ROWS);
var max = rows.reduce(function (acc, d) {
return Math.max(acc, hasSeconds ? (d.total_seconds || 0) : (d.percent || 0));
}, 0) || 1;
box.innerHTML = "";
rows.forEach(function (d) {
var basis = hasSeconds ? (d.total_seconds || 0) : (d.percent || 0);
var pct = Math.max(2, Math.round((basis / max) * 100));
var row = document.createElement("div");
row.className = "waka-bar-row";
var name = document.createElement("span");
name.className = "waka-bar-name";
name.textContent = d.name || "Unknown";
name.title = d.name || "";
var track = document.createElement("span");
track.className = "waka-bar-track";
var fill = document.createElement("span");
fill.className = "waka-bar-fill";
fill.style.width = pct + "%";
if (d.color) fill.style.background = d.color;
track.appendChild(fill);
var val = document.createElement("span");
val.className = "waka-bar-val";
val.textContent = valueLabel(d, hasSeconds);
row.appendChild(name);
row.appendChild(track);
row.appendChild(val);
box.appendChild(row);
});
showSection(box);
}
// Render the 7-day vertical bar chart + headline total.
function renderWeek(days) {
var box = el("waka-week");
if (!box) return;
if (!days || !days.length) { failSection(box, "No activity data yet."); return; }
var max = days.reduce(function (acc, d) { return Math.max(acc, d.total); }, 0) || 1;
var total = days.reduce(function (acc, d) { return acc + d.total; }, 0);
var headline = el("waka-total-val");
if (headline) headline.textContent = fmt(total);
var sub = el("waka-total-sub");
if (sub) sub.textContent = "across the last " + days.length + " days";
box.innerHTML = "";
days.forEach(function (d) {
var h = Math.max(3, Math.round((d.total / max) * 100));
var col = document.createElement("div");
col.className = "waka-day";
var barWrap = document.createElement("div");
barWrap.className = "waka-day-track";
var bar = document.createElement("div");
bar.className = "waka-day-fill";
bar.style.height = h + "%";
bar.title = d.label + ": " + fmt(d.total);
barWrap.appendChild(bar);
var lbl = document.createElement("span");
lbl.className = "waka-day-label";
lbl.textContent = d.short;
col.appendChild(barWrap);
col.appendChild(lbl);
box.appendChild(col);
});
showSection(box);
return total;
}
function showSection(box) {
var sec = box.closest(".waka-section");
if (sec) sec.hidden = false;
}
function failSection(box, msg) {
box.innerHTML = '<p class="waka-empty">' + msg + "</p>";
showSection(box);
}
// ---- shape parsers (defensive: WakaTime embed shapes vary) ----------
// Categorical embeds (languages/categories/editors/OS) -> data:[{name,total_seconds,percent,color,text}]
function asCategorical(json) {
var data = json && json.data;
if (!Array.isArray(data)) return [];
// Some embeds nest under data.<key>; flatten the first array we find.
if (data.length && data[0] && data[0].name === undefined && Array.isArray(data[0])) {
data = data[0];
}
return data.map(function (d) {
return {
name: d.name,
total_seconds: typeof d.total_seconds === "number" ? d.total_seconds : (d.seconds || 0),
percent: d.percent,
color: d.color,
text: d.text
};
});
}
// Coding-activity embed -> array of {label, short, total}
function asDays(json) {
var data = json && json.data;
if (!Array.isArray(data)) return [];
var out = [];
data.forEach(function (d) {
var seconds = 0, dateStr = "";
if (d.grand_total && typeof d.grand_total.total_seconds === "number") {
seconds = d.grand_total.total_seconds; // daily-summaries shape
} else if (typeof d.total_seconds === "number") {
seconds = d.total_seconds; // flat shape
}
if (d.range && (d.range.date || d.range.text)) {
dateStr = d.range.date || d.range.text;
} else if (d.date) {
dateStr = d.date;
}
// Anchor bare YYYY-MM-DD to local noon so the weekday label doesn't
// slip a day in timezones west of UTC (where it'd parse as UTC midnight).
var dateForParse = /^\d{4}-\d{2}-\d{2}$/.test(dateStr) ? dateStr + "T12:00:00" : dateStr;
var dt = dateStr ? new Date(dateForParse) : null;
var label = dt && !isNaN(dt) ? dt.toDateString() : (dateStr || "");
var short = dt && !isNaN(dt)
? dt.toLocaleDateString(undefined, { weekday: "short" })
: (label.slice(0, 3) || "?");
out.push({ label: label, short: short, total: seconds });
});
return out;
}
// ---- orchestration --------------------------------------------------
function load(url, onData, fallbackBoxId) {
if (!url) return Promise.resolve(null);
return jsonp(url).then(function (json) {
if (json && json.human_readable_range) setMeta("Range: " + json.human_readable_range);
else if (json && json.range && json.range.text) setMeta("Range: " + json.range.text);
onData(json);
return json;
}).catch(function (err) {
console.warn("[wakatime] failed to load", url, err);
if (fallbackBoxId) { var b = el(fallbackBoxId); if (b) failSection(b, "Couldn't load this chart."); }
return null;
});
}
function init() {
var configured = Object.keys(WAKATIME).some(function (k) { return !!WAKATIME[k]; });
var setup = el("waka-setup");
var content = el("waka-content");
if (!configured) {
if (setup) setup.hidden = false;
if (content) content.hidden = true;
return;
}
if (setup) setup.hidden = true;
if (content) content.hidden = false;
var jobs = [];
if (WAKATIME.codingActivity) {
jobs.push(load(WAKATIME.codingActivity, function (json) {
renderWeek(asDays(json));
}, "waka-week"));
}
if (WAKATIME.languages) {
jobs.push(load(WAKATIME.languages, function (json) {
renderBars("waka-languages", asCategorical(json));
}, "waka-languages"));
}
if (WAKATIME.categories) {
jobs.push(load(WAKATIME.categories, function (json) {
renderBars("waka-categories", asCategorical(json));
}, "waka-categories"));
}
if (WAKATIME.editors) {
jobs.push(load(WAKATIME.editors, function (json) {
renderBars("waka-editors", asCategorical(json));
}, "waka-editors"));
}
if (WAKATIME.operatingSystems) {
jobs.push(load(WAKATIME.operatingSystems, function (json) {
renderBars("waka-os", asCategorical(json));
}, "waka-os"));
}
// If no coding-activity embed was set, hide the headline total card.
if (!WAKATIME.codingActivity) {
var totEl = el("waka-total");
if (totEl) totEl.hidden = true;
}
Promise.all(jobs);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

433
js/terminal.js Normal file
View File

@ -0,0 +1,433 @@
/* =====================================================================
* terminal.js the homepage's interactive terminal.
*
* Flow: a short boot log streams in, the side chrome fades in alongside
* it, then the banner + a pinned prompt appear. You type a command (or a
* social's name) and the output is appended to the scrollback BELOW the
* input the input itself never moves.
* ===================================================================== */
(function terminal() {
const root = document.getElementById("terminal");
if (!root) return;
// ---- socials (keyword -> destination) ----------------------------------
const SOCIALS = {
gitgay: { label: "Git.Gay", sub: "@doughmination", url: "https://git.gay/doughmination", aliases: ["git.gay", "gitea", "github", "git"] },
twitter: { label: "Twitter", sub: "@DoughminCEO", url: "https://x.com/DoughminCEO", aliases: ["x"] },
bluesky: { label: "Bluesky", sub: "@doughmination.win", url: "https://bsky.app/profile/doughmination.win", aliases: ["bsky"] },
linkedin: { label: "LinkedIn", sub: "Clove Twilight", url: "https://www.linkedin.com/in/estrogen/" },
spotify: { label: "Spotify", sub: "doughmination", url: "https://open.spotify.com/user/x060f5w4ftwv8zc8fi9662t70" },
discord: { label: "Discord", sub: "Doughmination", url: "https://discord.com/users/1464890289922641993" },
twitch: { label: "Twitch", sub: "@doughminationgaming", url: "https://www.twitch.tv/doughminationgaming" },
reddit: { label: "Reddit", sub: "u/XerinDotZero", url: "https://www.reddit.com/user/XerinDotZero/" },
youtube: { label: "YouTube", sub: "@CloveTwiGaming", url: "https://www.youtube.com/@CloveTwiGaming", aliases: ["yt"] },
mastodon: { label: "Mastodon", sub: "@doughmination@mastodon.social", url: "https://mastodon.social/@doughmination" },
email: { label: "Email", sub: "admin@doughmination.win", url: "mailto:admin@doughmination.win", aliases: ["mail"] },
portfolio: { label: "Portfolio", sub: "doughmination.co.uk", url: "https://doughmination.co.uk/", aliases: ["website", "site"] }
};
const ALIASES = {};
Object.keys(SOCIALS).forEach((k) => {
(SOCIALS[k].aliases || []).forEach((a) => { ALIASES[a] = k; });
});
// keyword -> svg filename in /assets/socials
const SOCIAL_ICON = {
github: "github", gitgay: "git-gay", twitter: "twitter", bluesky: "bluesky",
linkedin: "linkedin", spotify: "spotify", discord: "discord", twitch: "twitch",
reddit: "reddit", youtube: "youtube", mastodon: "mastodon", email: "email",
company: "site", portfolio: "site"
};
function iconImg(key) {
return '<img class="t-social-ic" src="/assets/socials/' + (SOCIAL_ICON[key] || "site") + '.svg" alt="">';
}
// ---- friends (keyword -> who they are) ---------------------------------
const FRIENDS = {
ari: { name: "Ari", desc: "🩵 My wifey 🩵 the best 🩵 Her corner of the web:", url: "https://ariare.es", urlLabel: "ariare.es 🩵" },
saphie: { name: "Saphie", desc: "🩷 Cammy's partner, loves linguistics🩷", url: "" },
camilla: { name: "Camillia (Cammy)", desc: "🖤 Close friend who shares a passion for coding 🖤", url: "https://cammy-the-cat.com", urlLabel: "cammy-the-cat.com 🖤" },
ria: { name: "Ria", desc: "🤍 Close friend/platonic daughter who means a lot to me 🤍", url: "" },
lilly: { name: "Lilly (Lils)", desc: "💖 Pookie, really cool person 💖", url: "" },
primrose: { name: "Nimnose", desc: "💜 Lil's partner 💜", url: "" },
fin: { name: "Fin", desc: "💛 Ari's friend who is really nice and who I can be unfilered with 💛", url: "" }
};
let cache = null;
async function checkDomain(subdomain) {
if (!cache) {
const response = await fetch("https://raw.is-a.dev/v2.json");
cache = await response.json();
}
return cache.some((d) => d.subdomain === subdomain);
}
// arch.ascii (hyfetch format) is fetched once at startup for `hyfetch`.
let archLines = null;
function loadArt() {
fetch("/arch.ascii").then(function (r) { return r.ok ? r.text() : ""; }).then(function (t) {
if (!t) return;
var lines = t.replace(/\r/g, "").split("\n");
if (lines[0] && lines[0].trim().charAt(0) === "{") lines.shift();
lines = lines.map(function (l) { return l.replace(/\$\{c\d\}/g, ""); });
while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
archLines = lines;
}).catch(function () { });
}
// ---- ascii banner -------------------------------------------------------
const BANNER = [
" ██████╗██╗ ██████╗ ██╗ ██╗███████╗",
"██╔════╝██║ ██╔═══██╗██║ ██║██╔════╝",
"██║ ██║ ██║ ██║██║ ██║█████╗ ",
"██║ ██║ ██║ ██║╚██╗ ██╔╝██╔══╝ ",
"╚██████╗███████╗╚██████╔╝ ╚████╔╝ ███████╗",
" ╚═════╝╚══════╝ ╚═════╝ ╚═══╝ ╚══════╝"
].join("\n");
// ---- boot log -----------------------------------------------------------
const BOOT = [
["info", "starting clovesh..."],
["info", "mounting /dev/estrogen..."],
["ok", "estrogen levels nominal"],
["info", "loading kernel modules (catppuccin)..."],
["ok", "modules loaded"],
["info", "summoning cats..."],
["ok", "oneko ready"],
["info", "connecting to discord via lanyard..."],
["ok", "presence online"],
["info", "mounting button wall..."],
["ok", "88x31 buttons hung"],
["info", "starting terminal..."],
["ok", "ready — type 'help'"]
];
// ---- build DOM ----------------------------------------------------------
root.innerHTML =
'<pre class="t-boot" id="t-boot" aria-hidden="true"></pre>' +
'<div class="t-main" id="t-main" hidden>' +
'<pre class="t-banner">' + esc(BANNER) + "</pre>" +
'<div class="t-greet">Type <b>help</b> for commands, or <b>socials</b> to browse.</div>' +
'<div class="t-inputline">' +
'<span class="t-prompt">arch@arch<span class="t-path">:[~]$</span></span>' +
'<input class="t-input" id="t-input" type="text" autocomplete="off" autocapitalize="off" spellcheck="false">' +
"</div>" +
'<div class="t-output" id="t-output"></div>' +
"</div>";
const bootEl = root.querySelector("#t-boot");
const mainEl = root.querySelector("#t-main");
const input = root.querySelector("#t-input");
const output = root.querySelector("#t-output");
function esc(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function stamp() {
return new Date().toLocaleTimeString("en-GB", { hour12: false });
}
// ---- command handlers ---------------------------------------------------
const COMMANDS = {
help() {
const rows = [
["help", "show this list"],
["socials", "list all socials"],
["<social> [-open]", "show a social & ask to open it (append -open to do directly)"],
["system [person]", "open my system website (append a person's name to open their page)"],
["about", "a little about me"],
["hyfetch", "system info, with flair"]
];
let out = "Available commands:\n";
out += rows.map((r) => " " + r[0].padEnd(12) + r[1]).join("\n");
out += "\n\nTip: type a social's name (try 'socials') to open it.";
return { text: out };
},
ls() {
const rows = [
["help", "show this list"],
["socials", "list all socials"],
["<social>", "show a social & ask to open it (append -open to do directly)"],
["system [person]", "open my system website (append a person's name to open their page)"],
["about", "a little about me"],
["hyfetch", "system info, with flair"]
];
let out = "Available commands:\n";
out += rows.map((r) => " " + r[0].padEnd(12) + r[1]).join("\n");
out += "\n\nTip: type a social's name (try 'socials') to open it.";
return { text: out };
},
async system(args) {
const who = (args[0] || "").toLowerCase();
if (!who) {
window.open("https://system.doughmination.co.uk/", "_blank");
return { text: "Opening system site..." };
}
try {
const response = await fetch(
`https://system.doughmination.co.uk/api/member/${encodeURIComponent(who)}`
);
if (response.status === 200) {
window.open(`https://system.doughmination.co.uk/member/${encodeURIComponent(who)}`, "_blank");
return { text: `Opening ${who}'s profile...` };
}
if (response.status === 404) {
return { text: "That person doesn't exist." };
}
if (response.status === 502) {
return { text: "The server is currently having issues." };
}
return { text: `Unexpected response (${response.status}).` };
} catch (error) {
return { text: "Failed to contact the server." };
}
},
socials() {
const items = Object.keys(SOCIALS)
.map((k) => '<span class="t-ls-item">' + esc(k) + "</span>").join("");
return {
html: '<div class="t-ls">' + items + "</div>" +
'<div class="t-dim t-ls-foot">type one to view it, or <b>&lt;name&gt; -open</b> to open</div>'
};
},
about() {
return {
text:
"Clove Twilight — fae/faer\n" +
"Transfem developer from Southampton, UK. I make Discord bots,\n" +
"personal-site nonsense, and run a small corner of the internet\n" +
"under the trade mark 'doughmination'. Big on Linux, Catppuccin, and cats.\n\n" +
"This site is the beta playground for clove.is-a.dev — expect things\n" +
"to break in funny ways. Type 'socials' to find me elsewhere."
};
},
hyfetch() {
const info = [
'<b class="t-accent">arch</b>@<b class="t-accent">arch</b>',
"-----------------------",
"OS........ Arch Linux x86_64",
"GPU....... AMD ATI SPEEDSTER MERC 310 RX 7900 XTX",
"CPU....... AMD Ryzen 9 9950X3D (8) @ 5.7GHz",
"Host...... B850M AORUS ELITE WIFI6E ICE -CF-WCP-ADO",
"Kernel.... 7.0.11-arch1-1",
"Shell..... bash 5.3.12",
"Theme..... Breeze-Dark [GTK2/3]",
"Pronouns.. fae/faer",
"Uptime.... " + uptime(),
].join("\n");
if (!archLines || !archLines.length) {
return { html: '<pre class="hf-info">' + info + "</pre>" };
}
const colors = ["#5bcefa", "#f5a9b8", "#ffffff", "#f5a9b8", "#5bcefa"];
const n = archLines.length;
const logo = archLines.map(function (ln, i) {
const c = colors[Math.min(colors.length - 1, Math.floor((i / n) * colors.length))];
return '<span style="color:' + c + '">' + esc(ln) + "</span>";
}).join("\n");
return {
html: '<div class="hf"><pre class="hf-logo">' + logo + "</pre>" +
'<pre class="hf-info">' + info + "</pre></div>"
};
},
async isadotdev(parts) {
const arg = (parts[0] || "").toLowerCase().replace(/^https?:\/\//, "").replace(/\/+$/, "");
if (!arg || !arg.endsWith(".is-a.dev")) {
return { text: "usage: isadotdev <subdomain>.is-a.dev", error: true };
}
const sub = arg.replace(/\.is-a\.dev$/, "");
if (["clove", "doughmination"].indexOf(sub) >= 0) {
return { text: "nice try 👀", error: true };
}
showResult({ text: "checking " + arg + "…" });
let found;
try { found = await checkDomain(sub); }
catch (e) { return { text: "couldn't reach the is-a.dev registry — try again later.", error: true }; }
if (!found) return { text: arg + " isn't registered on is-a.dev.", error: true };
window.open("https://" + arg, "_blank", "noopener");
return { html: 'opening <b class="t-accent">' + esc(arg) + "</b> …" };
},
};
// ---- runtime ------------------------------------------------------------
const startedAt = Date.now();
function uptime() {
let s = Math.floor((Date.now() - startedAt) / 1000);
const h = Math.floor(s / 3600); s -= h * 3600;
const m = Math.floor(s / 60); s -= m * 60;
const parts = [];
if (h) parts.push(h + "h");
if (m) parts.push(m + "m");
parts.push(s + "s");
return parts.join(" ");
}
const history = [];
let histIdx = -1;
let pendingSocial = null;
function showResult(result) {
output.innerHTML = "";
if (!result) return;
const box = document.createElement("div");
box.className = "t-result";
if (result.error) box.classList.add("is-error");
if (result.html != null) box.innerHTML = result.html;
else if (result.text != null) box.textContent = result.text;
output.appendChild(box);
output.scrollTop = 0;
}
function runCommand(fn, args) {
let r;
try { r = fn(args); }
catch (e) { showResult({ text: "error running that command.", error: true }); return; }
if (r && typeof r.then === "function") {
r.then(showResult).catch(function () { showResult({ text: "something went wrong.", error: true }); });
} else {
showResult(r);
}
}
function run(raw) {
const cmd = raw.trim();
output.innerHTML = "";
if (!cmd) { pendingSocial = null; return; }
history.push(cmd); histIdx = history.length;
const parts = cmd.split(/\s+/);
const name = parts[0].toLowerCase();
const flags = parts.slice(1).map((p) => p.toLowerCase());
const wantsOpen = flags.indexOf("-open") >= 0 || flags.indexOf("--open") >= 0 || flags.indexOf("-o") >= 0;
if (pendingSocial) {
if (["y", "yes", "open", "o"].indexOf(name) >= 0) { openSocial(pendingSocial); return; }
if (["n", "no"].indexOf(name) >= 0) { pendingSocial = null; showResult({ text: "okay, leaving it closed." }); return; }
pendingSocial = null;
}
const socialKey = resolveSocial(name === "open" ? flags[0] : name);
if (socialKey) {
if (wantsOpen || name === "open") openSocial(socialKey);
else promptSocial(socialKey);
return;
}
if (name.endsWith(".is-a.dev")) {
runCommand(COMMANDS.isadotdev, [name]);
return;
}
if (COMMANDS[name]) { runCommand(COMMANDS[name], parts.slice(1)); return; }
showResult({ text: "clovesh: command not found: " + name + "\nType 'help' for a list, or 'socials' to browse.", error: true });
}
function promptSocial(key) {
const s = SOCIALS[key];
pendingSocial = key;
showResult({
html:
'<div class="t-social-card">' +
'<div class="t-sc-head">' + iconImg(key) +
'<span><b class="t-accent">' + esc(s.label) + "</b> " +
'<span class="t-dim">' + esc(s.sub) + "</span></span></div>" +
'<a class="t-sc-url" href="' + esc(s.url) + '"' +
(s.url.startsWith("mailto:") ? "" : ' target="_blank" rel="noopener"') + ">" + esc(s.url) + "</a>" +
'<div class="t-sc-ask t-dim">open it? type <b>y</b> · or run <b>' + esc(key) + " -open</b> · <b>n</b> to cancel</div>" +
"</div>"
});
}
function resolveSocial(key) {
if (!key) return null;
if (SOCIALS[key]) return key;
if (ALIASES[key]) return ALIASES[key];
return null;
}
function openSocial(key) {
pendingSocial = null;
const s = SOCIALS[key];
showResult({
html: '<a class="t-social-open" href="' + esc(s.url) + '"' +
(s.url.startsWith("mailto:") ? "" : ' target="_blank" rel="noopener"') + ">" +
iconImg(key) + "opening <b class=\"t-accent\">" + esc(s.label) + "</b> " +
'<span class="t-dim">' + esc(s.url) + "</span> …</a>"
});
if (s.url.startsWith("mailto:")) { window.location.href = s.url; }
else { window.open(s.url, "_blank", "noopener"); }
}
// ---- tab-complete + history --------------------------------------------
const COMPLETIONS = Object.keys(COMMANDS).concat(["open", "socials", "isadotdev"], Object.keys(SOCIALS), Object.keys(ALIASES));
function complete(prefix) {
if (!prefix) return null;
const hits = COMPLETIONS.filter((c) => c.indexOf(prefix) === 0);
if (hits.length === 1) return hits[0];
return null;
}
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const v = input.value;
input.value = "";
run(v);
} else if (e.key === "ArrowUp") {
e.preventDefault();
if (histIdx > 0) { histIdx--; input.value = history[histIdx] || ""; moveCaretEnd(); }
} else if (e.key === "ArrowDown") {
e.preventDefault();
if (histIdx < history.length - 1) { histIdx++; input.value = history[histIdx] || ""; }
else { histIdx = history.length; input.value = ""; }
moveCaretEnd();
} else if (e.key === "Tab") {
e.preventDefault();
const c = complete(input.value.trim().toLowerCase());
if (c) input.value = c;
}
});
function moveCaretEnd() {
requestAnimationFrame(() => { input.selectionStart = input.selectionEnd = input.value.length; });
}
root.addEventListener("click", () => {
if ((window.getSelection() + "") === "") input.focus();
});
// ---- boot then reveal ---------------------------------------------------
document.body.classList.add("term-booting");
let booted = false;
function finishBoot() {
if (booted) return;
booted = true;
bootEl.hidden = true;
mainEl.hidden = false;
document.body.classList.remove("term-booting");
document.body.classList.add("term-ready");
input.focus();
}
function streamBoot(i) {
if (booted) return;
if (i >= BOOT.length) { setTimeout(finishBoot, 350); return; }
const [kind, msg] = BOOT[i];
const tag = kind === "ok"
? '<span class="b-ok"> OK </span>'
: '<span class="b-info"> INFO </span>';
bootEl.insertAdjacentHTML("beforeend",
'<span class="b-line">[<span class="b-time">' + stamp() + "</span>] [" + tag + "] " + esc(msg) + "</span>\n");
bootEl.scrollTop = bootEl.scrollHeight;
setTimeout(() => streamBoot(i + 1), 120 + Math.random() * 120);
}
function skipHandler(e) {
if (e.type === "keydown" || e.type === "click") finishBoot();
}
document.addEventListener("keydown", skipHandler, { once: false });
loadArt();
requestAnimationFrame(() => document.body.classList.add("term-chrome-in"));
streamBoot(0);
})();

View File

@ -1,5 +1,5 @@
<div align="center"> <div align="center">
<img src="https://doughmination.is-a.dev/assets/favicon/avatar.png" alt="Clove Twilight avatar" height="100"> <img src="https://clove.is-a.dev/assets/favicon/avatar.png" alt="Clove Twilight avatar" height="100">
# clove.is-a.dev # clove.is-a.dev
</div> </div>