Compare commits

...

14 Commits

Author SHA1 Message Date
e2d4057f67 Fixed arrows broken on resize or img load 2026-04-06 14:17:10 +02:00
3868d1e7cb Add favicon 2026-04-06 14:10:23 +02:00
2a3ac0eb56 Updated projects n all 2026-04-06 14:07:58 +02:00
76da54f305 Added some images to projects 2026-04-05 10:22:41 +02:00
157a723711 Fixed arrows 2026-03-28 09:10:02 +01:00
a3707f25b3 Did some arrows stuff 2026-03-27 21:18:36 +01:00
535a4584aa Started working on arrows 2026-03-27 20:44:27 +01:00
94efc03593 Did some of the text on the projects page 2026-03-27 19:56:52 +01:00
87ca29e63c Did some stuff on the projects page 2026-03-27 18:37:24 +01:00
df387db473 Started working on the projects page 2026-03-27 13:34:33 +01:00
1d231e2dcf Worked some shit 2026-03-26 18:43:38 +01:00
579da7b357 Removed file 2026-03-26 14:20:43 +01:00
a56fa5522a About me page 2026-03-26 13:56:10 +01:00
136b1bdea9 Done some stuff 2026-03-26 09:28:03 +01:00
35 changed files with 685 additions and 80 deletions

47
about/index.html Normal file
View File

@@ -0,0 +1,47 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ayabusa club - About me</title>
<link rel="stylesheet" href="./style.css">
<!--<script src="script.js"></script> -->
</head>
<body>
<center>
<img class="smurf" src="../assets/smurf.jpg"/>
<h1 class="title_font">ABOUT ME</h1>
<p class="soos">
Hi it's me Ayabusa! I'm a CS student and I love tinkering with everything that falls my hands!<br>
As of 2026 to 2030 I am following a CS engineering degree in France.
</p>
<h2 class="title_font">INTERESTS</h2>
<p class="soos">
I'm currently interested in programming, reverse-engineering, and embedded systems and I'm eager to learn more about them.<br>
On the topic of embedded system, I especially like to tinker with various calculators. You can view my humble collection <a href="https://my.calcs.quest/u/725">here</a>.<br>
I also like to climb especially bouldering :D
</p>
<h2 class="title_font">SOFT I LIKE</h2>
<p class="soos">
<ul class="soos" style="list-style-position: inside;">
<li>Linux Mint: Damn this OS is just the best, you've got a free n opensource OS where everything just works out of da box.</li>
<li>Vim: I didn't like it at first, but I got to admit it's pretty good. I also like Zed in vim mode when working on bigger project.</li>
<li>Thorium: My personnal favorite. It's like chromium but on steroids.</li>
<li>IDA: What can I say, there is just nothing that comes close to it.</li>
</ul>
</p>
<div>
<img class="hcu" src="../assets/hcu1.gif" tppabs="" alt="hcu" border="0" vspace="0" hspace="0"/>
<br>
<a draggable="false" target="_blank" href="https://digdeeper.club/"><img class="button" draggable="false" src="https://digdeeper.club/images/button.png" alt="DigDeeper"></a>
<a href="https://gnu.org" aria-label="GNU website"><img class="button" alt="GNU/Linux" src="../assets/gnu_linux.jpeg" width="88" height="31"></a>
<img class="button" src="../assets/phonechump.gif" tppabs="" alt="hcu" border="0" vspace="0" hspace="0"/>
<a href="https://undertale.com" aria-label="Undetale website"><img class="button" alt="Undetrtale" src="../assets/undertale.gif" width="88" height="31"></a>
<img class="button" src="../assets/hatemac.gif" tppabs="" alt="hatemac" border="0" vspace="0" hspace="0"/>
<img class="button" src="../assets/hatems.gif" tppabs="" alt="hatems" border="0" vspace="0" hspace="0"/>
<a href="https://linuxmint.com/" aria-label="mint website"><img class="button" alt="mint" src="../assets/mint.png" width="88" height="31"></a>
<a href="https://lucida.to/" aria-label="lucida website"><img class="button" alt="lucida" src="../assets/lucida.gif" width="88" height="31"></a>
<img class="button" src="../assets/shit.gif" tppabs="" alt="Same shit" border="0" vspace="0" hspace="0"/>
</div>
</center>
</body>
</html>

54
about/style.css Normal file
View File

@@ -0,0 +1,54 @@
@font-face {
font-family: 'smurf';
src: url('../assets/smurf.woff') format('woff'), /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../assets/smurf.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */
}
@font-face {
font-family: 'soos';
src: url('../assets/soos.woff') format('woff'), /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../assets/soos.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */
}
body {
background-color: #f3d42f;
}
img {
height: 250px;
}
.title_font {
font-family: "smurf";
color: black;
margin: 0;
}
h1 {
font-size: 50px;
}
h2 {
font-size: 35px;
}
h3 {
font-size: 28px;
}
.soos {
font-family: "soos";
font-size: 25px;
}
.button {
width: 88px;
height: 31px;
image-rendering: pixelated;
}
.hcu {
width: 114px;
height: 43px;
image-rendering: pixelated;
}

BIN
assets/FreeMonoBold.ttf Normal file

Binary file not shown.

BIN
assets/FreeMonoBold.woff Normal file

Binary file not shown.

BIN
assets/alacarte_game.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/blogico.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

BIN
assets/gamma_bootloader.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

BIN
assets/gitico.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

BIN
assets/gnu_linux.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
assets/hatemac.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/hatems.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
assets/hcu1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/logo_rusty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/lucida.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/mint.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

BIN
assets/phonechump.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/pigeon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 KiB

BIN
assets/screen_myfiches.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 KiB

BIN
assets/shit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/smurf.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
assets/smurf.ttf Normal file

Binary file not shown.

BIN
assets/smurf.woff Normal file

Binary file not shown.

BIN
assets/soos.ttf Normal file

Binary file not shown.

BIN
assets/soos.woff Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

BIN
assets/undertale.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

27
contact/index.html Normal file
View File

@@ -0,0 +1,27 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ayabusa club - Contact</title>
<link rel="stylesheet" href="./style.css">
<!--<script src="script.js"></script> -->
</head>
<body>
<center>
<img class="pigeon" src="../assets/pigeon.png"/>
<h1 class="title_font">HOW TO CONTACT ME</h1>
<p class="soos">
If you want to contact me for any reason, I recommend you contact me through one of those media:
</p>
<ul class="soos">
<li>
<b>Mail:</b> Send me an email at <b>contact &lt;at&gt; ayabusa &lt;dot&gt; dev</b>. I should reply in about a week.
</li>
<li>
<b>Discord:</b> As of right now I'm still on this shitty platform, I'm pretty active on there. <b>@ayabusa_</b>
</li>
</ul>
<p class="soos">You can also find me on some other forums and message board, sometimes as Ayabusa, sometimes not :)</p>
</center>
</body>
</html>

58
contact/style.css Normal file
View File

@@ -0,0 +1,58 @@
@font-face {
font-family: 'smurf';
src: url('../assets/smurf.woff') format('woff'), /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../assets/smurf.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */
}
@font-face {
font-family: 'soos';
src: url('../assets/soos.woff') format('woff'), /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../assets/soos.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */
}
body {
background-color: #8bc8feff;
color: #071c2dff;
}
img {
height: 250px;
}
ul {
list-style-position: inside;
}
.title_font {
font-family: "smurf";
margin: 0;
}
h1 {
font-size: 50px;
}
h2 {
font-size: 35px;
}
h3 {
font-size: 28px;
}
.soos {
font-family: "soos";
font-size: 25px;
}
.button {
width: 88px;
height: 31px;
image-rendering: pixelated;
}
.hcu {
width: 114px;
height: 43px;
image-rendering: pixelated;
}

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<title>Ayabusa club</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
@@ -56,7 +57,7 @@
</div>
<div class="mt-[3vw] flex flex-row space-x-[3vw] w-full">
<div class="w-[55vw] font-[sftr] leading-[5vw] text-[7vw] flex flex-col">
<a href="projects.html" class="group flex flex-row w-full justify-between">
<a href="projects" class="group flex flex-row w-full justify-between">
<svg class="opacity-0 group-hover:opacity-100 mt-[-0.5vw] mb-[0.5vw] ms-[0.3vw]" xmlns="http://www.w3.org/2000/svg" height="5vw" fill="currentColor" class="bi bi-caret-right-fill" viewBox="5 0 8 16">
<path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
</svg>
@@ -80,7 +81,26 @@
<path d="m3.86 8.753 5.482 4.796c.646.566 1.658.106 1.658-.753V3.204a1 1 0 0 0-1.659-.753l-5.48 4.796a1 1 0 0 0 0 1.506z"/>
</svg>
</a>
<script src="sphere.js"></script>
<div class="flex flex-row w-full mt-[3vw]">
<div>
<canvas id="cvs" class="outline-[0.7vw]"></canvas>
<script src="sphere.js"></script>
</div>
<div class="flex flex-col w-full ms-[3vw] justify-between">
<a href="about" class="group flex flex-row w-full justify-between">
<svg class="opacity-0 group-hover:opacity-100 mt-[-0.5vw] mb-[0.5vw] ms-[0.3vw]" xmlns="http://www.w3.org/2000/svg" height="5vw" fill="currentColor" class="bi bi-caret-right-fill" viewBox="5 0 8 16">
<path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
</svg>
<p class="">About</p>
</a>
<a href="contact" class="group flex flex-row w-full justify-between">
<p class="">Contact</p>
<svg class="opacity-0 group-hover:opacity-100 mt-[-0.5vw] mb-[0.5vw] ms-[0.3vw]" xmlns="http://www.w3.org/2000/svg" height="5vw" fill="currentColor" class="bi bi-caret-left-fill" viewBox="3 0 10 16">
<path d="m3.86 8.753 5.482 4.796c.646.566 1.658.106 1.658-.753V3.204a1 1 0 0 0-1.659-.753l-5.48 4.796a1 1 0 0 0 0 1.506z"/>
</svg>
</a>
</div>
</div>
</div>
<div class="w-[45vw] space-y-[3vw]">
<div class="outline-[0.7vw] p-[0.7vw]">

124
projects/index.html Normal file
View File

@@ -0,0 +1,124 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ayabusa club - Projects</title>
<link rel="stylesheet" href="./style.css">
</head>
<body id="body">
<div style="display: flex; direction: row; justify-content: center; margin-bottom: 50px;">
<div class="disbox node" id="proj">
<div class="disboxh">
<a target="_blank" href="https://git.ayabusa.dev"><img class="ico" src="../assets/gitico.png"></img></a>
<a target="_blank" href="https://blog.ayabusa.dev"><img class="ico" src="../assets/blogico.png"></img></a>
</div>
<p class="disboxp">
loc_projects:<br><br>
During my <b>dev journey</b>, I have the opportunity to create some cool projects.<br><br>
I've collected here those that might <b>pick your interest</b>.
</p>
</div>
</div>
<div style="display: flex; direction: row; justify-content: center; margin-bottom: 50px;">
<div>
<div class="disbox node" id="fich" style="margin-bottom: 50px;">
<div class="disboxh">
<a target="_blank" href="https://myfiches.com/about"><img class="ico" src="../assets/blogico.png"></img></a>
</div>
<p class="disboxp">
loc_myfiches.com:<br><br>
In my <b>last year of highschool</b> I stumbled upon a problem with my <b>studying</b> methodology. I kept loosing my <b>revision sheets</b>. <br/>
So I decided to devellop my own solution, <a href="https://myfiches.com">myfiches.com</a>! This website works like a <b>social media</b>,
where like minded individuals can <b>post</b> their revision sheets, <b>like</b> and even <b>comment</b>.
It has enabled be to store and categorize my revision material, and also to <b>share it</b> with the world.
The site is <b>open for everyone</b> to contribute but it's mostly in <b>french</b>.<br/><br/>
<img src="../assets/screen_myfiches.png" style="width: 100%;"></img>
I develloped it with <b>Golang</b> for the backend and <b>svelte</b> for the frontend, and it has worked pretty well for me :D
</p>
</div>
<div class="disbox node" id="rust">
<div class="disboxh">
<a target="_blank" href="https://git.ayabusa.dev/ayabusa/Rusty-slicer"><img class="ico" src="../assets/gitico.png"></img></a>
</div>
<p class="disboxp">
loc_rusty_slicer:<br><br>
<img src="../assets/logo_rusty.png" style="display: block; margin: auto; margin-top: 0; margin-bottom: 0;"></img><br/>
<b>Rusty Slicer</b> is a small app I built using <b>rust</b>, <b>tauri</b> and <b>ffmpeg</b>.
I built it because I was frustrated with some <b>youtube playlist</b> video,
that I wanted to <b>"split"</b> in individual music files.
So you just put your video or music, the time chapters and <b>voilà</b> you've got your files.
</p>
</div>
</div>
<div style="width: 20px;"></div>
<div class="disbox node" id="hack">
<div class="disboxh">
<a target="_blank" href="https://git.ayabusa.dev/ayabusa/Numworks-zeta-os"><img class="ico" src="../assets/gitico.png"></img></a>
<a target="_blank" href="https://blog.ayabusa.dev/2024/04/21/lets-create-an-os-for-a-calculator-numworks-part-1-blinking-led/"><img class="ico" src="../assets/blogico.png"></img></a>
<a target="_blank" href="https://blog.ayabusa.dev/2025/11/07/lets-create-an-os-for-the-numworks-a-different-approach-part-2/"><img class="ico" src="../assets/blogico.png"></img></a>
</div>
<p class="disboxp">
loc_numworks_hacking:<br/><br/>
The <b>Numworks</b> is the first <b>graphing calculator</b> that I got in my hands, in my first year of high school.<br/>
And after a few weeks of common calc usage, I looked into the <b>homebrew community</b> that formed around this piece of hardware.<br/>
And in a matter of weeks I used my <b>raspi</b> to <b>jailbreak</b> it, which allowed me to dive in and learn a ton of cool stuff about reversing and embedded system.<br/><br/>
The first project I did on it, was a <b>baremetal OS</b> for the <b>n0110 model</b>, called <b>Zeta OS</b>. I did this OS from scratch and I got the <b>LED</b> and <b>keyboard</b> working.
You can view my article on it <a href="https://blog.ayabusa.dev/2024/04/21/lets-create-an-os-for-a-calculator-numworks-part-1-blinking-led/">here</a>.<br/><br/>
Then more than one year later I got my hand on the latest <b>n0120 model</b>, which had <b>not any CFW</b>. So I started working on <b>reversing</b> the official bootloader, and made
my own by injecting some code in a <b>code cave</b>. This now let me run any OS even if it's not signed by NW and reflash both the bootloader and the OS.<br/>
<img src="../assets/gamma_bootloader.jpg" style="width: 100%;"></img>
Once again I did an <a href="https://blog.ayabusa.dev/2025/11/07/lets-create-an-os-for-the-numworks-a-different-approach-part-2/">article on it</a> to document my process.
</p>
</div>
</div>
<div style="display: flex; direction: row; justify-content: center; margin-bottom: 50px;">
<div class="disbox node" id="alac">
<div class="disboxh">
<a target="_blank" href="https://git.ayabusa.dev/ayabusa/A-la-carte"><img class="ico" src="../assets/gitico.png"></img></a>
<a target="_blank" href="https://tiplanet.org/forum/viewtopic.php?p=278211"><img class="ico" src="../assets/blogico.png"></img></a>
</div>
<p class="disboxp">
loc_a_la_carte:<br><br>
<b>À la carte</b> is a <b>game</b> I built for the <b>Numworks</b>, in the context of a <a href="https://tiplanet.org/">TI Planet</a> contest.
It's built in <b>python</b> and optimized for it to <b>run smoothly</b> on the target <b>calculator</b>.
<img src="../assets/alacarte_game.png" style="width: 100%;"></img>
</p>
</div>
</div>
<div style="display: flex; direction: row; justify-content: center; margin-bottom: 50px;">
<div class="disbox" id="mine">
<div class="disboxh">
<a target="_blank" href="https://git.ayabusa.dev/ayabusa/Modern-Chunk-Detector"><img class="ico" src="../assets/gitico.png"></img></a>
<a target="_blank" href="https://github.com/ayabusa/NoMoreSpire"><img class="ico" src="../assets/gitico.png"></img></a>
<a target="_blank" href="https://blog.ayabusa.dev/2023/10/30/how-minecraft-mods-works-for-dummies/"><img class="ico" src="../assets/blogico.png"></img></a>
</div>
<p class="disboxp">
loc_minecraft_mods:<br><br>
During some of my (too) many <b>Minecraft two weeks phases</b>, I develloped some <b>minecraft mods</b>. Mainly in <b>Fabric</b>,
I even did a <a href="https://blog.ayabusa.dev/2023/10/30/how-minecraft-mods-works-for-dummies/">tutorial to get
started</a> and be introduced to the wonderfull world of <b>Mixins</b> xD.<br/><br/>
<a href="https://modrinth.com/mod/no-more-spire">No more spire</a>: I did this mod for the server that I was playing back then <b>OriginRealms</b>.
Everyone was spamming about the <b>spire update</b> in chat, so I decided to do some <b>cleanup</b> and free the chat from this <b>pollution</b>.<br/><br/>
<a href="https://modrinth.com/mod/modern-chunk-detector">Modern chunk detector</a>: This mod was made for the server <b>9b9t</b> that my friend and I were playing. It <b>highlights</b> the chunk that were
generated in <b>modern version of MC</b>. Great to track bases trails ;)
</p>
</div>
</div>
<svg id="arrows">
<defs>
<!-- Arrowhead marker — inherits line color via context-stroke -->
<marker id="head"
viewBox="0 0 10 10" refX="8" refY="5"
markerWidth="6" markerHeight="6"
orient="auto-start-reverse">
<path d="M1 1L9 5L1 9Z"
fill="context-stroke" stroke="none"/>
</marker>
</defs>
<!-- One <path> per connection (added by JS below) -->
</svg>
<script src="script.js"></script>
</body>
</html>

118
projects/script.js Normal file
View File

@@ -0,0 +1,118 @@
// ─── Configuration ────────────────────────────────────────────
// Each pair is [sourceId, targetId].
// Arrows go from the bottom-center of the source
// to the top-center of the target.
const CONNECTIONS = [
['proj', 'fich'],
['proj', 'hack'],
['fich', 'rust'],
['rust', 'alac'],
['hack', 'alac'],
['alac', 'mine'],
];
// ─── Setup ────────────────────────────────────────────────────
const scene = document.getElementById('body');
const svg = document.getElementById('arrows');
// Create one SVG <path> per connection
const pathEls = CONNECTIONS.map(() => {
const p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
p.setAttribute('class', 'arrow-path');
p.setAttribute('marker-end', 'url(#head)');
svg.appendChild(p);
return p;
});
// ─── Core: Manhattan path between two elements ────────────────
// r1 = bounding rect of source, r2 = bounding rect of target
// Both rects are relative to the scene container.
function manhattanPath(r1, r2) {
let x1 = r1.left + r1.width / 2; // bottom-center of source
const y1 = r1.top + r1.height + 10;
let x2 = r2.left + r2.width / 2; // top-center of target
const y2 = r2.top + 5; // stop 8px above target (arrowhead gap)
let shift = 5;
if (x1 > x2) {
x1 -= shift;
x2 += shift;
} else if (x2 > x1) {
x1 += shift;
x2 -= shift;
}
const ymid = y1 + (y2 - y1) / 2; // elbow halfway between the two
// M: move to source bottom
// L: go straight down to mid-y
// L: go horizontally to target x
// L: go straight down to target top
return `M ${x1} ${y1}
L ${x1} ${ymid}
L ${x2} ${ymid}
L ${x2} ${y2}`;
}
// ─── Redraw all arrows ────────────────────────────────────────
function redraw() {
const sceneRect = scene.getBoundingClientRect();
CONNECTIONS.forEach(([srcId, tgtId], i) => {
const src = document.getElementById(srcId).getBoundingClientRect();
const tgt = document.getElementById(tgtId).getBoundingClientRect();
// Convert from viewport coords → scene-local coords
const r1 = {
left: src.left - sceneRect.left,
top: src.top - sceneRect.top,
width: src.width,
height: src.height,
};
const r2 = {
left: tgt.left - sceneRect.left,
top: tgt.top - sceneRect.top,
width: tgt.width,
height: tgt.height,
};
pathEls[i].setAttribute('d', manhattanPath(r1, r2));
if (tgt.left > src.left) {
pathEls[i].style.stroke = "#008000";
} else if (tgt.left < src.left){
pathEls[i].style.stroke = "#ff0000";
} else {
pathEls[i].style.stroke = "#0000ff";
}
});
}
// ─── Drag logic ───────────────────────────────────────────────
document.querySelectorAll('.node').forEach(node => {
node.addEventListener('mousedown', e => {
e.preventDefault();
const startX = e.clientX;
const startY = e.clientY;
const originL = parseInt(node.style.left);
const originT = parseInt(node.style.top);
const onMove = e => {
node.style.left = Math.max(0, originL + e.clientX - startX) + 'px';
node.style.top = Math.max(0, originT + e.clientY - startY) + 'px';
redraw();
};
const onUp = () => {
window.removeEventListener('mousemove', onMove);
window.removeEventListener('mouseup', onUp);
};
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseup', onUp);
});
});
// ─── Initial draw ─────────────────────────────────────────────
window.addEventListener('load', redraw);
// also redraws if anything inside the scene changes size
new ResizeObserver(redraw).observe(scene);

157
projects/style.css Normal file
View File

@@ -0,0 +1,157 @@
@font-face {
font-family: 'fmono';
src: url('../assets/FreeMonoBold.woff') format('woff'), /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../assets/FreeMonoBold.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */
}
html {
min-height: 100vh;
background-image: linear-gradient(to top, #c2fcff, white);
color: #071c2dff;
}
b {
font-weight: normal;
color: #008000;
}
a {
font-weight: normal;
color: #008000;
text-decoration-thickness: 2px;
text-decoration-color: #008000;
}
.disbox {
font-family: "fmono";
max-width: 300px;
filter: drop-shadow(5px 5px 4px #707070);
height: fit-content;
}
.disboxp {
color: #000080;
outline: solid;
outline-width: 2px;
outline-color: black;
background-color: white;
margin: 0px;
padding: 5px;
}
.disboxh {
background-color: #c2fcff;
outline: solid;
outline-width: 2px;
margin: 0;
margin-bottom: 2px;
padding-left: 5px;
}
.ico {
margin-bottom: -3px;
}
/* ─── SVG overlay ────────────────────────────────────────── */
#arrows {
position: absolute;
inset: 0; /* fill the scene */
width: 100%;
height: 100%;
pointer-events: none; /* clicks pass through to divs */
overflow: visible; /* arrows can leave the box if needed */
}
/* Arrow path style */
.arrow-path {
fill: none;
stroke: #aaa9a2;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Win32 classic scrollbar */
::-webkit-scrollbar {
width: 17px;
height: 17px;
}
::-webkit-scrollbar-track {
background: repeating-conic-gradient(
#b0b0b0 0% 25%,
#c8c8c8 0% 50%
) 0 0 / 2px 2px;
}
::-webkit-scrollbar-thumb {
background: #c0c0c0;
border-top: 2px solid #ffffff;
border-left: 2px solid #ffffff;
border-right: 2px solid #404040;
border-bottom: 2px solid #404040;
border-radius: 0;
}
::-webkit-scrollbar-thumb:active {
border-top: 2px solid #404040;
border-left: 2px solid #404040;
border-right: 2px solid #ffffff;
border-bottom: 2px solid #ffffff;
}
::-webkit-scrollbar-button {
background: #c0c0c0;
border-top: 2px solid #ffffff;
border-left: 2px solid #ffffff;
border-right: 2px solid #404040;
border-bottom: 2px solid #404040;
display: block;
width: 17px;
height: 17px;
}
::-webkit-scrollbar-button:active {
border-top: 2px solid #404040;
border-left: 2px solid #404040;
border-right: 2px solid #ffffff;
border-bottom: 2px solid #ffffff;
}
/* Up arrow */
::-webkit-scrollbar-button:vertical:decrement {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='17' height='17'%3E%3Cpolygon points='8.5,4 13,13 4,13' fill='%23000'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
/* Down arrow */
::-webkit-scrollbar-button:vertical:increment {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='17' height='17'%3E%3Cpolygon points='8.5,13 4,4 13,4' fill='%23000'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
/* Left arrow */
::-webkit-scrollbar-button:horizontal:decrement {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='17' height='17'%3E%3Cpolygon points='4,8.5 13,4 13,13' fill='%23000'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
/* Right arrow */
::-webkit-scrollbar-button:horizontal:increment {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='17' height='17'%3E%3Cpolygon points='13,8.5 4,4 4,13' fill='%23000'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
::-webkit-scrollbar-corner {
background: #c0c0c0;
}
::-webkit-scrollbar-button:start:increment,
::-webkit-scrollbar-button:end:decrement {
display: none;
}

123
sphere.js
View File

@@ -1,69 +1,63 @@
const {cos, sin, sqrt, acos, atan, atan2, abs, PI} = Math
const clamp = (a, b, x) => x < a ? a : x > b ? b : x
const cvs = document.createElement('canvas')
const cvs = document.getElementById("cvs")
const ctx = cvs.getContext('2d')
const RADIUS = 150
const NB_SECTIONS = 6
const LINE_WIDTH = 3
const NB_SECTIONS = 6
const SCALE = devicePixelRatio
const SCALE = devicePixelRatio
const width = RADIUS * 2 + 20
const height = RADIUS * 2 + 20
cvs.width = width * SCALE
cvs.height = height * SCALE
cvs.style.width = `${width }px`
cvs.style.height = `${height}px`
let RADIUS, width, height, LINE_WIDTH
document.body.appendChild(cvs)
const SPHERE_SIZE = 0.15 // % of viewport width
const vec = (x = 0, y = 0, z = 0) => ({x, y, z})
vec.set = (o, x = 0, y = 0, z = 0) => {
o.x = x
o.y = y
o.z = z
return o
function resize() {
const vw = window.innerWidth * SPHERE_SIZE
RADIUS = Math.round((vw - 20) / 2)
LINE_WIDTH = vw / 50 // adjust the divisor to taste
width = RADIUS * 2 + 20
height = RADIUS * 2 + 20
cvs.width = width * SCALE
cvs.height = height * SCALE
cvs.style.width = `${SPHERE_SIZE * 100}vw`
cvs.style.height = `${SPHERE_SIZE * 100}vw`
ctx.setTransform(SCALE, 0, 0, SCALE, 0, 0)
ctx.fillStyle = '#071c2dff'
ctx.lineCap = 'round'
}
const X = vec(1, 0, 0)
const Y = vec(0, 1, 0)
const Z = vec(0, 0, 1)
resize()
window.addEventListener('resize', resize)
// orientation of camera
let theta, phi
const vec = (x = 0, y = 0, z = 0) => ({x, y, z})
vec.set = (o, x = 0, y = 0, z = 0) => { o.x = x; o.y = y; o.z = z; return o }
const Z = vec(0, 0, 1)
function project(o, {x, y, z}) {
let ct = cos(theta), st = sin(theta)
let cp = cos(phi), sp = sin(phi)
// original projection
let a = x * ct + y * st
let a = x * ct + y * st
let px = y * ct - x * st
let py = cp * z - sp * a
let pz = cp * a + sp * z
// --- add subtle right tilt (KEY PART) ---
let tilt = -0.2
let cr = cos(tilt), sr = sin(tilt)
let tx = cr * px - sr * pz
let tz = sr * px + cr * pz
return vec.set(o, tx, py, tz)
}
// draw camera-facing section of sphere with normal v and offset o (-1 < o < 1)
const _p = vec()
function draw_section(n, o = 0) {
let {x, y, z} = project(_p, n) // project normal on camera
let a = atan2(y, x) // angle of projected normal -> angle of ellipse
let ry = sqrt(1 - o * o) // radius of section -> y-radius of ellipse
let rx = ry * abs(z) // x-radius of ellipse
let {x, y, z} = project(_p, n)
let a = atan2(y, x)
let ry = sqrt(1 - o * o)
let rx = ry * abs(z)
let W = sqrt(x * x + y * y)
let sa = acos(clamp(-1, 1, o * (1 / W - W) / rx)) // ellipse start angle
let sb = z > 0 ? 2 * PI - sa : - sa // ellipse end angle
let sa = acos(clamp(-1, 1, o * (1 / W - W) / rx))
let sb = z > 0 ? 2 * PI - sa : -sa
ctx.beginPath()
ctx.ellipse(x * o * RADIUS, y * o * RADIUS, rx * RADIUS, ry * RADIUS, a, sa, sb, z <= 0)
ctx.stroke()
@@ -71,67 +65,40 @@ function draw_section(n, o = 0) {
const _n = vec()
function draw_arcs() {
if (with_great_circles.checked)
for (let i = NB_SECTIONS; i--;) {
let a = i / NB_SECTIONS * Math.PI
draw_section(vec.set(_n, cos(a), sin(a)))
}
for (let i = NB_SECTIONS; i--;) {
let a = i / NB_SECTIONS * PI
draw_section(vec.set(_n, cos(a), sin(a)))
}
for (let i = NB_SECTIONS - 1; i--;) {
let a = (i + 1) / NB_SECTIONS * Math.PI
let a = (i + 1) / NB_SECTIONS * PI
draw_section(Z, cos(a))
if (with_sections.checked) {
draw_section(X, cos(a))
draw_section(Y, cos(a))
}
}
}
const front_grad = ctx.createRadialGradient(0, 0, RADIUS * 2 / 3, 0, 0, RADIUS)
const back_grad = ctx.createRadialGradient(0, 0, RADIUS * 2 / 3, 0, 0, RADIUS)
front_grad.addColorStop(0, '#8bc8feff')
front_grad.addColorStop(1, '#8bc8feff')
back_grad.addColorStop(1, '#8bc8feff')
back_grad.addColorStop(0, '#8bc8feff')
ctx.fillStyle = '#071c2dff'
ctx.lineCap = 'round'
ctx.scale(SCALE, SCALE)
function render() {
requestAnimationFrame(render)
theta = performance.now() / 6000 * PI
phi = 1
// 1. change the basis of the canvas
ctx.save()
ctx.fillRect(0, 0, width, height)
ctx.clearRect(0, 0, width, height)
ctx.translate(width >> 1, height >> 1)
ctx.scale(1, -1)
// 2. draw back arcs
if (with_back.checked) {
ctx.lineWidth = LINE_WIDTH / 2
ctx.strokeStyle = with_gradient.checked ? back_grad : '#8bc8feff'
ctx.scale(-1, -1) // the trick is to flip the canvas
draw_arcs()
ctx.scale(-1, -1)
}
// 3. draw sphere border
ctx.strokeStyle = with_gradient.checked ? '#8bc8feff' : '#8bc8feff'
ctx.lineWidth = LINE_WIDTH / 2
ctx.strokeStyle = '#8bc8feff'
ctx.scale(-1, -1)
draw_arcs()
ctx.scale(-1, -1)
ctx.strokeStyle = '#8bc8feff'
ctx.lineWidth = LINE_WIDTH + 2
ctx.beginPath()
ctx.arc(0, 0, RADIUS, 0, 2 * Math.PI)
ctx.arc(0, 0, RADIUS, 0, 2 * PI)
ctx.stroke()
// 4. draw front arcs
ctx.lineWidth = LINE_WIDTH
ctx.strokeStyle = with_gradient.checked ? front_grad : '#8bc8feff'
ctx.strokeStyle = '#8bc8feff'
draw_arcs()
ctx.restore()
}

View File

@@ -4,6 +4,26 @@
url('assets/sftr.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #071c2dff;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #8bc8feff;
border-radius: 5px;
margin: 3px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #8bc8feff;
}
.txt-cyan {
color: #8bc8feff;
}
@@ -19,3 +39,16 @@
font-weight: 900;
font-style: normal;
}
*::selection {
background: #8bc8feff;
color: #071c2dff;
}
*::-moz-selection {
background: #8bc8feff;
color: #071c2dff;
}
*::-webkit-selection {
background: #8bc8feff;
color: #071c2dff;
}