adelfaure.net/devlog/2025-12-04
---------------------------- Support me on ko-fi! ------------------------------
_ _____
/ \ | | | |
|___| |___ ___ __|__ | ___ __|__ ___ __
| | | \ / \ | | | | /___) \___/ | / \ | \
| | |___/ \___/ |___| |__ | \___ / \ |__ \___/ |
| |
___ ___ ___| _ _ ___ ___ ___ ___ ___
___| | | / | | | | ___| | \ | \ | | | / |
(___| | | \___| | | | (___| |___/ |___/ | | | \___|
| | ___|
_ ___ __ ___ ___
| | / \ / \ / \ | |
| ___ ___ __|__ ___ |___| \___ | | |
|___/ /___) | | (___ | / \ | | \ | | |
| \ \___ |___| ___) |__ \___/ | | \___/ \__/ _|_ _|_
___|
For my first week as full-time ASCII artist (hurray !) I worked on my textmode
engine Textor.
Textor is now my all-in-one tool for making artworks, games, music and live
performances and while it is still poorly released and undocumented I think it
is getting close to something sufficient.
It is basically a JavaScript and a C++ library that handle the creation of a
text based scene (bitmap or outline) to draw or generate animated, interactive
and potentially colorized glyphs and semigraphics while playing sounds.
On the visual part, it rely on a fragment-shader-like method for drawing
characters on the fly, returning integrals representing glyphs, tiles or
semigraphics codepoints instead of pixel tuples. It also bring classes
representing still or animated sprites for adding hand drawn artworks to
the scene.
This shader-like approach of textmode is directly inspired by Anreas Gysin's
play. Which while using such a clever idea is also an awesome live-coding
playground and its sources helped me greatly to understand and using the
JavaScript Module syntax.
On the audio part, it rely on a digital signal processing method, allowing to
write audio buffer samples one by one while interacting with visual output.
Both this DSP approach and how to live-code it is directly inspired by Felix
Roos experiments documented on his garten.salat.dev blog.
I started to publish Textor most recent state on Codeberg here.
To give you something tangible about what is going one there I added two
examples in the repo.
One for outline font, plain/text rendering:
Link
Source
0 import {loop} from "textor/utils.mjs";
1 const {floor} = Math;
2
3
4 function fragment(ctx)
5 {
6 const {x,y,i} = ctx;
7 const {animation} = ctx.textor;
8 const {width,height} = ctx.textor.scene;
9 if (x == width-1)
10 {
11 return 10;
12 }
13 const {frame} = animation;
14 var dist = Math.hypot((x - width/2)/2, y - height/2)
15 var distX = Math.hypot(x, width/2)
16 var distY = Math.hypot(y, height/2)
17 var glyph = Math.abs(
18 loop(floor(dist+Math.sin(frame/400)*(x+y)+frame/10),95)
19 )+32;
20 var glyphOffset = Math.abs(
21 loop(floor(dist+Math.sin(frame/800)*(x+y)+frame/20),95)
22 )+32;
23 var glyphOffset2 = Math.abs(
24 loop(floor(dist/1000+Math.sin(frame/1600)*(distX/distY)),95)
25 )+32;
26 return (
27 floor(glyph/5)*50 +
28 floor(glyphOffset/10)*100 +
29 floor(glyphOffset2/20)*200
30 )%95+32;
31 }
32
33 export function main(textor)
34 {
35 textor.scene.map(fragment,textor);
36 return {
37 fontSize: 16,
38 color: 0x000000,
39 backgroundColor: 0xFFFFFF,
40 fps: 60
41 };
42 }
One for bitmap rendering:
Link
Source
0 import {loop} from "textor/utils.mjs";
1 const {floor} = Math;
2
3
4 function fragment(ctx)
5 {
6 const {x,y,i} = ctx;
7 const {bitmapConfig,animation} = ctx.textor;
8 const {width,height} = ctx.textor.scene;
9 const {fg,bg,tilesetSize,palette} = bitmapConfig;
10 const {frame} = animation;
11 var dist = Math.hypot(x - width/2, y - height/2)
12 var distX = Math.hypot(x, width/2)
13 var distY = Math.hypot(y, height/2)
14 var glyph = Math.abs(
15 loop(floor(dist+Math.sin(frame/400)*(x+y)+frame/10),tilesetSize)
16 );
17 var fgColor = Math.abs(
18 loop(floor(dist+Math.sin(frame/800)*(x+y)+frame/20),palette.length)
19 );
20 var bgColor = Math.abs(
21 loop(floor(dist/1000+Math.sin(frame/1600)*(distX/distY)),palette.length)
22 );
23
24 return glyph + fg * fgColor + bg * bgColor;
25 }
26
27 export function main(textor)
28 {
29 textor.scene.map(fragment,textor);
30 return {
31 type: "bitmap",
32 tilesetSource: "adlfont8x8",
33 semigraphSize: 96,
34 glyphSize: 128,
35 tileWidth: 8,
36 tileHeight: 8,
37 palette: [
38 0x000000,
39 0xFF0000,
40 0x00FF00,
41 0x0000FF,
42 0xFFFFFF
43 ],
44 fps: 60,
45 scale: 2
46 };
47 }
You'll quickly find out that nothing is yet documented and that comment
presence on the sources is very rare. This is where this devlog comes into
play. One of the purposes of theses posts being to help me verbalize what's
going on in order to help me documenting all of this.
For starters let's talk about the most recents progress. This week I worked on
mapping keyboard keys inputs into ASCII codepoints.
For exemple when I press the "KeyQ" physical key of my keyboard, I want Textor
to get 113 as the value corresponding to the letter 'q' in ASCII/unicode
codepoint. I want to get 81 corresponding to the letter 'Q' if was also
pressing "ShiftLeft" or "ShiftRight" while pressing "KeyQ". Actually I also
want to get 97 and 65 if I'm using an AZERTY keyboard and not a QWERTY one (yes
I'm french).
Overall getting theses codepoints can be useful in this Textor environnment
where integers can quickly be passed as visual output of letters, glyphs or
tiles if you gave it a tilset in its bitmap mode. But more importantly it is
useful for making an editor out of it.
Here a quick test from this week's experiments:
(click on the iframe for being able to interact with it with your keyboard)
Link
Source
0 import {BitmapEditor} from "app/src/editor.mjs";
1 import {system16} from "assets/palettes.mjs";
2
3 var editor = new BitmapEditor(32,32);
4 var local_textor = 0;
5 var keyboardLayoutSelect = (
6 document.getElementById("keyboardLayout-select")
7 );
8
9 export function main(textor)
10 {
11 editor.handleKeys(textor,keyboardLayoutSelect.value);
12 textor.scene.map(
13 editor.fragment.bind(editor),
14 textor
15 );
16 return {
17 type: "bitmap",
18 fps: 60,
19 tilesetSource: "adlfont8x8",
20 semigraphSize: 96,
21 glyphSize: 128,
22 scale: 4,
23 tileWidth: 8,
24 tileHeight: 8,
25 palette: system16
26 }
27 }
Editor source
0 import {Sprite} from "textor/core.mjs";
1 import {keyToASCII} from "textor/utils.mjs";
2 const {floor,ceil} = Math;
3
4 export class BitmapEditor
5 {
6 page = 0;
7 caret = new Sprite(1,1,()=>1);
8 caretX = 0;
9 caretY = 0;
10
11 constructor(
12 pageWidth,
13 pageHeight
14 )
15 {
16 this.page = new Sprite(pageWidth,pageHeight,()=>0);
17 }
18
19 handleKeys(textor,layout)
20 {
21 var dirs = [0,0,0,0];
22 var dirsKeys = [
23 "ArrowUp","Numpad8",
24 "ArrowRight","Numpad6",
25 "ArrowDown","Numpad2",
26 "ArrowLeft","Numpad4",
27 ];
28 var pressing = textor.animation.timeToFrame(250);
29 var fast = textor.animation.timeToFrame(75);
30 for (var i = 0; i < 4; i++)
31 {
32 var input = (
33 textor.keyboard.key(dirsKeys[i*2]) +
34 (
35 !textor.keyboard.NumLock ?
36 textor.keyboard.key(dirsKeys[i*2+1]) :
37 0
38 )
39 )
40 dirs[i] = (
41 input == 1 || input > pressing && input%fast == 1
42 )
43 }
44 var dirX = (
45 dirs[1] ? 1 :
46 dirs[3] ? -1 :
47 0
48 );
49 var dirY = (
50 dirs[2] ? 1 :
51 dirs[0] ? -1 :
52 0
53 )
54 var ascii = keyToASCII(
55 layout,
56 textor.keyboard.lastKey,
57 textor.keyboard.keys["ShiftLeft"] ||
58 textor.keyboard.keys["ShiftRight"] ||
59 textor.keyboard.CapsLock,
60 textor.keyboard.keys["AltRight"],
61 )
62 var input = textor.keyboard.key(textor.keyboard.lastKey);
63 if (
64 ascii > 31 &&
65 ascii < 129 &&
66 (input == 1 || input > pressing && input%fast == 1)
67 )
68 {
69 this.page.write(
70 ascii+
71 textor.bitmapConfig.fg*15+
72 textor.bitmapConfig.normalize,
73 this.caretX,
74 this.caretY
75 );
76 this.caretX ++;
77 }
78 if (
79 textor.keyboard.typing == textor.animation.frame && ascii
80 )
81 {
82 this.caret.map(()=>ascii);
83 }
84 this.caretX = (
85 this.caretX + dirX < 0 ?
86 0 :
87 this.caretX + dirX > this.page.width-1 ?
88 this.page.width-1 :
89 this.caretX + dirX
90 );
91 this.caretY = (
92 this.caretY + dirY < 0 ?
93 0 :
94 this.caretY +dirY > this.page.height-1 ?
95 this.page.height-1 :
96 this.caretY + dirY
97 );
98 }
99
100 fragment(ctx)
101 {
102 const {bitmapConfig} = ctx.textor;
103 const {fg,bg} = bitmapConfig;
104 var documentPrint = this.page.read(ctx.x,ctx.y);
105 var caretPrint = this.caret.read(ctx.x-this.caretX,ctx.y-this.caretY);
106 if (caretPrint)
107 {
108 var info = bitmapConfig.tileInfo(documentPrint);
109 if (info.fg == info.bg)
110 {
111 info.bg++;
112 }
113 return (
114 info.tile + fg * info.bg + bg * info.fg
115 );
116 }
117 if (documentPrint)
118 {
119 return documentPrint;
120 }
121 return (
122 32 +
123 ctx.textor.bitmapConfig.normalize +
124 ctx.textor.bitmapConfig.bg * 7
125 );
126 }
127 }
This may sound like a very trivial issue but the DOM keyboard event property
that returns codepoint of currently pressed keys, is now deprecated. It is also
a problem in SDL3 that I use for the C++ version of Textor where key codes
follows a logic specific to SDL. Overall keys input are not well standardized,
and it gets really complex with all the possible keyboard layouts and while
waiting for the arrival of the Keyboard API I had to make my own key handling
JavaScript system that I would anyway need in C++ (and also I like doing
everything from scratch).
In my past experiments making Textor based editors I was using the most
recommanded "key" property of DOM keyboard events. This very useful property
return the character currently being pressed adapting to any keyboard layout,
all I had to do was to use charCodeAt() method to get the ASCII code point,
but it has some flaws.
First of all, "key" know nothing about the key position on keybard, if you want
to make an interface based on spatial placement of keys rather that what keys
print, it cannot help.
Secondly, and more important for me, it led to unsolvable situations due to the
animation system underlying Textor.
Textor follows a logic of sucessive frames, the "main" function you give to it
will represent the behaviour of each frame, executed N times per second
according to a given framerate. In this context, for reasons such as multi key
pressing handling and to ensure synchronicity between user input and program's
output/events, I needed to define a interface giving to each frame global
information about which keys are currently pressed and since how many frames.
This took the form of an object holding all "key" results as entries:
0 document.addEventListener("keydown",
1 function(e)
2 {
3 keyDownStart[e.key] = (
4 !keyDownStart[e.key] ?
5 currentFrame :
6 keyDownStart[e.key]
7 );
8 }
9 );
10
11 document.addEventListener("keyup",
12 function(e)
13 {
14 keyboard.keyDownStart[e.key] = 0;
15 }
16 );
17
18 keyDownStart {
19 Q: 59, // pressed for 59 frames, near to 1 second at 60 fps
20 W: 120, // 2 secondes at 60 fps
21 E: 2, // just began to be pressed
22 Shift: 150,
23 q: 0,
24 w: 0,
25 ...
26 }
When I was using "key" this way, I ended up in a bad situation where "key"
result formed by a combination of keys, like uppercases, were not detecting end
of pressing if the key needed for combination was released first.
For example, your are typing multiple letters "A" by holding "ShiftLeft" and
"KeyA" during a long time, when stopping, if you release "ShiftLeft" before
"KeyA" the DOM "keyup" event will return "key" "a" being up and not "A" being
up so "A" will continue being considered to be pressed, leading to an
incomprehensible behavior of "A" continuing to be typed.
Long story short, this is why I finally used "code" property instead of "key",
returning spatial information of the pressed or released key not its
corresponding character, quite similar to SDL key handling.
This week, based on this situation, I took the time to write a mapping function
converting DOM "code" properties to ASCII code points and here is the result
this far, including QWERTY and AZERTY layout for starting:
keyToASCII function source
0 export function keyToASCII(layout,key,shift,alt)
1 {
2 return (
3 layout == "qwerty" ?
4 (
5 key == "Space" && !shift ? 32 :
6 key == "Digit1" && shift ? 33 :
7 key == "Quote" && shift ? 34 :
8 key == "Digit3" && shift ? 35 :
9 key == "Digit4" && shift ? 36 :
10 key == "Digit5" && shift ? 37 :
11 key == "Digit7" && shift ? 38 :
12 key == "Quote" && !shift ? 39 :
13 key == "Digit9" && shift ? 40 :
14 key == "Digit0" && shift ? 41 :
15 key == "Digit8" && shift ? 42 :
16 key == "Equal" && shift ? 43 :
17 key == "Comma" && !shift ? 44 :
18 key == "Minus" && !shift ? 45 :
19 key == "Period" && !shift ? 46 :
20 key == "Slash" && !shift ? 47 :
21 key == "Digit0" && !shift ? 48 :
22 key == "Digit1" && !shift ? 49 :
23 key == "Digit2" && !shift ? 50 :
24 key == "Digit3" && !shift ? 51 :
25 key == "Digit4" && !shift ? 52 :
26 key == "Digit5" && !shift ? 53 :
27 key == "Digit6" && !shift ? 54 :
28 key == "Digit7" && !shift ? 55 :
29 key == "Digit8" && !shift ? 56 :
30 key == "Digit9" && !shift ? 57 :
31 key == "Semicolon" && shift ? 58 :
32 key == "Semicolon" && !shift ? 59 :
33 key == "Comma" && shift ? 60 :
34 key == "Equal" && !shift ? 61 :
35 key == "Period" && shift ? 62 :
36 key == "Slash" && shift ? 63 :
37 key == "Digit2" && shift ? 64 :
38 key == "KeyA" && shift ? 65 :
39 key == "KeyB" && shift ? 66 :
40 key == "KeyC" && shift ? 67 :
41 key == "KeyD" && shift ? 68 :
42 key == "KeyE" && shift ? 69 :
43 key == "KeyF" && shift ? 70 :
44 key == "KeyG" && shift ? 71 :
45 key == "KeyH" && shift ? 72 :
46 key == "KeyI" && shift ? 73 :
47 key == "KeyJ" && shift ? 74 :
48 key == "KeyK" && shift ? 75 :
49 key == "KeyL" && shift ? 76 :
50 key == "KeyM" && shift ? 77 :
51 key == "KeyN" && shift ? 78 :
52 key == "KeyO" && shift ? 79 :
53 key == "KeyP" && shift ? 80 :
54 key == "KeyQ" && shift ? 81 :
55 key == "KeyR" && shift ? 82 :
56 key == "KeyS" && shift ? 83 :
57 key == "KeyT" && shift ? 84 :
58 key == "KeyU" && shift ? 85 :
59 key == "KeyV" && shift ? 86 :
60 key == "KeyW" && shift ? 87 :
61 key == "KeyX" && shift ? 88 :
62 key == "KeyY" && shift ? 89 :
63 key == "KeyZ" && shift ? 90 :
64 key == "BracketLeft" && !shift ? 91 :
65 key == "Backslash" && !shift ? 92 :
66 key == "BracketRight" && !shift ? 93 :
67 key == "Digit6" && shift ? 94 :
68 key == "Minus" && shift ? 95 :
69 key == "Backquote" && !shift ? 96 :
70 key == "KeyA" && !shift ? 97 :
71 key == "KeyB" && !shift ? 98 :
72 key == "KeyC" && !shift ? 99 :
73 key == "KeyD" && !shift ? 100 :
74 key == "KeyE" && !shift ? 101 :
75 key == "KeyF" && !shift ? 102 :
76 key == "KeyG" && !shift ? 103 :
77 key == "KeyH" && !shift ? 104 :
78 key == "KeyI" && !shift ? 105 :
79 key == "KeyJ" && !shift ? 106 :
80 key == "KeyK" && !shift ? 107 :
81 key == "KeyL" && !shift ? 108 :
82 key == "KeyM" && !shift ? 109 :
83 key == "KeyN" && !shift ? 110 :
84 key == "KeyO" && !shift ? 111 :
85 key == "KeyP" && !shift ? 112 :
86 key == "KeyQ" && !shift ? 113 :
87 key == "KeyR" && !shift ? 114 :
88 key == "KeyS" && !shift ? 115 :
89 key == "KeyT" && !shift ? 116 :
90 key == "KeyU" && !shift ? 117 :
91 key == "KeyV" && !shift ? 118 :
92 key == "KeyW" && !shift ? 119 :
93 key == "KeyX" && !shift ? 120 :
94 key == "KeyY" && !shift ? 121 :
95 key == "KeyZ" && !shift ? 120 :
96 key == "BracketLeft" && shift ? 123 :
97 key == "Backslash" && shift ? 124 :
98 key == "BracketRight" && shift ? 125 :
99 key == "Backquote" && shift ? 126 :
100 key == "Delete" && !shift ? 127 :
101 0
102 ) :
103 layout == "azerty" ?
104 (
105 key == "Space" && !alt && !shift ? 32 :
106 key == "Slash" && !alt && !shift ? 33 :
107 key == "Digit3" && !alt && !shift ? 34 :
108 key == "Digit3" && alt && !shift ? 35 :
109 key == "BracketRight" && !alt && !shift ? 36 :
110 key == "Quote" && !alt && shift ? 37 :
111 key == "Digit1" && !alt && !shift ? 38 :
112 key == "Digit4" && !alt && !shift ? 39 :
113 key == "Digit5" && !alt && !shift ? 40 :
114 key == "Minus" && !alt && !shift ? 41 :
115 key == "Backslash" && !alt && !shift ? 42 :
116 key == "Equal" && !alt && shift ? 43 :
117 key == "KeyM" && !alt && !shift ? 44 :
118 key == "Digit6" && !alt && !shift ? 45 :
119 key == "Comma" && !alt && shift ? 46 :
120 key == "Period" && !alt && shift ? 47 :
121 key == "Digit0" && !alt && shift ? 48 :
122 key == "Digit1" && !alt && shift ? 49 :
123 key == "Digit2" && !alt && shift ? 50 :
124 key == "Digit3" && !alt && shift ? 51 :
125 key == "Digit4" && !alt && shift ? 52 :
126 key == "Digit5" && !alt && shift ? 53 :
127 key == "Digit6" && !alt && shift ? 54 :
128 key == "Digit7" && !alt && shift ? 55 :
129 key == "Digit8" && !alt && shift ? 56 :
130 key == "Digit9" && !alt && shift ? 57 :
131 key == "Period" && !alt && !shift ? 58 :
132 key == "Comma" && !alt && !shift ? 59 :
133 key == "IntlBackslash" && !alt && !shift ? 60 :
134 key == "Equal" && !alt && !shift ? 61 :
135 key == "IntBackslash" && !alt && shift ? 62 :
136 key == "KeyM" && !alt && shift ? 63 :
137 key == "Digit0" && alt && !shift ? 64 :
138 key == "KeyQ" && !alt && shift ? 65 :
139 key == "KeyB" && !alt && shift ? 66 :
140 key == "KeyC" && !alt && shift ? 67 :
141 key == "KeyD" && !alt && shift ? 68 :
142 key == "KeyE" && !alt && shift ? 69 :
143 key == "KeyF" && !alt && shift ? 70 :
144 key == "KeyG" && !alt && shift ? 71 :
145 key == "KeyH" && !alt && shift ? 72 :
146 key == "KeyI" && !alt && shift ? 73 :
147 key == "KeyJ" && !alt && shift ? 74 :
148 key == "KeyK" && !alt && shift ? 75 :
149 key == "KeyL" && !alt && shift ? 76 :
150 key == "Semicolon" && !alt && shift ? 77 :
151 key == "KeyN" && !alt && shift ? 78 :
152 key == "KeyO" && !alt && shift ? 79 :
153 key == "KeyP" && !alt && shift ? 80 :
154 key == "KeyA" && !alt && shift ? 81 :
155 key == "KeyR" && !alt && shift ? 82 :
156 key == "KeyS" && !alt && shift ? 83 :
157 key == "KeyT" && !alt && shift ? 84 :
158 key == "KeyU" && !alt && shift ? 85 :
159 key == "KeyV" && !alt && shift ? 86 :
160 key == "KeyZ" && !alt && shift ? 87 :
161 key == "KeyX" && !alt && shift ? 88 :
162 key == "KeyY" && !alt && shift ? 89 :
163 key == "KeyW" && !alt && shift ? 90 :
164 key == "Digit5" && alt && !shift ? 91 :
165 key == "Digit8" && alt && !shift ? 92 :
166 key == "Minus" && alt && !shift ? 93 :
167 key == "BracketLeft" && !alt && !shift ||
168 key == "Digit9" && alt && !shift ? 94 :
169 key == "Digit8" && !alt && !shift ? 95 :
170 key == "Digit7" && alt && !shift ? 96 :
171 key == "KeyQ" && !alt && !shift ? 97 :
172 key == "KeyB" && !alt && !shift ? 98 :
173 key == "KeyC" && !alt && !shift ? 99 :
174 key == "KeyD" && !alt && !shift ? 100 :
175 key == "KeyE" && !alt && !shift ? 101 :
176 key == "KeyF" && !alt && !shift ? 102 :
177 key == "KeyG" && !alt && !shift ? 103 :
178 key == "KeyH" && !alt && !shift ? 104 :
179 key == "KeyI" && !alt && !shift ? 105 :
180 key == "KeyJ" && !alt && !shift ? 106 :
181 key == "KeyK" && !alt && !shift ? 107 :
182 key == "KeyL" && !alt && !shift ? 108 :
183 key == "Semicolon" && !alt && !shift ? 109 :
184 key == "KeyN" && !alt && !shift ? 110 :
185 key == "KeyO" && !alt && !shift ? 111 :
186 key == "KeyP" && !alt && !shift ? 112 :
187 key == "KeyA" && !alt && !shift ? 113 :
188 key == "KeyR" && !alt && !shift ? 114 :
189 key == "KeyS" && !alt && !shift ? 115 :
190 key == "KeyT" && !alt && !shift ? 116 :
191 key == "KeyU" && !alt && !shift ? 117 :
192 key == "KeyV" && !alt && !shift ? 118 :
193 key == "KeyZ" && !alt && !shift ? 119 :
194 key == "KeyX" && !alt && !shift ? 120 :
195 key == "KeyY" && !alt && !shift ? 121 :
196 key == "KeyW" && !alt && !shift ? 122 :
197 key == "Digit4" && alt && !shift ? 123 :
198 key == "Digit6" && alt && !shift ? 124 :
199 key == "Equal" && alt && !shift ? 125 :
200 key == "Digit2" && alt && !shift ? 126 :
201 key == "Delete" && !alt && !shift ? 127 :
202 0
203 ) :
204 0
205 );
206 }
To conclude this post, I am probably just reinventing the wheel, so if you have
any advice, are wiling to extend this function with other layouts or if you
know a repository that holds a correspondence of all keyboard layouts in a
compatible manner, please don't hesitate to send me a message !
---------------------------- Support me on ko-fi! ------------------------------
_
l ' _ _ l_ _ _ l_
| l l| l| .-ll '|
'-''-'' ''-''-''-''-'
Email..................................................... contact@adelfaure.net
Mastodon ............................................... mastodon.art/@adelfaure
Bluesky .................................................. adelfaure.bsky.social
Itch ......................................................... adelfaure.itch.io
_
l_ll_ _ l_
| || ll l| ||
' ''-''-''-''-'
Hey! My name is Adel Faure, I live in France and I'm an ASCII artist. I use
text and code to make fonts, art, tools, games, music and performances.
All art and software I make is released under GNU GPL 3 license and you are
free to use, copy and modify them as long as you apply the same license on
derivative work and mention where it came from who made it.
If you like what I do please consider making a donation on my ko-fi page.
I'm also available to work as freelance for your projects or for
artwork commission.
I don't store any of your data.
I use Sunrise/sunset script by Matt Kane for the day/night cycle of the site.
You can check the source code of this website (or any other) by adding
`view-source:` at the beginning of the URL, or by right-clicking on the page
and select "View page source" from the pop-up menu that appears.
Adel Faure Website Copyright (C) 2021-2025, licensed under GNU GPL 3.
_
|_' _ ' _ _ _l _
| | ' | l_l| ll |'-.
' ' ' '- ' ''-''-'
|V| ' _ _
| | | '-.l '
' ' ' '-''-'