[{"data":1,"prerenderedAt":-1},["Reactive",2],{"content-query-1DxZ1vYQk5":3,"content-query-p2jvGpC4Jz":114},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":5,"title":7,"description":8,"body":9,"_type":109,"_id":110,"_source":111,"_file":112,"_extension":113},"/","",false,"b195","I'm a 20-year-old self-taught developer. I occasionally write projects\nfor fun in JavaScript (or TypeScript), Lua, Go, HTML,\nand CSS.",{"type":10,"children":11,"toc":106},"root",[12,19,67,72,77,81,85],{"type":13,"tag":14,"props":15,"children":16},"element","h1",{"id":7},[17],{"type":18,"value":7},"text",{"type":13,"tag":20,"props":21,"children":22},"p",{},[23,25,31,33,38,40,45,47,52,53,58,60,65],{"type":18,"value":24},"I'm a 20-year-old self-taught developer. I occasionally write projects\nfor fun in ",{"type":13,"tag":26,"props":27,"children":28},"strong",{},[29],{"type":18,"value":30},"JavaScript",{"type":18,"value":32}," (or ",{"type":13,"tag":26,"props":34,"children":35},{},[36],{"type":18,"value":37},"TypeScript",{"type":18,"value":39},"), ",{"type":13,"tag":26,"props":41,"children":42},{},[43],{"type":18,"value":44},"Lua",{"type":18,"value":46},", ",{"type":13,"tag":26,"props":48,"children":49},{},[50],{"type":18,"value":51},"Go",{"type":18,"value":46},{"type":13,"tag":26,"props":54,"children":55},{},[56],{"type":18,"value":57},"HTML",{"type":18,"value":59},",\nand ",{"type":13,"tag":26,"props":61,"children":62},{},[63],{"type":18,"value":64},"CSS",{"type":18,"value":66},".",{"type":13,"tag":20,"props":68,"children":69},{},[70],{"type":18,"value":71},"If you're here because you saw my resume on HH or elsewhere, I'm a\nfriendly (though shy) person who likes to giggle a lot and enjoys\ncollaborating with others (without a tight deadline, cough cough)!",{"type":13,"tag":20,"props":73,"children":74},{},[75],{"type":18,"value":76},"Below, you can explore some of the projects I've worked on.",{"type":13,"tag":78,"props":79,"children":80},"projectsrenderer",{},[],{"type":13,"tag":82,"props":83,"children":84},"hr",{"id":5},[],{"type":13,"tag":20,"props":86,"children":87},{},[88,97,99],{"type":13,"tag":89,"props":90,"children":94},"a",{"href":91,"rel":92},"https://github.com/be195",[93],"nofollow",[95],{"type":18,"value":96},"GitHub",{"type":18,"value":98}," · ",{"type":13,"tag":89,"props":100,"children":103},{"href":101,"rel":102},"https://codeberg.org/b195",[93],[104],{"type":18,"value":105},"Codeberg",{"title":5,"searchDepth":107,"depth":107,"links":108},3,[],"markdown","content:index.md","content","index.md","md",[115,301,551,716,851,949],{"_path":116,"_dir":117,"_draft":6,"_partial":6,"_locale":5,"title":118,"description":119,"thumb":120,"body":121,"_type":109,"_id":299,"_source":111,"_file":300,"_extension":113},"/projects/foxtrot","projects","foxtrot","foxtrot started as a personal alternative to other Discord\napplications that use a bot user to play audio in voice channels.\nIt was originally called \"glowing memory,\" but was rebranded twice\nbetween 2022–2023. The bot was designed to be free for everyone, which is\nwhy it is also open source.","foxtrot.png",{"type":10,"children":122,"toc":291},[123,127,136,153,189,196,201,208,231,236,242,253,259,277,282],{"type":13,"tag":14,"props":124,"children":125},{"id":118},[126],{"type":18,"value":118},{"type":13,"tag":20,"props":128,"children":129},{},[130,134],{"type":13,"tag":26,"props":131,"children":132},{},[133],{"type":18,"value":118},{"type":18,"value":135}," started as a personal alternative to other Discord\napplications that use a bot user to play audio in voice channels.\nIt was originally called \"glowing memory,\" but was rebranded twice\nbetween 2022–2023. The bot was designed to be free for everyone, which is\nwhy it is also open source.",{"type":13,"tag":20,"props":137,"children":138},{},[139,141,145,147,152],{"type":18,"value":140},"foxtrot is written in ",{"type":13,"tag":26,"props":142,"children":143},{},[144],{"type":18,"value":37},{"type":18,"value":146}," and ",{"type":13,"tag":26,"props":148,"children":149},{},[150],{"type":18,"value":151},"C++",{"type":18,"value":66},{"type":13,"tag":154,"props":155,"children":156},"ul",{},[157,174],{"type":13,"tag":158,"props":159,"children":160},"li",{},[161,166,168],{"type":13,"tag":26,"props":162,"children":163},{},[164],{"type":18,"value":165},"Website:",{"type":18,"value":167}," ",{"type":13,"tag":89,"props":169,"children":172},{"href":170,"rel":171},"https://foxtrot.litterbin.dev/",[93],[173],{"type":18,"value":170},{"type":13,"tag":158,"props":175,"children":176},{},[177,182,183],{"type":13,"tag":26,"props":178,"children":179},{},[180],{"type":18,"value":181},"Source code:",{"type":18,"value":167},{"type":13,"tag":89,"props":184,"children":187},{"href":185,"rel":186},"https://github.com/LitterbinCollective/foxtrot.ts",[93],[188],{"type":18,"value":185},{"type":13,"tag":190,"props":191,"children":193},"h2",{"id":192},"complexity",[194],{"type":18,"value":195},"Complexity",{"type":13,"tag":20,"props":197,"children":198},{},[199],{"type":18,"value":200},"Here are some reasons why this project is complex.",{"type":13,"tag":202,"props":203,"children":205},"h3",{"id":204},"lack-of-a-voice-helper",[206],{"type":18,"value":207},"Lack of a Voice Helper",{"type":13,"tag":20,"props":209,"children":210},{},[211,213,220,222,229],{"type":18,"value":212},"The library I used, ",{"type":13,"tag":89,"props":214,"children":217},{"href":215,"rel":216},"https://github.com/detritusjs/client/",[93],[218],{"type":18,"value":219},"Detritus.js",{"type":18,"value":221},",\nis quite barebones when it comes to voice.\nIt can encode PCM samples to Opus (if specified), create packets, and\nsend them through the Discord voice server connection. However, it lacks\nhigher-level helpers to play audio for you (see ",{"type":13,"tag":89,"props":223,"children":226},{"href":224,"rel":225},"https://github.com/discordjs/discord.js/tree/main/packages/voice",[93],[227],{"type":18,"value":228},"discord.js/voice",{"type":18,"value":230},").",{"type":13,"tag":20,"props":232,"children":233},{},[234],{"type":18,"value":235},"Surprisingly, this limitation actually helped the development of the bot,\nas it allowed me to implement real-time audio alterations, which was on\nof my goals.",{"type":13,"tag":202,"props":237,"children":239},{"id":238},"audio-mixer",[240],{"type":18,"value":241},"Audio Mixer",{"type":13,"tag":20,"props":243,"children":244},{},[245,247,251],{"type":18,"value":246},"The audio mixer is used for mixing short audio clips with either silence\nor the currently playing audio.\nTwo or more versions were made, and the current one is written in\n",{"type":13,"tag":26,"props":248,"children":249},{},[250],{"type":18,"value":151},{"type":18,"value":252},", as it is much faster than a JavaScript-based mixer.",{"type":13,"tag":190,"props":254,"children":256},{"id":255},"branding",[257],{"type":18,"value":258},"Branding",{"type":13,"tag":20,"props":260,"children":261},{},[262,264,269,271,275],{"type":18,"value":263},"The project initially featured a robot as its mascot (\"glowing memory\").\nIt was rebranded to ",{"type":13,"tag":26,"props":265,"children":266},{},[267],{"type":18,"value":268},"catvox",{"type":18,"value":270}," in 2022, but later in the same year, I chose ",{"type":13,"tag":26,"props":272,"children":273},{},[274],{"type":18,"value":118},{"type":18,"value":276}," as the final name and adopted a fox as the icon.",{"type":13,"tag":278,"props":279,"children":281},"vuevideo",{"src":280},"/videos/haikuproj..mp4",[],{"type":13,"tag":20,"props":283,"children":284},{},[285],{"type":13,"tag":286,"props":287,"children":290},"img",{"alt":288,"src":289},"Abstract vector image showing the foxtrot logo in the center","/images/abstract.png",[],{"title":5,"searchDepth":107,"depth":107,"links":292},[293,298],{"id":192,"depth":294,"text":195,"children":295},2,[296,297],{"id":204,"depth":107,"text":207},{"id":238,"depth":107,"text":241},{"id":255,"depth":294,"text":258},"content:projects:foxtrot.md","projects/foxtrot.md",{"_path":302,"_dir":117,"_draft":6,"_partial":6,"_locale":5,"title":303,"description":5,"thumb":304,"body":305,"_type":109,"_id":549,"_source":111,"_file":550,"_extension":113},"/projects/gmod","Garry's Mod Works","televator.jpg",{"type":10,"children":306,"toc":540},[307,312,318,323,329,337,341,345,351,386,398,406,412,428,438,451,457,466,471,483,509,519,525],{"type":13,"tag":14,"props":308,"children":310},{"id":309},"garrys-mod-works",[311],{"type":18,"value":303},{"type":13,"tag":190,"props":313,"children":315},{"id":314},"meta-construct",[316],{"type":18,"value":317},"Meta Construct",{"type":13,"tag":20,"props":319,"children":320},{},[321],{"type":18,"value":322},"I joined the Meta Construct development team in 2018.",{"type":13,"tag":202,"props":324,"children":326},{"id":325},"design-work",[327],{"type":18,"value":328},"Design Work",{"type":13,"tag":20,"props":330,"children":331},{},[332],{"type":13,"tag":286,"props":333,"children":336},{"alt":334,"src":335},"elevator with a screen","/images/televator.jpg",[],{"type":13,"tag":278,"props":338,"children":340},{"src":339},"/videos/servcrash.webm",[],{"type":13,"tag":278,"props":342,"children":344},{"src":343},"/videos/loadinfo.webm",[],{"type":13,"tag":202,"props":346,"children":348},{"id":347},"artboard",[349],{"type":18,"value":350},"Artboard",{"type":13,"tag":20,"props":352,"children":353},{},[354,356,360,362,369,370,377,379,385],{"type":18,"value":355},"I created ",{"type":13,"tag":26,"props":357,"children":358},{},[359],{"type":18,"value":350},{"type":18,"value":361}," for Meta Construct, which allows players to draw\non an in-game screen.\nIt was inspired by ",{"type":13,"tag":89,"props":363,"children":366},{"href":364,"rel":365},"https://pxls.space/",[93],[367],{"type":18,"value":368},"pxls.space",{"type":18,"value":146},{"type":13,"tag":89,"props":371,"children":374},{"href":372,"rel":373},"https://reddit.com/r/place",[93],[375],{"type":18,"value":376},"r/place",{"type":18,"value":378},".\nThe board resets both the image and the palette on the 1st and 15th of\nevery month. You can view the current state here:\n",{"type":13,"tag":89,"props":380,"children":383},{"href":381,"rel":382},"https://artboard.metastruct.net/",[93],[384],{"type":18,"value":381},{"type":18,"value":66},{"type":13,"tag":20,"props":387,"children":388},{},[389,391,397],{"type":18,"value":390},"The API was initially built in plain JavaScript with Node.js, then later\nrewritten in TypeScript.\nIt is now open source: ",{"type":13,"tag":89,"props":392,"children":395},{"href":393,"rel":394},"https://github.com/Metastruct/artboard-api/",[93],[396],{"type":18,"value":393},{"type":18,"value":66},{"type":13,"tag":20,"props":399,"children":400},{},[401],{"type":13,"tag":286,"props":402,"children":405},{"alt":403,"src":404},"artboard showcase","/images/artboard.jpg",[],{"type":13,"tag":202,"props":407,"children":409},{"id":408},"metaluvit",[410],{"type":18,"value":411},"Metaluvit",{"type":13,"tag":20,"props":413,"children":414},{},[415,419,421,427],{"type":13,"tag":26,"props":416,"children":417},{},[418],{"type":18,"value":411},{"type":18,"value":420}," was an application that used Discord's API and an IRC\nconnection to relay chat between in-game and those platforms.\nIt is open source: ",{"type":13,"tag":89,"props":422,"children":425},{"href":423,"rel":424},"https://github.com/Metastruct/metaluvit/tree/master-preneutering",[93],[426],{"type":18,"value":423},{"type":18,"value":66},{"type":13,"tag":429,"props":430,"children":432},"pre",{"code":431},"Discord \u003C-> IRC \u003C-> In-Game\n   ↑                   |\n   ╰-------------------╯\n",[433],{"type":13,"tag":434,"props":435,"children":436},"code",{"__ignoreMap":5},[437],{"type":18,"value":431},{"type":13,"tag":20,"props":439,"children":440},{},[441,443,450],{"type":18,"value":442},"Before I was given the developer rank, Metaluvit lacked core features,\nso I ",{"type":13,"tag":89,"props":444,"children":447},{"href":445,"rel":446},"https://github.com/Metastruct/metaluvit/pull/1",[93],[448],{"type":18,"value":449},"kickstarted development",{"type":18,"value":66},{"type":13,"tag":190,"props":452,"children":454},{"id":453},"crosschat",[455],{"type":18,"value":456},"CrossChat",{"type":13,"tag":20,"props":458,"children":459},{},[460,464],{"type":13,"tag":26,"props":461,"children":462},{},[463],{"type":18,"value":456},{"type":18,"value":465}," is an addon that allows multiple servers to communicate,\nenabling players to see messages from other servers.",{"type":13,"tag":20,"props":467,"children":468},{},[469],{"type":18,"value":470},"Support was eventually dropped, but the addon can still function\n(with or without modifications).",{"type":13,"tag":20,"props":472,"children":473},{},[474,476,481],{"type":18,"value":475},"Instead of handling communication through our own servers like\nsimilar addons, we used the ",{"type":13,"tag":26,"props":477,"children":478},{},[479],{"type":18,"value":480},"luasocket",{"type":18,"value":482}," library to implement both\nserver protocols and clients. This approach ensured that the addon was:",{"type":13,"tag":154,"props":484,"children":485},{},[486,499,504],{"type":13,"tag":158,"props":487,"children":488},{},[489,491,497],{"type":18,"value":490},"More private ",{"type":13,"tag":492,"props":493,"children":494},"em",{},[495],{"type":18,"value":496},"(-ish?)",{"type":18,"value":498},";",{"type":13,"tag":158,"props":500,"children":501},{},[502],{"type":18,"value":503},"Easier for users, even without official support;",{"type":13,"tag":158,"props":505,"children":506},{},[507],{"type":18,"value":508},"Less work for us, avoiding server migration issues.",{"type":13,"tag":20,"props":510,"children":511},{},[512],{"type":13,"tag":89,"props":513,"children":516},{"href":514,"rel":515},"https://www.gmodstore.com/market/view/crosschat-communicate-with-players-on-another-server",[93],[517],{"type":18,"value":518},"View on GmodStore",{"type":13,"tag":190,"props":520,"children":522},{"id":521},"re-dream",[523],{"type":18,"value":524},"Re-Dream",{"type":13,"tag":20,"props":526,"children":527},{},[528,532,534],{"type":13,"tag":26,"props":529,"children":530},{},[531],{"type":18,"value":524},{"type":18,"value":533}," was an alternative to Meta Construct. I have been involved\nsince 2017.\nMost of its code is open source: ",{"type":13,"tag":89,"props":535,"children":538},{"href":536,"rel":537},"https://github.com/Re-Dream/",[93],[539],{"type":18,"value":536},{"title":5,"searchDepth":107,"depth":107,"links":541},[542,547,548],{"id":314,"depth":294,"text":317,"children":543},[544,545,546],{"id":325,"depth":107,"text":328},{"id":347,"depth":107,"text":350},{"id":408,"depth":107,"text":411},{"id":453,"depth":294,"text":456},{"id":521,"depth":294,"text":524},"content:projects:gmod.md","projects/gmod.md",{"_path":552,"_dir":117,"_draft":6,"_partial":6,"_locale":5,"title":553,"description":554,"thumb":555,"body":556,"_type":109,"_id":714,"_source":111,"_file":715,"_extension":113},"/projects/misc","Miscellaneous","The projects that do not deserve their own pages will be displayed here.","litterbin.png",{"type":10,"children":557,"toc":702},[558,563,567,573,578,594,598,604,609,617,621,625,630,635,639,645,651,661,668,676,682,695],{"type":13,"tag":14,"props":559,"children":561},{"id":560},"miscellaneous",[562],{"type":18,"value":553},{"type":13,"tag":20,"props":564,"children":565},{},[566],{"type":18,"value":554},{"type":13,"tag":190,"props":568,"children":570},{"id":569},"litterbindev",[571],{"type":18,"value":572},"litterbin.dev",{"type":13,"tag":20,"props":574,"children":575},{},[576],{"type":18,"value":577},"I found it unnecessary to create a full website for Litterbin Collective,\nso the only feature is a semi-animated Kaomoji button.",{"type":13,"tag":20,"props":579,"children":580},{},[581,586,588],{"type":13,"tag":26,"props":582,"children":583},{},[584],{"type":18,"value":585},"Live preview",{"type":18,"value":587},": ",{"type":13,"tag":89,"props":589,"children":592},{"href":590,"rel":591},"https://litterbin.dev/",[93],[593],{"type":18,"value":590},{"type":13,"tag":278,"props":595,"children":597},{"src":596},"/videos/kaomoji.mp4",[],{"type":13,"tag":190,"props":599,"children":601},{"id":600},"nyebunoyo",[602],{"type":18,"value":603},"nyebun/OYO",{"type":13,"tag":20,"props":605,"children":606},{},[607],{"type":18,"value":608},"nyebun was a TikTok-like project I worked on for fun. This was never\nfinished and was abandoned one month after starting development.",{"type":13,"tag":20,"props":610,"children":611},{},[612],{"type":13,"tag":286,"props":613,"children":616},{"alt":614,"src":615},"nyebun logo and navbar","/images/nyebun.png",[],{"type":13,"tag":278,"props":618,"children":620},{"src":619},"/videos/nyebun.webm",[],{"type":13,"tag":278,"props":622,"children":624},{"src":623},"/videos/nyebun2.mp4",[],{"type":13,"tag":190,"props":626,"children":628},{"id":627},"filerecv-game",[629],{"type":18,"value":627},{"type":13,"tag":20,"props":631,"children":632},{},[633],{"type":18,"value":634},"An RPG game that was abandoned. This project was made entirely in vanilla\nJavaScript - no libraries or tools were used.",{"type":13,"tag":278,"props":636,"children":638},{"src":637},"/videos/filerecv.mp4",[],{"type":13,"tag":190,"props":640,"children":642},{"id":641},"commission-work",[643],{"type":18,"value":644},"Commission work",{"type":13,"tag":202,"props":646,"children":648},{"id":647},"personal-website-1",[649],{"type":18,"value":650},"Personal website 1",{"type":13,"tag":652,"props":653,"children":655},"alert",{"type":654},"info",[656],{"type":13,"tag":20,"props":657,"children":658},{},[659],{"type":18,"value":660},"None of these have live previews available.",{"type":13,"tag":662,"props":663,"children":665},"h4",{"id":664},"version-2",[666],{"type":18,"value":667},"Version 2",{"type":13,"tag":20,"props":669,"children":670},{},[671],{"type":13,"tag":286,"props":672,"children":675},{"alt":673,"src":674},"image showcase of version 2","/images/piaempi.ver2.png",[],{"type":13,"tag":662,"props":677,"children":679},{"id":678},"version-1",[680],{"type":18,"value":681},"Version 1",{"type":13,"tag":20,"props":683,"children":684},{},[685,687,693],{"type":18,"value":686},"I wanted the character on the center to be more \"alive\" rather than just\na static image (similar to ",{"type":13,"tag":89,"props":688,"children":691},{"href":689,"rel":690},"https://litterbin.dev",[93],[692],{"type":18,"value":572},{"type":18,"value":694},"). As a\nresult, I made it interactable even when it wasn't strictly necessary.",{"type":13,"tag":20,"props":696,"children":697},{},[698],{"type":13,"tag":286,"props":699,"children":701},{"alt":673,"src":700},"/images/piaempi.ver1.png",[],{"title":5,"searchDepth":107,"depth":107,"links":703},[704,705,706,707],{"id":569,"depth":294,"text":572},{"id":600,"depth":294,"text":603},{"id":627,"depth":294,"text":627},{"id":641,"depth":294,"text":644,"children":708},[709],{"id":647,"depth":107,"text":650,"children":710},[711,713],{"id":664,"depth":712,"text":667},4,{"id":678,"depth":712,"text":681},"content:projects:misc.md","projects/misc.md",{"_path":717,"_dir":117,"_draft":6,"_partial":6,"_locale":5,"title":718,"description":5,"thumb":719,"body":720,"_type":109,"_id":849,"_source":111,"_file":850,"_extension":113},"/projects/personal","Personal websites","personal.png",{"type":10,"children":721,"toc":843},[722,727,735,740,752,758,764,786,795,806,814,820,825,835],{"type":13,"tag":14,"props":723,"children":725},{"id":724},"personal-websites",[726],{"type":18,"value":718},{"type":13,"tag":652,"props":728,"children":729},{"type":654},[730],{"type":13,"tag":20,"props":731,"children":732},{},[733],{"type":18,"value":734},"Albeit being kind of personal, this portfolio does not count as a \"personal website.\"",{"type":13,"tag":20,"props":736,"children":737},{},[738],{"type":18,"value":739},"These websites were not meant to be \"professional\" in any way, neither were they\nused as a \"business card site\". They did not have any practical use and just\nmostly showcased visual aids and random things that I found funny.",{"type":13,"tag":20,"props":741,"children":742},{},[743,745,751],{"type":18,"value":744},"The current one is very simple and can be viewed at ",{"type":13,"tag":89,"props":746,"children":749},{"href":747,"rel":748},"https://wico.lol/",[93],[750],{"type":18,"value":747},{"type":18,"value":66},{"type":13,"tag":190,"props":753,"children":755},{"id":754},"old-versions",[756],{"type":18,"value":757},"Old versions",{"type":13,"tag":202,"props":759,"children":761},{"id":760},"_2022-2023",[762],{"type":18,"value":763},"2022-2023",{"type":13,"tag":20,"props":765,"children":766},{},[767,769,776,778,785],{"type":18,"value":768},"This version features 3D graphics, made possible with ",{"type":13,"tag":89,"props":770,"children":773},{"href":771,"rel":772},"https://threejs.org",[93],[774],{"type":18,"value":775},"three.js",{"type":18,"value":777},").\nI made the monitor model using ",{"type":13,"tag":89,"props":779,"children":782},{"href":780,"rel":781},"https://ephtracy.github.io",[93],[783],{"type":18,"value":784},"MagicaVoxel",{"type":18,"value":66},{"type":13,"tag":652,"props":787,"children":789},{"type":788},"warning",[790],{"type":13,"tag":20,"props":791,"children":792},{},[793],{"type":18,"value":794},"The following page is CPU- or GPU-expensive and can drain your device's battery.",{"type":13,"tag":20,"props":796,"children":797},{},[798,800],{"type":18,"value":799},"You can see it here: ",{"type":13,"tag":89,"props":801,"children":804},{"href":802,"rel":803},"https://wico.lol/old2.html",[93],[805],{"type":18,"value":802},{"type":13,"tag":20,"props":807,"children":808},{},[809],{"type":13,"tag":286,"props":810,"children":813},{"alt":811,"src":812},"image showcase of wico.lol","/images/wico.lol.png",[],{"type":13,"tag":202,"props":815,"children":817},{"id":816},"_2021-2022",[818],{"type":18,"value":819},"2021-2022",{"type":13,"tag":20,"props":821,"children":822},{},[823],{"type":18,"value":824},"This version used vector graphics and DOM elements, making it simpler\nand much more performant.",{"type":13,"tag":20,"props":826,"children":827},{},[828,829],{"type":18,"value":799},{"type":13,"tag":89,"props":830,"children":833},{"href":831,"rel":832},"https://wico.lol/old/",[93],[834],{"type":18,"value":831},{"type":13,"tag":20,"props":836,"children":837},{},[838],{"type":13,"tag":286,"props":839,"children":842},{"alt":840,"src":841},"image showcase of an 2021-2022 version of wico.lol","/images/old_wico.lol.png",[],{"title":5,"searchDepth":107,"depth":107,"links":844},[845],{"id":754,"depth":294,"text":757,"children":846},[847,848],{"id":760,"depth":107,"text":763},{"id":816,"depth":107,"text":819},"content:projects:personal.md","projects/personal.md",{"_path":852,"_dir":117,"_draft":6,"_partial":6,"_locale":5,"title":853,"description":854,"thumb":855,"body":856,"_type":109,"_id":947,"_source":111,"_file":948,"_extension":113},"/projects/streaming","Streaming service","This is an internal project that only our friends are allowed to use.\nDespite its name containing a Russian swear word, its main purpose is\nto allow users to privately live stream.","nahuy.png",{"type":10,"children":857,"toc":945},[858,863,867,872,895,907,922],{"type":13,"tag":14,"props":859,"children":861},{"id":860},"streaming-service",[862],{"type":18,"value":853},{"type":13,"tag":20,"props":864,"children":865},{},[866],{"type":18,"value":854},{"type":13,"tag":20,"props":868,"children":869},{},[870],{"type":18,"value":871},"I was asked to work on the frontend of the project, since the previous\ndesigns were quite basic - both in appearance and in the way CSS was\nwritten. (Even the main developer admitted it wasn't great.)",{"type":13,"tag":20,"props":873,"children":874},{},[875,877,882,883,888,890,894],{"type":18,"value":876},"During its development I decided to use ",{"type":13,"tag":26,"props":878,"children":879},{},[880],{"type":18,"value":881},"Nuxt.js (Vue.js)",{"type":18,"value":46},{"type":13,"tag":26,"props":884,"children":885},{},[886],{"type":18,"value":887},"Sass",{"type":18,"value":889}," and\n",{"type":13,"tag":26,"props":891,"children":892},{},[893],{"type":18,"value":37},{"type":18,"value":66},{"type":13,"tag":20,"props":896,"children":897},{},[898,900,906],{"type":18,"value":899},"The frontend source code can be viewed here: ",{"type":13,"tag":89,"props":901,"children":904},{"href":902,"rel":903},"https://github.com/be195/lt-frontend/",[93],[905],{"type":18,"value":902},{"type":18,"value":66},{"type":13,"tag":652,"props":908,"children":909},{"type":654},[910],{"type":13,"tag":20,"props":911,"children":912},{},[913,915,921],{"type":18,"value":914},"This service does not have a landing page or an index. You must log in or have\na direct stream URL to access it. This is the only page accessible\nwithout logging in: ",{"type":13,"tag":89,"props":916,"children":919},{"href":917,"rel":918},"https://nahuy.tv/login",[93],[920],{"type":18,"value":917},{"type":18,"value":66},{"type":13,"tag":20,"props":923,"children":924},{},[925,930,935,940],{"type":13,"tag":286,"props":926,"children":929},{"alt":927,"src":928},"panel overview","/images/nahpanel.png",[],{"type":13,"tag":286,"props":931,"children":934},{"alt":932,"src":933},"panel stream settings","/images/nahpanelstream.png",[],{"type":13,"tag":286,"props":936,"children":939},{"alt":937,"src":938},"panel user settings","/images/nahpaneluser.png",[],{"type":13,"tag":286,"props":941,"children":944},{"alt":942,"src":943},"watch page","/images/nahwatchshowcase.gif",[],{"title":5,"searchDepth":107,"depth":107,"links":946},[],"content:projects:streaming.md","projects/streaming.md",{"_path":950,"_dir":117,"_draft":6,"_partial":6,"_locale":5,"title":951,"description":952,"thumb":953,"body":954,"_type":109,"_id":1097,"_source":111,"_file":1098,"_extension":113},"/projects/typewriter","Typewriting games","Since 2021, I have been developing typewriting games on my own initiative.","typewriter.png",{"type":10,"children":955,"toc":1091},[956,961,965,970,976,981,993,998,1003,1029,1037,1043,1048,1053,1059,1064,1069,1074,1079],{"type":13,"tag":14,"props":957,"children":959},{"id":958},"typewriting-games",[960],{"type":18,"value":951},{"type":13,"tag":20,"props":962,"children":963},{},[964],{"type":18,"value":952},{"type":13,"tag":20,"props":966,"children":967},{},[968],{"type":18,"value":969},"All of these projects were designed to run in a web browser, using HTML, CSS,\nand JavaScript. The specific tools and libraries used vary between versions.",{"type":13,"tag":190,"props":971,"children":973},{"id":972},"_2021-version",[974],{"type":18,"value":975},"2021 version",{"type":13,"tag":20,"props":977,"children":978},{},[979],{"type":18,"value":980},"This was quickly abandoned in the same year, as I simply forgot about\nit.",{"type":13,"tag":20,"props":982,"children":983},{},[984,986,991],{"type":18,"value":985},"I recycled my helper/engine from ",{"type":13,"tag":89,"props":987,"children":989},{"href":988},"/projects/misc#filerecv-game",[990],{"type":18,"value":627},{"type":18,"value":992},"\nand built the game on top of that (therefore, it's written in vanilla\nJS).",{"type":13,"tag":20,"props":994,"children":995},{},[996],{"type":18,"value":997},"There is only one level, and it doesn't go any further than that.",{"type":13,"tag":20,"props":999,"children":1000},{},[1001],{"type":18,"value":1002},"All assets were drawn using Krita, and I recorded myself hitting keycaps\non my keyboard using Audacity.",{"type":13,"tag":20,"props":1004,"children":1005},{},[1006,1010,1011,1017,1022,1023],{"type":13,"tag":26,"props":1007,"children":1008},{},[1009],{"type":18,"value":585},{"type":18,"value":587},{"type":13,"tag":89,"props":1012,"children":1015},{"href":1013,"rel":1014},"https://nahuy.life/typewriter/",[93],[1016],{"type":18,"value":1013},{"type":13,"tag":26,"props":1018,"children":1019},{},[1020],{"type":18,"value":1021},"Source code",{"type":18,"value":587},{"type":13,"tag":89,"props":1024,"children":1027},{"href":1025,"rel":1026},"https://github.com/be195/typewriter-game-public",[93],[1028],{"type":18,"value":1025},{"type":13,"tag":20,"props":1030,"children":1031},{},[1032],{"type":13,"tag":286,"props":1033,"children":1036},{"alt":1034,"src":1035},"typewriter splash screen","/images/thumbs/typewriter.png",[],{"type":13,"tag":190,"props":1038,"children":1040},{"id":1039},"_2022-version",[1041],{"type":18,"value":1042},"2022 version",{"type":13,"tag":20,"props":1044,"children":1045},{},[1046],{"type":18,"value":1047},"I began working on it in 2022 and is still in development. I prefer\nnot to disclose any details at this time.",{"type":13,"tag":20,"props":1049,"children":1050},{},[1051],{"type":18,"value":1052},"Assets are drawn using Aseprite, while sound effects and\nmaking music are made using FL Studio and/or Tenacity (which is a\nfork of Audacity).",{"type":13,"tag":202,"props":1054,"children":1056},{"id":1055},"engine",[1057],{"type":18,"value":1058},"Engine",{"type":13,"tag":20,"props":1060,"children":1061},{},[1062],{"type":18,"value":1063},"I decided to create another helper/engine, which would utilize\nparent-child system of states/components.",{"type":13,"tag":20,"props":1065,"children":1066},{},[1067],{"type":18,"value":1068},"Every component can handle its own mouse and keyboard events, has its own\nbounding box, and can serve as \"entities\", \"enemies\" and \"hitboxes\", as\nwell as UI elements such as buttons or text.",{"type":13,"tag":20,"props":1070,"children":1071},{},[1072],{"type":18,"value":1073},"A state is based on the component object and can be utilized by the\ncontainer to render the viewport. The container can render only\none state at a time.",{"type":13,"tag":20,"props":1075,"children":1076},{},[1077],{"type":18,"value":1078},"The engine is written in TypeScript, using Vite and other libraries.\nI chose this approach to be more careful and organized in development.",{"type":13,"tag":20,"props":1080,"children":1081},{},[1082,1084,1090],{"type":18,"value":1083},"Before realizing I would dedicate mroe time to this project, I made the\nengine public. It can be accessed here: ",{"type":13,"tag":89,"props":1085,"children":1088},{"href":1086,"rel":1087},"https://github.com/be195/eng.ts",[93],[1089],{"type":18,"value":1086},{"type":18,"value":66},{"title":5,"searchDepth":107,"depth":107,"links":1092},[1093,1094],{"id":972,"depth":294,"text":975},{"id":1039,"depth":294,"text":1042,"children":1095},[1096],{"id":1055,"depth":107,"text":1058},"content:projects:typewriter.md","projects/typewriter.md"]