<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>easwee.net // tiny digital playground</title>
	<subtitle>Easwee&#39;s personal tiny digital playground with funky scripts, wicked visuals, cool photos and coding stories</subtitle>
	
	<link href="https://www.easwee.net/feed/feed.xml" rel="self"/>
	<link href="https://www.easwee.net/"/>
	<updated>2026-01-24T00:00:00Z</updated>
	<id>https://www.easwee.net/</id>
	<author>
		<name>Easwee</name>
		<email>easwee@gmx.com</email>
	</author>
	
	<entry>
		<title>ubuntu 24 guake shortcut remap to cedilla</title>
		<link href="https://www.easwee.net/ubuntu-24-guake-shortcut-remap-to-cedilla/"/>
		<updated>2026-01-24T00:00:00Z</updated>
		<id>https://www.easwee.net/ubuntu-24-guake-shortcut-remap-to-cedilla/</id>
		<content type="html">&lt;p&gt;Another note.&lt;/p&gt;
&lt;p&gt;I always use the cedilla / grave key (the one left of &lt;code&gt;1&lt;/code&gt;, under Escape) as a dedicated key shortcut to toggle Guake.&lt;/p&gt;
&lt;p&gt;This used to work well in previous Ubuntu versions and I could set it from Guake settings, but since on Ubuntu 24 and supposedly because of Wayland, now if I&#39;m in an editor it types the &lt;code&gt;¸&lt;/code&gt; mark which is annoying. Setting it as a custom keyboard shortcut also does not override the mark output event. So I came up with this convoluted solution, but it does the job.&lt;/p&gt;
&lt;p&gt;Install &lt;a href=&quot;https://github.com/rvaiya/keyd&quot;&gt;keyd&lt;/a&gt; and enable it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable keyd --now
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create &lt;code&gt;/etc/keyd/default.conf&lt;/code&gt; that will map the key to a key combination that is recognized by the system (after trying many keys like scrolllock, F13 and others that were taken or not recognized, i picked meta + F12)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ids]
*

[main]
grave = M-f12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart keyd:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart keyd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Disable Guake’s internal shortcut:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gsettings set guake.keybindings.global show-hide &amp;quot;&#39;disabled&#39;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a GNOME custom shortcut in &lt;strong&gt;Settings → Keyboard → Custom Shortcuts&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Name: Guake toggle&lt;/li&gt;
&lt;li&gt;Command:&lt;br /&gt;
guake -t&lt;/li&gt;
&lt;li&gt;Shortcut: press the cedilla / grave key (recorded as &lt;code&gt;Super+F12&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The cedilla key no longer types anything and reliably toggles Guake everywhere, on Wayland, on both laptop and external keyboards.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>distro hopping</title>
		<link href="https://www.easwee.net/distro-hopping/"/>
		<updated>2025-11-23T00:00:00Z</updated>
		<id>https://www.easwee.net/distro-hopping/</id>
		<content type="html">&lt;p&gt;Some light laptop and OS rambling for end of year.&lt;/p&gt;
&lt;p&gt;I like distro hopping a lot. Usually I will try at least one new Linux flavor per year. I started with OpenSUSE, moved to Mint, then Ubuntu 12 came, tried some Fedora in different flavors inbetween, obviously Arch because you have to do that experience and then Manjaro, tried some &amp;quot;hacking&amp;quot; with Kali (which is probably the most fun I had with Linux and I swear when I have enough money to retire I&#39;m gonna abuse the shit out of it). This time it was time for Omarchy - a lot of fuss around. I liked the looks and vibe around it - lots of people even trying Linux for the first time with it (brave choice), so I obviously had to try it. And since it&#39;s Arch it&#39;s a special needs system - not something I currently have time for, so after playing with it for a good week I just went back to Ubuntu.&lt;/p&gt;
&lt;p&gt;Since I also switched to a new daily work laptop, I may as well write down some of my thinking around the whole laptop and distro choice.&lt;/p&gt;
&lt;p&gt;People have very different workspace arrangements, some use a single home workstation, but I currently tend to switch between a single ultrawide monitor on my  office desk, a few random TVs when meeting and collaborating and if I work from home, I don&#39;t plug in the monitor at all anymore and just work on the laptop from couch.&lt;/p&gt;
&lt;p&gt;Some wisdom I picked up over the years is that Macs have great battery life - I&#39;m always impressed when I have to do the ios builds from the spare Mac I have, that battery bar there does not move. To bad I can&#39;t stand MacOS ecosystem. Another thing I learned is also that if you don&#39;t want a Mac, you buy a Thinkpad - I hated every single other laptop I had - there is just something about Thinkpads that clicks for me (keyboard).&lt;/p&gt;
&lt;p&gt;So battery life and Thinkpad in mind, I did a short research and ended up with T14s gen 6 with Intel chip and a low-power 60hz monitor and 32gb ram. This works perfectly for me since I only code on this laptop, I don&#39;t see any benefit in more hz other than battery drain. Internet people say ~20h battery life - I&#39;ve got ~11h out doing common daily work running two dev servers on hot reload and running some music in background. For me this is solid - tbh anything above 8 is great.&lt;/p&gt;
&lt;p&gt;So getting back to the OS - after trying Omarchy for a week I was not really seeing any benefit over the good old Ubuntu. Most of the stuff that I wanted to have out of the box require manual configuration/changing dotfiles and personally I just find that a waste of time - I used to do that for years when I had time and wanted to learn the ways of Linux - now I just want to run it. Ricing is fun, but I can do that on any distro - but tweaking monitor configs and other basic features that should just work when I have tons of other more productive work to do is not an option. I spend most of my time between terminal, IDE and browser anyway, so I often don&#39;t even get to see anything else for days.&lt;/p&gt;
&lt;p&gt;Anyway, Ubuntu LTS is up and running - this time fingerprint reader works without any driver or tweak which is a first (I always try to set it up and until now it never worked first try). The system is smooth and every single device works out of the box. Obviously over the weekend I riced it to a minimal look through tweaks and Gnome extensions, but Yaru theme is beautiful and I decided to not change it and just picked the Viridian flavor and a nice wallpaper to fit with it - a must.&lt;/p&gt;
&lt;p&gt;I always install &lt;a href=&quot;https://guake.github.io/&quot;&gt;Guake&lt;/a&gt; as my terminal and rebind the trigger key to cedilla (one key to the left of &amp;quot;1&amp;quot;), because it reminds me of old Action Quake console and I just love the dropdown overlay + I can read docs underneath through it&#39;s transparency.&lt;/p&gt;
&lt;p&gt;I also always reconfigure all the OS navigation shortcuts to my personal liking so workspaces management is more manageable and I don&#39;t use a tiling window manager - basic Ubuntu tiling is good enough for me to split window to half-screen and move it left/right/up/down. I prefer to alt-tab (and func-tab which is my shortcut to switch between workspaces). Specially when working on laptop I find tiling to get in the way and prefer to see the entire app/terminal/window. IDE already has multiple sub-sections and I split the terminal many times too - so tiling wm is not so useful to me. They look cool in screenshots and on &lt;a href=&quot;https://www.reddit.com/r/unixporn/&quot;&gt;r/unixporn&lt;/a&gt; though, love seeing those.&lt;/p&gt;
&lt;p&gt;Also a must to run is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gsettings set org.gnome.shell.extensions.tiling-assistant enable-tiling-popup &amp;quot;false&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To disable the Ubuntu 24 &amp;quot;select window to fill&amp;quot; feature when using super + left/right - I absolutely hate it - it&#39;s a horrible UX. Credit to &lt;a href=&quot;https://www.reddit.com/r/Ubuntu/comments/1ffatf1/comment/lmu4pz7/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button&quot;&gt;Medo64&lt;/a&gt; on how to do that.&lt;/p&gt;
&lt;p&gt;I also want to comment on snaps - everybody hates on those and I also don&#39;t really praise them because I did have issues in past (Firefox upload input not working - then again Firefox is becoming trash so not sure who to blame), but I also don&#39;t see them getting in the way - the startup time delay is unnoticeable and I hope that they managed to fix some of the issues that were reported in older versions. I&#39;m not much of an app user, but for Discord that constantly bugs with updates I kinda like to have it running as snap and autoupdate.&lt;/p&gt;
&lt;p&gt;I may expand more rambling here if I come up with it - it&#39;s not December yet.&lt;/p&gt;
&lt;p&gt;Take care of your favorite distro.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>ffmpeg - extend last video frame duration</title>
		<link href="https://www.easwee.net/ffmpeg-extend-last-video-frame-duration/"/>
		<updated>2025-07-29T00:00:00Z</updated>
		<id>https://www.easwee.net/ffmpeg-extend-last-video-frame-duration/</id>
		<content type="html">&lt;p&gt;I needed this last week and will probably need it again soon, so here&#39;s a public note to myself.&lt;/p&gt;
&lt;p&gt;Again ffmpeg proves his strengths.&lt;/p&gt;
&lt;p&gt;Count frames:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;ffprobe &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; error &lt;span class=&quot;token parameter variable&quot;&gt;-count_frames&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-select_streams&lt;/span&gt; v:0 &lt;span class=&quot;token parameter variable&quot;&gt;-show_entries&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;nb_read_frames &lt;span class=&quot;token parameter variable&quot;&gt;-of&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;csv&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; test.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Outputs video frames count - for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;125
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Extract last frame - zero-indexed (or really any frame):&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;ffmpeg &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; test.mp4 &lt;span class=&quot;token parameter variable&quot;&gt;-vf&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;select=&#39;eq(n&#92;,124)&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-vsync&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-vframes&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; frame.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make 2 sec (or desired time -t) video from frame:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;ffmpeg &lt;span class=&quot;token parameter variable&quot;&gt;-loop&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; frame.png &lt;span class=&quot;token parameter variable&quot;&gt;-c:v&lt;/span&gt; libx264 &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class=&quot;token parameter variable&quot;&gt;-vf&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fps=30&quot;&lt;/span&gt; frame_video.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a &lt;code&gt;files.txt&lt;/code&gt; files that contains list of files concat:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; files.txt&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;file &#39;test.mp4&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; files.txt&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;file &#39;frame_video.mp4&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; files.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Concat videos from &lt;code&gt;files.txt&lt;/code&gt; into a single output video:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;ffmpeg &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; concat &lt;span class=&quot;token parameter variable&quot;&gt;-safe&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; files.txt &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; copy output.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last frame of output.mp4 video now lasts for 2 seconds at the end.&lt;/p&gt;
&lt;p&gt;There&#39;s maybe a better way to do it, but I found this useful enough for myself and adjusting the commands allows for flexibility, if other frames are to be extended in duration.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>monsgeek m1 v5</title>
		<link href="https://www.easwee.net/monsgeek-m1-v5/"/>
		<updated>2025-03-07T00:00:00Z</updated>
		<id>https://www.easwee.net/monsgeek-m1-v5/</id>
		<content type="html">&lt;p&gt;I had a keycap set laying around, so it was time to build another keyboard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2025_03_07_monsgeek_m1_v5.jpg&quot; alt=&quot;MonsGeek M1 V5 customized mechanical keyboard&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Base&lt;/strong&gt;: Monsgeek M1 V5 Moonlight White&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Switches:&lt;/strong&gt; Akko Penguin silent tactile&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keycap set:&lt;/strong&gt; KAT Napoleonic Hungarian &amp;amp; South Slavic (Doppelkaiser)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight:&lt;/strong&gt; 1860g&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This time I took the barebone version of MonsGeek M1 V5 as base - in Moonlight White color. Lovely.&lt;/p&gt;
&lt;p&gt;For the switches I first decided to go with Akko x MonsGeek Green Fog switches, but this is an office build and it was to loud emitting a funky &amp;quot;thock&amp;quot; that was not appreciated by coworkers, so I switched for Akko Penguin silent tactile switches - they have a very enjoyable tactile typing feel, which compared to the linear Green Fogs, I now prefer. For future builds I will be buying tactile switches again for sure.&lt;/p&gt;
&lt;p&gt;The keycap set is KAT Napoleonic - Hungarian &amp;amp; South Slavic version also called Doppelkaiser (after Francis II, Holy Roman Emperor). I love the color scheme that fits so nicely on white base, for sure better than going with a black base.&lt;/p&gt;
&lt;p&gt;I&#39;m including a photo of the original Monsgeek M1 V5 user manual in English in case anyone ever needs: &lt;a href=&quot;https://easwee.net/media/photography/2025_03_07_monsgeek_m1_v5_manual_en.jpg&quot;&gt;Download&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>hobby guide to ai ep.2 - ai audio intelligence</title>
		<link href="https://www.easwee.net/hobby-guide-to-ai-ep.2-ai-audio-intelligence/"/>
		<updated>2025-02-06T00:00:00Z</updated>
		<id>https://www.easwee.net/hobby-guide-to-ai-ep.2-ai-audio-intelligence/</id>
		<content type="html">&lt;h2 id=&quot;tldr&quot; tabindex=&quot;-1&quot;&gt;TLDR &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/hobby-guide-to-ai-ep.2-ai-audio-intelligence/#tldr&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;!NOTE: Omnio is now a deprecated model not available anymore :(&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone git@github.com:easwee/ai-hobby-lab.git&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;uv &lt;span class=&quot;token function&quot;&gt;sync&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; .venv/bin/activate&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SONIOX_API_KEY=&amp;lt;your_soniox_api_key&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; .env&lt;br /&gt;&lt;br /&gt;python main.py &lt;span class=&quot;token parameter variable&quot;&gt;-w&lt;/span&gt; youtube_audio_data_extractor &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;[&quot;https://www.youtube.com/watch?v=AyOxay5AtMM&quot;]&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output file will be created as &lt;code&gt;./output/&amp;lt;video_title&amp;gt;.html&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;i-want-to-know-more&quot; tabindex=&quot;-1&quot;&gt;I want to know more &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/hobby-guide-to-ai-ep.2-ai-audio-intelligence/#i-want-to-know-more&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This workflow demonstrates how handy can be the native audio reasoning of &lt;a href=&quot;https://soniox.com/blog/omnio/&quot;&gt;Soniox Omnio&lt;/a&gt; multimodal LLM.&lt;/p&gt;
&lt;p&gt;Our workflow consists of 3 steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Input: Download cooking videos from Youtube using yt-dlp and extract audio from a list of input urls.&lt;/li&gt;
&lt;li&gt;Processing: Process the audio using Soniox Omnio API and prompt for a cooking recipe format.&lt;/li&gt;
&lt;li&gt;Output: Save Soniox Omnio markdown response to a .html file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s go over the fully commented code:&lt;/p&gt;
&lt;pre class=&quot;language-py&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; base64&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Tuple&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; concurrent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;futures &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ThreadPoolExecutor&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; yt_dlp &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; YoutubeDL&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; markdown2 &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Markdown&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Sample input - a list of youtube cooking videos:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# [&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;#     &quot;https://www.youtube.com/watch?v=AyOxay5AtMM&quot;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;#     &quot;https://www.youtube.com/watch?v=vlEGY8IPD-Q&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# ]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# USER_PROMPT_TEMPLATE&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# - defines instructions for our model.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# - it is currently instructing Soniox Omnio multimodal LLM&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;#   to extract formatted food recipes data from input audio&lt;/span&gt;&lt;br /&gt;USER_PROMPT_TEMPLATE &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;&lt;br /&gt;Input audio contains cooking recipe data. Output formatted markdown document containing:&lt;br /&gt;1. Recipe title&lt;br /&gt;2. Short summary paragraph of what is being cooked.&lt;br /&gt;3. List of ingredients where each list item contains &quot;&amp;lt;ingredient&gt; &amp;lt;required amount&gt;&quot;&lt;br /&gt;4. Step by step cooking instructions as explained in the audio.&lt;br /&gt;&lt;br /&gt;Use wording as if you were conveying the recipe over radio. Make sure every listed ingredient is also used in the cooking process, otherwise do not list it.&lt;br /&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strip&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# define our workflow class (we could define a Workflow abstract to extend from, since .run method is always required)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;YoutubeAudioDataExtractor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# We will be triggering the run method and pass in a list of youtube video urls&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# from which we want to extract data&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Workflow started.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# We can speed up the processing with multi-threading - increase max_workers amount if you can afford more&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; ThreadPoolExecutor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;max_workers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; executor&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# execute the process method on each of our input objects&lt;/span&gt;&lt;br /&gt;            results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# Print out informational tuple list&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# Our process method will be in charge of chaining together multiple steps of data extraction&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# We want to run the process in a separate thread for each url, to speed things up in case of multiple urls&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Tuple&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Processing &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# combine all the steps of this workflow together&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# 1. download youtube video and obtain an audio file and it&#39;s name&lt;/span&gt;&lt;br /&gt;            file_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;download&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# 2. run Soniox Omnio multimodal llm and extract data from audio&lt;/span&gt;&lt;br /&gt;            data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extract_data&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# 3. write output to pdf&lt;/span&gt;&lt;br /&gt;            self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create_pdf&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# success&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Done.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# indicate simple failure&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Failed.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# In download step we use yt-dlp to download video/audio from youtube&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;[download] Start &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# configuration options for yt-dlp:-&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# - we are interested in audio only, we like flac&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# - after the video is downloaded the yt-dlp postprocessor will convert video to .flac instantly&lt;/span&gt;&lt;br /&gt;        options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token string&quot;&gt;&#39;format&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;bestaudio/best&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token string&quot;&gt;&#39;outtmpl&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./%(title)s.%(ext)s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token string&quot;&gt;&#39;postprocessors&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token string&quot;&gt;&#39;key&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FFmpegExtractAudio&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token string&quot;&gt;&#39;preferredcodec&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;flac&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token string&quot;&gt;&#39;preferredquality&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# start downloading the file using yt-dlp library, and also extract audio file meta information&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# like downloaded file name, so we can reference the file later&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; YoutubeDL&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ydl&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            info &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ydl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extract_info&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; download&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# we want to store and return the fle name because we need it later&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# Gfirst get the original filename  that yt-dlp read from web (.m4a or .webm)&lt;/span&gt;&lt;br /&gt;            original_file_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ydl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prepare_filename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;# Manually change the extension to .flac to match file on disk&lt;/span&gt;&lt;br /&gt;            downloaded_file_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;splitext&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;original_file_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.flac&#39;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;[download] &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;downloaded_file_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; done.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; downloaded_file_name&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;extract_data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file_name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;[ExtractData]: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# We need to read the downloaded audio file and convert it to base64&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# so we can pass it to Soniox Omnio API&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            audio_data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;read&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            audio_data_b64 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; base64&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;b64encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;audio_data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;utf-8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# Soniox Omnio API is fully compatible with the now standard OpenAI SDK,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# we just need to point the base_url to Soniox api url, instead of the default OpenAI api&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# and also we use our SONIOX_API_KEY that is set in .env file&lt;/span&gt;&lt;br /&gt;        client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getenv&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SONIOX_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            base_url&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://api.llm.soniox.com/v1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[ExtractData]: Running audio intelligence...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# we use completions method to create a new request&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# important part here is that audio base64 data has to be set as a partial content message&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# as &quot;audio_data_b64&quot; prop, so the API will grab audio properly&lt;/span&gt;&lt;br /&gt;        completion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;completions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;omnio-chat-audio-preview&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            messages&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;audio_data_b64&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; audio_data_b64&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; USER_PROMPT_TEMPLATE&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# this is optional part, but if we don&#39;t need the downloaded file anymore&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# we can just remove it to not waste space&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;remove&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# read llm response - non-streamed all in one&lt;/span&gt;&lt;br /&gt;        data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; completion&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;choices&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-- [ExtractData] Done.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create_pdf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file_name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# since the LLM returns markdown output we need a parser for that&lt;/span&gt;&lt;br /&gt;        markdowner &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Markdown&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        output_dir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getenv&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OUTPUT_DIR&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./output&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# make sure our output path exists&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;makedirs&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# create new writable file&lt;/span&gt;&lt;br /&gt;        recipe_file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;./output/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;file_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.html&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# convert markdown to HTML&lt;/span&gt;&lt;br /&gt;        recipe_file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;write&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdowner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;convert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# we are done&lt;/span&gt;&lt;br /&gt;        recipe_file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;close&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, writing custom AI workflows does not really require advanced AI frameworks - the abstraction layers those frameworks bring in mostly make the entire code less flexible and harder to understand and maintain.&lt;/p&gt;
&lt;p&gt;While such frameworks do provide a lot of tested util functions, I would much rather just extract those utils out and include them in my workflow separately. Full control to tap-in at each step is what we want.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>hobby guide to ai ep.1 - uv project setup</title>
		<link href="https://www.easwee.net/hobby-guide-to-ai-ep.1-uv-project-setup/"/>
		<updated>2025-02-04T00:00:00Z</updated>
		<id>https://www.easwee.net/hobby-guide-to-ai-ep.1-uv-project-setup/</id>
		<content type="html">&lt;p&gt;&lt;em&gt;This is a hobby/beginner guide to AI driven workflows. Some basic terminal and Python knowledge is preferred, but not strictly needed. I will try to provide all of the commands in a sequence so even if just copy/pasted, they should yield results. I am running all of this on Ubuntu with bash, curl and latest Python (currently 3.12) installed, but it should be similar on a Mac, and most likely more painful on Windows. If you run into problems you can hit me up on &lt;a href=&quot;http://x.com/&quot;&gt;x.com&lt;/a&gt; under @easwee and we can chat, maybe. Let&#39;s dive in!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So why even write code, when we already have first AI agents that can code applications for us? It&#39;s because I hope some young soul will read this post at some point in the future, get excited about coding, get motivated and insanely good at it and then come fix my future elder-care home robot when it gets jammed. I do care about self-preservation. And also coding is a fun mental exercise, like crosswords. And also just to write down some thoughts.&lt;/p&gt;
&lt;h2 id=&quot;project-setup&quot; tabindex=&quot;-1&quot;&gt;Project setup &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/hobby-guide-to-ai-ep.1-uv-project-setup/#project-setup&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python has many package managers, but most of them are trash, so let&#39;s just use the best we have at the moment - &lt;strong&gt;&lt;a href=&quot;https://github.com/astral-sh/uv&quot;&gt;uv&lt;/a&gt;&lt;/strong&gt;. uv is fast and has a ton of other improvements, that you don&#39;t really have to care about just now. Just use uv - trust me bro - and later when you get curious, go read more in their docs.&lt;/p&gt;
&lt;p&gt;Open your terminal.&lt;/p&gt;
&lt;p&gt;Unless the docs changed - run the uv install command:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see &lt;a href=&quot;https://curl.se/&quot;&gt;curl&lt;/a&gt; downloads the uv bash install script and runs it. Wait for it to complete.&lt;/p&gt;
&lt;p&gt;uv initializes a Python &amp;quot;project&amp;quot; folder and all your files will be contained within it. Let&#39;s initialize one, so we have a base from where to start. I&#39;m gonna call it &lt;code&gt;ai-hobby-lab&lt;/code&gt;, but you can call it whatever you like.&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;uv init ai-hobby-lab&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move into your new project:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; ai-hobby-lab&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And list the contents of this folder including hidden stuff:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-la&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see something like this:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;drwxrwxr-x &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; easwee easwee &lt;span class=&quot;token number&quot;&gt;4096&lt;/span&gt; jan &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;:19 .git&lt;br /&gt;-rw-rw-r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; easwee easwee  &lt;span class=&quot;token number&quot;&gt;109&lt;/span&gt; jan &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;:19 .gitignore&lt;br /&gt;-rw-rw-r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; easwee easwee  &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; jan &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;:19 hello.py&lt;br /&gt;-rw-rw-r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; easwee easwee  &lt;span class=&quot;token number&quot;&gt;168&lt;/span&gt; jan &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;:19 pyproject.toml&lt;br /&gt;-rw-rw-r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; easwee easwee    &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; jan &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;:19 .python-version&lt;br /&gt;-rw-rw-r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; easwee easwee    &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; jan &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;:19 README.md&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;uv creates your project base files - &lt;a href=&quot;http://hello.py/&quot;&gt;hello.py&lt;/a&gt; is the entry file and the file we will execute once our code is written. But i don&#39;t like the name - let&#39;s rename it to &lt;code&gt;main.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mv&lt;/span&gt; hello.py main.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s test if it works:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;python main.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see something like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Hello from ai-hobbylab!&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;virtual-environment&quot; tabindex=&quot;-1&quot;&gt;Virtual environment &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/hobby-guide-to-ai-ep.1-uv-project-setup/#virtual-environment&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We don&#39;t want to pollute our computer with random Python libraries, because sooner or later we will start running in conflicting versions across projects. We will install libraries/packages into a Python virtual environment. uv can &lt;a href=&quot;https://docs.astral.sh/uv/pip/environments/&quot;&gt;create one&lt;/a&gt; for us:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;uv venv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a &lt;code&gt;.venv&lt;/code&gt; folder inside of your project - typical python development stuff - encapsulating your project. Now you need to activate the virtual environment in your terminal, so all the stuff we will do later will run within this virtual environment:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;source&lt;/span&gt; .venv/bin/activate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: You need to activate every time you reopen your terminal.&lt;/p&gt;
&lt;p&gt;Note 2: Depending on which terminal shell you use, an active venv could be somehow visually indicated. Also there are ways to have a venv activate automatically each time you open the project folder in terminal - look it up if you want.&lt;/p&gt;
&lt;h2 id=&quot;installing-required-dependencies&quot; tabindex=&quot;-1&quot;&gt;Installing required dependencies &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/hobby-guide-to-ai-ep.1-uv-project-setup/#installing-required-dependencies&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My goal for next episodes is to write a few different nicely commented Python workflow scripts, that you can just copy/paste and run on your machine (and also modify if you wish to be an adventurer).&lt;/p&gt;
&lt;p&gt;I obviously don&#39;t want to write everything from scratch, so let&#39;s install 2 popular battle-tested libraries that will simplify my life a bit.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yt-dlp/yt-dlp&quot;&gt;yt-dlp&lt;/a&gt; - a feature-rich command-line audio/video downloader with support for thousands of sites (their description couldn&#39;t be more accurate). At some point we will download audio and video from Youtube, but yt-dlp also has adapters for many other popular media websites, if you ever wish to download from elsewhere. It&#39;s the best lib in this field.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Install it using uv:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;uv &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; yt-dlp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: if you do read the docs, they always say &lt;code&gt;pip install &amp;lt;something&amp;gt;&lt;/code&gt;, but we use &lt;strong&gt;uv&lt;/strong&gt; package manager instead of old boy &lt;strong&gt;pip&lt;/strong&gt;, so just replace &lt;code&gt;pip install&lt;/code&gt; with &lt;code&gt;uv add&lt;/code&gt; when you encounter this.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/openai-python&quot;&gt;openai python API&lt;/a&gt; - the gipity guys offer a Python library that allows you to call their API if you have an OPENAI_API_KEY, but the good part about this library is that many other companies copy their API structure, so with a tiny change config change, it will work with popular AI APIs too. You will see later.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Install it using uv:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;uv &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; openai&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here on - I don&#39;t know about you - but I prefer to write code in a nice code editor. I use VS Code, because it&#39;s free and everybody uses it (and because it doesn&#39;t cause brain damage like Vim). Always use what most people use, unless you found your reason why something else works for you - but don&#39;t be that guy who goes &amp;quot;I did everything as in instructions, but I write code on a typewriter...&amp;quot;.&lt;/p&gt;
&lt;p&gt;Assuming you have VS Code installed and you are in your project folder inside of terminal, type:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;code &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it will open this project in VS code.&lt;/p&gt;
&lt;p&gt;Note: VS Code has a simple integrated terminal - if you have your project opened, you can activate your .venv there and run all scripts within it and you don&#39;t need to keep switching out.&lt;/p&gt;
&lt;p&gt;This is enough for today. We will do AI things in the next one. And maybe learn something new about uv too!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.easwee.net/hobby-guide-to-ai-ep.2-ai-audio-intelligence/&quot;&gt;continue to episode 2&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>text2cypher</title>
		<link href="https://www.easwee.net/text2cypher/"/>
		<updated>2024-02-01T00:00:00Z</updated>
		<id>https://www.easwee.net/text2cypher/</id>
		<content type="html">&lt;p&gt;Code generation is still a pain point of all LLMs. I built this opensource app for evaluation (and possibly crowdsourcing) of Neo4j cypher queries. Queries and data responses are produced using Neo4j Langchain implementation and GPT4 as model.&lt;/p&gt;
&lt;p&gt;Read more about on Github repo:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/easwee/text2cypher&quot;&gt;https://github.com/easwee/text2cypher&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also HTMX rocks.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>the quest for the ultimate snack - a feline adventure</title>
		<link href="https://www.easwee.net/the-quest-for-the-ultimate-snack-a-feline-adventure/"/>
		<updated>2023-05-06T00:00:00Z</updated>
		<id>https://www.easwee.net/the-quest-for-the-ultimate-snack-a-feline-adventure/</id>
		<content type="html">&lt;p&gt;The sun was setting over the rolling hills of the countryside, the golden light casting a warm glow over the vineyards and olive groves that stretched out as far as the eye could see. The landscape was dotted with charming stone houses and rustic farmhouses, and the air was filled with the sweet scent of wildflowers. It was a scene straight out of a storybook, reminiscent of the idyllic countryside of a far-off land.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2023_05_06_1.jpg&quot; alt=&quot;Image of Kaela and Rolf sitting and watching the fields.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As two feline adventurers set out on their quest for the ultimate snack, they couldn&#39;t help but feel a sense of excitement and wonder. The first cat, a sleek black feline with piercing green eyes, was named Kaela. Her companion, a fluffy orange tabby with bright yellow eyes, went by the name of Rolf. They were both dressed in tiny jackets and pants, complete with belts and buckles, which added to their charming and adventurous spirit.&lt;/p&gt;
&lt;p&gt;As they walked through the fields, they came upon a slow-moving turtle, ambling along with a small wooden platform strapped to its back. Kaela and Rolf looked at each other, then back at the turtle. Without a word, they climbed onto the platform and settled in for the ride, eager to begin their adventure.&lt;/p&gt;
&lt;p&gt;The turtle moved at a leisurely pace, allowing the feline duo to take in the sights and sounds of their surroundings. They passed by fields of lavender and sunflowers, and through small villages with charming stone buildings and narrow cobblestone streets. As the sun sank lower on the horizon, they finally arrived at a grand, ancient necropolis.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2023_05_06_2.jpg&quot; alt=&quot;An image of acient necropolis.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The necropolis was a sprawling complex of tombs and mausoleums, some of them dating back hundreds of years. Kaela and Rolf explored the winding paths and shadowy corners, marveling at the intricate carvings and inscriptions on the stone walls. As they turned a corner, they came face to face with a towering stone church, its bell tower reaching towards the sky.&lt;/p&gt;
&lt;p&gt;Without hesitation, Kaela and Rolf made their way inside. The interior of the church was dimly lit, but the warm glow of candlelight flickered on the walls. The feline duo padded down the aisle, past rows of pews, until they reached the altar. There, they found a small alcove, where a statue of a saint stood watch over a glittering treasure trove.&lt;/p&gt;
&lt;p&gt;Kaela and Rolf gazed at the treasure in amazement. There were silver coins and precious gems, sparkling in the flickering candlelight. But their eyes were drawn to something else entirely: a small, unassuming box, tucked away in the corner. It was plain and unremarkable, but something about it called to them.&lt;/p&gt;
&lt;p&gt;Without a second thought, the feline duo pried open the lid of the box. Inside, they found a single, perfectly formed biscuit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2023_05_06_3.png&quot; alt=&quot;An image of the small box with a biscuit.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It was unlike any snack they had ever seen before, golden brown and crispy on the outside, with a soft, chewy center. Kaela and Rolf looked at each other in amazement. This was it - the ultimate snack.&lt;br /&gt;
As Kaela and Rolf savored the flavor of the ultimate biscuit, an older cat stepped out from the shadows. He was dressed in a long coat and hat, and his eyes shone with a wise and mysterious glimmer.&lt;br /&gt;
&amp;quot;Greetings, young travelers,&amp;quot; he said, his voice low and gravelly. &amp;quot;I couldn&#39;t help but overhear your conversation, and I must say, you have yet to find the ultimate snack.&amp;quot;&lt;br /&gt;
Kaela and Rolf looked at each other in surprise. They had thought that the biscuit was the end of their quest.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2023_05_06_4.jpg&quot; alt=&quot;An image of the older cat.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The older cat continued, &amp;quot;The ultimate snack is not in the form of a sweet biscuit, my friends. No, it is a rare and unique raw fish, caught only by a fishmonger who lives by the sea. His name is Salty Sam, and he never sells his catch. Salty Sam is a proud cat, who believes that his catch is the most exceptional in the land. He would only share it with the world if he were to receive the recognition he feels he deserves - perhaps from a renowned chef who could showcase the unique flavor in a delicious dish, or from a prestigious culinary magazine that could feature his catch in a glowing review. But he would never share his catch with ordinary diners, only those who appreciate the art of fine dining and the true value of exceptional cuisine.&lt;/p&gt;
&lt;p&gt;Kaela and Rolf exchanged a determined glance. They knew that they had to find a way to taste that rare and unique raw fish, no matter what it took.&lt;/p&gt;
&lt;p&gt;The older cat seemed to read their thoughts. &amp;quot;I can help you, young adventurers,&amp;quot; he said. &amp;quot;I know the way to Salty Sam&#39;s lair, but be warned - it won&#39;t be easy to steal that fish from him. He&#39;s a wily old cat, and he never lets his guard down.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2023_05_06_5.jpg&quot; alt=&quot;An image of the older cat leading Kaela and Rolf towards the sea.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With the older cat as their guide, Kaela and Rolf set out towards the sea. Along the way, they stopped at a couple of local bistros to replenish their energy and savor the delicious local fare. At &#39;The Cheesy Mouse,&#39; they indulged in plates of creamy cheese and warm bread, while at &#39;The Fisherman&#39;s Whisker,&#39; they feasted on platters of crispy calamari and fries, washing it all down with tall glasses of chilled lemonade. As they drew closer to Salty Sam&#39;s lair, they could feel their hearts pounding with excitement and anticipation.&lt;/p&gt;
&lt;p&gt;Finally, they arrived at the seaside town where Salty Sam lived. The salty air filled their nostrils, and the sound of seagulls echoed in their ears. They prowled through the town, keeping a low profile and searching for any sign of their target.&lt;/p&gt;
&lt;p&gt;As they crept through the narrow streets, they finally spotted him - a gruff old cat with a thick beard and a weathered face, sitting outside his shop, cleaning his catch of the day. Kaela and Rolf exchanged a nervous glance. This was it - the moment of truth.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2023_05_06_6.jpg&quot; alt=&quot; An image of Salty Sam sitting outside his shop..&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As Kaela and Rolf watched Salty Sam cleaning his catch of the day, they knew they had to act fast. They huddled together, whispering and planning their next move. Rolf suggested that they try to trick Salty Sam into thinking they were researchers studying the unique flavors of different types of fish. They could ask him to prepare a sample for them and then swap it with a fake one when he wasn&#39;t looking.&lt;/p&gt;
&lt;p&gt;Kaela and Rolf put their plan into motion. Rolf approached Salty Sam and explained that they were conducting a study on the unique flavors of different types of fish, and they had heard that his catch was the most exceptional in the region. Salty Sam was skeptical, but Rolf showed him a paw print ID from the Royal Institute of Culinary Research and began to explain the details of their study in a convincing manner. Salty Sam eventually agreed to prepare a sample of his catch for the &amp;quot;researchers&amp;quot;, but in reality he was about to trick them with a common fish. As Salty Sam went to prepare the sample, Kaela and Rolf watched him closely. They noticed that he was preparing a common fish, not the rare and unique raw fish they had been seeking. Kaela distracted Salty Sam by asking him questions about the common fish, while Rolf sneaked behind the counter to search for the ultimate catch. After a few tense moments, Rolf found the jewel of Salty Sam&#39;s catch - a rare and delectable raw fish with a shimmering silver skin - and signaled to Kaela. Rolf quickly wrapped the prized fish in a cloth and made his escape, while Kaela kept Salty Sam distracted with silly questions.&lt;/p&gt;
&lt;p&gt;Kaela and Rolf soon savored the exquisite flavor of the rare and unique fish, knowing that they had finally found the ultimate delicacy. As they lounged in the warm sun, basking in the glow of their victory, they knew that they had truly become feline adventurers of the highest order.&lt;/p&gt;
&lt;p&gt;Salty Sam, unaware of what had happened, continued to savor his catch, believing that he had successfully thwarted the &amp;quot;researchers.&amp;quot; Little did he know that Kaela and Rolf had already outsmarted him and tasted the real &amp;quot;ultimate snack.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This story was inspired by a recent road trip to Tuscany and generated by prompting ChatGPT v3.5 for text and Stable Diffusion 2.1 for images. Fine tuning to desired input took around 30 prompts on ChatGPT, and around 50 prompts on Stable Diffusion to get some images that fit the context. Total time spent on this project was around 1:30h. Generated images could be improved by using more refined prompts.&lt;/em&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>job ad - prompt operator</title>
		<link href="https://www.easwee.net/job-ad-prompt-operator/"/>
		<updated>2023-03-30T00:00:00Z</updated>
		<id>https://www.easwee.net/job-ad-prompt-operator/</id>
		<content type="html">&lt;p&gt;In our environment of loud, opinionated tinkers, meeting enthusiasts, and general work avoiders, we would like to welcome a&lt;/p&gt;
&lt;h1 id=&quot;prompt-operator&quot; tabindex=&quot;-1&quot;&gt;Prompt Operator &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/job-ad-prompt-operator/#prompt-operator&quot;&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Key skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Good hearing&lt;/li&gt;
&lt;li&gt;Ability to creatively word our ideas into an LLM AI prompt&lt;/li&gt;
&lt;li&gt;Copy &amp;amp; paste&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Knowledge of a wide range of programming languages is a plus.&lt;/p&gt;
&lt;p&gt;If you see yourself as someone who possesses these key skills, don&#39;t hesitate to send us your best prompt now. Let&#39;s iterate on recycled ideas from a database of the entire internet together. Who knows, maybe the next t-shirt e-commerce website will be born right from your prompt.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>ode to PWAs</title>
		<link href="https://www.easwee.net/ode-to-pwas/"/>
		<updated>2023-02-03T00:00:00Z</updated>
		<id>https://www.easwee.net/ode-to-pwas/</id>
		<content type="html">&lt;p&gt;Want it in the browser?&lt;/p&gt;
&lt;p&gt;PWA works in the browser.&lt;/p&gt;
&lt;p&gt;Want it on desktop? You are still packing into Electron?&lt;/p&gt;
&lt;p&gt;PWA works as a desktop app on any modern OS.&lt;/p&gt;
&lt;p&gt;Want it on Android?&lt;/p&gt;
&lt;p&gt;PWA works on Android.&lt;/p&gt;
&lt;p&gt;Want it on iOS?&lt;/p&gt;
&lt;p&gt;PWA works on iOS.&lt;/p&gt;
&lt;p&gt;Want to run it on a smart refrigerator?&lt;/p&gt;
&lt;p&gt;PWA works also on a smart refrigerator.&lt;/p&gt;
&lt;p&gt;Embrace PWAs. PWAs are the future!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps&quot;&gt;P W A s !&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>return of eventko</title>
		<link href="https://www.easwee.net/return-of-eventko/"/>
		<updated>2022-07-04T00:00:00Z</updated>
		<id>https://www.easwee.net/return-of-eventko/</id>
		<content type="html">&lt;p&gt;Recently had some time to rethink and rewrite &lt;a href=&quot;https://eventko.easwee.net/&quot;&gt;Eventko&lt;/a&gt; - scraper for Ljubljana events. It is now officially a live as beta version. So far scraping venues that have events publicly posted on their websites. Will be adding more in the future.&lt;/p&gt;
&lt;p&gt;Also rewrote the entire frontend web app - it is now a Progressive Web App (PWA), meaning that it can be saved to mobile phone desktop and be used in a headless browser mode - makes the UX a bit nicer, also adds caching and some other tiny improvements. Will be expanding it&#39;s functionalities as I go.&lt;/p&gt;
&lt;p&gt;For now the service seems stable.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>devlog - git squash</title>
		<link href="https://www.easwee.net/devlog-git-squash/"/>
		<updated>2022-05-23T00:00:00Z</updated>
		<id>https://www.easwee.net/devlog-git-squash/</id>
		<content type="html">&lt;p&gt;Sometimes you need to squash - do it properly. Short devlog so I stick it in my mind forever and you as a reader get a zero-bullshit guide to git squash.&lt;/p&gt;
&lt;p&gt;1.) First forget about Github&#39;s and similar services &amp;quot;Rebase and merge&amp;quot; button and what that does - we do Git here.&lt;/p&gt;
&lt;p&gt;2.) Start your work on a new feature - either from master or develop - we go from develop because that&#39;s how we flow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout develop
git pull
git checkout -b feature/xyz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.) Do some hard work and commit as you wish. Sometimes you do stupid commits - that&#39;s why you wanna squash - to hide your shame of failure and laziness out of commits messages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &amp;quot;Add cat icon to submit button&amp;quot;
git commit -m &amp;quot;Linter :(&amp;quot;
git commit -m &amp;quot;Forgot something&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.) Ok it was easy task, we are done. Let&#39;s fetch and start our interactive rebase against develop:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch

git rebase --interactive origin/develop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Interactive rebase starts an interactive interface in your shell, that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pick a23b543 Add cat icon to submit button
pick fafa34c Linter
pick fdfd8a6 Forgot something

# more commented junk below here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Focus on the lines that start with pick - this are your commits. Pick one that will become the base and change other &amp;quot;pick&amp;quot;s into &amp;quot;squash&amp;quot; - so they get squashed into the picked one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pick a23b543 Add cat icon to submit button
squash fafa34c Linter
squash fdfd8a6 Forgot something
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save and rebase will continue.&lt;/p&gt;
&lt;p&gt;5.) If merge conflict occurs first curse and then check what, who, where:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fix conflicted file with whatever is your favorite way to solve conflicts. When you solve save and:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add &amp;lt;file&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(no need to commit - rebase handles it)&lt;/p&gt;
&lt;p&gt;6.) Then continue with rebase:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git rebase --continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once your commits have been squashed, Git will prompt you to write a commit message for your new squashed commit. Ask your coworkers how they want it to look or use your logic (there are guides on internet how to write good commit messages - if you do it properly certain popular git services will pre-fill their web interface from your message and save you time).&lt;/p&gt;
&lt;p&gt;7.) Now we push - here we have 2 scenarios:&lt;/p&gt;
&lt;p&gt;a.) First push of this new branch:&lt;/p&gt;
&lt;p&gt;Push to origin as always:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push --set-upstream origin feature/xyz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;b.) Already pushed this branch before, but you forgot a lot of important stuff, did more work and now you wanna squash and hide your poor work performance from git history:&lt;/p&gt;
&lt;p&gt;We force push (yes - FORCE - don&#39;t panic - all good and legal - we are on our own branch).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push --force origin feature/xyz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;8.) Open a pull request targeting develop (or whatever you branched from).&lt;/p&gt;
&lt;p&gt;Success.&lt;/p&gt;
&lt;p&gt;You are done like a pro - single commit message - rebased - entire feature implemented - no bugs - off we go.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>clippy zalgo diacritics</title>
		<link href="https://www.easwee.net/clippy-zalgo-diacritics/"/>
		<updated>2022-05-16T00:00:00Z</updated>
		<id>https://www.easwee.net/clippy-zalgo-diacritics/</id>
		<content type="html">&lt;p&gt;&lt;a href=&quot;https://sample.easwee.net/zalgo/&quot;&gt;He comes to those who dare to invoke!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Anyway, this probably renders shit across browsers and systems, but some day we will have consistent stacking diacritics.&lt;/p&gt;
&lt;p&gt;Meanwhile enjoy his recording taken on Ubuntu Firefox.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2022_05_16_clippy_he_comes.gif&quot; alt=&quot;The Nezperdian Hivemind of Chaos, The One Who Will End The World&quot; /&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>florentia</title>
		<link href="https://www.easwee.net/florentia/"/>
		<updated>2022-05-03T00:00:00Z</updated>
		<id>https://www.easwee.net/florentia/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2022_05_03_florentia_memory.jpg&quot; alt=&quot;florentia und scentia&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Overlaid memories of Florence.&lt;/p&gt;
&lt;p&gt;1st may 2022, florence, italy&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>gmk yugo</title>
		<link href="https://www.easwee.net/gmk-yugo/"/>
		<updated>2022-04-06T00:00:00Z</updated>
		<id>https://www.easwee.net/gmk-yugo/</id>
		<content type="html">&lt;p&gt;A good year ago I was writing about my &lt;a href=&quot;https://www.easwee.net/customized-keyboard&quot;&gt;custom keyboard adventure&lt;/a&gt; and the struggles of getting a custom keycap set for Slovene and Serbo-Croatian Latin keyboard layout, main problem being the fact that it has five additional special characters: Č, Ć, Ž, Š and Đ. Luckily GMK Yugo is here and even though I missed the group buy I managed to snatch extras when they were stocked on &lt;a href=&quot;http://mykeyboard.eu/&quot;&gt;mykeyboard.eu&lt;/a&gt; (they sold out in a few hours).&lt;/p&gt;
&lt;p&gt;A short amateur review is in place considering the long wait.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2022_04_06_yugo_base_kit.png&quot; alt=&quot;GMK Yugo 45 kit&quot; /&gt;&lt;br /&gt;
&lt;small&gt;image from &lt;a href=&quot;https://geekhack.org/index.php?topic=103263.0&quot;&gt;https://geekhack.org/index.php?topic=103263.0&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Yugo 45 kit comes in a nice Yugo themed packaging showcasing the car&#39;s hood, headlights, plate and chassis at the bottom which made me giggle a bit, but it&#39;s a nice detail. Both ISO and ANSI are included and ESC and ENTER keys come in two versions - beige (RAL 090 90 20) or red (RAL 030 30 45), which leaves you some tiny space for variation depending on your taste - I used red versions. I wish that the red spacebar was also included in the base kit, but for that you needed to order the Cabrio kit.&lt;/p&gt;
&lt;p&gt;Clean look and a pleasing beige color are what makes this set a joy to look at. Since I have a backlit keyboard the beige also fits nicely with a yellow-ish led backlight and the letters are perfectly visible also in the dark even if the letter layer is not translucent - the monitor emits enough light to make it pleasing. Real retro nostalgia of the iconic car.&lt;/p&gt;
&lt;p&gt;So here is the final assembly:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2022_04_06_yugo_keychron.jpg&quot; alt=&quot;GMK Yugo Keychron&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That&#39;s pretty much all I have to say - big thumbs up to the &lt;a href=&quot;https://discord.gg/Fe922t97&quot;&gt;YugoMK team&lt;/a&gt; for making this set a reality.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>losOS</title>
		<link href="https://www.easwee.net/losos/"/>
		<updated>2022-04-01T00:00:00Z</updated>
		<id>https://www.easwee.net/losos/</id>
		<content type="html">&lt;dl&gt;
    &lt;dt&gt;losos / salmon &lt;/dt&gt;
    &lt;dd&gt;a marine and freshwater food fish, Salmo salar, of the family Salmonidae, having pink flesh, inhabiting waters off the North Atlantic coasts of Europe and North America near the mouths of large rivers, which it enters to spawn.&lt;/dd&gt;
    &lt;dt&gt;los / moose&lt;/dt&gt;
    &lt;dd&gt;a ruminant mammal (Alces alces) with humped shoulders, long legs, and broadly palmated antlers that is the largest existing member of the deer family and inhabits forested areas of Canada, the northern U.S., Europe, and Asia&lt;/dd&gt;
    &lt;dt&gt;operacijski sistem / operating system (OS)&lt;/dt&gt;
    &lt;dd&gt;software that controls the operation of a computer and directs the processing of programs (as by assigning storage space in memory and controlling input and output functions)&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2022_03_28_losos.jpg&quot; alt=&quot;salmon moose operating system - losOS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Fictional fault tolerant system.&lt;/p&gt;
&lt;p&gt;Slovenian play of words.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>about central bank digital currencies and basic universal income</title>
		<link href="https://www.easwee.net/about-central-bank-digital-currencies-and-basic-universal-income/"/>
		<updated>2022-03-18T00:00:00Z</updated>
		<id>https://www.easwee.net/about-central-bank-digital-currencies-and-basic-universal-income/</id>
		<content type="html">&lt;p&gt;This post is just a reminder and a dark 10 year bet that I hope will fail.&lt;/p&gt;
&lt;p&gt;With US signing the executive order about digital assets and EU&#39;s ECB starting to consider digital euro, all while digital yuan is already live with the first test group in China, we can say that central bank digital currencies (CBDCs) are gaining traction with governments. At first glance the tech world would get excited that we are finally getting the deserved payment system for the digital age without the need of third-party transactions, but there seems to be more at stake.&lt;/p&gt;
&lt;p&gt;If the promise of early crypto currencies was decentralization, avoidance of central authority and enabling anybody with access to the web to pay and get paid, CBDCs are reversing this to the direction where government has full authority over the entire digital currency - something that could not have been done with classic currencies - and we all know how this can be used. You may argue, that you can choose to not keep your funds in CBDCs and rather use other kinds of private crypto (Bitcoin and pals), but truth is that there is no guarantee that they will not be banned by law - like they already are in certain countries, mostly under the pretense of money laundering and criminal activities, but it is also a statement of power and monopoly of the currency. You may go the rebel way and decide to not play with your country anymore and move to wherever sanity is still a thing and be creative with your funds, but not everybody can and this defeats the main promise of crypto empowering everybody equally and giving everybody souverenity over their right to transact with other people freely.&lt;/p&gt;
&lt;p&gt;Which brings us to basic universal income (BUI). BUI is the idea of giving every citizen a base monthly sum of money that would enable him to live with dignity and avoid poverty without the need to work for it. If you want more you should join the market. Different models are being tested around the world and generally it is not a bad idea if the economy of your country can support it somehow. I will not be going into the debate on why we need it in this writeup, since there are better sources about it around, but there is always a but...&lt;/p&gt;
&lt;p&gt;Let&#39;s try to combine BUI with CBDCs and you can see that the government now has complete control over your assets. The first step is to only allow receiving BUI in the form of CBDC. Disable the ability to convert CBDC in any other currency for the receivers of BUI (which can be easily done, since this is programmable money) and in the end if govt goes fully totalitarian, limit the spending categories for BUI receivers.&lt;/p&gt;
&lt;p&gt;You want to go gamble? Well not on BUI. Do you want to treat yourself to a craft beer? We only allow you to buy state owned lager brands. You were saving up part of your monthly BUI to purchase a home or somehow get something of your own? You can only pay rents with BUI - or even worse - let me just set an expiration date on your funds to drive the economy - whatever you don&#39;t spend this month is gone.&lt;/p&gt;
&lt;p&gt;You know &amp;quot;it&#39;s OUR money not yours&amp;quot;.&lt;/p&gt;
&lt;p&gt;This may sound extreme and paranoid, but totalitarian governments already exist (or can turn into one quite quickly during any crisis - proven many times in last century, heck even last two years) and CBDCs are a lovely tool in such situations.&lt;/p&gt;
&lt;p&gt;Let&#39;s hope this bet fails miserably and that common citizens don&#39;t give up their right to transact freely - I will be revisiting this post in 10 years.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>svg drawing with github copilot</title>
		<link href="https://www.easwee.net/svg-drawing-with-github-copilot/"/>
		<updated>2022-02-26T00:00:00Z</updated>
		<id>https://www.easwee.net/svg-drawing-with-github-copilot/</id>
		<content type="html">&lt;p&gt;This week I noticed that I&#39;ve got &lt;a href=&quot;https://copilot.github.com/&quot;&gt;Github Copilot&lt;/a&gt; invitation that has been sitting in my mailbox for some time now - not sure how I missed that. Anyway, started playing around with it to see how capable it is and it definetly speeds up typing code - I can see myself getting lazy and hooked on this (and why not, I want to build features and products, not type code).&lt;/p&gt;
&lt;p&gt;Soon enough I&#39;ve got this idea that maybe the boy is smart enough to generate some random vector graphics and maybe get something interesting out of it. Well turns out it is not that interesting at the end of the day, but was a fun experiment:&lt;/p&gt;
&lt;img src=&quot;https://www.easwee.net/media/photography/2022_02_26_copilot_drawing.svg&quot; alt=&quot;Github Copilot says Hello World&quot; style=&quot;max-width: 100%; width: 600px;&quot; /&gt;
&lt;p&gt;So how was this SVG generated?&lt;/p&gt;
&lt;p&gt;I decided I will pick a few different SVG elements like &lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;rect&amp;gt;&lt;/code&gt; etc. and let Github Copilot complete the properties however it feels. I had to change a few colors in the end, because for certain elements it just kept rendering white fill, and also when you use the same element multiple times, it will just keep repeating the same numbers, so I give him a bit more help and setting a &amp;quot;seed&amp;quot; prop from which then it gets a bit more creative and computes different numbers.&lt;/p&gt;
&lt;p&gt;I giggled at the &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; tag, where Copilot decided to just put in the text Hello World - I liked that bit. The second time I tried, it put in PHP tags - crazy.&lt;/p&gt;
&lt;p&gt;I was disappointed with the &lt;code&gt;&amp;lt;ellipse&amp;gt;&lt;/code&gt; tag since it always defines props that result in a circle, so that one is a bit boring.&lt;/p&gt;
&lt;p&gt;Github Copilot certainly is not a creative artist, but a great tool.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>devlog - github actions</title>
		<link href="https://www.easwee.net/devlog-github-actions/"/>
		<updated>2022-02-02T00:00:00Z</updated>
		<id>https://www.easwee.net/devlog-github-actions/</id>
		<content type="html">&lt;p&gt;I&#39;ve been migrating a lot of Gitlab deployment pipelines to Github lately. Github actions are a nice thing, but take some getting used to, before you can mirror your deployment strategy to the one you had on Gitlab. Writing down some public personal notes for future reference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;install a yaml validator plugin for your editor before you even start&lt;/li&gt;
&lt;li&gt;when searching for resources do not trust any blogs including this one; they are a good reference but syntax they provide is most likely outdated; always consult official Github Actions docs&lt;/li&gt;
&lt;li&gt;always check if there is a newer version of a public action&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/nektos/act&quot;&gt;Act&lt;/a&gt; is a nice tool, but it is not 1:1 compared to running actions on Github&lt;/li&gt;
&lt;li&gt;use reusable workflows, they take some time to set up, but once all input vars are set correctly it speeds up setting up multiple environments pipelines&lt;/li&gt;
&lt;li&gt;reusable workflows can&#39;t have a dynamic tag; you either have to specify a branch, version or commit hash (obvious security reasons), same goes for actions&lt;/li&gt;
&lt;li&gt;don&#39;t forget to cache dependencies and everything you can to speed up your pipelines; pipeline re-run times can be greatly reduced with caching and storing artifacts&lt;/li&gt;
&lt;li&gt;don&#39;t forget to expire your artifacts in a reasonable time (after some time Github will expire them for you)&lt;/li&gt;
&lt;li&gt;you can read Github token with &lt;code&gt;${{ secrets.GITHUB_TOKEN }}&lt;/code&gt;, but in a reusable workflow you have to read it with &lt;code&gt;${{ github.token }}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;reduce log output using --silent, --quiet or whatever flag silences your log output for desired command&lt;/li&gt;
&lt;li&gt;you can&#39;t run deploy feature branch reviews on Github Pages; this would be a nice feature, but they only allow 1 Github Pages deployment per repository, so you will need external hosting for review deployments&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;${{ github.head_ref }}&lt;/code&gt; when you need your branch name&lt;/li&gt;
&lt;li&gt;consult docs for &lt;a href=&quot;https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners&quot;&gt;hosted runners&lt;/a&gt; to see which tools are preinstalled for you in hosted runners before you go complicating your workflows with custom installs; complete lists of what each runner contains are available, like this one for ubuntu runner: &lt;a href=&quot;https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md&quot;&gt;https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;you can use &lt;code&gt;workflow_dispatch&lt;/code&gt; to trigger workflows manually from Github; UI for this is kinda clunky at the moment, but it works (do note GH actions are in active development); you also have API if you want&lt;/li&gt;
&lt;li&gt;you can specify a deployment environment and protect the workflow to only allow pipeline runs triggered from branches allowed to deploy to specified environments. this will instantly exit the job if requirements to deploy to env are not met:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;job_name:
  environment:
    name: production
    url: https://www.example.com

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;you can share values between jobs with outputs; I needed to sanizite a branch name to reuse in deployment urls later. Example of using outputs between jobs:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  sanitize_branch:
    name: sanitize branch name job
    runs-on: ubuntu-latest
    outputs:
      output: ${{ steps.sanitize_branch.outputs.sanitized_branch }}
    steps:
      - id: sanitize_branch
        run: |
          REPO=&amp;quot;${{ github.head_ref }}&amp;quot;
          REPO_SANITIZED=&amp;quot;${REPO////-}&amp;quot;
          echo &amp;quot;::set-output name=sanitized_branch::${REPO_SANITIZED}&amp;quot;
  another_job:
    needs: [sanitize_branch]
    name: just another separate job
    runs-on: ubuntu-latest
    steps:
    	- run: echo ${{needs.sanitize_branch.outputs.output}}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;use &lt;a href=&quot;https://docs.npmjs.com/cli/v8/commands/npm-ci&quot;&gt;&lt;code&gt;npm ci&lt;/code&gt;&lt;/a&gt; to run your npm actions (or equivalent for your package manager)&lt;/li&gt;
&lt;li&gt;macOS runners cost 10x more per minute than Linux runners; pick macOS only when you really need it, it is not a thing of preference&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Github is not the same in terms of features compared to Gitlab, but with a few workarounds on Github and Github Actions you get to same or similar results.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>picture gallery mini site</title>
		<link href="https://www.easwee.net/picture-gallery-mini-site/"/>
		<updated>2022-01-23T00:00:00Z</updated>
		<id>https://www.easwee.net/picture-gallery-mini-site/</id>
		<content type="html">&lt;p&gt;Picture gallery now lives on: &lt;a href=&quot;https://gallery.easwee.net/&quot;&gt;gallery.easwee.net&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It is built on the new &lt;a href=&quot;https://remix.run/&quot;&gt;Remix&lt;/a&gt; framework that recently launched as opensource software. It was a pleasant developing experience - great framework 10/10 would recommend.&lt;/p&gt;
&lt;p&gt;Some of the shots in the gallery used to be part of my Instagram profile, but I ditched that all together and prefer to keep it here and expand &lt;a href=&quot;http://easwee.net/&quot;&gt;easwee.net&lt;/a&gt; a bit more.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>css filmstrip</title>
		<link href="https://www.easwee.net/css-filmstrip/"/>
		<updated>2022-01-10T00:00:00Z</updated>
		<id>https://www.easwee.net/css-filmstrip/</id>
		<content type="html">&lt;p&gt;2022 - what a time to be alive!&lt;/p&gt;
&lt;p&gt;I can make a container that looks like a filmstrip with a single CSS class.&lt;/p&gt;
&lt;p&gt;Either vertical - &lt;a href=&quot;https://codepen.io/easwee/pen/NWaOpRM&quot;&gt;yay a demo!&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.filmstrip-vertical {
  background-image: linear-gradient(
      to right,
      transparent 20px,
      black 20px,
      black calc(100% - 20px),
      transparent calc(100% - 20px)
    ),
    repeating-linear-gradient(#000, #000 20px, #fff 20px, #fff 40px);
  background-repeat: repeat-y;
  box-sizing: border-box;
  border-left: 20px solid black;
  border-right: 20px solid black;
  padding: 20px 40px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or horizontal - &lt;a href=&quot;https://codepen.io/easwee/pen/rNGqyLg&quot;&gt;another demo!&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.filmstrip-horizontal {
  background-image: linear-gradient(
      to bottom,
      transparent 20px,
      black 20px,
      black calc(100% - 20px),
      transparent calc(100% - 20px)
    ),
    repeating-linear-gradient(90deg, #000, #000 20px, #fff 20px, #fff 40px);
  box-sizing: border-box;
  border-top: 20px solid black;
  border-bottom: 20px solid black;
  padding: 40px 20px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I needed this today.&lt;/p&gt;
&lt;p&gt;You may need it tomorrow.&lt;/p&gt;
&lt;p&gt;Enjoy CSS.&lt;/p&gt;
&lt;p&gt;CSS is freedom.&lt;/p&gt;
&lt;p&gt;CSS is life.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>eventko service stopped</title>
		<link href="https://www.easwee.net/eventko-service-stopped/"/>
		<updated>2021-11-28T00:00:00Z</updated>
		<id>https://www.easwee.net/eventko-service-stopped/</id>
		<content type="html">&lt;p&gt;Due to Facebook being more and more persistent in blocking scrapers I will no longer maintain Eventko scraping project (&lt;a href=&quot;https://eventko.easwee.net/&quot;&gt;https://eventko.easwee.net&lt;/a&gt;), since it is way to much effort to keep it running for no profit. This was a handy tool for me and a few of my friends to have a nice and clean daily list of quality events happening in our city.&lt;/p&gt;
&lt;h3 id=&quot;a-short-development-history&quot; tabindex=&quot;-1&quot;&gt;A short development history &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/eventko-service-stopped/#a-short-development-history&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I first started Eventko as a service calling Facebook /events endpoint through their official API. As you may know Facebook (now Meta) removed the /events endpoint from their Graph API a few years ago. After rewriting the scraper to parse the &lt;a href=&quot;http://mobile.facebook.com/&quot;&gt;mobile.facebook.com&lt;/a&gt; that serves SSR content (or at least used to at the time), which was easier to parse, they started serving login walls that prevented scraping without authenticating. To avoid this I switched to Puppeteer and did the actual login flow, auth token storing and just simulated the user flow on the latest FB site. While this lasted for a while, eventually the server that hosts Eventko scraper got blacklisted. I took time again and rewrote it to scrape with rotating residential proxies. This works ok, but good residential proxies don&#39;t come as a free service and require a monthly fee, which I&#39;m not interested in subscribing to just to run a service used by 10 people. Using free public proxy lists is not an option, since those lists are already blacklisted and often contain offline or very slow entries.&lt;/p&gt;
&lt;p&gt;So until I get a new idea on how to serve a quality list of events I&#39;ll just keep it offline. At least it was a good project from which I learned a lot about scraping and bypassing anti-scraping measures.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>rage driven development</title>
		<link href="https://www.easwee.net/rage-driven-development/"/>
		<updated>2021-08-27T00:00:00Z</updated>
		<id>https://www.easwee.net/rage-driven-development/</id>
		<content type="html">&lt;p&gt;Rage Driven Development (RDD) can be a valuable technique when tackling larger legacy code refactors or making new trending tech work as promised by their documentation.&lt;/p&gt;
&lt;p&gt;Let&#39;s face it - sooner or later you introduce some legacy in your codebase and there comes a day when you finally get that management allowance that will enable you to start refactoring and tackling this tech debt. Or maybe you will use it to switch to that new praised library that supposedly solves all the problems you&#39;ve been having in the last 5 years. Both legit actions. There are many methodologies on how to tackle legacy refactors and changes, but I&#39;m here to talk about RDD.&lt;/p&gt;
&lt;p&gt;So you did your analysis, sketched it out, know the bad parts of it and you are now down to the last step - start refactoring the codebase.&lt;/p&gt;
&lt;p&gt;In order for RDD to really work, git blame is a must to use - you really have to see those people&#39;s names written on each line you&#39;re gonna be touching, so you can blame it on their family tree. A playlist of random songs that you would normally be playing at 3am under certain weird substances is another tool in your box. If you are not a smoker, you should become one - helps with the boring part while waiting for tests to pass. Let&#39;s dive into it.&lt;/p&gt;
&lt;p&gt;So it&#39;s monday and you have the next 14 days free of meetings - you should focus. To start we first need to move a few util functions to a proper lib - no problem it&#39;s just I/O - this should be easy. Ah, there is one that also has dependency on that service method, hmm, not good - who did this? Yeah, fuck that guy, freelancers suck anyway, glad he doesn&#39;t work for us anymore. Ok split it out, move to utils lib, refactor calls, refactor imports, fix any missing params, yeah just make it work. Done! 30 files changed, no biggie, commit. Fuck yeah, this is smooth! Uh, forgot tests - let&#39;s run affected - nah just run everything to be sure nothing broke. 3027 test suites running in jsdom gonna take some time - let&#39;s go for a cig.&lt;/p&gt;
&lt;p&gt;Fuuuu, 16 test suites failed, 46 tests failing. Piece of crap of tests, why do we write this crap anyway, it&#39;s not that it affects the amount of bugs those juniors generate. Also dafuq is up with Jest mocks. Ok let&#39;s fix this tests one by one, it&#39;s probably just mocks playing dirty anyway. 6 hours and many more curse words and outdated mock patterns and a lunch with a beer later: rerun failed tests - ok finally works. We moved and decoupled a few util functions. I still have it. Ah let&#39;s call it a day. Also fuck Jest again, what a piece of crap, should have just deleted those tests anyway.&lt;/p&gt;
&lt;p&gt;Week quickly zooms by and once you get in the rage routine nothing can stop you. You are a senior dev after all. In the morning you now salute people with cynicism and insults because your codebase is getting better and stronger - they should praise and tolerate you for that. You think I&#39;ve been binge-watching series for the last 15 years like you lazy peasants do after work? I get my coding patterns down, I polish them, I read all the best stuff, I prove people wrong in the comment sections of the most obscure internet blogs that San Francisco graduates just started to get a CV boost for their first job applications. I engange in the community and... ah shit.. dafuq is this... fucking typescript generics. Not that shit again. We are doing frontend React, it&#39;s not rocket science, why the fuck did we even install Typescript, it just slows the delivery of features. Javascript was designed without types in mind, just abuse it. Type-safety my ass. Those third world country freelancers and their generic types extending generic shit. No wonder nukes have been invented, they should have thrown more of that crap around - we would have Japan level work efficiency as a standard. Also I like anime, which reminds me I could throw up some random shitty songs like Shittyflute to boost the morale, gotta put that on loudspeakers, it&#39;s funny after all. What? You in a call? Fuck you, we have meeting rooms, I&#39;m doing refactor work here, we have no time for meetings - delivery of good code is my life now. Deal with it - it&#39;s friday afternoon after all, you should be going home anyway. Oh shit - it&#39;s Friday already! 678 files changed, 3 new libs created, stuff decoupled, antipatterns screamed at and removed - gotta explain some of this shit to others. Also someone should really start reviewing my MR. C&#39;mon I need 2 dev sacrifices that are gonna swipe through those 666 files until Monday, so QA can do a quick smoke test and we smack it into RC.&lt;/p&gt;
&lt;p&gt;Monday morning - no reviews - I have to be nice - rage driven development is only in my mind, when talking to people you have to be sweet to get stuff done. Can anyone pretty pleaseee check my MR - it solves and brings you all the best warez. Yes! Got 2 approvals, QA did a smoke test, nothing is broken - my refactor skills are still godlike - smack that into RC.&lt;/p&gt;
&lt;p&gt;&amp;quot;Your branch is 174 commits behind release-candidate.&amp;quot;&lt;/p&gt;
&lt;p&gt;Pft.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout release-candidate
git pull
git checkout -
git merge release-candidate
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;54 conflicts can’t be automerged.&lt;/p&gt;
&lt;p&gt;Fuck you lazy fucks ,I said on friday check and approve, why... now I hate you. Ok no worries I&#39;m pro.&lt;/p&gt;
&lt;p&gt;Solve conflicts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add .
git commit
ctrl-x
git push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Need re-approvals pretty please. The fuck everybody is in meetings. Am I the only one working here? Screw this I&#39;m off to lunch and a beer.&lt;/p&gt;
&lt;p&gt;&amp;quot;Your branch is 22 commits behind release-candidate.&amp;quot;&lt;/p&gt;
&lt;p&gt;Who the fuck did this? Who dared to get his MR in before my refactor during lunch - I have 678 touched files - this is no joke. You want this code in FIRST! Anyway - re-approve, I merged again.&lt;/p&gt;
&lt;p&gt;&amp;quot;Your branch is 31 commits behind release-candidate.&amp;quot;&lt;/p&gt;
&lt;p&gt;...raging in apathy now. Solved again. Merged. Pushed. Reapprove please.&lt;/p&gt;
&lt;p&gt;Bling!&lt;/p&gt;
&lt;p&gt;Email: &amp;quot;Pipeline for branch best-refactor failed.&amp;quot;&lt;/p&gt;
&lt;p&gt;How the fuck - some random test that I didn&#39;t even touch is now failing. Life is bad. Jest is bad. Why do we even use Javascript at all? This would have never happened if we&#39;d picked a proper language. Someone was talking about ELM. A delightful language for reliable web applications. My ass, those underpaid hipsters probably have it even worse. JS is freedom! And job security in a way. Ha I&#39;m glad after all. Ah yes all is good, some insanity never killed anybody I guess. He probably killed himself for other reasons. But let&#39;s debug the problem - ah easy - just Jest version update screwing some stuff up. Nothing I can&#39;t fix with sword and flames - rewrite that crappy test, it&#39;s legacy anyway, we know better now, hate that guy that wrote this if we had no HR, god what I would... mothe...&lt;/p&gt;
&lt;p&gt;Pipeline passing! Reapprove! Yes! Merged to RC!&lt;/p&gt;
&lt;p&gt;Oh god yes, tears, let&#39;s have a shot of schnapps, it&#39;s after 3PM anyway!&lt;/p&gt;
&lt;p&gt;This was the best sprint, code is now good, we learned so much, kudos to everyone for reviews and approvals, I&#39;m best, I&#39;m sane again. This team delivers! That guy FUCKS! Wait, who&#39;s that guy anyway? Is that the new guy? Oh, we have 3 new guys now? Didn&#39;t notice... Anyway, thank god we have Typescript or else this refactor would have never happened in 1 sprint worth of man-days, if it hadn&#39;t been for those types and interfaces... An Jest, you lovely Jest - so much functionality would have been broken if it wasn&#39;t tested. Was anybody doubting JS? Not me! Ah yes, we are good. I bet I could pass some nice knowledge to those freelancers if they were still around. Miss those guys.&lt;/p&gt;
&lt;p&gt;Life is good and when you leave for the weekend after the sprint ends, you have peace of mind and get home in a pure zen mood. After all, you already threw all the rage out at work.&lt;/p&gt;
&lt;p&gt;Rage Driven Development delivers.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Above article is satire and you should tackle big refactors with caution and care and always care for a good mood in your team. Also don&#39;t swear at work. Any resemblance to real world situations is purely casual.&lt;/em&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>gameboy css replica</title>
		<link href="https://www.easwee.net/gameboy-css-replica/"/>
		<updated>2021-02-06T00:00:00Z</updated>
		<id>https://www.easwee.net/gameboy-css-replica/</id>
		<content type="html">&lt;p&gt;Today was a rainy day.&lt;/p&gt;
&lt;p&gt;A replica of Nintendo GameBoy made with HTML and CSS.&lt;/p&gt;
&lt;iframe title=&quot;gameboy css replica&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/css-gameboy-replica/&quot; width=&quot;600&quot; height=&quot;600&quot; style=&quot;background-color: white;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Fork it here: &lt;a href=&quot;https://codepen.io/easwee/pen/yLVObNZ&quot;&gt;https://codepen.io/easwee/pen/yLVObNZ&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>customized keyboard</title>
		<link href="https://www.easwee.net/customized-keyboard/"/>
		<updated>2021-01-20T00:00:00Z</updated>
		<id>https://www.easwee.net/customized-keyboard/</id>
		<content type="html">&lt;p&gt;This xmas I wished for a silent tenkeyless mechanical keyboard with ISO layout, preferably with Slovenian keycaps. Turns out it’s not so easy to get one.&lt;/p&gt;
&lt;p&gt;I was digging into the custom mech keyboards community and the beautiful renders and build streams you can find around the net really get you hyped into getting a nice custom keyboard. But not for long, since you soon give up when you see everything is a group buy and has raffles and waitlists of 6-9 months before you get any parts delivered. And stuff gets really expensive really fast! It’s like Lego, just keyboards.&lt;/p&gt;
&lt;p&gt;I decided to take a middle path, which still took me a month, but I somehow put something together with some average spending (far from cheap).&lt;/p&gt;
&lt;p&gt;I ordered a &lt;a href=&quot;https://www.keychron.com/products/keychron-k8-wireless-mechanical-keyboard-german-iso-de-layout?variant=32259131998297&quot;&gt;Keychron K8&lt;/a&gt; with hot-swappable PCB and German ISO key layout. Close enough to what I need and from here I can start modifying.&lt;/p&gt;
&lt;p&gt;Keychron K8 has a metal top plate, there is also nice metal frame that goes all around the keyboard and adds some weight to it, but the bottom is plastic, and most mech keeb fans won’t like it, but that’s what you get for that price - I think it’s a solid deal.&lt;/p&gt;
&lt;p&gt;The first common mod I did was to add some foam inside the case that should improve the sound of it - it changes a bit but don’t expect wonders. I used the foam that comes with the packaging, 2 layers of it fit perfectly so you can still fit the PCB back in without problems.&lt;/p&gt;
&lt;p&gt;Second thing, I pulled out all the switches. My K8 came with Gateron Red switches. Since this is a hot-swap PCB no soldering is required, you can just unplug each switch (you get a switch puller tool with it, although it is quite low quality - if you plan to work with hot-swappable PCBs more definitely get a better tool).&lt;/p&gt;
&lt;p&gt;There is a never ending debate around switches, but all I wanted is silence (mechanical silence). Just get whatever you like and don’t listen much to opinions. At first I wanted to get Cherry MX silent blacks, but stock was low on those, so a few hours of mechanical keyboard typing ASMR videos later, I settled for ZealPC’s &lt;a href=&quot;https://zealpc.net/products/tealio&quot;&gt;Tealios&lt;/a&gt; linear switches (67g version 2). They seemed quiet enough for what I needed and it is also a quality switch - a bit pricey at 1$ per switch, but I was willing to spend money here - this was a treat for myself. Good, got them. Obviously you need to lube them - that makes them smooth, so I also ordered a 5ml capsule of Tribosys 3204 switch lubricant (supposed to be enough for 400 switches) and also some TX switch films. You put the films between the top and the bottom of the switch case to minimize the rattle. Lubing itself took a few hours to do the entire TKL (lucky I&#39;ve got a helper - thanks!).&lt;/p&gt;
&lt;p&gt;Since silence was my top priority, I also found bought a component called &lt;a href=&quot;https://uniqey.net/en/accessories/18/qmx-clips-plate-mount-110-pcs.&quot;&gt;QMX Clips&lt;/a&gt; from Unikey. They cancel the bottom-up hit of the switch stem supposedly reducing the noise by 10dB (I was skeptical while ordering, but they work). This makes a huge difference in how the keyboard sounds, but it also changes the typing experience a bit, since the key travel distance is now a bit shorter and some people may not like that. It’s fine for me and I intend to keep the clips on and I also like the softer typing experience. QMX Clips are specifically made for Cherry-style switch housings, so they will work with Zeal, although it was a huge effort to click them in place (never tried on Cherry - maybe that’s just how they normally fit). It helps if you first clip it on one side and wobble it a bit, then unclip and clip the other side and in the end push it down, so it clips on both sides. Also I was not able to fit the clips on the backspace, enter and shift key due to stabilizers not leaving enough space to fit the clip.&lt;/p&gt;
&lt;p&gt;For the keycaps, I found two beautiful sets that contained all the keys I needed for a Slovenian layout - &lt;a href=&quot;https://en.zfrontier.com/products/gmk-yugo&quot;&gt;GMK Yugo&lt;/a&gt; and &lt;a href=&quot;https://en.zfrontier.com/products/kat-napoleonic&quot;&gt;KAT Napoleonic&lt;/a&gt;. Obviously both are sold out, so no luck there. I went with an alternative and purchased a set of blank keycaps to which I planned to add a printed set of Slovenian symbols on the south side of the cap. A clear look from top, but still have the symbols on the south side of the keycap. In the end I dropped the idea since blanks looked so good and I would probably have a lot of trouble aligning the stickers anyway.&lt;/p&gt;
&lt;p&gt;So I ended up with a very silent blank ISO keyboard, fully lubed and equipped with QMX clips. I don’t really like the color of the keycaps, they look a bit boring, but they will do fine until I find a set I like and that fits my needs.&lt;/p&gt;
&lt;p&gt;Total price of all parts was ~200€ without shipping costs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2021_01_20_keeb.jpg&quot; alt=&quot;Blank keyboard is blank&quot; /&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>testing domain level localization on localhost</title>
		<link href="https://www.easwee.net/testing-domain-level-localization-on-localhost/"/>
		<updated>2020-12-30T00:00:00Z</updated>
		<id>https://www.easwee.net/testing-domain-level-localization-on-localhost/</id>
		<content type="html">&lt;p&gt;This approach is useful when you are making a multilanguage website that starts localizing already at domain level - meaning that each domain caters to it&#39;s own locale.&lt;/p&gt;
&lt;p&gt;First add custom local domains to your hosts file. Make them unique and make sure they are not registered domains:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1       mylocaldomainxyz.com
127.0.0.1       mylocaldomainxyz.ch
127.0.0.1       mylocaldomainxyz.fr
127.0.0.1       mylocaldomainxyz.de
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you pick registered domain names like &lt;a href=&quot;http://test.com/&quot;&gt;test.com&lt;/a&gt; or similar, &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&quot;&gt;HSTS&lt;/a&gt; will kick in and it will force redirect your local domain to https. In Firefox you can disable HSTS list preloading by setting &lt;code&gt;network.stricttransportsecurity.preloadlist&lt;/code&gt; to false in about:config, I couldn&#39;t manage to disable it in latest Chrome - also didn&#39;t bother much since using unique local domains is way more simple.&lt;/p&gt;
&lt;p&gt;Now I want to use this with latest NextJS (at the time of writing v10).&lt;/p&gt;
&lt;p&gt;Simple enough it can now all be configured in &lt;code&gt;next.config.js&lt;/code&gt; without the need of any additional libraries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  i18n: {
    http: true,
    locales: [&amp;quot;en&amp;quot;, &amp;quot;fr&amp;quot;, &amp;quot;de&amp;quot;, &amp;quot;fr-CH&amp;quot;, &amp;quot;it-CH&amp;quot;, &amp;quot;de-CH&amp;quot;],
    defaultLocale: &amp;quot;en&amp;quot;,
    domains: [
      {
        http: true,
        domain: &amp;quot;mylocaldomainxyz.com:3000&amp;quot;,
        defaultLocale: &amp;quot;en&amp;quot;,
      },
      {
        http: true,
        domain: &amp;quot;mylocaldomainxyz.de:3000&amp;quot;,
        defaultLocale: &amp;quot;de&amp;quot;,
      },
      {
        http: true,
        domain: &amp;quot;mylocaldomainxyz.fr:3000&amp;quot;,
        defaultLocale: &amp;quot;fr&amp;quot;,
      },
      {
        http: true,
        domain: &amp;quot;mylocaldomainxyz.ch:3000&amp;quot;,
        defaultLocale: &amp;quot;de-CH&amp;quot;,
        locales: [&amp;quot;fr-CH&amp;quot;, &amp;quot;it-CH&amp;quot;],
      },
    ],
  }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The entry for Swiss uses multiple locales - default locale when hitting &lt;code&gt;mylocaldomainxyz.ch&lt;/code&gt; will be German; French and Italian versions will be available on &lt;code&gt;mylocaldomainxyz.ch/fr-CH&lt;/code&gt; and &lt;code&gt;mylocaldomainxyz.ch/it-CH&lt;/code&gt;. Language detection is triggered only when hitting root index page. You can rewrite the urls if &lt;code&gt;-CH&lt;/code&gt; suffix looks like overhead, but you will need to have the locales defined correctly, since &lt;code&gt;de&lt;/code&gt; locale is not same as &lt;code&gt;de-CH&lt;/code&gt;, ISO standard applies. For more configuration and usage options check &lt;a href=&quot;https://nextjs.org/docs/advanced-features/i18n-routing&quot;&gt;NextJS Internationalized Routing docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Optional:&lt;/p&gt;
&lt;p&gt;If you are running your apps on a custom port (3000 is popular) and find it annoying to have port appended to your custom url, you can redirect default port 80 to whatever port you want using iptables:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now access your app directly without specifying port: &lt;a href=&quot;http://mylocaldomainxyz.fr/&quot;&gt;http://mylocaldomainxyz.fr&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To remove the output redirect when you don&#39;t need it anymore, first get numbered list of output redirects:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -t nat -v -L OUTPUT -n --line-number
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A list of redirects is returned.&lt;/p&gt;
&lt;p&gt;Check which line number is your custom redirect and remove it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -t nat -D OUTPUT 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Entry with line number 3 is removed.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>blyat – brickout-like game done with godot engine</title>
		<link href="https://www.easwee.net/blyat-brickout-like-game-done-with-godot-engine/"/>
		<updated>2020-10-02T00:00:00Z</updated>
		<id>https://www.easwee.net/blyat-brickout-like-game-done-with-godot-engine/</id>
		<content type="html">&lt;p&gt;Tinkering with Godot features, this was a good learning project to get a small game done from start to end. 3 levels total.&lt;/p&gt;
&lt;p&gt;HTML5 wasm build for preview:&lt;br /&gt;
&lt;a href=&quot;https://sample.easwee.net/blyat/blyat.html&quot;&gt;https://sample.easwee.net/blyat/blyat.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Forkable repository – feel free to grab:&lt;br /&gt;
&lt;a href=&quot;https://github.com/easwee/blyat&quot;&gt;https://github.com/easwee/blyat&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>devlog - godot dump</title>
		<link href="https://www.easwee.net/devlog-godot-dump/"/>
		<updated>2020-09-16T00:00:00Z</updated>
		<id>https://www.easwee.net/devlog-godot-dump/</id>
		<content type="html">&lt;p&gt;It’s been a few months since I’ve started playing with &lt;a href=&quot;https://godotengine.org/&quot;&gt;Godot engine&lt;/a&gt; and it has been an enjoyable experience.&lt;/p&gt;
&lt;p&gt;Here I’m dumping a list of “aha moments” that I’ve pilled up while learning how to use it, hoping come handy to some other newcomer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;signals are Godot’s events; they are locally scoped to the component defining/using them; defining them in an auto-loaded singleton is a pattern I found useful using them as a cross-project event system&lt;br /&gt;
Vector2 has methods for most of the stuff you think you need to write a function for (need a distance between two points? no need for writing it from scratch – use distance_to), always check the latest docs&lt;/li&gt;
&lt;li&gt;KinematicBody is easier to use to implement fine tuned player movement; compared to Rigidbody physics that will require you to understand forces and may be a pain to optimize into a smooth and simple game-play experience – always evaluate your approaches&lt;br /&gt;
multi-line comment shortcut: ctrl + k&lt;/li&gt;
&lt;li&gt;if you have problems with imported asset types always check the “import” tab and hit “Reimport” with appropriate asset type selected&lt;br /&gt;
you can reduce your build size by compiling Godot from source and omitting unused features&lt;br /&gt;
Godot’s filesystem dock only shows built-in resources. JSON files are not built-in resources (&lt;a href=&quot;https://godotengine.org/qa/30357/json-file-not-showing-in-editor&quot;&gt;https://godotengine.org/qa/30357/json-file-not-showing-in-editor&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;when debugging collision shapes enable: Debug -&amp;gt; Visible Collision Shapes&lt;/li&gt;
&lt;li&gt;when debugging mobile touch input enable: Project -&amp;gt; Project settings -&amp;gt; Input Devices -&amp;gt; Emulate touch from mouse&lt;/li&gt;
&lt;li&gt;mouse_entered() and mouse_exited() also trigger with touch events&lt;/li&gt;
&lt;li&gt;when exporting _.json, _.csv, *.txt, etc… data files, you need to specify file types in Export -&amp;gt; Resources -&amp;gt; Filters to export non-resource files/folders&lt;/li&gt;
&lt;li&gt;need to setup for Android? This video tutorial is spot on: &lt;a href=&quot;https://www.youtube.com/watch?v=6646oo2YSBY&quot;&gt;https://www.youtube.com/watch?v=6646oo2YSBY&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;but there can be a trick with older Android device running on Adreno based GPUs – they don’t support GLES3. Go to Project settings -&amp;gt; Rendering/Quality -&amp;gt; Render Name and set it to GLES2; Project settings -&amp;gt; Rendering/Vram Compression and enable Import Etc&lt;/li&gt;
&lt;li&gt;you can have custom-shaped clickable buttons using BMP click masks and TextureButton (white clickable; black non-clickable; BMP must be 24-bit)&lt;/li&gt;
&lt;li&gt;when you save the BMP into Godot folder it imports it as Texture – reimport it with correct format&lt;br /&gt;
mobile size of 1920×1080 works quite well; also try to play with stretch/size in Project settings&lt;br /&gt;
portrait mode can also be forced in Project settings&lt;/li&gt;
&lt;li&gt;while we are at Project settings – don’t forget they have a “search” input&lt;br /&gt;
RichTextLabel support BB code formatting – brings back bulletin board memories, but is also a quite handy and useful text formatting method (at least I liked it)&lt;/li&gt;
&lt;li&gt;there is also VisualScript inside Godot for “non-coders” – I don’t use it, but interesting&lt;/li&gt;
&lt;li&gt;last bullet for this dump – but probably the most important: if you want to get anything done with Godot stop worrying about your code and architecture and just make your idea work smoothly, make it playable, polish the details for the user, not for developer, get it done, ship it out or you will never release anything if you just keep polishing the code. The engine is built for mess and node/component isolation allows you to keep even messy code manageable. If you are coming into Godot with years of development experience from other areas, over-optimizing/organizing your code can be a trap of getting frustrated with it – enjoy it – it’s GAME development – should be playful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hope this knowledge dump comes handy for some lonely indie game dev newbie soul. And let’s hope there is a part II in a few months.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>convert large .pbf to .map using osmosis</title>
		<link href="https://www.easwee.net/convert-large-.pbf-to-.map-using-osmosis/"/>
		<updated>2020-09-12T00:00:00Z</updated>
		<id>https://www.easwee.net/convert-large-.pbf-to-.map-using-osmosis/</id>
		<content type="html">&lt;p&gt;Prerequisites:&lt;br /&gt;
– osmosis&lt;br /&gt;
– mapsforge-map-writer plugin&lt;br /&gt;
– as much free HD space as possible&lt;/p&gt;
&lt;p&gt;Download &lt;a href=&quot;https://github.com/openstreetmap/osmosis&quot;&gt;Osmosis&lt;/a&gt; and &lt;a href=&quot;https://github.com/mapsforge/mapsforge/blob/master/docs/Getting-Started-Map-Writer.md&quot;&gt;mapsforge-map-writer plugin&lt;/a&gt; as specified in their docs (as of writing this I used &lt;code&gt;osmosis-0.48.2&lt;/code&gt; and &lt;code&gt;mapsforge-map-writer-master-20200902.143123-360-jar-with-dependencies.jar&lt;/code&gt;). Inside of &lt;code&gt;/osmosis&lt;/code&gt; root folder create a &lt;code&gt;/plugins&lt;/code&gt; folder and put &lt;code&gt;mapsforge-map-writer-&amp;lt;version&amp;gt;-jar-with-dependencies.jar&lt;/code&gt; inside it.&lt;br /&gt;
You can run Osmosis from &lt;code&gt;/bin&lt;/code&gt; folder – if any additional plugins are present inside the &lt;code&gt;/plugins&lt;/code&gt; folder, you will be able to use additional options when running commands.&lt;/p&gt;
&lt;p&gt;By default Osmosis runs in-memory (default &lt;code&gt;type=ram&lt;/code&gt;), however for large map conversions, it can run out of memory and even allocating more (J&lt;code&gt;AVACMD_OPTIONS=-Xmx8G&lt;/code&gt; or more) is sometimes not enough (throwing &lt;code&gt;OutOfMemory: Java heap space&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Switching to &lt;code&gt;type=hd&lt;/code&gt;, it offloads the temporary data to your hard drive:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;osmosis --rb file=your_map_file.pbf --mapfile-writer file=converted_file_name.map type=hd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Above command will store temporary files in your OS temporary folder location instead of using memory, but the process itself takes more data, so you need a larger amount of free space compared to running &lt;code&gt;type=ram&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Note: You can run &lt;code&gt;java -XshowSettings&lt;/code&gt; and check &lt;code&gt;java.io.tmpdir&lt;/code&gt; value to see your temporary files location. In case your drive is full, it can be changed to another drive if you need more space. To convert a ~2.5GB .pbf file I needed ~50.0GB of free space for temporary files. I did the conversion on a Windows 10 machine, but it works the same on Linux.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>hexagons in a spiral grid</title>
		<link href="https://www.easwee.net/hexagons-in-a-spiral-grid/"/>
		<updated>2020-07-12T00:00:00Z</updated>
		<id>https://www.easwee.net/hexagons-in-a-spiral-grid/</id>
		<content type="html">&lt;p&gt;Had some obsession with hexagons lately while planning for a hobby project I want to build some time in the future, result of it is this trippy rainbows demo, drawing hexagons in a spiral grid.&lt;/p&gt;
&lt;p&gt;The longer you let it run, more trippy it gets 🙂&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sample.easwee.net/rainbow-hexagons/&quot;&gt;Click to run&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2020_07_12_spiral.png&quot; alt=&quot;Image of a hexagon in a spiral grid&quot; /&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>dom tribute to conway</title>
		<link href="https://www.easwee.net/dom-tribute-to-conway/"/>
		<updated>2020-05-05T00:00:00Z</updated>
		<id>https://www.easwee.net/dom-tribute-to-conway/</id>
		<content type="html">&lt;p&gt;A small and rather low-performance DOM tribute to John Conway and his iconic Game of life.&lt;/p&gt;
&lt;p&gt;The known rules are:&lt;br /&gt;
1.) Any live cell with fewer than two live neighbours dies, as if by underpopulation.&lt;br /&gt;
2.) Any live cell with two or three live neighbours lives on to the next generation.&lt;br /&gt;
3.) Any live cell with more than three live neighbours dies, as if by overpopulation.&lt;br /&gt;
4.) Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.&lt;/p&gt;
&lt;p&gt;The fascinating aspect of the game is that there is no known algorithm that can predict for how many generations an initial setup will survive. It could be 1, 10 or 10000, it could run for 1.000.000 generations and still go extinct in the 1.000.001.&lt;/p&gt;
&lt;iframe title=&quot;dom tribute to conway&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/dom-conway/&quot; width=&quot;400&quot; height=&quot;370&quot;&gt;
&lt;/iframe&gt;</content>
	</entry>
	
	<entry>
		<title>PDF invoice generator</title>
		<link href="https://www.easwee.net/pdf-invoice-generator/"/>
		<updated>2019-09-28T00:00:00Z</updated>
		<id>https://www.easwee.net/pdf-invoice-generator/</id>
		<content type="html">&lt;p&gt;As mentioned in my previous post I’ve been trying out Svelte for the last few weeks. Considering making the classic TO-DO app demo sounded a bit boring, I decided to build something more useful for myself – so I’ve put together an invoice generator that allows me to generate invoices for my company faster, compared to using a spreadsheet, as I used to do it until now.&lt;/p&gt;
&lt;p&gt;It’s free to use and GDPR compliant, since all of the data you input is stored into IndexedDB, which lives inside your own browser only and is never sent to any server. A cheap but fast solution (avoiding to do any auth) . Once you set up the template, you will have it ready each time you return back. So you just update the client and invoice rows and generate a new PDF.&lt;/p&gt;
&lt;p&gt;Check it out on this subdomain: &lt;a href=&quot;https://invoicer.easwee.net/&quot;&gt;https://invoicer.easwee.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hope you find it useful.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>oneplus one OS upgrade</title>
		<link href="https://www.easwee.net/oneplus-one-os-upgrade/"/>
		<updated>2019-09-05T00:00:00Z</updated>
		<id>https://www.easwee.net/oneplus-one-os-upgrade/</id>
		<content type="html">&lt;p&gt;It’s been past 5 years since OnePlus released their first model One and this is still my main phone today. You may question why, but in reality except boosting specs and increasing camera picture quality, nothing impressive really happened in the phone tech, that would make me want to buy a new cutting edge phone (if I want quality pictures I use my DSLR, but also as a software developer I already have access to all the latest phones at work, so I really have no incentive of buying a new personal phone, not even just for fun).&lt;/p&gt;
&lt;p&gt;But as time passes, I’ve also got a challenge idea – how long can I keep using this phone without having to give up any new app features, deal with slowdowns or let’s hope not breaking it (although I could replace the screen just for the sake of seeing how far I can go) .&lt;/p&gt;
&lt;p&gt;In the last year I’ve noticed slowdowns in certain apps. The phone was running on Cyanogen 12.1 which is built on top of Android 5.1 and this was the last update I did (Cyanogen development has stopped 2 years ago – it’s a discontinued project).&lt;/p&gt;
&lt;p&gt;As time goes by, we now have Android 10 out and major app developers started dropping support for Android 5. While I try to not be an app power user, there are a few apps I use daily and one of them is Revolut, that is my main way of payment for online shopping and also lunch bill splitting with work colleagues. Recently (Nov 13th, 2019) they also dropped support for Android 5 and there is really nothing to do but upgrade your phone OS (I obviously support this – we all hate those IE users, that still haven’t upgraded – and I’m a similar case, just in Android world). Considering I kinda want to keep using this handy app and still don’t want to buy a new phone, I started searching for alternatives to do a ROM upgrade for my OnePlus One.&lt;/p&gt;
&lt;p&gt;Most reviews praised LineageOS as being a solid system that focuses on performance and simplicity, while still supporting all of the Google stuff without hassle – and must say that I’m very satisfied with it.&lt;/p&gt;
&lt;p&gt;Install process is well documented and their website provides all of the info you need to get it running (took me about 1h to wipe and format, read through docs and install the new OS – just note I have adb and entire android development env already set up on work station – it may take a bit more for someone doing a custom ROM install for the first time).&lt;/p&gt;
&lt;p&gt;Currently the version 16 runs on Android 9 and this brings a nice performance boost in terms of navigation and UX, also another very welcome feature is their “Extreme power saver” mode, which will extend daily duration of the now 5 years old battery that has seen better days (a full charge is needed every 1.5 days now – used to last above 3 days in beginning – all considering my personal usage, I would say some users would drain it faster). Add in nice personalization options as far as UX and theming goes and I think you have all you really need in a phone OS. Oh and enhanced privacy (Privacy Guard feature let’s you control what your apps do in background with a wider set of options)!&lt;/p&gt;
&lt;p&gt;So if you are looking for a decent Android custom ROM, I would recommend to give LineageOS a try.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>migration</title>
		<link href="https://www.easwee.net/migration/"/>
		<updated>2019-09-01T00:00:00Z</updated>
		<id>https://www.easwee.net/migration/</id>
		<content type="html">&lt;p&gt;After 12 years since &lt;a href=&quot;http://easwee.net/&quot;&gt;easwee.net&lt;/a&gt; was first registered, I decided that it’s time to move from a managed to a dedicated hosting to support new exciting projects. Migration is now done, including all of the subdomains and hosted files, so easwee’s network now runs on a more mature setup.&lt;/p&gt;
&lt;p&gt;Stay tuned for interesting new future projects!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>brief history of Slovenian game development</title>
		<link href="https://www.easwee.net/brief-history-of-slovenian-game-development/"/>
		<updated>2019-07-28T00:00:00Z</updated>
		<id>https://www.easwee.net/brief-history-of-slovenian-game-development/</id>
		<content type="html">&lt;p&gt;In a country with a population of just 2 million people you can quickly figure out that not many are developers and if you take those that are, you can find out that game developers are even more uncommon, since earning your everyday bread in this wild industry is similar to playing lottery. Yet, there has always been a lively game development scene in Slovenia and some players managed to make it big. Out of curiosity I researched it a bit, trying to collect some of the pieces together into a brief history of Slovenian game development, since I couldn’t find anything around.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_07_28_si_gamedev_1_kontrabant.gif&quot; alt=&quot;Image of Kontrabant&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In a country with a population of just 2 million people you can quickly figure out that not many are developers and if you take those that are, you can find out that game developers are even more uncommon, since earning your everyday bread in this wild industry is similar to playing lottery. Yet, there has always been a lively game development scene in Slovenia and some players managed to make it big. Out of curiosity I researched it a bit, trying to collect some of the pieces together into a brief history of Slovenian game development, since I couldn’t find anything around.&lt;/p&gt;
&lt;p&gt;It all started in June 1984 when Kontrabant was published by Žiga Turk and Matevž Kmet. It was a text adventure game, costing 700 Yugoslavian dinars, and it ran on ZX Spectrum. Game theme was quite popular for that time in history – build your own computer by smuggling parts into Yugoslavia. The publisher – Radio Študent (student radio) also ran a prize contest at the time, where the best smugglers would submit letters by mail, containing passwords, number of steps and score points and a short funny write-up on how it was playing the game. The main prize was the ZX Spectrum which was awarded by a coin toss between the first two guys who finished the game – Iztok Saje as first to solve it and Peter Sokolov as second (as reported by the magazine Moj Mikro, November 1984, Peter won the ZX and Iztok got a “Zlatorog” sponsor package – probably beer considering what the company produced).&lt;/p&gt;
&lt;p&gt;A sequel followed in December the same year with Kontrabant 2. Later on Žiga Turk in Ciril Kraševec created the publishing company Xenon, which released a few more Spectrum games – Smrkci (Smurfs), Eurorun and Bajke, together with Croatian publisher Suzy Soft from Zagreb, which was another adventure and treasure hunting game based on stories from Slovenian writer and historian Janez Trdina – Bajke in povesti o Gorjancih (Tales and stories about Gorjanci).&lt;/p&gt;
&lt;p&gt;The next mention is a game called Zakladi Slovenije (Treasures of Slovenia) by Matej Kurent, which came out in October 1986 and was the first self-published game (indie), costing 1200 DIN. By playing the game you got to know Slovenia better, while you had to find a ship, sail and radar in order to be able to sail away (a bit silly since Slovenia has a lot of beautiful landmarks, but you know – abroad is always more interesting than at home).&lt;/p&gt;
&lt;p&gt;The Commodore 64 scene was mostly made of pirated games or copies of popular games. Pirated games were advertised in magazines and even broadcasted over national radio for anyone free to record. While there were various demos and programs written, I couldn’t find anything major in Commodore gaming.&lt;br /&gt;
In 1989 Arxel Tribe was founded by Matjaž Požlep and Diego Zanco with the initial idea of producing computer generated images. They made several TV shows and opened a Paris based 3D subsidiary and later expanded into an international collaboration switching to game production in 1996. After the first successful game release in France with Pilgrim (250.000 units sold, published by Infogrames), they had their first international breakthrough of the studio in 1998 with Ring: The Legend of the Nibelungen published under Cryo, which sold an amazing 700.000 copies — a lot at the time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_07_28_si_gamedev_3_ring.jpg&quot; alt=&quot;Image of Arxel Tribe The Legend of the Nibelungen box cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ring is an interactive science fiction opera adventure, a 3D graphical adventure set in the 40th century that follows the story of Richard Wagner’s epic opera Ring of the Nibelungen. As one of four characters, the player must conclude the legend of the Ring and restore humanity to its rightful place as leaders of the universe by defeating four evil powers.&lt;/p&gt;
&lt;p&gt;In 1999 Arxel Tribe became a video game publisher releasing games in 35 countries developed by fellow studios based in Europe, North America and Korea, along its own creations. The company was sold in 2001 to one of its clients, CTO S.p.A., Italy’s largest video game distributor.&lt;/p&gt;
&lt;p&gt;Arxel Tribe in a way paved the way for ZootFly, a game studio founded in 2002 by Boštjan Troha, Denis Rožaj and David Pangerl, who came from Arxel Tribe’s team. One of the more know titles developed was Panzer Elite Action: Fields of glory. It’s a story of three tank commanders and their crew. The German commander, encouraged by easy successes in Poland and France, moves on to the Eastern Front and the brink of victory with the taking of Stalingrad. Players meet the Russian commander in desperate straits as he helps defend Stalingrad, and follow him as the tide turns against the Germans and he joins the massive battle of Kursk. The American commander enters the war at the Normandy Beachhead on D-Day. After the struggle for the Bocage, he defends against the German outbreak at the Battle of the Bulge and then drives on to the victorious crossing of the Rhine. Game was released on PC and the Xbox.&lt;/p&gt;
&lt;p&gt;Another well selling title done in the later years was Marlow Briggs and the Mask of Death, co-developed with Microsoft on a budget of $5 million, bringing in $45 million.&lt;/p&gt;
&lt;p&gt;Zootfly also invested a lot of time into research of psychometrics – building psychological profiles of players using their inputs and adapting the game accordingly. For example, a game could have three distinctly different conclusions and the psychometrics engine would pick the right one for the player. The goal was to avoid unwanted endings, as the game would hopefully have enough feedback to deliver what the player wanted. The immediate reactions of the game would work on positive feedback. If the player were cerebral, they would receive more cerebral challenges; if violent, more violence.&lt;/p&gt;
&lt;p&gt;Immediate responses could improve the usability/learning curves as well. For example, if the player were to quick-save often, the game might be too difficult, and if they were to not discover many rooms in a time period, they might be lost and need additional stimuli to proceed. If they were too fast and the game was not fun anymore, they might require more interesting obstacles.&lt;/p&gt;
&lt;p&gt;In the end this was probably also what sparkled interest for the acquisition of the Zootfly by a company that made luxury casino and gaming products, with hopes that this could be integrated into their machines.&lt;/p&gt;
&lt;p&gt;2005 brought us Dugout Online a free to play online football manager browser game developed by Melderon. I remember playing this game at the time and compared to the Football Manager series by Sports Interactive, Dugout used auto-generated and country-localized player names (probably to avoid licensing), which brought a lot of fun into the game since often the name and surname combinations sounded silly, which added extra fun when buying and selling players (that also aged with season) and also got you a bit emotionally attached to them. Worth checking out if you are a football enthusiast, it is still running today.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_07_28_si_gamedev_2_bosh.jpg&quot; alt=&quot;Image of Bosh - Linerider main character&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In 2006, in times when Flash games were still thriving, you may remember the global phenomena game Line Rider done by Boštjan Čadež and released first on DeviantArt in September 2006. The game itself is pretty simple, you are presented with a blank white space on which you can draw lines where Bosh, the main character, will ride with his sledge. This opens up millions of options for the user to create tracks and exploit the game physics in every possible way you can imagine. On July 1, 2008, the original Flash version was replaced by a new one written in Silverlight. It included a new feature that allowed people to send tracks to other people via Windows Messenger. On October 23, 2009, this was replaced by Beta 3, which has the option to use dual players, a camera, trapdoor and deceleration lines. A second release of the game also followed on Nintendo DS and Wii in 2008. It definitely became a cult game, which got featured all over the net and also won the Best Webtoy of 2006 award.&lt;/p&gt;
&lt;p&gt;Moving forward to 2008 a new company called Actalogic was founded by David Pangerl from Zootfly. With a small team, which made a few different simulator games in the begining (Skyscraper simulator, Tank simulator, Hotel Empire), their first major success came in 2010 with the release of Agrar Simulator on Steam, which sold quite well in its early days. The company had some ups and downs, but they are still kicking today with the release of Hypernova: Escape from Hadea in 2017, a tower defense type of game.&lt;/p&gt;
&lt;p&gt;With the rise of Android phones and mobile gaming we got more attempts of trying to break through in this field too.&lt;/p&gt;
&lt;p&gt;The next big player – Outfit7 – started their journey in 2009. Samo and Iza Login founded it with the idea to enter the business of mobile apps, but soon turned into mobile gaming, when they developed a children’s app called Talking Tom Cat. It is a tamagotchi-like game, where you feed and pet your 3D cartoon-ish cat, but with an interesting feature where you can use a voice input to talk to it and it will repeat you words with a high pitched voice. Kids loved it and it sparkled a huge success and soon Talking Tom and friends followed, which added a few new characters into the game. Talking Tom and Friends reached 300 million downloads 19 months after its launch. The app franchise then went on to hit one billion downloads in June 2013 and reached over 230 countries worldwide. Later they expanded the story by releasing YouTube singles, various merchandise and an animated series in collaboration with Disney. The company sold in 2017 to the chinese for an incredible sum of $1 billion, but they still continue making new games.&lt;/p&gt;
&lt;p&gt;An interesting case in indie scene is Bojan Klabjan with his Matches puzzle game. In 2015 he wrote the game out of boredom at his daily job. The game is as simple as it gets – solve logical and mathematical problems by adding or removing matches from patterns on screen. The games keeps the user engaged with over 1000 different levels. Level difficulty does not increase with each new level, but rather keeps the user engaged and entertained by mixing easy and hard levels through the game progression. This way an average user actually completes the game in approximately 14 days. The game was translated in 16 different languages, very well received by the public and quickly reached 10 million downloads, which allowed him to quit his job as bank sysadmin and concentrate on what he really enjoys doing.&lt;/p&gt;
&lt;p&gt;Motiviti has been around since 2009, but I would like to specifically expose their currently-in-development game Elroy and the aliens. Elroy and the aliens is an adventure action puzzle game that reminds me a bit of Monkey Island as far as graphics go, and while it has been in development since 2015, the creators are promising a release soon (end of 2018, most probably 2019 considering they are putting in the free hours besides their regular projects), but for all the fans of this forgotten genre the wait is worth it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_07_28_si_gamedev_4_elroy.jpg&quot; alt=&quot;Image of Elroy and the aliens&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There has already been a huge wave of good feedback from Steam community and getting green-lit was the first step. The game is running on Unity engine and features amazing hand-drawn and colorful graphics and they are promising us a good dose of humor including voice acting. I’ll be keeping an eye on it since there hasn’t been a game release of this type for a long time now.&lt;/p&gt;
&lt;p&gt;Note: the following write-up was put together purely from my knowledge of the Slovenian game dev scene and from some minor research I did online in order to cover the early years. Link references can be found below for extra info. If you think that any major player is missing (early commodore scene? I was a kinder-garden kid at the time) contact me and I’ll be glad to expand the article with merits.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://retrospec.sgn.net/users/tomcat/yu/ZX_list.php&quot;&gt;http://retrospec.sgn.net/users/tomcat/yu/ZX_list.php&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Arxel_Tribe&quot;&gt;https://en.wikipedia.org/wiki/Arxel_Tribe&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.mobygames.com/game/ring-the-legend-of-the-nibelungen&quot;&gt;https://www.mobygames.com/game/ring-the-legend-of-the-nibelungen&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/ZootFly&quot;&gt;https://en.wikipedia.org/wiki/ZootFly&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.linerider.com/&quot;&gt;https://www.linerider.com/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Line_Rider&quot;&gt;https://en.wikipedia.org/wiki/Line_Rider&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.deviantart.com/fsk/art/Line-Rider-beta-40255643&quot;&gt;https://www.deviantart.com/fsk/art/Line-Rider-beta-40255643&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.actalogic.com/&quot;&gt;http://www.actalogic.com/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://store.steampowered.com/app/650750/&quot;&gt;https://store.steampowered.com/app/650750/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.delo.si/druzba/delova-borza-dela/z-racunalniskimi-igrami-na-svetovni-trg.html&quot;&gt;https://www.delo.si/druzba/delova-borza-dela/z-racunalniskimi-igrami-na-svetovni-trg.html&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Outfit&quot;&gt;https://en.wikipedia.org/wiki/Outfit&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.dugout-online.com/&quot;&gt;https://www.dugout-online.com&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.klabjan.movethematchespuzzles&amp;amp;hl=en_US&quot;&gt;https://play.google.com/store/apps/details?id=com.klabjan.movethematchespuzzles&amp;amp;hl=en_US&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.rtvslo.si/rckc/slo/druzba/devet-milijonov-prenosov-iger-z-vzigalicami-in-direktorska-placa-za-ustvarjalca/367790&quot;&gt;https://www.rtvslo.si/rckc/slo/druzba/devet-milijonov-prenosov-iger-z-vzigalicami-in-direktorska-placa-za-ustvarjalca/367790&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.motiviti.com/&quot;&gt;https://www.motiviti.com/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://elroythegame.com/&quot;&gt;https://elroythegame.com/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://javka.mn3njalnik.com/igrovje/slovenska-dogodivscina-na-obzorju-elroy-and-the-aliens/&quot;&gt;http://javka.mn3njalnik.com/igrovje/slovenska-dogodivscina-na-obzorju-elroy-and-the-aliens/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.slogamedev.net/&quot;&gt;http://www.slogamedev.net/&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>rendering 10.000 svg icons</title>
		<link href="https://www.easwee.net/rendering-10.000-svg-icons/"/>
		<updated>2019-05-08T00:00:00Z</updated>
		<id>https://www.easwee.net/rendering-10.000-svg-icons/</id>
		<content type="html">&lt;p&gt;A quick performance test on what is the best approach to render 10.000 SVG icons.&lt;/p&gt;
&lt;p&gt;Tests were done in Chrome version 79.0.3945.117 (Official Build) (64-bit) running on ThinkPad x270 16GB RAM.&lt;/p&gt;
&lt;p&gt;Same tests done in Firefox actually render SVGs much faster – so Firefox seems to have a better handler for SVG rendering.&lt;/p&gt;
&lt;p&gt;Test files are linked for each test so you can try them on your own machine.&lt;/p&gt;
&lt;p&gt;1.) All inline – AWFUL performance (&lt;a href=&quot;https://sample.easwee.net/svg-10000/inline.html&quot;&gt;Inline test file&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_05_08_svg_test_1_inline.png&quot; alt=&quot;Svg test 1 results&quot; /&gt;&lt;/p&gt;
&lt;p&gt;2.) Inline using symbol – BEST when you don’t want extra requests (&lt;a href=&quot;https://sample.easwee.net/svg-10000/inline-symbol-1.html&quot;&gt;Inline with 1 symbol&lt;/a&gt; | &lt;a href=&quot;https://sample.easwee.net/svg-10000/inline-symbol-2.html&quot;&gt;Inline with multiple symbols&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_05_08_svg_test_2_inline_symbol.png&quot; alt=&quot;Svg test 1 results&quot; /&gt;&lt;/p&gt;
&lt;p&gt;3.) External – ABSOLUTE FASTEST to render but makes extra request for each file so that may slow it down when many icons are used (&lt;a href=&quot;https://sample.easwee.net/svg-10000/external-1.html&quot;&gt;External with 1 svg&lt;/a&gt; | &lt;a href=&quot;https://sample.easwee.net/svg-10000/external-2.html&quot;&gt;External with multiple SVGs&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_05_08_svg_test_3_external.png&quot; alt=&quot;Svg test 1 results&quot; /&gt;&lt;/p&gt;
&lt;p&gt;4.) External using symbol – SLOWER than #2 and #3 (&lt;a href=&quot;https://sample.easwee.net/svg-10000/external-symbol.html&quot;&gt;External with symbol test file&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_05_08_svg_test_4_external_with_symbol.png&quot; alt=&quot;Svg test 1 results&quot; /&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>devlog - svelte app development experience</title>
		<link href="https://www.easwee.net/devlog-svelte-app-development-experience/"/>
		<updated>2019-02-02T00:00:00Z</updated>
		<id>https://www.easwee.net/devlog-svelte-app-development-experience/</id>
		<content type="html">&lt;p&gt;This is a short write-up after completing a single page app project in Svelte.&lt;/p&gt;
&lt;p&gt;The goal was to create an invoicing app that allows me to generate invoices for my company and export them into PDF with minimal click steps. It also includes conversion rates API fetching to support on-the-fly $ to € conversion, and simple client-side data storage inside IndexedDB, to prevent having to write in company data each time – ideal for recurring monthly invoices (I did not want to deal with setting up a server and database and user authentication at this point).&lt;/p&gt;
&lt;p&gt;The initial setup really requires no effort. I decided to pick Rollup instead of Webpack as a bundler, just to test it out – no problems (your decision – both can be used).&lt;/p&gt;
&lt;p&gt;So what is so amazing about Svelte?&lt;/p&gt;
&lt;p&gt;First thing, writing your components requires minimal boilerplate. A custom date-picker component with change handler that fires and stores date into IndexedDB, and also fetches new conversion rates from API endpoint when user stops typing or picks a date, would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  import { onMount } from &amp;quot;svelte&amp;quot;;
  import { fetchRates } from &amp;quot;api&amp;quot;;
  import { format } from &amp;quot;date-fns&amp;quot;;
  import { rates, ownerData } from &amp;quot;store&amp;quot;;
  import { idbRead, idbUpdate } from &amp;quot;utils&amp;quot;;
  let typingTimeout = null;
  function handleInputChange() {
    window.clearTimeout(typingTimeout);
    $ownerData = $ownerData;
    typingTimeout = window.setTimeout(function() {
      idbUpdate(&amp;quot;db&amp;quot;, &amp;quot;owner&amp;quot;, 1, $ownerData)
        .then(() =&amp;gt; idbRead(&amp;quot;biro_db&amp;quot;, &amp;quot;owner&amp;quot;, 1))
        .then(async result =&amp;gt; {
          ownerData.set(result);
          const response = await fetchRates(
            format($ownerData.issue_date, &amp;quot;YYYY-MM-DD&amp;quot;),
            $ownerData.base_currency
          );
          rates.set(response.data.rates);
        });
    }, 500);
  }
&amp;lt;/script&amp;gt;
&amp;lt;style&amp;gt;
  label {
    display: block;
    font-weight: 700;
    color: white;
    margin-bottom: 5px;
  }
&amp;lt;/style&amp;gt;
&amp;lt;form&amp;gt;
  &amp;lt;div class=&amp;quot;field&amp;quot;&amp;gt;
    &amp;lt;label&amp;gt;Issue date:&amp;lt;/label&amp;gt;
    &amp;lt;input
      type=&amp;quot;date&amp;quot;
      bind:value={$ownerData.issue_date}
      on:keyup={() =&amp;gt; handleInputChange()}
      on:change={() =&amp;gt; handleInputChange()}
      placeholder=&amp;quot;Issue date&amp;quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see while there is quite some functionality done, the code itself is mostly plain html/js/css.&lt;/p&gt;
&lt;p&gt;Proprietary syntax is:&lt;br /&gt;
– $ sign, which means automatic store subscription onMount and unsubscription on onDestroy (shorthand for store .subscribe()/.unsubscribe()methods). This makes it easy to read data from store inside templates.&lt;br /&gt;
– bind:value which makes two way data binding with input fields a piece of cake.&lt;br /&gt;
– on: event supports any of the regular js events.&lt;/p&gt;
&lt;p&gt;The next thing that amazed me is how simple it is to create transitions with Svelte. For fading in and out a component when it mounts and unmounts all it takes is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  import { fade } from &amp;quot;svelte/transition&amp;quot;;
&amp;lt;/script&amp;gt;
&amp;lt;div transition:fade&amp;gt;It fades!&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it – and you have a fading element out of the box – and it’s a smooth CSS transition! Transition also accepts a configuration, so you can customize it to your likes.&lt;/p&gt;
&lt;p&gt;The last thing I’d like to point out in this post (to avoid listing all the Svelte features, which are also nicely available in their online interactive tutorial) is the global store integration.&lt;br /&gt;
If you’ve been coding SPAs for last few years you may have dealt with at least 3-4 different libs for handling global state changes and you may know that most of them take some complexity in the process of making them work. Svelte comes with it’s own solution – with readable and writable stores and a bunch of other handy features and so far this has been the easiest one to implement for me. Some examples:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// define it in a store.js or wherever you want
export const rates = writable({USD: 0.8643});
// import it when you need it
import { rates } from &amp;quot;store&amp;quot;;
// simply use it in code with $ shortcut
&amp;lt;p&amp;gt;Total is: {100 * $rates.USD}
// set it to a new value (let&#39;s say after API response)
rates.set(response.data.rates);
// .update() that takes function also available
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again no bloated code – clean and readable.&lt;/p&gt;
&lt;p&gt;To recap: minimal boilerplate code, simple way of achieving transitions and animations and dirt simple global state management add so much to development speed that you can finally focus on developing features and stop wasting time on bloated patterns wrapped in tons of boilerplate code (khm… I’m looking at you Redux…) that at the end of the day don’t add anything to the final product and usability. All of this combined with performance that beats React’s speed in re-rendering definitely make me wanna use it again.&lt;/p&gt;
&lt;p&gt;For more of Svelte’s features check out the interactive tutorial:&lt;br /&gt;
&lt;a href=&quot;https://svelte.dev/tutorial/basics&quot;&gt;https://svelte.dev/tutorial/basics&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now there is one more question – would I use it in production?&lt;/p&gt;
&lt;p&gt;If I was making a fully client based single page app with heavy animations and a lot of UX state changes – I would definitely dare to pitch it to a client. Svelte also offers Sapper, which we could say is an equivalent of Next.js, but at the time of writing this (august 2019) I haven’t dived into Sapper much yet, so I’m not confident enough to give an opinion on it, but we could say it is pretty much an equivalent to Next.js in terms of features. I would first like to understand how Svelte works fully and try out all of it’s features. Considering occasionally you still stumble upon some bugs, I want to be confident that I understand the mechanics of the basic Svelte library before diving into Sapper, so I can easily work around those (or maybe contribute a fix). An excellent point of reference for me is the very welcoming Svelte Discord community – live and active.&lt;/p&gt;
&lt;p&gt;In conclusion I would say that Svelte is a framework I will be watching and using in the near future and I have a feeling it has great potential to grow in user adoption. Keep an eye on it!&lt;/p&gt;
&lt;p&gt;Big thumbs up to the Svelte dev team!&lt;/p&gt;
&lt;p&gt;P.S.: Maybe I should generalize the invoicer app and just release it to public – a TODO if I find some more time to spend on it.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>devlog - how I use chrome devtools</title>
		<link href="https://www.easwee.net/devlog-how-i-use-chrome-devtools/"/>
		<updated>2019-01-12T00:00:00Z</updated>
		<id>https://www.easwee.net/devlog-how-i-use-chrome-devtools/</id>
		<content type="html">&lt;p&gt;When collaborating on projects and helping debugging websites and web apps, I often notice that a lot of developers miss out on a lot of useful features that Chrome DevTools have to offer, making their debugging experience more painful and time consuming, so I’m presenting my “tool-bag” of Chrome DevTools features that help me solve code issues faster.&lt;/p&gt;
&lt;h3 id=&quot;general&quot; tabindex=&quot;-1&quot;&gt;General &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/devlog-how-i-use-chrome-devtools/#general&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ctrl + p&lt;/strong&gt;&lt;br /&gt;
You may be used to this command from your favorite popular code editors and IDEs, but this is also available inside Chrome DevTools and let’s you open files and resources loaded by the page and browse through them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_01_12_tools_1_file_browser.jpg&quot; alt=&quot;Image showing file browsing&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ctrl + shift + p&lt;/strong&gt;&lt;br /&gt;
Similar to the previous shortcut, this one opens a list of all available commands in a nicely formatted list. Super useful shortcut and feature that will certainly introduce you to a lot of other features you may not even notice at first glance. Take your time to go through the list – you will most probably find something new.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_01_12_tools_5_list_commands.jpg&quot; alt=&quot;Image showing commands list&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;elements-tab&quot; tabindex=&quot;-1&quot;&gt;Elements tab &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/devlog-how-i-use-chrome-devtools/#elements-tab&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Elements tab presents you with DOM and CSS and is generally used for debugging layout and checking DOM props. But it also contains a few nice hidden features:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reorder DOM elements&lt;/strong&gt;&lt;br /&gt;
By clicking-holding an element you can actually drag it around and rearrange it’s order or nest it out of it’s parent or move it into another DOM element. Note that by deleting element by element you can sometimes easily identify which one is causing your layout to break.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shift-click on CSS colors&lt;/strong&gt;&lt;br /&gt;
By holding down shift key you can click on any CSS color and change it’s format. Available formats are: hex, short-hex, rgb and hsl. Switching will convert the values to appropriate format, which is often useful when you need to quickly convert hex to rgba since you decided you want to introduce the alpha channel to the color.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Toggle element state&lt;/strong&gt;&lt;br /&gt;
While it’s nicely visible it’s often a forgotten feature – clicking :hov expands a menu where you can select different element states so you can test the styles for them.&lt;/p&gt;
&lt;h3 id=&quot;console&quot; tabindex=&quot;-1&quot;&gt;Console &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/devlog-how-i-use-chrome-devtools/#console&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Timestamps&lt;/strong&gt;&lt;br /&gt;
Hit F1 (or click the three vertical dots in top right corner and select Settings) to open Settings and under the Console section check “timestamps”. Now any console output will have a timestamp printed, which already gives you a better overview of events in time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;console.table()&lt;/strong&gt;&lt;br /&gt;
Besides console.log you probably also use console.error and console.warning, but there is also the very handy way of printing out data with console.table, which often looks much more readable. This is not a Chrome DevTools feature, since you can use it in any console output, but I feel like I should mention it here.&lt;br /&gt;
Check MDN for details: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Console/table&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/Console/table&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;copy(var)&lt;/strong&gt;&lt;br /&gt;
With copy() you can copy the value of any variable from console to your clipboard.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preserve log&lt;/strong&gt;&lt;br /&gt;
I always have preserve log turned on in the console settings to keep logs between page refreshes. I use clear if I need to empty the console.&lt;/p&gt;
&lt;h3 id=&quot;source-tab&quot; tabindex=&quot;-1&quot;&gt;Source tab &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/devlog-how-i-use-chrome-devtools/#source-tab&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Breakpoints&lt;/strong&gt;&lt;br /&gt;
Amazing as it is the debugger statement is often ignored by JS developers. Stopping code inside your functions is the most common way of debugging in most languages, but somehow it is not the go-to practice in the JS world, where throwing console.logs all over the place is the most seen practice. Using debugger will let you easily access and print out any variable to the console, but also will enable you to step through your code line by line and facilitate identifying the problematic line. DevTools also let you place inline breakpoints on-the-fly, that speed up the debugging when you just want to skip a few lines of code and stop at another point, and also let you step into functions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deactivate breakpoints&lt;/strong&gt;&lt;br /&gt;
When debugging a complex problem you will usually end up with multiple breakpoints cross multiple functions. When trying out a solution, you can quickly deactivate all breakpoints and let the code run to the end without having to manually progress over breakpoints.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_01_12_tools_2_deactivate_breakpoints.jpg&quot; alt=&quot;Image showing how to block requests&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pause on exception&lt;/strong&gt;&lt;br /&gt;
Sometimes you deal with errors that show up once your app has been running for some time or you just want to stop before the error throws. Enabling pause on exception will catch the error when it happens and pause code execution right before throwing it, letting you analyze the last state before it happened.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pretty print&lt;/strong&gt;&lt;br /&gt;
When catching production only bugs, you will often have to deal with hard-to-read minified code. Clicking the pretty print button will reformat back to a human readable source and let you place inline breakpoints on it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_01_12_tools_4_pretty_print.jpg&quot; alt=&quot;Image showing how to use pretty print&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;network-tab&quot; tabindex=&quot;-1&quot;&gt;Network tab &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/devlog-how-i-use-chrome-devtools/#network-tab&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Network tab is straight forward with it’s UI – two features I’d like to expose:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Block request URL / domain&lt;/strong&gt;&lt;br /&gt;
When you need to test how something would render if a network request is blocked or fails, you can temporarily block/unblock specific URLs or entire domain by right clicking on the specific item from the list and selecting it in the menu. Useful also to quickly check out alt texts by blocking image URLs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2019_01_12_tools_3_block_requests.jpg&quot; alt=&quot;Image showing how to block requests&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bandwidth throttling&lt;/strong&gt;&lt;br /&gt;
This comes handy when your app rendering depends on the network speed and you want to see how partially loaded containers look like or check loading animations.&lt;/p&gt;
&lt;h3 id=&quot;performance-tab&quot; tabindex=&quot;-1&quot;&gt;Performance tab &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/devlog-how-i-use-chrome-devtools/#performance-tab&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CPU throttling&lt;/strong&gt;&lt;br /&gt;
When you want to simulate the rendering of a low-tier device with bad CPU you can slow down the profiler by setting a 6x or 10x slower CPU rendering simulation, and see which potential bottlenecks in rendering times can arise when you don’t have powerful CPUs available.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Save/Load profile&lt;/strong&gt;&lt;br /&gt;
After each profiling you can also save the data gathered and load it later for comparison when optimizations have been done on your code.&lt;/p&gt;
&lt;p&gt;Rest of tabs mostly contain just various data representation and analysis and are straight forward obvious to read and understand.&lt;/p&gt;
&lt;p&gt;Don’t forget that there are also many additional plugins you can add to Chrome DevTools depending on which technologies you use on your project. Check out Chrome Web Store to see what’s available on: &lt;a href=&quot;https://chrome.google.com/webstore/category/ext/11-web-development&quot;&gt;https://chrome.google.com/webstore/category/ext/11-web-development&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>eventko v2</title>
		<link href="https://www.easwee.net/eventko-v2/"/>
		<updated>2018-11-07T00:00:00Z</updated>
		<id>https://www.easwee.net/eventko-v2/</id>
		<content type="html">&lt;p&gt;For a few years I’ve been running a daily events collector that pooled Facebook events for our capital city Ljubljana from a curated list of venues. Unfortunately in 2018 Facebook limited the amount of data that you can get from their Graph API, so my events collector stopped working.&lt;/p&gt;
&lt;p&gt;Since the Facebook events feed on their main website is still messy and the event results returned there are cluttered with a lot of content I really do not care for, I re-wrote the events collector.&lt;/p&gt;
&lt;p&gt;You can visit it here: &lt;a href=&quot;https://eventko.easwee.net/&quot;&gt;https://eventko.easwee.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;List is updated once per day and pulls in events from ~170 venues in Ljubljana. The whole app runs on Node.js on a EC2 Linux instance. Cron job is triggered daily to collect fresh events and store them to a temporary database. I do not keep any old events – only future events are collected, old data is dumped every morning, in order to keep the AWS costs to a minimum.&lt;/p&gt;
&lt;p&gt;This project is probably useless to the larger global population, but I believe it can be handy to the citizens of Ljubljana, who can have a clean source of quality daily events (concerts, movies, theater, public educational events, exhibitions,…).&lt;/p&gt;
&lt;p&gt;In the future I hope to come up with more projects like this, that cater to a local community, but bring tailored content that may suit it better, than the results you would get on big websites.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>snooffline</title>
		<link href="https://www.easwee.net/snooffline/"/>
		<updated>2018-10-01T00:00:00Z</updated>
		<id>https://www.easwee.net/snooffline/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2018_10_01_snoofline.jpg&quot; alt=&quot;Snoofline&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://js13kgames.com/entries/snooffline&quot;&gt;js13k2018 challenge&lt;/a&gt; done in collaboration with &lt;a href=&quot;https://agialab.com/&quot;&gt;Adam Giacomelli&lt;/a&gt;.&lt;br /&gt;
A 13kb javascript game done for the “offline” or “off-the-line and keeping the party going” 2018 code challenge theme, including hand written midi music tribute to Eric Clapton 🙂&lt;/p&gt;
&lt;p&gt;Keep the bar leveled.&lt;/p&gt;
&lt;p&gt;Code freely available for forking on &lt;a href=&quot;https://github.com/easwee/snooffline&quot;&gt;https://github.com/easwee/snooffline&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also check out my previous years entries &lt;a href=&quot;https://js13kgames.com/entries/balls-juggle&quot;&gt;Balls juggle&lt;/a&gt; and &lt;a href=&quot;http://js13kgames.com/entries/baloon-operator&quot;&gt;Baloon operator&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>quick setup for husky with lint-staged</title>
		<link href="https://www.easwee.net/quick-setup-for-husky-with-lint-staged/"/>
		<updated>2018-06-24T00:00:00Z</updated>
		<id>https://www.easwee.net/quick-setup-for-husky-with-lint-staged/</id>
		<content type="html">&lt;p&gt;1.) Install dev dependencies:&lt;/p&gt;
&lt;p&gt;npm install husky lint-staged eslint --save-dev&lt;/p&gt;
&lt;p&gt;Note: if you get an error like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ENOENT: no such file or directory, mkdir &#39;node_modules/husky/.git/hooks&#39;
husky &amp;gt; Failed to install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;just update your git to latest version and it should install fine.&lt;/p&gt;
&lt;p&gt;2.) Update package.json with husky and lint-staged definitions by adding:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ...
 &amp;quot;husky&amp;quot;: {
    &amp;quot;hooks&amp;quot;: {
      &amp;quot;pre-commit&amp;quot;: &amp;quot;lint-staged&amp;quot;
    }
  },
  &amp;quot;lint-staged&amp;quot;: {
    &amp;quot;*.{js}&amp;quot;: [
      &amp;quot;./node_modules/.bin/eslint --fix&amp;quot;,
    ]
  }
  ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: you may want to also add ts or jsx like {js,jsx,ts} depending on what kind of setup you are working on&lt;/p&gt;
&lt;p&gt;This is the very basic setup – for all extra consult the docs: &lt;a href=&quot;https://github.com/okonet/lint-staged&quot;&gt;https://github.com/okonet/lint-staged&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>morse code – text-to-morse translator</title>
		<link href="https://www.easwee.net/morse-code-text-to-morse-translator/"/>
		<updated>2018-04-22T00:00:00Z</updated>
		<id>https://www.easwee.net/morse-code-text-to-morse-translator/</id>
		<content type="html">&lt;p&gt;The invention of the telegraph as the first electrical telecommunications system is an important landmark in the history of communication, but while the tech itself is very interesting hardware, from the coding point of view I’m more interested in the Morse code used to send messages with it.&lt;/p&gt;
&lt;p&gt;Morse code consists of dots, dashes and spaces which when linearly combined and transmitted make up letters, words and sentences. Named after Samuel Finley Breese Morse who helped co-develop it, it was later refined by Friedrich Clemens Gerke and after the adoption by the Deutsch-Österreichischer Telegraphenverein (German-Austrian Telegraph Society) in 1851, we’ve got the International Morse code in 1865, which, while slowly declining in usage and making way for better ways of communication, stayed in use up to this days and that’s the alphabet set we are gonna use to code our text-to-morse translator in Javascript.&lt;/p&gt;
&lt;p&gt;We have 26 letters and 10 numerals available. Dots and dash sequences (dot vocalized as “dit” and dash as “dah”) are assigned to letters based on how commonly they are used – shorter combinations are assigned to the most used letters. To keep it simple we will not be integrating the prosigns in this version (like SOS = …—…)&lt;/p&gt;
&lt;p&gt;We are translating text into an audible audio signal over the Javascript Audio API, using the OscillatorNode and a GainNode connected to it – with this two we have all the tools needed to simulate the telegraph behavior (we could go further and even simulate the communication between two telegraph devices over a socket connection or even better with WebRTC which is ideal since a telegraph is basically a p2p connection, but let’s leave this for a part 2 maybe).&lt;/p&gt;
&lt;p&gt;With the above info all we have to do is to convert the text into a sequence of dots, dashes and spaces which can be then looped over and by altering the gainNode volume for each character in the sentence we get the telegraph behavior.&lt;/p&gt;
&lt;p&gt;If we consider that we have a time unit of X ms, the rules for emitting the signals are:&lt;/p&gt;
&lt;p&gt;dot = 1 unit of sound&lt;br /&gt;
dash = 3 units of sound&lt;br /&gt;
sign space = 1 unit of silence&lt;br /&gt;
letter space = 3 units of silence&lt;br /&gt;
word space = 7 units of silence&lt;/p&gt;
&lt;p&gt;Morse code also does not distinguish between upper and lowercase, so we can transform all text to lowercase and just work with that. With this rules in mind I can define a few constants to concatenate into the morse sequence that will be interpreted by the oscillator:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const SEQUENCE_SIGNS = {
    DIT: &amp;quot;.&amp;quot;,
    DAH: &amp;quot;-&amp;quot;,
    SIGN_SPACE: &amp;quot;x&amp;quot;,
    LETTER_SPACE: &amp;quot;xxx&amp;quot;,
    WORD_SPACE: &amp;quot;xxxxxxx&amp;quot;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the common test string “PARIS” using the above sign set we get a sequence (let’s us “x” to represent a space unit for clarity):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.x-x-x.xxx.x-xxx.x-x.xxx.x.xxx.x.x.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All that is left to do is to loop over it and toggle the gainNode volume on and off. All that is left to do is to loop over it and toggle the gainNode volume on and off and we end up with a very basic morse code interpreter – play with it:&lt;/p&gt;
&lt;iframe title=&quot;text-to-morse translator&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/morse/&quot; width=&quot;100%&quot; height=&quot;300&quot;&gt;
&lt;/iframe&gt;</content>
	</entry>
	
	<entry>
		<title>100k decimals of pie</title>
		<link href="https://www.easwee.net/100k-decimals-of-pie/"/>
		<updated>2017-05-20T00:00:00Z</updated>
		<id>https://www.easwee.net/100k-decimals-of-pie/</id>
		<content type="html">&lt;p&gt;100k decimals of pie visualized on HTML canvas – simple script alternates between 4 quadrants.&lt;/p&gt;
&lt;p&gt;Fiddle script: &lt;a href=&quot;https://jsfiddle.net/easwee/adq4a4e7/&quot;&gt;https://jsfiddle.net/easwee/adq4a4e7/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Javascript’s Math.PI is limited to 15 decimal places, so we need to use a pre-rendered set of decimals (taken from &lt;a href=&quot;http://www.piday.org/million/&quot;&gt;http://www.piday.org/million/&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2017_05_20_100kpiedigits.jpg&quot; alt=&quot;Image of 100k decimals of pie visualisation&quot; /&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>canvas based music visualizations</title>
		<link href="https://www.easwee.net/canvas-based-music-visualizations/"/>
		<updated>2016-04-07T00:00:00Z</updated>
		<id>https://www.easwee.net/canvas-based-music-visualizations/</id>
		<content type="html">&lt;p&gt;Sound wave to music javascript conversion – pixel by pixel, manipulated in a pattern.&lt;/p&gt;
&lt;p&gt;Sample song by Soundtribe Sector 9 – Better Day&lt;/p&gt;
&lt;iframe title=&quot;canvas based music visualizations&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/canvas-visualizer/&quot; width=&quot;100%&quot; height=&quot;1000&quot;&gt;
&lt;/iframe&gt;</content>
	</entry>
	
	<entry>
		<title>panorama of valletta</title>
		<link href="https://www.easwee.net/panorama-of-valletta/"/>
		<updated>2016-03-14T00:00:00Z</updated>
		<id>https://www.easwee.net/panorama-of-valletta/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2016_03_14_panorama_valetta.jpg&quot; alt=&quot;Image of panoramic view of Valetta&quot; /&gt;&lt;/p&gt;
&lt;p&gt;valetta, malta, march 2016&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>precise-text-size</title>
		<link href="https://www.easwee.net/precise-text-size/"/>
		<updated>2015-12-04T00:00:00Z</updated>
		<id>https://www.easwee.net/precise-text-size/</id>
		<content type="html">&lt;p&gt;Script that returns precise text width and height - from first non-transparent pixel to the last-one, ignoring any kerning space before/after word, which is usually accounted into a getTextWidth() calculation.&lt;/p&gt;
&lt;p&gt;Works with custom fonts too. Measurement is done by traversing pixels of word raster drawn on inmemory canvas.&lt;/p&gt;
&lt;p&gt;Grab from github: &lt;a href=&quot;https://github.com/easwee/precise-text-size&quot;&gt;https://github.com/easwee/precise-text-size&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>rastapico</title>
		<link href="https://www.easwee.net/rastapico/"/>
		<updated>2015-11-30T00:00:00Z</updated>
		<id>https://www.easwee.net/rastapico/</id>
		<content type="html">&lt;p&gt;Rastapico from Jamaica is gonna chop some plants today! A simple Pico-8 8-bit game done in 12 hours.&lt;/p&gt;
&lt;p&gt;Due to console controller key setup X = N – move with arrow keys.&lt;/p&gt;
&lt;iframe title=&quot;rastapico&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/picoman/picoman.html&quot; width=&quot;100%&quot; height=&quot;700&quot; style=&quot;background-color: white;&quot;&gt;&lt;/iframe&gt;
</content>
	</entry>
	
	<entry>
		<title>grab 33</title>
		<link href="https://www.easwee.net/grab-33/"/>
		<updated>2015-09-15T00:00:00Z</updated>
		<id>https://www.easwee.net/grab-33/</id>
		<content type="html">&lt;p&gt;Tiny Sunday project – HTML game based on Unicode characters as graphics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_09_15_grab33.png&quot; alt=&quot;Grab 33 - unicode graphics based html game by easwee.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This will probably render differently across various OS/browsers – depends on Unicode support and it’s rendering. As far as I tested it works and renders nicely in latest Opera and Chrome (both Linux and Windows) – Firefox cuts part of smiley since it renders it bigger.&lt;/p&gt;
&lt;p&gt;Try it:&lt;/p&gt;
&lt;iframe title=&quot;grab 33&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/grab-33/&quot; width=&quot;400&quot; height=&quot;400&quot; style=&quot;background-color: white;&quot;&gt;&lt;/iframe&gt;
</content>
	</entry>
	
	<entry>
		<title>3d football field fiddle</title>
		<link href="https://www.easwee.net/3d-football-field-fiddle/"/>
		<updated>2015-09-14T00:00:00Z</updated>
		<id>https://www.easwee.net/3d-football-field-fiddle/</id>
		<content type="html">&lt;p&gt;Three.js football field basic 3d model test.&lt;/p&gt;
&lt;iframe title=&quot;3d football field fiddle&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/3d-football/&quot; width=&quot;100%&quot; height=&quot;450&quot;&gt;&lt;/iframe&gt;
</content>
	</entry>
	
	<entry>
		<title>generating funky patterns from a weird youtube comment</title>
		<link href="https://www.easwee.net/generating-funky-patterns-from-a-weird-youtube-comment/"/>
		<updated>2015-08-15T00:00:00Z</updated>
		<id>https://www.easwee.net/generating-funky-patterns-from-a-weird-youtube-comment/</id>
		<content type="html">&lt;p&gt;Recently I stumbled upon a weird, but interesting Youtube video comment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_08_15_ocd.png&quot; alt=&quot;That make my OCD go crazy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I decided to take a look at what characters may compose such a pattern, so I put up a tiny script to process the string, output unique characters and generate new random patterns with them.&lt;/p&gt;
&lt;p&gt;Few interesting patterns that triggered some references for me (Opera on Windows 7):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_08_15_ocd_patterns.png&quot; alt=&quot;Fantasy patterns&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The ceiling vines example is the most interesting since it concatenates the Combining Diacritical Marks directly without space and makes the pattern organic in a way. I’ve tried to run the same sample on Ubuntu Chrome, but it seems like it does not support rendering of concatenated marks, however Ubuntu Firefox does it well. The rendering is very inconsistent across OS/browsers.&lt;/p&gt;
&lt;p&gt;Running sample (switch in select):&lt;/p&gt;
&lt;iframe title=&quot;generating funky patterns from a weird youtube comment&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/weird-patterns/&quot; width=&quot;400&quot; height=&quot;400&quot; style=&quot;background-color: white;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;You can fork it from jsfiddle and try your own patterns in the config: &lt;a href=&quot;https://jsfiddle.net/easwee/7wzrha77/1/&quot;&gt;https://jsfiddle.net/easwee/7wzrha77/1/&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>dotted polygons</title>
		<link href="https://www.easwee.net/dotted-polygons/"/>
		<updated>2015-06-30T00:00:00Z</updated>
		<id>https://www.easwee.net/dotted-polygons/</id>
		<content type="html">&lt;p&gt;A script that arranges dots into dotted polygons with N sides.&lt;/p&gt;
&lt;iframe title=&quot;dotted polygons&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/dotted-polygons/&quot; width=&quot;100%&quot; height=&quot;700&quot; style=&quot;background: white&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;View on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/CIuEj&quot;&gt;http://codepen.io/easwee/pen/CIuEj&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>noise tv</title>
		<link href="https://www.easwee.net/noise-tv/"/>
		<updated>2015-06-15T00:00:00Z</updated>
		<id>https://www.easwee.net/noise-tv/</id>
		<content type="html">&lt;iframe title=&quot;noise tv&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/noise-tv/&quot; width=&quot;100%&quot; height=&quot;370&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;You can see and play with the code on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/lweIk&quot;&gt;http://codepen.io/easwee/pen/lweIk&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>panoramic view from štanjel</title>
		<link href="https://www.easwee.net/panoramic-view-from-stanjel/"/>
		<updated>2015-05-31T00:00:00Z</updated>
		<id>https://www.easwee.net/panoramic-view-from-stanjel/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_05_31_stanjelpanorama.jpg&quot; alt=&quot;Image of panoramic view from Štanjel&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Panoramic view from Štanjel, Slovenia.&lt;/p&gt;
&lt;p&gt;31st may 2015, štanjel, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>in-house bicycle</title>
		<link href="https://www.easwee.net/in-house-bicycle/"/>
		<updated>2015-03-31T00:00:00Z</updated>
		<id>https://www.easwee.net/in-house-bicycle/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_03_31_inhousebicycle.jpg&quot; alt=&quot;Image of in-house bicycle&quot; /&gt;&lt;/p&gt;
&lt;p&gt;30th march 2015, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>train front</title>
		<link href="https://www.easwee.net/train-front/"/>
		<updated>2015-03-15T00:00:00Z</updated>
		<id>https://www.easwee.net/train-front/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_03_15_trainfront.jpg&quot; alt=&quot;Image of train front&quot; /&gt;&lt;/p&gt;
&lt;p&gt;15th march 2015, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>twins</title>
		<link href="https://www.easwee.net/twins/"/>
		<updated>2015-02-28T00:00:00Z</updated>
		<id>https://www.easwee.net/twins/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_02_28_twins.jpg&quot; alt=&quot;Image of trees in teh sky&quot; /&gt;&lt;/p&gt;
&lt;p&gt;28th february 2015, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>goriška panorama</title>
		<link href="https://www.easwee.net/goriska-panorama/"/>
		<updated>2014-12-15T00:00:00Z</updated>
		<id>https://www.easwee.net/goriska-panorama/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_12_15_panoramagoriske.jpg&quot; alt=&quot;Image of goriška region&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A panoramic photo of my home region – Goriška, taken on Slovenian/Italian border.&lt;/p&gt;
&lt;p&gt;goriška, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>klinika industriale</title>
		<link href="https://www.easwee.net/klinika-industriale/"/>
		<updated>2014-10-31T00:00:00Z</updated>
		<id>https://www.easwee.net/klinika-industriale/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_10_31_klinikaindustriale.jpg&quot; alt=&quot;Image of an abandoned building&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Something similar to a meth lab found doing urbex around Ljubljana.&lt;/p&gt;
&lt;p&gt;31st october 2014, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>turntable service facility panorama</title>
		<link href="https://www.easwee.net/turntable-service-facility-panorama/"/>
		<updated>2014-10-11T00:00:00Z</updated>
		<id>https://www.easwee.net/turntable-service-facility-panorama/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_12_30_turntableservicefacilitypanorama.jpg&quot; alt=&quot;Image of a panoramic view of turntble service facility&quot; /&gt;&lt;/p&gt;
&lt;p&gt;train museum, 10th october 2014, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>transit station</title>
		<link href="https://www.easwee.net/transit-station/"/>
		<updated>2014-09-21T00:00:00Z</updated>
		<id>https://www.easwee.net/transit-station/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_09_30_transitstation.jpg&quot; alt=&quot;Image of railway station&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ljubljana train station in a post-industrial futuristic atmosphere.&lt;/p&gt;
&lt;p&gt;30th september 2014, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>encrypted</title>
		<link href="https://www.easwee.net/encrypted/"/>
		<updated>2014-09-15T00:00:00Z</updated>
		<id>https://www.easwee.net/encrypted/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_05_24_encrypted.jpg&quot; alt=&quot;Image of Post edited Mandelbulb3d render&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Post edited Mandelbulb3d render.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>circular wheel css illusion</title>
		<link href="https://www.easwee.net/circular-wheel-css-illusion/"/>
		<updated>2014-09-15T00:00:00Z</updated>
		<id>https://www.easwee.net/circular-wheel-css-illusion/</id>
		<content type="html">&lt;p&gt;Focus on one dot to understand this illusion.&lt;/p&gt;
&lt;iframe title=&quot;circular wheel css illusion&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/circular-wheel-illusion/&quot; width=&quot;100%&quot; height=&quot;275&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Check the code on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/qxgzw&quot;&gt;http://codepen.io/easwee/pen/qxgzw&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>three.js 3d rotating object with textures test</title>
		<link href="https://www.easwee.net/three.js-3d-rotating-object-with-textures-test/"/>
		<updated>2014-08-18T00:00:00Z</updated>
		<id>https://www.easwee.net/three.js-3d-rotating-object-with-textures-test/</id>
		<content type="html">&lt;p&gt;Playing around with three.js 3d javascript library.&lt;/p&gt;
&lt;iframe title=&quot;three.js 3d rotating object with textures test&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/rotating-cube/&quot; width=&quot;100%&quot; height=&quot;400&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Object with custom textures sample: &lt;a href=&quot;https://sample.easwee.net/rotating-cube/&quot;&gt;https://sample.easwee.net/rotating-cube/&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>cik cak</title>
		<link href="https://www.easwee.net/cik-cak/"/>
		<updated>2014-08-16T00:00:00Z</updated>
		<id>https://www.easwee.net/cik-cak/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_08_31_fibonaccitriangles.jpg&quot; alt=&quot;Image of triangles in Fibonacci sequence&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Triangles in Fibonacci sequence.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>html5 audio api – oscillator</title>
		<link href="https://www.easwee.net/html5-audio-api-oscillator/"/>
		<updated>2014-08-10T00:00:00Z</updated>
		<id>https://www.easwee.net/html5-audio-api-oscillator/</id>
		<content type="html">&lt;p&gt;A very primitive oscillator device done with HTML5 audio api using oscillator node. Requires HTML5 audio api browser support (runs in latest Chrome, Opera and Firefox).&lt;/p&gt;
&lt;iframe title=&quot;html5 audio api – oscillator&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/nadlezni-oscilator/index.html&quot; width=&quot;100%&quot; height=&quot;400&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;View on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/sFpmo&quot;&gt;http://codepen.io/easwee/pen/sFpmo&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>roflcopter</title>
		<link href="https://www.easwee.net/roflcopter/"/>
		<updated>2014-07-15T00:00:00Z</updated>
		<id>https://www.easwee.net/roflcopter/</id>
		<content type="html">&lt;p&gt;A silly animation done by replacing characters in pre-formatted text.&lt;/p&gt;
&lt;iframe title=&quot;roflcopter&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/roflcopter/&quot; width=&quot;100%&quot; height=&quot;190&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Play with the code on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/wbvue&quot;&gt;http://codepen.io/easwee/pen/wbvue&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>grad rakičan</title>
		<link href="https://www.easwee.net/grad-rakican/"/>
		<updated>2014-05-18T00:00:00Z</updated>
		<id>https://www.easwee.net/grad-rakican/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_05_18_gradrakican.jpg&quot; alt=&quot;Image of castle in Prekmurje&quot; /&gt;&lt;/p&gt;
&lt;p&gt;18th may 2014, rakican, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>circular equalizer</title>
		<link href="https://www.easwee.net/circular-equalizer/"/>
		<updated>2014-05-15T00:00:00Z</updated>
		<id>https://www.easwee.net/circular-equalizer/</id>
		<content type="html">&lt;p&gt;Exploring the possibilities of CSS animations. No javascript.&lt;/p&gt;
&lt;iframe title=&quot;circular equalizer&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/circular-equalizer/index.html&quot; width=&quot;100%&quot; height=&quot;450&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Check the code on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/ehiog&quot;&gt;http://codepen.io/easwee/pen/ehiog&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>css pacman</title>
		<link href="https://www.easwee.net/css-pacman/"/>
		<updated>2014-05-07T00:00:00Z</updated>
		<id>https://www.easwee.net/css-pacman/</id>
		<content type="html">&lt;p&gt;Pacman tribute – pure css animation.&lt;/p&gt;
&lt;iframe title=&quot;css pacman&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/css-pacman-tribute/&quot; width=&quot;100%&quot; height=&quot;300&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Source available on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/qFmDb&quot;&gt;http://codepen.io/easwee/pen/qFmDb&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>looking at sarajevo</title>
		<link href="https://www.easwee.net/looking-at-sarajevo/"/>
		<updated>2014-05-01T00:00:00Z</updated>
		<id>https://www.easwee.net/looking-at-sarajevo/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_05_01_sarajevo.jpg&quot; alt=&quot;Image of a girl looking at Sarajevo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;1st may 2014, sarajevo, bih&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>koliko?</title>
		<link href="https://www.easwee.net/koliko/"/>
		<updated>2014-03-28T00:00:00Z</updated>
		<id>https://www.easwee.net/koliko/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2014_03_28_koliko.jpg&quot; alt=&quot;Image of poster for a poem Koliko&quot; /&gt;&lt;/p&gt;
&lt;p&gt;An attempt to visualize perception of a poem. Poem written by Dejan Koban from Razporeditve, 2013.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>vortex</title>
		<link href="https://www.easwee.net/vortex/"/>
		<updated>2014-03-14T00:00:00Z</updated>
		<id>https://www.easwee.net/vortex/</id>
		<content type="html">&lt;iframe title=&quot;vortex&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/vortex/index.html&quot; width=&quot;100%&quot; height=&quot;400&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Code available on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/LydFH&quot;&gt;http://codepen.io/easwee/pen/LydFH&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>invokelist – invoker spellcasting trainer</title>
		<link href="https://www.easwee.net/invokelist-invoker-spellcasting-trainer/"/>
		<updated>2013-12-14T00:00:00Z</updated>
		<id>https://www.easwee.net/invokelist-invoker-spellcasting-trainer/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2013_12_14_invokelist.jpg&quot; alt=&quot;Invokelist GUI&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I’ve been doing some javascript practice and remade the Dota 2 Invoker casting interface. It could be further improved, but&lt;/p&gt;
&lt;p&gt;I’m quite satisfied where this practice snippet got.&lt;/p&gt;
&lt;p&gt;Run it here: &lt;a href=&quot;https://sample.easwee.net/invokelist/&quot;&gt;https://sample.easwee.net/invokelist/&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>core</title>
		<link href="https://www.easwee.net/core/"/>
		<updated>2013-12-10T00:00:00Z</updated>
		<id>https://www.easwee.net/core/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2013_12_10_core.jpg&quot; alt=&quot;Image of Fibonacci sequence&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Building on Fibonacci series with curve drawing using HTML5 canvas and some simple javascript. Post edited in Photoshop.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>animate elements on spiral path with css</title>
		<link href="https://www.easwee.net/animate-elements-on-spiral-path-with-css/"/>
		<updated>2013-12-06T00:00:00Z</updated>
		<id>https://www.easwee.net/animate-elements-on-spiral-path-with-css/</id>
		<content type="html">&lt;p&gt;Live sample: &lt;a href=&quot;https://sample.easwee.net/css-spiral-path/index.html&quot;&gt;https://sample.easwee.net/css-spiral-path/index.html&lt;/a&gt;&lt;/p&gt;
&lt;iframe title=&quot;animate elements on spiral path with css&quot; frameBorder=&quot;0&quot; src=&quot;https://sample.easwee.net/css-spiral-path/&quot; width=&quot;100%&quot; height=&quot;555&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Check the code on Codepen: &lt;a href=&quot;http://codepen.io/easwee/pen/gtnJi&quot;&gt;http://codepen.io/easwee/pen/gtnJi&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>jezersko</title>
		<link href="https://www.easwee.net/jezersko/"/>
		<updated>2013-08-15T00:00:00Z</updated>
		<id>https://www.easwee.net/jezersko/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2013_08_15_jezersko.jpg&quot; alt=&quot;Image of a panoramic view from Jezersko&quot; /&gt;&lt;/p&gt;
&lt;p&gt;15th august 2013, zgornje jezersko, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>bending space and time</title>
		<link href="https://www.easwee.net/bending-space-and-time/"/>
		<updated>2013-02-17T00:00:00Z</updated>
		<id>https://www.easwee.net/bending-space-and-time/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2013_02_17_spacetime.jpg&quot; alt=&quot;Surreal image of a clock bent in time&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Bending with &lt;a href=&quot;https://en.wikipedia.org/wiki/Droste_effect&quot;&gt;Droste effect&lt;/a&gt;. Done in Photoshop using custom filters.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>CSS only responsive horizontaly and verticaly centered content overlay</title>
		<link href="https://www.easwee.net/css-only-responsive-horizontaly-and-verticaly-centered-content-overlay/"/>
		<updated>2012-12-22T00:00:00Z</updated>
		<id>https://www.easwee.net/css-only-responsive-horizontaly-and-verticaly-centered-content-overlay/</id>
		<content type="html">&lt;p&gt;!!!Note: this is a very old way of doing this when we still supported Internet Explorer 8 and responsive design became a thing.&lt;/p&gt;
&lt;p&gt;Centers content horizontally and vertically without setting parent’s height. Works with any width value. Supports IE8 and above. Supports mobile screen resolution (responsive).&lt;/p&gt;
&lt;h3 id=&quot;html%3A&quot; tabindex=&quot;-1&quot;&gt;HTML: &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/css-only-responsive-horizontaly-and-verticaly-centered-content-overlay/#html%3A&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&amp;quot;sample-container&amp;quot;&amp;gt;
    &amp;lt;img src=&amp;quot;http://placehold.it/300x200&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
    &amp;lt;div class=&amp;quot;sample-container-overlay&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;sample-container-overlay-inner&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;sample-container-overlay-inner-content&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;link1&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Item 1&amp;lt;/a&amp;gt;
                &amp;lt;a class=&amp;quot;link2&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Item 2&amp;lt;/a&amp;gt;
                &amp;lt;a class=&amp;quot;link3&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Item 3&amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;css%3A&quot; tabindex=&quot;-1&quot;&gt;CSS: &lt;a class=&quot;direct-link&quot; href=&quot;https://www.easwee.net/css-only-responsive-horizontaly-and-verticaly-centered-content-overlay/#css%3A&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;.sample-container {
    position:relative;
    max-width:500px;
    margin:0 auto;
}
.sample-container img {
    display:block;
    max-width:100%;
    width:100%;
}
.sample-container-overlay {
    position:absolute;
    top:0;
    left:0;
    bottom:0;
    right:0;
    width:200px;
    margin:auto;
}
.sample-container-overlay-inner {
    display:table;
    width:100%;
    height:100%;
}
.sample-container-overlay-inner-content {
    display:table-cell;
    vertical-align:middle;
}
.sample-container-overlay-inner-content a {
    display:block;
    padding:10px;
    color:#fff;
    text-align:center;
    text-decoration:none;
    opacity:.8;
}
.sample-container-overlay-inner-content a:hover {
    opacity:1;
}
.sample-container .link1 {background:red;}
.sample-container .link2 {background:green;}
.sample-container .link3 {background:blue;}
&lt;/code&gt;&lt;/pre&gt;
</content>
	</entry>
	
	<entry>
		<title>snowy train ride</title>
		<link href="https://www.easwee.net/snowy-train-ride/"/>
		<updated>2012-12-03T00:00:00Z</updated>
		<id>https://www.easwee.net/snowy-train-ride/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2012_12_03_snowytrainride.jpg&quot; alt=&quot;Image of a snowy landscape&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Riding in train during a snow storm. Taken on Slovenian Karst.&lt;/p&gt;
&lt;p&gt;15th february 2015, karst, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>redbull flugtag skydiver</title>
		<link href="https://www.easwee.net/redbull-flugtag-skydiver/"/>
		<updated>2012-06-11T00:00:00Z</updated>
		<id>https://www.easwee.net/redbull-flugtag-skydiver/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2015_04_15_redbullskydiver.jpg&quot; alt=&quot;Image of redbull skydiver&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Opening of Red Bull Flugtag Ljubljana with skydivers.&lt;/p&gt;
&lt;p&gt;15th april 2015, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>detail of my spaceship</title>
		<link href="https://www.easwee.net/detail-of-my-spaceship/"/>
		<updated>2012-06-11T00:00:00Z</updated>
		<id>https://www.easwee.net/detail-of-my-spaceship/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2012_06_11_myspaceshipdetailbig.jpg&quot; alt=&quot;Image of an old gas station roof&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Detail of an old gas station roof on Tivolska street in Ljubljana. Architect Milan Mihelič.&lt;/p&gt;
&lt;p&gt;11th june 2012, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>filmstrip</title>
		<link href="https://www.easwee.net/filmstrip/"/>
		<updated>2012-05-25T00:00:00Z</updated>
		<id>https://www.easwee.net/filmstrip/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2012_05_25_passengercart.jpg&quot; alt=&quot;Image of a passenger cart&quot; /&gt;&lt;/p&gt;
&lt;p&gt;passenger cart, 25th may 2012, ljubljana, slovenia&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>wired</title>
		<link href="https://www.easwee.net/wired/"/>
		<updated>2012-02-18T00:00:00Z</updated>
		<id>https://www.easwee.net/wired/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2012_02_18_wired.jpg&quot; alt=&quot;Image of wired&quot; /&gt;&lt;/p&gt;
&lt;p&gt;electric wires, ljubljana train station, winter 2012&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>hellcopter operation</title>
		<link href="https://www.easwee.net/hellcopter-operation/"/>
		<updated>2007-10-01T00:00:00Z</updated>
		<id>https://www.easwee.net/hellcopter-operation/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2007_10_01_hellcopter_operation_1.png&quot; alt=&quot;Hellcopter Operation title screen&quot; /&gt;&lt;/p&gt;
&lt;p&gt;First game I managed to develop from start to finish. This was done in 2007 towards the end of my study days, using Flash/ActionScript 2.0 and compiled into .exe using Zinc. It&#39;s a simple sidescroller game where you control a helicopter named &amp;quot;Hellcopter&amp;quot; that needs to shoot and save the city from alien invaders. Watch out to not crash to the ground or get hit by aliens.&lt;/p&gt;
&lt;p&gt;Executable for Windows still exists here: &lt;a href=&quot;https://sample.easwee.net/hellcopter-operation/hellcopter-operation-v1.zip&quot;&gt;https://sample.easwee.net/hellcopter-operation/hellcopter-operation-v1.zip&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.easwee.net/media/photography/2007_10_01_hellcopter_operation_2.png&quot; alt=&quot;Hellcopter Operation gameplay&quot; /&gt;&lt;/p&gt;
</content>
	</entry>
</feed>