<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Foxy Panda ~]]></title><description><![CDATA[A small personal tech blog.]]></description><link>https://foxypanda.me/</link><image><url>https://foxypanda.me/favicon.png</url><title>Foxy Panda ~</title><link>https://foxypanda.me/</link></image><generator>Ghost 5.75</generator><lastBuildDate>Tue, 27 Jan 2026 09:04:46 GMT</lastBuildDate><atom:link href="https://foxypanda.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Making i3 windows float without for_window]]></title><description><![CDATA[<p><a href="https://i3wm.org/docs/userguide.html?ref=foxypanda.me#configuring">i3 configs</a> often include bindings to launch applications. For example, to run Konsole terminal when <code>Super+Enter</code> is pressed, one would add this line to their config:</p>
<pre><code class="language-bash">bindsym --release Mod4+Return exec &quot;konsole&quot;
</code></pre>
<p>The standard approach to make windows flaot is to find out the window&apos;s</p>]]></description><link>https://foxypanda.me/making-i3-windows-float-without-for-window/</link><guid isPermaLink="false">6590b838a84a2ca7df12d558</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Sat, 13 Jul 2019 20:00:00 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://i3wm.org/docs/userguide.html?ref=foxypanda.me#configuring">i3 configs</a> often include bindings to launch applications. For example, to run Konsole terminal when <code>Super+Enter</code> is pressed, one would add this line to their config:</p>
<pre><code class="language-bash">bindsym --release Mod4+Return exec &quot;konsole&quot;
</code></pre>
<p>The standard approach to make windows flaot is to find out the window&apos;s class and (optionally) instance name. Running <code>xprop</code> and clicking on the window would output:</p>
<pre><code class="language-bash">$ euql1n@dominator ~&gt; xprop | grep CLASS
WM_CLASS(STRING) = &quot;konsole&quot;, &quot;konsole&quot;
</code></pre>
<p>Then, one could force the window to float using <code>for_window</code> in their config:</p>
<pre><code class="language-bash">for_window [class=&quot;konsole&quot;] floating enable
</code></pre>
<h1 id="problem-statement">Problem statement</h1>
<p>Consider the situation where you don&apos;t want all instances of the application to float. For example, you want a floating terminal window to appear on <code>Super+Shift+Enter</code> key press, and a non-floating on <code>Super+Enter</code>.</p>
<p>Some programs, like <code>urxvt</code> and <code>termite</code>, make this task easy by letting you explicitly specify the instance name. For example, the following lines can be added to the config to get the desired behaviour with <code>termite</code>:</p>
<pre><code class="language-bash"># Setup key bindings
set $mod=Mod4
bindsym --release $mod+Return exec &quot;termite&quot;
bindsym --release $mod+Shift+Return exec &quot;termite --name=floating&quot;

# Make only the `floating` version of termite float
for_window [class=&quot;Termite&quot; instance=&quot;floating&quot;] floating enable
</code></pre>
<p>Unfortunately, this isn&apos;t always possible - <code>konsole</code>, for example, doesn&apos;t take any command line arguments that define the instance name. What do we do in this case?</p>
<h1 id="solution">Solution</h1>
<p>The solution is based on <a href="https://unix.stackexchange.com/questions/450700/opening-a-programme-in-a-floating-window-in-i3?ref=foxypanda.me">this answer</a>. We create a special wrapper script that will force our apps to float. The script will take our desired command as an argument, and will execute it making sure the resultant window is set to float.</p>
<p>You can download the <code>float-runner.sh</code> from <a href="https://gist.github.com/TimboKZ/d05bda63f7f818fdc20c3afa3f6a624d?ref=foxypanda.me">this gist</a>, or simply create file called <code>float-runner.sh</code> and put this inside:</p>
<pre><code class="language-bash">#!/usr/bin/env bash

# Get the command from arguments
arg_str=&quot;$*&quot;
$arg_str &amp;
pid=&quot;$!&quot;

# Wait for the window to open and grab its window ID
winid=&apos;&apos;
while : ; do
    winid=&quot;`wmctrl -lp | awk -vpid=$pid &apos;$3==pid {print $1; exit}&apos;`&quot;
    [[ -z &quot;${winid}&quot; ]] || break
done

# Focus the window we found
wmctrl -ia &quot;${winid}&quot;

# Make it float
i3-msg floating enable &gt; /dev/null;

# Move it to the center for good measure
i3-msg move position center &gt; /dev/null;

# Wait for the application to quit
wait &quot;${pid}&quot;;
</code></pre>
<p>I put this script into <code>~/.config/i3/</code> directory, but you can put it wherever you want. You might also want to make sure <code>wmctrl</code> is installed before running it.</p>
<p>Finally, wrap relevant command in your i3 config with this script. For example, to get the behaviour described above with konsole, one would write:</p>
<pre><code class="language-bash">set $mod=Mod4
bindsym --release $mod+Return exec &quot;konsole&quot;
bindsym --release $mod+Shift+Return exec &quot;bash ~/.config/i3/float-runner.sh konsole&quot;
</code></pre>
<p>And voila, konsole will float whenever you start it using <code>Super+Shift+Enter</code>.</p>
]]></content:encoded></item><item><title><![CDATA[Clean inter-process communication in Electron]]></title><description><![CDATA[<p>Recently, I was working on an <a href="https://electronjs.org/?ref=foxypanda.me">Electron</a> app and I had to write a module for inter-process communication (IPC), namely between the main process (&quot;backend&quot;) and the renderer (&quot;browser window&quot; or &quot;frontend&quot;). This is a pretty common concern for Electron developers - they often</p>]]></description><link>https://foxypanda.me/clean-interprocess-communication-in-electorn/</link><guid isPermaLink="false">6592c4e3872ab6db7481a112</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Sat, 29 Dec 2018 00:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/11/electron.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/11/electron.jpg" alt="Clean inter-process communication in Electron"><p>Recently, I was working on an <a href="https://electronjs.org/?ref=foxypanda.me">Electron</a> app and I had to write a module for inter-process communication (IPC), namely between the main process (&quot;backend&quot;) and the renderer (&quot;browser window&quot; or &quot;frontend&quot;). This is a pretty common concern for Electron developers - they often need to perform some logic in the main process and report the result to the renderer.</p>
<p>As a huge fan of <a href="https://www.jetbrains.com/?ref=foxypanda.me">JetBrains IDEs</a>, I always try to write code that can be easily interpreted by my IDE, giving me access to auto-completion, type-checking, and many other goodies. I also like to write code that is easy to read and easy to maintain. In this article, I&apos;ll talk about how I achieved both of these things for Electron IPC.</p>
<blockquote>
<p>A couple of notes before we start:</p>
<ol>
<li>I&apos;ll explain how one would implement that solution step-by-step. If you want quick answers, you should scroll to the end of the article.</li>
<li>I&apos;ll drop some obvious constructs in my code examples to make them more concise. It&apos;s up to you to adapt said examples to your application.</li>
<li>I&apos;ll be talking about IPC with a single main process and a single renderer. It&apos;s up to you to extend this strategy to multiple renderers.</li>
<li>I&apos;ll focus on asynchronous logic using promises. It shouldn&apos;t be hard to make this strategy work for synchronous requests.</li>
</ol>
</blockquote>
<h1 id="problem-statement">Problem statement</h1>
<p>Let&apos;s take a look at the naive way of handling IPC in Electron. Consider the two code snippets below.</p>
<pre><code class="language-javascript">// In your main process (e.g. main.js)
const {ipcMain} = require(&apos;electron&apos;);
const backendData  = {foo: 42, bar: 322};

ipcMain.on(&apos;get-backend-value&apos;, (event, key) =&gt; {
    console.log(`Main process receved ${key}.`); // prints &quot;foo&quot;
    const value = backendData[key];
    event.sender.send(&apos;response&apos;, value);
});
</code></pre>
<pre><code class="language-javascript">// In your renderer (e.g. &lt;script&gt; tag inside index.html)
const {ipcRenderer} = require(&apos;electron&apos;);

ipcRenderer.on(&apos;response&apos;, (event, value) =&gt; {
    console.log(`Renderer received ${value}.`); // prints &quot;42&quot;
});
ipcRenderer.send(&apos;get-backend-value&apos;, &apos;foo&apos;);
</code></pre>
<p>As you can see, we had to write quite a lot of code to exchange a couple of simple messages. Additionally, we had to write some IPC logic for both the main process and the renderer, which feels like code duplication (or <em>logic duplication</em>, if you like). If we&apos;d add different types of messages and requests, this code would quickly become a nightmare to maintain. Our goal is to produce a simple solution that solves all of these problems.</p>
<h1 id="solution-overview">Solution overview</h1>
<p>Let&apos;s start by looking at the brief overview of our solution. This is our desired outcome (compressed for clarity):</p>
<pre><code class="language-javascript">// Main process
const IpcModule = require(&apos;./IpcModule&apos;);
const backendData  = {foo: 42, bar: 322};
const ipc = new IpcModule({mode: &apos;server&apos;, backendData});

// This part is identical for BOTH main process and renderer:
ipc.init();
ipc.getBackendValue({key: &apos;foo&apos;})
    .then(value =&gt; console.log(`Renderer received ${value}.`)); // prints &quot;42&quot;
</code></pre>
<pre><code class="language-javascript">// Renderer
const IpcModule = require(&apos;./IpcModule&apos;);
const ipc = new IpcModule({mode: &apos;client&apos;});

// This part is identical for BOTH main process and renderer:
ipc.init();
ipc.getBackendValue({key: &apos;foo&apos;})
    .then(value =&gt; console.log(`Renderer received ${value}.`)); // prints &quot;42&quot;
</code></pre>
<pre><code class="language-javascript">// IpcModule.js (our class)
class IpcModule {
    /**
     * @param {object} data
     * @param {string} data.mode
     * @param {object} [data.backendData]
     */
    constructor(data) {
        this.mode = data.mode;
        if (this.mode === &apos;server&apos;) this.backendData = data.backendData;
    }
    
    // Some magic functions, explained later
    init() {/*...*/} 
    _prepareActiveMethod(data) {/*...*/} 
    _preparePassiveMethod(data) {/*...*/} 
    
    /**
     * @alias IpcModule.getBackendValue
     * @param {object} data
     * @param {string} data.key
     */
    server_getBackendValue(data) {
        console.log(`Main process receved ${key}.`); // prints &quot;foo&quot;
        return this.backendData[data.key];
    }
}
module.exports = IpcModule;
</code></pre>
<p>That might look like a lot of code, but let&apos;s think about which parts we <em>actually</em> need to define for our key-value exchange. It turns out there are only two:</p>
<pre><code class="language-javascript">// From main process and renderer:
ipc.getBackendValue({key: &apos;foo&apos;})
    .then(value =&gt; console.log(`Renderer received ${value}.`)); // prints &quot;42&quot;
</code></pre>
<pre><code class="language-javascript">// From IpcModule.js:
server_getBackendValue(data) {
    console.log(`Main process receved ${key}.`); // prints &quot;foo&quot;
    return this.backendData[data.key];
}
</code></pre>
<p><strong>That&apos;s right!</strong> To define a new inter-process exchange, we just need to add a new method to <code>IpcModule</code> class. Then, we can call this method directly - from both the main process and renderer code, and the result we&apos;ll get will be the same! This approach avoids any possible code duplication.</p>
<p>Note that we don&apos;t even need to use electron IPC functions to define our new method. This is possible thanks to the &quot;magic&quot; functions <code>init()</code>, <code>_prepareActiveMethod()</code> and <code>_preparePassiveMethod()</code>, which will be described below.</p>
<p>Another useful outcome is that this code can be parsed by IntelliSense, thanks to <a href="https://en.wikipedia.org/wiki/JSDoc?ref=foxypanda.me">JSDoc comments</a>. This means that you can type <code>ipc.</code> in your renderer code and <code>getBackendValue</code> method will come up as one of the auto-complete options. Don&apos;t be surprised that the method we use, <code>getBackendValue</code>, has a different name to the method we defined, <code>server_getBackendValue</code>. This is intentional and will also be explained later.</p>
<h1 id="solution-implementation">Solution implementation</h1>
<h2 id="dependencies">Dependencies</h2>
<p>The only package we&apos;ll need is <a href="https://www.npmjs.com/package/electron-promise-ipc?ref=foxypanda.me">electorn-promise-ipc</a>. As the name implies, it gives a nice promise-based abstraction for IPC in Electron. With this package, our naive IPC code becomes this:</p>
<pre><code class="language-javascript">// Main process
const promiseIpc = require(&apos;electron-promise-ipc&apos;);
const backendData  = {foo: 42, bar: 322};
 
promiseIpc.on(&apos;get-backend-value&apos;, key =&gt; {
   console.log(`Main process received ${key}`); // prints &quot;foo&quot;
   return backendData[key];
});
</code></pre>
<pre><code class="language-javascript">// Renderer
const promiseIpc = require(&apos;electron-promise-ipc&apos;);

promiseIpc.send(&apos;get-backend-value&apos;, &apos;foo&apos;)
    .then(arg =&gt; console.log(`Renderer received ${value}.`)); // prints &quot;42&quot;
</code></pre>
<p>This is definitely a huge improvement over the naive approach, but there is still much left to do.</p>
<h2 id="separate-class-for-ipc-attempt-1">Separate class for IPC (attempt #1)</h2>
<p>Now we&apos;re going to create a new ES6 class that&apos;s going to hold all of our IPC logic. As you&apos;ve seen in the overview, we want to include this file in both the main process and the renderer code to avoid code duplication.</p>
<p>We&apos;ll try to mimic what we had in the solution overview section, but you will notice that it looks much closer to the code from the previous section:</p>
<pre><code class="language-javascript">// IpcModule.js (attempt #1)
const promiseIpc = require(&apos;electron-promise-ipc&apos;);

class IpcModule {
    constructor(data) {
        this.mode = data.mode;
        if (this.mode === &apos;server&apos;) this.backendData = data.backendData;
    }
    
    init() {
        if (this.mode === &apos;server&apos;) {
            promiseIpc.on(&apos;get-backend-value&apos;, data =&gt; {
               console.log(`Main process received ${data.key}`); // prints &quot;foo&quot;
               return this.backendData[key];
            });
        }
    }

    getBackendValue(data) {
        if (this.mode === &apos;server&apos;) {
            return Promise.resolve(this.backendData[data.key]);
        } else {
            // this.mode === &apos;client&apos;
            return promiseIpc.send(&apos;get-backend-value&apos;, data);
        }
    }
}
module.exports = IpcModule;
</code></pre>
<pre><code class="language-javascript">// After we initialize &apos;ipc = new IpcModule(...)&apos;, we can use
// this method in BOTH the main process and renderer code:
ipc.getBackendValue({key: &apos;foo&apos;})
    .then(value =&gt; console.log(`Renderer received ${value}.`)); // prints &quot;42&quot;
</code></pre>
<p>Sure, this class can do what we need, but the code looks very dirty - there are a lot of if statements and our business logic is spread between <code>init()</code> and <code>getBackendValue()</code>.</p>
<h2 id="a-better-class-for-ipc-attempt-2">A better class for IPC (attempt #2)</h2>
<p>We will rewrite the class from attempt #1 to remove some of the code duplication. First of all, we&apos;ll add a prefix to our method name. The prefix is either <code>server_</code> or <code>client_</code>.</p>
<p>If the prefix is <code>server_</code>, we know that the body of the method should be executed in the main process. For example, let&apos;s call our method <code>server_getCpuUsage()</code>. If you call <code>ipc.server_getCpuUsage()</code> from the main process, we just run the method as-is. If you call <code>ipc.server_getCpuUsage()</code> from the renderer, we make a request to the main process to send us the result. This logic is reversed if the prefix is <code>client_</code>.</p>
<p>To make our method call look the same in both main process and renderer, we&apos;ll introduce a method wrapper. That is, if the original function was called <code>client_getCpuUsage()</code> or <code>server_getCpuUsage</code>, we create a wrapper method called just <code>getCpuUsage()</code>. This wrapper method will also incorporate the logic described in the paragraph above.</p>
<p>Putting all of this together:</p>
<pre><code class="language-javascript">// IpcModule.js (attempt #2)
class IpcModule {
    constructor(data) {/*...*/} // same as before
    
    init() {
        const method = &apos;server_getBackendValue&apos;;
        
        // Extract prefix and prepare alias name
        let methodMode;
        if (method.startsWith(&apos;server_&apos;) methodMode = &apos;server&apos;;
        if (method.startsWith(&apos;client_&apos;) methodMode = &apos;client&apos;;
        let alias = method.replace(`${methodMode}_`, &apos;&apos;);
        
        if (methodMode === this.mode) { // Can execute logic locally
        
            // Create a simple alias method
            this[alias] = this[method];
            // Create a listener for remote requests
            promiseIpc.on(alias, this[method].bind(this));
            
        } else { // Need to make a remote request
        
            // Create a wrapper method that makes a request
            this[alias] = function() {
                return promiseIpc.send(alias, ...arguments);
            };
            
        }
    }

    server_getBackendValue(data) {
        console.log(`Main process receved ${key}.`); // prints &quot;foo&quot;
        return this.backendData[data.key];
    }
}
module.exports = IpcModule;
</code></pre>
<p>Okay, we got rid of code duplicaiton and the solution is already looking quite good. The final thing we need to address is how we get the method names inside <code>init()</code>: Right now we have a hardcoded constant called <code>method</code> - we can definitely better than this.</p>
<h2 id="best-class-for-ipc-final-attempt">Best class for IPC (final attempt)</h2>
<p>To get the list of method names automatically, we&apos;ll use the <code>Object.getOwnPropertyNames()</code> function (check <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames?ref=foxypanda.me">this MDN link</a> for more info). We&apos;ll also add a way to check that we&apos;re not accidentally redefining the same alias twice, and we&apos;ll extract alias-creation into separate functions.</p>
<p>Finally, we&apos;ll add a lot of JSDoc comments to make sure our IDE can understand what&apos;s going on. To make sure our IDE picks up the new alias method, we&apos;ll add <code>@alias IpcModule.getBackendValue</code> to our <code>server_getBackendValue()</code> definition.</p>
<p>The final code for the <code>IpcModule</code> class can be seen below. It should be fully functional.</p>
<pre><code class="language-javascript">// IpcModule.js (final)
const promiseIpc = require(&apos;electron-promise-ipc&apos;);

class IpcModule {
    /**
     * @param {object} data
     * @param {string} data.mode
     * @param {object} [data.backendData]
     */
    constructor(data) {
        this.mode = data.mode;
        this.otherMode = data.mode === &apos;server&apos; ? &apos;client&apos; : &apos;server&apos;;
        if (this.mode === &apos;server&apos;) this.backendData = data.backendData;
    }

    init() {
        const takenNames = {};
        const methodNames = Object.getOwnPropertyNames(IpcModule.prototype);
        for (const methodName of methodNames) {
            let methodMode;

            // Determine whether we even need to prepare this method
            if (methodName.startsWith(`${this.mode}_`)) methodMode = this.mode;
            else if (methodName.startsWith(`${this.otherMode}_`)) methodMode = this.otherMode;
            else continue;

            // Check that we&apos;re not redefining a method multiple times
            const alias = methodName.replace(`${methodMode}_`, &apos;&apos;);
            if (takenNames[alias])
                throw new Error(`Tried to redefine method ${alias} in IpcModule!`);
            takenNames[alias] = true;

            // Define method as passive or active depending on the mode
            const data = {methodName, alias};
            if (methodMode === this.mode) this._prepareActiveMethod(data);
            else this._preparePassiveMethod(data);
        }
    }

    /**
     * @param {object} data
     * @param {string} data.methodName
     * @param {string} data.alias
     */
    _prepareActiveMethod(data) {
        this[data.alias] = this[data.methodName];
        promiseIpc.on(data.alias, this[data.methodName].bind(this));
    }

    /**
     * @param {object} data
     * @param {string} data.alias
     */
    _preparePassiveMethod(data) {
        this[data.alias] = function () {
            return promiseIpc.send(data.alias, ...arguments);
        };
    }

    /**
     * @alias IpcModule.getBackendValue
     * @param {object} data
     * @param {string} data.key
     */
    server_getBackendValue(data) {
        console.log(`Main process receved ${key}.`); // prints &quot;foo&quot;
        return this.backendData[data.key];
    }
}

module.exports = IpcModule;
</code></pre>
<p>This class can now be used the same way it was used in the <a href="#solutionoverview">Solution Overview section</a>. It&apos;s worth noting that you could replace prefixes with <a href="https://github.com/tc39/proposal-decorators?ref=foxypanda.me#decorators">decorators</a> if you wanted. In my case, prefixes worked just fine since they didn&apos;t require any additional compilation.</p>
]]></content:encoded></item><item><title><![CDATA[Summer internship at UCL Surgical Robot Vision lab]]></title><description><![CDATA[<p>In the summer of 2017 I had the pleasure of spending several months as a research intern at UCL&apos;s <a href="https://github.com/surgical-vision?ref=foxypanda.me">Surgical Robot Vision</a> (SRV) lab. I was primarily working with <a href="https://www.microsoft.com/hololens?ref=foxypanda.me">HoloLens</a>, developing <a href="https://www.youtube.com/watch?v=QdiNdCASFNA&amp;ref=foxypanda.me">a system</a> for tracking different surgical tools and displaying relevant information as holograms.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/tracking_tools.gif" alt="A GIF showing me holding a surgical needle in my hand. The needle is tracked by IR cameras, and hologram is drawn on top of the needle as a visual guide." loading="lazy"></p>
<p>Almost a year has</p>]]></description><link>https://foxypanda.me/summer-internship-at-ucl-surgical-robot-vision-lab/</link><guid isPermaLink="false">65905171a84a2ca7df12d442</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Sat, 29 Dec 2018 00:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/src-hololens.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/src-hololens.jpg" alt="Summer internship at UCL Surgical Robot Vision lab"><p>In the summer of 2017 I had the pleasure of spending several months as a research intern at UCL&apos;s <a href="https://github.com/surgical-vision?ref=foxypanda.me">Surgical Robot Vision</a> (SRV) lab. I was primarily working with <a href="https://www.microsoft.com/hololens?ref=foxypanda.me">HoloLens</a>, developing <a href="https://www.youtube.com/watch?v=QdiNdCASFNA&amp;ref=foxypanda.me">a system</a> for tracking different surgical tools and displaying relevant information as holograms.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/tracking_tools.gif" alt="Summer internship at UCL Surgical Robot Vision lab" loading="lazy"></p>
<p>Almost a year has passed since I joined SRV for a two month internship. It&apos;s true that I&apos;ve since forgotten some minor things, but there are several important lessons I have learned back then that are still with me now. Truth be told, I was planning to publish this article shortly after my internship has ended (pic below) but, for some reason, I felt like it would be incomplete - now I have finally understood why.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/procrastination.jpg" alt="Summer internship at UCL Surgical Robot Vision lab" loading="lazy"></p>
<h1 id="what-ive-been-waiting-for">What I&apos;ve been waiting for...</h1>
<p>As I said above, something just didn&apos;t feel right before. A couple of days ago I finished <a href="https://github.com/TimboKZ/caltech_samaritan?ref=foxypanda.me">a project</a> involving autonomous drone exploration using <a href="http://www.ros.org/?ref=foxypanda.me">ROS</a>, and it dawned on me - if only I&apos;d use ROS more during my time at SRV, my development speed would be much higher.</p>
<p>A year ago, back when I joined SRV, I had very little robotics experience. During my first year in UCL I&apos;ve taken a class called <a href="http://www.cs.ucl.ac.uk/current_students/syllabus_index_2016_2017/undergrad/105p_robotics_programming/?ref=foxypanda.me">COMP105P Robotics Programming</a>. This class didn&apos;t even mention ROS, which was definitely a huge oversight since ROS is a crucial part of robotics these days. In UCL&apos;s defense, the timeline for that class had to be rushed because of the exam period, but I still wish they would include ROS in some shape or form.</p>
<p><a href="http://www.caltech.edu/?ref=foxypanda.me">Caltech</a>, on the other hand, has a very strong robotics track with amazing classes like <a href="http://robotics.caltech.edu/wiki/images/a/a0/MECS_133_overview_2017.pdf?ref=foxypanda.me">ME/CS 133 Robotics</a>. This year they offered an new class called <em>CS/EE/ME 134 Autonomy</em>. As the name implies, it focused on autonomous systems, mainly autonomous exploration and <a href="https://en.wikipedia.org/wiki/Simultaneous_localization_and_mapping?ref=foxypanda.me">SLAM</a> using different robots. The practical part of the class relied heavily on ROS, with a fair bit of handholding from the TAs during labs.</p>
<p>This soft introduction to ROS helped me understand just how powerful (and, in a sense, simple) ROS is. Thinking about the final product of my SRV project, I can see exactly where ROS would make my life easier - most of the time it&apos;s about standardadising communication between, say, HoloLens, and the ultrasound machine.</p>
<p>Considering SRV literally has the word &quot;Robot&quot; in its name, it should be obvious that people there use ROS all the time. Back when I was just starting my project, my colleagues suggested I use ROS. I quickly brushed off the idea because the learning curve was a bit too steep. Additionally, I had to use Windows for HoloLens development and ROS doesn&apos;t play well with Windows.</p>
<p>This wasn&apos;t a bad decision overall: I still ended up building a decent proof-of-concept system even without ROS. That said, if I were to redo the whole project, I would definitely use a different approach. I only needed Windows on my development machine to compile my HoloLens application, so it wasn&apos;t a runtime dependency. This meant that, potentially, I could do HoloLens development on Windows but then switch to Linux to get access to all ROS features. This is the main change I would make if back then I had the experience I have now.</p>
<h1 id="mistakes-made-and-lessons-learned">Mistakes made and lessons learned</h1>
<blockquote>
<p><em>Note:</em> Don&apos;t get the wrong idea - although I start the article by reflecting on my mistakes, my experience with SRV was dominantly positive. In fact, I had a blast working with them! Check out the end of the article for my thoughts on this.</p>
</blockquote>
<p>First of all, I must say that the proof-of-concept system I built ended up working as expected. <a href="https://www.youtube.com/watch?v=QdiNdCASFNA&amp;ref=foxypanda.me">This video</a> shows one of the final prototypes in action. You can notice that the hologram for the ultrasound probe (green mesh) doesn&apos;t perfectly align with the actual probe - I initially used a wrong transform in the <a href="https://definedterm.com/absolute_orientation?ref=foxypanda.me">absolute orentation</a> algorithm. I fixed it later, but I didn&apos;t shoot any videos after the fix was made, which leads us to my first mistake.</p>
<h2 id="mistake-1-not-enough-videos">Mistake #1: Not enough videos</h2>
<p>As the saying goes, a picture is worth a thousand words. Extrapolating to videos, your average PAL stream shows you 25 thousand words per second. In my case, shooting more videos of the final setup would not only make it easier to publicize the project, but also help my colleagues reproduce my experimental setup.</p>
<p>This might sound silly but making video instructions actually makes a huge difference. In my case, the setup was using a Python server, OptiTrack Motive software, infrared cameras, ultrasound machine and appropriate software, Unity3d, HoloLens-specific Visual Studio plugins and some other minor tools. During development, I&apos;ve become familiar enough with these tools to setup the whole system with ease - but it is unreasonable to expect other people to learn the whole pipeline just to test a small component of my system (say, IR tracking).</p>
<p>Producing more videos showing the whole setup in action and explaining how to setup each separate component (using some voiceover) would definitely make things clearer. Unfortunately, I didn&apos;t have the time to do that, which leads us up to my second mistake.</p>
<h2 id="mistake-2-hard-to-reproduce">Mistake #2: Hard to reproduce</h2>
<p>While it&apos;s true that I was learning some new things on my way, I wouldn&apos;t say I wrote bad code. The code I wrote was reasonably easy to follow and I tried to clarify any ambigious points in the README. The issue was that there was <em>a lot</em> of code - not only that, but it was also split between a Python (backend) codebase and a C# (HoloLens) codebase.</p>
<p>Near the end of my internship, I decided to use my last week primarily to fix bugs and increase the quality of codebase in general. While this wasn&apos;t a bad idea, I had my priorities wrong - I should&apos;ve spent more time writing documentation instead. I learnt the hard way that mediocre code with good, detailed documentation can be better than perfect, poorly-documented code.</p>
<p>Despite the little time I dedicated to documentation, I still ended up writing a very long README file describing everything in detail. Sadly, quantity doesn&apos;t imply quality and there were still things in the README that were ambigious (mostly related to experimental setup). I believe that, if I would spend more time bouncing documentation off my colleagues, I&apos;d be able to make things more clear.</p>
<p>I still don&apos;t have a universal solution to writing detailed documentation for complex systems. That said, in my particular case I could avoid some of the complexity by not reinventing the wheel and using ROS, as I mentioned in the beginning of this article.</p>
<h2 id="mistake-3-not-enough-expiremental-data">Mistake #3: Not enough expiremental data</h2>
<p>With the help of my supervisor I ended up producing <a href="https://arxiv.org/abs/1802.03274?ref=foxypanda.me">a short paper</a> for the SPIE 2018 Medical Imaging conference. That paper wasn&apos;t particularly good because it didn&apos;t display much experimental data - because I didn&apos;t record much data in the first place.</p>
<p>I was too focused on improving the prototype that I completely forgot to measure quantitative things like hologram drift, lag in ultrasound imagery, error in hologram placement and so on. I didn&apos;t really have any research-related experience before SRV so I didn&apos;t realise how important data is before I actually begun writing the paper.</p>
<p>Another important lesson was that <em>just</em> recording data isn&apos;t enough - you must also make sure to store it in a safe place. I used a MacBook provided by SRV for my work and stored all of the data on it. After I left, the laptop was repurposed for other projects, and the need for my data didn&apos;t come up until several months later. At that point, tracking down the data proved to be a bit of a challenge, which could easily be avoided if I&apos;d just store the data online.</p>
<h2 id="conclusion-on-my-mistakes">Conclusion on my mistakes</h2>
<p>Some of these mistakes might seem silly - they certainly do to me when I re-read the draft of this article. Nevertheless, those are issues that I have actually experienced and I think it&apos;s important to reflect on them. After all, this is exactly what internships are for.</p>
<h1 id="working-at-srv">Working at SRV</h1>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/tim_in_hololens-1.jpg" alt="Summer internship at UCL Surgical Robot Vision lab" loading="lazy"></p>
<p>The list of mistakes above is the result of me reflecting on my SRV experience for a year or so. During the actual internship I had an amazing time - not only did I get to play with cool, expensive hardware (see pic below) but I was also surrounded with friendly, smart and helpful people.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/cool_hardware-2.jpg" alt="Summer internship at UCL Surgical Robot Vision lab" title="From left to right: OptiTrack V120:Trio IR cameras, Arctic Spyder 3 3D scanner, Microsoft HoloLens." loading="lazy"></p>
<p>As I said earlier, when I joined SRV I hardly had any research experience. I was also not too familiar with robotics and computer vision. Much to my surprise, that wasn&apos;t an issue most of the time - I got so much support from SRV members that I was never stuck on some particular problem. Apart from helping me out with my project, they were happy to answer my questions about their PhD and postdoc experience, as well as give me academic advice.</p>
<p>Now I&apos;d like to take some time to thank Dan Stoyanov, my supervisor and the head of SRV, for inviting me to work with them and guiding me in my work. I&apos;d also like to thank Mirek for introducing me to Dan and answering numerous questions I had during my internship. Thanks to Francisco for explaining computer vision and ultrasound concepts to me, to Francois for helping out with hardware and video conversion, to Evangelos for giving me a Quaternions 101 lecture and to George for giving me a hand with 3D printing. Huge thanks to Neil for helping out with the paper, it would be impossible without him!</p>
<p>I&apos;ll wrap up this article with a picture of a $2 million da Vinci robot that I had a chance to play with. I had a very good time interning at SRV and I hope I&apos;ll get a chance to work with them again in the future.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/da-vinci.jpg" alt="Summer internship at UCL Surgical Robot Vision lab" loading="lazy"></p>
]]></content:encoded></item><item><title><![CDATA[Clearing local costmap in ROS when using PointCloud2]]></title><description><![CDATA[<p>Recently, I finished developing a simple framework for autonomous exploration of 3D environments using drones in ROS. It was <a href="https://web.archive.org/web/20230127173616/https://github.com/TimboKZ/caltech_samaritan">my final project</a> for Caltech&apos;s <em>ME 134 Autonomy</em> class. I was using the <a href="http://wiki.ros.org/move_base?ref=foxypanda.me"><code>move_base</code> navigation stack</a> and I ran into a weird issue where my local costmap would</p>]]></description><link>https://foxypanda.me/clearing-local-costmap-in-ros-with-pointcloud2/</link><guid isPermaLink="false">6592277cf387b3adbb6dea98</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Fri, 15 Jun 2018 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/ros.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/ros.jpg" alt="Clearing local costmap in ROS when using PointCloud2"><p>Recently, I finished developing a simple framework for autonomous exploration of 3D environments using drones in ROS. It was <a href="https://web.archive.org/web/20230127173616/https://github.com/TimboKZ/caltech_samaritan">my final project</a> for Caltech&apos;s <em>ME 134 Autonomy</em> class. I was using the <a href="http://wiki.ros.org/move_base?ref=foxypanda.me"><code>move_base</code> navigation stack</a> and I ran into a weird issue where my local costmap would correctly display obstacles as they appear, but will not get cleared once the obstacles move out of drone&apos;s field of view. In this article I&apos;ll talk about how I fixed this issue - hopefully this will help someone out there facing the same issue.</p>
<h1 id="problem-statement">Problem statement</h1>
<p>This problem is a bit hard to explain so I&apos;m going to include some animations to illustrate the issue. It&apos;s important that you understand exactly what my issue was - there are many different reasons why certain parts of the <code>move_base</code> stack can fail and I don&apos;t want you to waste time reading about my solution if it isn&apos;t relevant to you.</p>
<h2 id="the-drone-and-local-costmap">The drone and local costmap</h2>
<p>The type of sensors you use for your navigation stack matters a lot - in my project, I used a simulated drone with a Kinect attached to it (from <a href="http://wiki.ros.org/hector_quadrotor?ref=foxypanda.me"><code>hector_quadrotor</code> package</a>). Kinect published depth information as point cloud, i.e. message of type <code>sensor_msgs/PointCloud2</code>. Below you can see how the drone and Kinect depth data looked in rviz. Even if you&apos;re using <code>LaserScan</code> instead of a point cloud, this article might still be relevant to you.</p>
<h2 id="desired-result">Desired result</h2>
<p>The animation below shows the result we want to get. The white square around the drone represents the local costmap, which is populated using the data from the depth cloud. Note that the costmap gets cleared when the obstacle is no longer in the way.</p>
<div style="position:relative;max-width: 500px;margin:auto;">
<div style="position:relative;padding-bottom:74%;">
    <iframe src="https://web.archive.org/web/20230127173616if_/https://gfycat.com/ifr/CleanSillyArkshell" frameborder="0" scrolling="no" width="100%" height="100%" style="position:absolute;top:0;left:0" allowfullscreen data-ruffle-polyfilled></iframe>
</div>
</div>
<h2 id="incorrect-costmap-behaviour">Incorrect costmap behaviour</h2>
<p>Now the actual issue I encountered: note how on the animation below the costmap does not get cleared once the drone flies away from the wall. This is a big problem because it gets in the way of the local cost planner, which can fail because it has an incorrect costmap.</p>
<div style="position:relative;max-width: 500px;margin:auto;">
<div style="position:relative;padding-bottom:74%;">
    <iframe src="https://web.archive.org/web/20230127173616if_/https://gfycat.com/ifr/SomberGlamorousKitten" frameborder="0" scrolling="no" width="100%" height="100%" style="position:absolute;top:0;left:0" allowfullscreen data-ruffle-polyfilled></iframe>
</div>
</div>
<h1 id="the-solution">The solution</h1>
<p>There are several reasons why your costmap might not be updated. I&apos;m going to suggest several steps you can take to get it working.</p>
<h2 id="step-1-getting-the-obstacle-height-right">Step 1: Getting the obstacle height right</h2>
<p>When running your navigation stack (i.e. <code>move_base</code>) you might see the following warning in the console output:</p>
<pre><code class="language-bash">[WARN] [1529117350.880168708, 781.744000000]: The origin for the
sensor at (0.05, -0.02, 2.42) is out of map bounds. So, the costmap
cannot raytrace for it.
</code></pre>
<p>One possible (trivial) reason could be that you&apos;re using an incorrect transform for your sensor - I will assume you&apos;re using the correct transform. Now, the actual issue could be that there is some displacement between your sensor and the costmap in the Z-axis. Since I had a drone, I had to make it hover at the altitude of 1 metre above ground. This meant that the costmap has to be generated at approximately the same level. I had to make the following changes to my <code>costmap_common_params.yaml</code> (<a href="https://github.com/TimboKZ/caltech_samaritan/blob/master/param/costmap_common_params.yaml?ref=foxypanda.me">GitHub link</a> if you want to see the full config):</p>
<pre><code class="language-yaml">max_obstacle_height: 1.2
# ...
map_type: voxel
obstacle_layer:
  enabled: true
  # ...
  origin_z: 0.8
  z_resolution: 0.4
  z_voxels: 1
  publish_voxel_map: false
  observation_sources: depth
  depth:
    data_type: PointCloud2
    topic: /camera/depth/points
    marking: true
    clearing: true
    min_obstacle_height: 0.8
    max_obstacle_height: 1.2
# ...
</code></pre>
<p>Note that now the Z-origin of the obstacle layer is at 0.8 metres, it has a height of 1 unit cell and the size of a cell is 0.4 metres. Putting everything together, I essentially have a 2D costmap that considers obstacles of height between 0.8 and 1.2 metres. The Kinect sensor on my drone was always at around 1m altitude so the costmap was always able to raytrace it.</p>
<p><em>Aside:</em> Make sure your obstacle layer is a part of the local costmap. For example, in my <code>local_costmap_params.yaml</code> (<a href="https://github.com/TimboKZ/caltech_samaritan/blob/master/param/local_costmap_params.yaml?ref=foxypanda.me">GitHub link</a>) I have the following specified:</p>
<pre><code class="language-yaml">local_costmap:
  # ...
  static_map: false
  rolling_window: true
  # ...
  plugins:
    - {name: obstacle_layer,      type: &quot;costmap_2d::VoxelLayer&quot;}
    - {name: inflation_layer,     type: &quot;costmap_2d::InflationLayer&quot;}
</code></pre>
<h2 id="step-2-clearing-the-costmap-using-laserscan">Step 2: Clearing the costmap using LaserScan</h2>
<p>Following instructions above might&apos;ve made the warning disappear, but chances are your costmap still doesn&apos;t get cleared correctly.</p>
<p>In the previous step we have specified <code>marking: true</code> and <code>clearing: true</code> for our depth point cloud data source. Marking essentially means using the point cloud to add obstacles to the costmap. Clearing means using point cloud data to remove obstacles from the costmap. It&apos;s apparent that clearing functionality isn&apos;t working.</p>
<p>A possible reason could be that the <code>costmap_2d</code> doesn&apos;t know that it should interpret &quot;no points&quot; as &quot;free space&quot;. There is actually a way to enable this logic, but it only works for <code>LaserScan</code> data, so we&apos;ll need to convert our <code>PointCloud2</code> into <code>LaserScan</code>. We can use <a href="http://wiki.ros.org/pointcloud_to_laserscan?ref=foxypanda.me"><code>pointcloud_to_laserscan</code> package</a> to carry out this conversion: install it using <code>sudo apt install ros-kinetic-pointcloud-to-laserscan</code> (or your ROS version equivalent) and run the conversion node as follows:</p>
<pre><code class="language-markup">&lt;launch&gt;
  &lt;node pkg=&quot;pointcloud_to_laserscan&quot; type=&quot;pointcloud_to_laserscan_node&quot; name=&quot;pointcloud_to_laserscan&quot;&gt;
    &lt;!-- See all params at http://wiki.ros.org/pointcloud_to_laserscan --&gt;
      
    &lt;!-- Min and max height to sample from depth data - these values worked for my drone --&gt;
    &lt;param name=&quot;min_height&quot; value=&quot;0.3&quot;/&gt;
    &lt;param name=&quot;max_height&quot; value=&quot;1.7&quot;/&gt;
      
    &lt;!-- Min and max range range of generated laser scan - set this to match your depth sensor --&gt;
    &lt;param name=&quot;range_min&quot; value=&quot;0.2&quot;/&gt;
    &lt;param name=&quot;range_max&quot; value=&quot;4.0&quot;/&gt;
    
    &lt;!-- Frame of your depth sensor --&gt;
    &lt;param name=&quot;target_frame&quot; value=&quot;camera_link&quot;/&gt;

    &lt;!-- Topic from which to read PointCloud2 --&gt;
    &lt;remap from=&quot;cloud_in&quot; to=&quot;/camea/depth/points&quot;/&gt;
    
    &lt;!-- Topic to which LaserScan will be published --&gt;
    &lt;remap from=&quot;scan&quot; to=&quot;/scan&quot;/&gt;
  &lt;/node&gt;
&lt;/launch&gt;
</code></pre>
<p>Once you&apos;ve specified the correct settings, you can visualise your <code>PointCloud2</code> and <code>LaserScan</code> in rviz. You should get something that looks like the animation below. Note the red <code>LaserScan</code> points generated from the depth data.</p>
<p>Finally, you need to tweak your obstacle layer <code>observation_sources</code> to use the new laser scan data, the example is shown below. The most important part is the <code>inf_is_valid: true</code>. This tells <code>costmap_2d</code> to treat &quot;no reading&quot; from laser scan data as free space, which is exactly what we want. This is possible because <code>pointcloud_to_laserscan_node</code> we enabled before by default publishes <code>+inf</code> values when it doesn&apos;t see any depth cloud data for a particular angle.</p>
<pre><code class="language-yaml"># ...
obstacle_layer:
  # ...
  observation_sources: scan
  scan:
    data_type: LaserScan
    topic: scan # Your LaserScan topic
    marking: true
    clearing: true
    inf_is_valid: true # This parameter does the trick!
    min_obstacle_height: 0.8
    max_obstacle_height: 1.2
# ...

</code></pre>
<h2 id="step-3-recovery-behaviours">Step 3: Recovery behaviours</h2>
<p>If you&apos;ve done step 1 and 2 correctly, your problems should be fixed by now. Nevertheless it&apos;s worth specifying some recovery behaviours for your navigation planners. This is also useful if (for some weird reason) you can&apos;t do step 2 and must rely on <code>PointCloud2</code> data only.</p>
<p>Recovery behaviours are triggered as a last resort when your local planner fails to generate a local plan. Since we&apos;re concerned with clearing the local costmap, we&apos;ll add this logic to our recovery behaviours. In my case, I added the following to my <code>move_base_params.yaml</code> (<a href="https://github.com/TimboKZ/caltech_samaritan/blob/master/param/move_base_params.yaml?ref=foxypanda.me">GitHub link</a>):</p>
<pre><code class="language-yaml"># ...
recovery_behaviors:
  - name: &apos;conservative_reset&apos;
    type: &apos;clear_costmap_recovery/ClearCostmapRecovery&apos;
  - name: &apos;aggressive_reset&apos;
    type: &apos;clear_costmap_recovery/ClearCostmapRecovery&apos;
conservative_reset:
  reset_distance: 0.1
  layer_names: [&apos;obstacle_layer&apos;]
aggressive_reset:
  reset_distance: 0.0
  layer_names: [&apos;obstacle_layer&apos;]
</code></pre>
<p>It is very important to specify the <code>obstacle_layer</code> as one of the <code>layer_names</code> or the costmap won&apos;t get reset correctly. You might wanna increase <code>reset_distance</code> for <code>conservative_reset</code> if you don&apos;t wanna clear the full costmap.</p>
]]></content:encoded></item><item><title><![CDATA[Emphasizing the active pane in Vim using ColorColumn]]></title><description><![CDATA[<p>Around a month ago I switched to using Vim full-time. I&apos;ve already been using Vim for all of my command-line editing, but now I&apos;ve enabled Vim emulation in all of the other editors I use (namely those coming from JetBrains).</p>
<p>To keep my Vim skills versatile,</p>]]></description><link>https://foxypanda.me/emphasizing-the-active-pane-in-vim/</link><guid isPermaLink="false">65922a0df387b3adbb6deab8</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Thu, 24 May 2018 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/vim-article.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/vim-article.jpg" alt="Emphasizing the active pane in Vim using ColorColumn"><p>Around a month ago I switched to using Vim full-time. I&apos;ve already been using Vim for all of my command-line editing, but now I&apos;ve enabled Vim emulation in all of the other editors I use (namely those coming from JetBrains).</p>
<p>To keep my Vim skills versatile, <a href="https://github.com/TimboKZ/dotfiles/blob/master/.vim/vimrc?ref=foxypanda.me">my Vim configuration</a> uses vanilla key mappings. That said, I&apos;ve still made a lot of quality-of-life adjustments to the config, including colour scheme tweaks. One of such tweaks is changing the style of the active pane to make it more obvious - which is also the focus of this article.</p>
<h1 id="getting-it-to-work">Getting it to work</h1>
<p>There are several steps needed to get the whole thing working. <a href="#finalcode">Jump to the end</a> of this article if you want to see the final code.</p>
<h2 id="initial-setup">Initial setup</h2>
<p>For the sake of this article, I will use red for my colour column (from here on I will call it just &quot;column&quot;). To make screenshots more compact I will use values <code>20</code> and <code>40</code>. Let&apos;s assume I start off with this <code>.vimrc</code>:</p>
<pre><code class="language-vim">&quot; Make cc red
highlight ColorColumn ctermbg=Red

&quot; Set global cc default to 20
set colorcolumn=20

&quot; Set cc in Markdown files to 40
autocmd FileType markdown setlocal colorcolumn=40
</code></pre>
<p>Now, if I open files <code>config.ini</code> (left pane) and <code>text.md</code> (right pane) I will get this:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/05/Selection_005.png" alt="Emphasizing the active pane in Vim using ColorColumn" loading="lazy"></p>
<p>Note that the column in the left pane is draw at position <code>20</code>, and in the right column and position <code>40</code>.</p>
<h2 id="problem-statement">Problem statement</h2>
<p>We want the column to only appear in the active window. Here&apos;s the result we want to get:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/05/pane-result.gif" alt="Emphasizing the active pane in Vim using ColorColumn" loading="lazy"></p>
<h2 id="naive-solution-that-doesnt-work">Naive solution (that doesn&apos;t work)</h2>
<p>A naive solution, that uses only <code>autocmd</code>, could look like this:</p>
<pre><code class="language-vim">augroup BgHighlight
    autocmd!
    autocmd WinEnter * set colorcolumn=40
    autocmd WinLeave * set colorcolumn=0
augroup END
</code></pre>
<p>This will indeed only show the colour column in the active window, but there is one drawback - the position at which it is drawn is always <code>40</code>. This defeats the purpose of using different colour column settings for different file types. We can do better!</p>
<h2 id="better-solution">Better solution</h2>
<p>Instead of using one-liners with <code>autocmd Win&lt;Enter/Leave&gt;</code>, let&apos;s define some functions:</p>
<pre><code class="language-vim">function! OnWinEnter()
    &quot; We will put some code here soon
endfunction
function! OnWinLeave()
    &quot; We will put some code here soon
endfunction
augroup BgHighlight
    autocmd!
    autocmd WinEnter * call OnWinEnter()
    autocmd WinLeave * call OnWinLeave()
augroup END
</code></pre>
<p>By now, you have probably guessed what we will do: We will hide the colour column in <code>OnWinLeave()</code> and reveal it in <code>OnWinEnter()</code>. Doing this is pretty straight-forward, but there are some things we have to consider.</p>
<p>We will &quot;remember&quot; the initial <code>colorcolumn</code> setting of a window using a window-scoped variable. Window-scoped variables are created by prefixing their names with <code>w:</code> (check out <a href="https://www.gilesorr.com/blog/vim-variable-scope.html?ref=foxypanda.me">Vim variable scoping</a> for more info). To remember the value, we&apos;ll need to execute the <code>let w:initial_cc=&amp;colorcolumn</code> command at some point.</p>
<p>The question is, where do we put it? There&apos;s a catch: If we put it inside <code>OnWinEnter()</code>, we&apos;ll actually remember the <code>colorcolumn</code> of the <em>window that we&apos;re leaving</em>. This makes the logic somewhat complicated, so, instead, we&apos;re gonna put it inside <code>OnWinLeave()</code>. We&apos;re also gonna add some code to hide the colour column (since the window we&apos;re leaving will become inactive):</p>
<pre><code class="language-vim">function! OnWinLeave()
    &quot; Define w:initial_cc if it isn&apos;t already defined
    if !exists(&apos;w:initial_cc&apos;)
        let w:initial_cc=&amp;colorcolumn
    endif

    &quot; Hide colour column
    let &amp;colorcolumn = 0
endfunction
</code></pre>
<p>Now, variable <code>w:initial_cc</code> holds the <code>colorcolumn</code> value of the window to whose scope it belongs. All we have left to do is use this value in <code>OnWinEnter()</code>:</p>
<pre><code class="language-vim">function! OnWinEnter()
    &quot; Use w:initial_cc if it&apos;s defined
    &quot; (default value will be used otherwise)
    if exists(&apos;w:initial_cc&apos;)
        let &amp;colorcolumn = w:initial_cc
    endif
endfunction
</code></pre>
<p>And we&apos;re done! Putting everything into our Vim config we&apos;ll get the desired behaviour. I know that some of my explanations lack detail - there are just too many small things to explain here. If you want to understand why (and how) this works, try implementing it yourself.</p>
<h1 id="final-code">Final code</h1>
<p>Below you can see the full Vimscript snippet that you can put into your <code>.vimrc.json</code>. Note that this technique can be applied to other Vim parameters too - go make something cool with it!</p>
<pre><code class="language-vim">&quot; Global default
set colorcolumn=80

&quot; Specific length for different filetypes
autocmd FileType javascript setlocal colorcolumn=120

&quot; Logic for entering/leaving a pane
function! OnWinEnter()
    if exists(&apos;w:initial_cc&apos;)
        let &amp;colorcolumn = w:initial_cc
    endif
endfunction
function! OnWinLeave()
    if !exists(&apos;w:initial_cc&apos;)
        let w:initial_cc=&amp;colorcolumn
    endif
    let &amp;colorcolumn = 0
endfunction
augroup BgHighlight
    autocmd!
    autocmd WinEnter * call OnWinEnter()
    autocmd WinLeave * call OnWinLeave()
augroup END
</code></pre>
]]></content:encoded></item><item><title><![CDATA[2018 So Far: Von, Rammy, Trek*]]></title><description><![CDATA[<p>I was hoping to write at least one article every month, but as you can see this is my first post since January. I sometimes find it hard to decide which ideas or projects I want to write about, which could explain this 4-month gap.</p>
<p>Although I didn&apos;t</p>]]></description><link>https://foxypanda.me/2018-so-far/</link><guid isPermaLink="false">6592c8a3872ab6db7481a154</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Fri, 11 May 2018 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/05/2018-so-far.png" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/05/2018-so-far.png" alt="2018 So Far: Von, Rammy, Trek*"><p>I was hoping to write at least one article every month, but as you can see this is my first post since January. I sometimes find it hard to decide which ideas or projects I want to write about, which could explain this 4-month gap.</p>
<p>Although I didn&apos;t post anything in that time period, I&apos;ve been working on some things, including several open source projects. In this post, I&apos;m going to tell you about a few of them: <a href="#von">Von</a>, <a href="#rammy">Rammy</a> and <a href="#trek">Trek*</a> (click to jump to relevant section).</p>
<h1 id="von">Von</h1>
<p>Around a year ago I wrote <a href="https://foxypanda.me/blitz-static-site-generator/">Blitz</a>, an opinionated static site generator. That project was doomed from the start for several significant reasons. One of them was that I contradicted the motto of the project, &quot;dead simple static site generation&quot;, by trying to support too many features and making the logic too complicated. Needless to say, I quickly abandoned that project.</p>
<p>This time I was much less ambitious - I tried to make a command line tool that generates a single page gallery based on the images you provide. I also decided that this tool should require minimum-to-none configuration and only generate a single <code>.html</code> file. This is how Von was born.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/11/von.png" alt="2018 So Far: Von, Rammy, Trek*" loading="lazy"></p>
<p>You can always read more about how Von works on <a href="https://github.com/TimboKZ/Von?ref=foxypanda.me">its GitHub page</a>, but I think this diagram sums it up pretty well:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/12/von-concept.png" alt="2018 So Far: Von, Rammy, Trek*" loading="lazy"></p>
<p>You pick a folder on your computer that has some images in it, open that folder in terminal and simply type <code>von</code>... And, voila, you can see your new gallery in the <code>index.html</code> file. The default (and only template at the moment) is responsive and supports lazy-loading by default, but you can always define a custom Pug template.</p>
<p>It is true that this tool is pretty niche. At the same time, Von is so easy to use that you might as well download it and try running it in your <code>Pictures</code> folder. You might get some pretty interesting results, having spent less than 20 seconds on the whole process. Personally, I use Von for a self-hosted Tumblr-like gallery.</p>
<h1 id="rammy">Rammy</h1>
<p>I have recently switched to Vim as my main editor - for small projects, I use NeoVim; for larger projects, I use JetBrains IDEs with Vim emulation. Thanks to this, I&apos;ve significantly increased the speed at which I can manipulate text and, as a side effect, I&apos;ve become incredibly efficient at typing up my notes in $\LaTeX$.</p>
<p>This might sound crazy, but I now do all of my note-taking and document production in $\LaTeX$ - problem sets, lecture notes, reports, you name it. As such, I found myself often reusing the same templates and code snippets over and over again in my <code>.tex</code> files. After looking around for a while, I decided that none of the existing tools do a well enough job at <code>.tex</code> template management, so I made Rammy.</p>
<p><img src="https://cdn.rawgit.com/TimboKZ/Rammy/6a5cc0b4/assets/rammy-banner.png" alt="2018 So Far: Von, Rammy, Trek*" loading="lazy"></p>
<p>You can read more about Rammy on <a href="https://github.com/TimboKZ/Rammy?ref=foxypanda.me">its GitHub page</a>. In essence, it lets you define collections of $\LaTeX$ templates and snippets and groups them into Rammy modules. These modules can then be imported into any project or Git repository using the <code>rammy add &lt;module&gt;</code> command. Check out the <a href="https://github.com/TimboKZ/Rammy?ref=foxypanda.me#quick-example">Quick Example section</a> in the docs to see an example workflow with Rammy.</p>
<p>Rammy is not an incredibly sophisticated tool, but it fulfills its purpose pretty well - it makes setting up new $\LaTeX$ projects a painless process, so you don&apos;t need to remember what snippets you need to include to make you documents look nice. Additionally, since Rammy modules can be added to your current project as Git submodules, managing versions and dependencies becomes very straightforward. Check out <a href="https://github.com/TimboKZ/Rammy/blob/master/Modules.md?ref=foxypanda.me">Rammy modules documentation</a> for more info.</p>
<p>Personally, I&apos;m using Rammy in some shape or form almost every day and I think this tool has some potential.</p>
<h1 id="trek">Trek*</h1>
<p>Trek*, or <em>TrekStar</em> (formerly known as Ariadne&apos;s Thread), is a project I&apos;m working on with a group of students from Caltech. We&apos;re trying to take a brand new approach to automatic route planning. Instead of going for the shortest or quickest path like most route planners today, we&apos;re trying to make the route more enjoyable. Our users can specify a bunch of non-trivial preferences, such as the amount of greenery they want to see on their way, and we will generate them a route that best matches these constraints.</p>
<p>This project is still in active development so there isn&apos;t much to present at this point. All of the code is publicly available under the <a href="https://github.com/ariadnes-thread?ref=foxypanda.me">Ariadne&apos;s Thread GitHub organization</a>.</p>
<h1 id="wrap-up">Wrap up</h1>
<p>I know that the descriptions I provided in this post are a bit too brief - you&apos;re welcome to check the documentation for each project separately. I feel like the docs already describe everything in detail, and I don&apos;t want to repeat myself.</p>
]]></content:encoded></item><item><title><![CDATA[How I got into coding]]></title><description><![CDATA[<p>I had to move from London, UK, to Pasadena, CA, USA for a year as a part of a study abroad program. Unfortunately, I couldn&apos;t take my battlestation with me so I had to bring one of my old laptops instead. While looking through my old coding projects,</p>]]></description><link>https://foxypanda.me/how-i-got-into-coding/</link><guid isPermaLink="false">6592c621872ab6db7481a121</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Wed, 31 Jan 2018 00:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/evo-thumbnail-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/evo-thumbnail-1.jpg" alt="How I got into coding"><p>I had to move from London, UK, to Pasadena, CA, USA for a year as a part of a study abroad program. Unfortunately, I couldn&apos;t take my battlestation with me so I had to bring one of my old laptops instead. While looking through my old coding projects, I found a couple of nice images and blog posts I wrote. This sparked some memories of how I first got into coding, so here&apos;s a short article about it.</p>
<h1 id="early-days">Early days</h1>
<p>At some point in the past, my dad used to be a programmer and an IT specialist. Although he left the industry before I realised how cool it was, this meant that I had my own PC pretty early in my life. At first, I used to game, <em>a lot</em> (don&apos;t judge). Then I slowly picked up some photo manipulation and video editing skills, and eventually some music production. Considering I&apos;m pretty much tone-deaf, the music I made sounded horrible.</p>
<p>Photo manipulation and video editing was a completely different story - I became quite good at it and volunteered to fix &quot;issues&quot; on all of the family photos accumulated over the years. While spending day and night playing with Photoshop, I was also producing tons of video compilations and other slideshows for the whole family.</p>
<p>According to <a href="https://en.wikipedia.org/wiki/Supply_creates_its_own_demand?ref=foxypanda.me">Say&apos;s law</a>, &quot;supply creates its own demand&quot;. This might not be true in economics but this certainly worked with my family - to this day, I&apos;m still getting requests for videos for different celebrations, weddings, and less festive occasions. When I find myself editing a family video at 3 o&apos;clock in the morning, with several deadlines coming up later same day, I ask myself: &quot;would I have changed anything in the past given a chance?&quot;. I still don&apos;t know the answer, but it always feels nice when your skills are recognised, no matter how humble they may be.</p>
<p>Surprisingly enough I still remember the exact Photoshop tutorial that made me shift from the &quot;consumer&quot; mindset to the &quot;original content creator&quot; mindset, as silly as it sounds. Unfortunately I couldn&apos;t find the actual link (it was a Russian website which no longer exists, I believe) but the tutorial focused on the &quot;Wind&quot; effect, which can you see in step 4 <a href="http://tutorialspalace.com/wind-text-effect-photoshop-tutorial/?ref=foxypanda.me">on this page</a>. Learning how to do this got me interested in the things I could do with my PC besides gaming, so I spent some time experimenting. Here are some &quot;works&quot; from 2008:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/01/tima.png" alt="How I got into coding" loading="lazy"></p>
<p>This might not sound like much - but this is all it took. Here I am, 10 years later, studying Computer Science as an exchange student in California Institute of Technology. One could argue that there many other things that contributed towards my interest in computer science. That is certainly true, but learning how to use Photoshop was the first time I sat down and thought:</p>
<blockquote>
<p><em><strong>Damn.</strong> Computers are powerful.</em></p>
</blockquote>
<p>This marked the start of my career as a 3d artist, graphic designer, music producer, video editor, photographer, author of numerous mods for numerous games and so on. Thinking back, I never produced anything particularly good but it was all useful experience - it helped me realise just how many things there are that you can do with a computer.</p>
<p>You would expect that I would stumble upon the concept of &quot;programming&quot; by this time, but somehow I managed to remain oblivious to the fact that you can actually produce your own programs. This is especially surprising if you consider the fact that I successfully made several game mods that involved using scripts written by others, but I never wrote anything myself. If I remember correctly, the first thing I wrote that at least remotely resembled &quot;code&quot; were ActionScript snippets for Macromedia Flash. Back then, I couldn&apos;t understand how a Flash scene interacts with ActionScript so this experience didn&apos;t leave any particular impression on me.</p>
<p>Before we dive into the next section, I&apos;d like to share a picture of my cat. The timestamp suggests that it was taken in 2012, which means she&apos;s 7 years old already. Time flies.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/01/lucy.jpg" alt="How I got into coding" loading="lazy"></p>
<h1 id="web-design">Web design</h1>
<p>The first &quot;real&quot; coding experience came in middle school. I can&apos;t recall exactly if it was grade 6 or 7. As a part of our IT education curriculum, we had to make simple portfolio websites. This was the first exposure I had to HTML and CSS. Before that, I never really thought about how the two worked together. The moment I created my first page and styled it with CSS, I clicked - the next couple of months, I&apos;ll spend several days a week making websites, and after that I&apos;ll code for several hours almost every day.</p>
<p>My IT teacher at the time was <a href="https://web.archive.org/web/20180131082950/http://www.miras-kids.kz/team/15">Svetlana Borisovna</a>. She also taught my older brother (who went to the same school), covering calculus lessons and homeroom. The reason I&apos;m bringing this up is because she actually let me teach an IT class once - after she saw my progress, she decided it would be a good idea to let me share what I&apos;ve learnt with my classmates. If memory serves me right, they weren&apos;t as excited about it as I was, but it definitely was a great experience. Years later, during my first year as a computer scientist at UCL, I&apos;ll get a chance to talk about my project as a part of software engineering lecture (thanks to a good acquaintance of mine, <a href="http://www.cs.ucl.ac.uk/people/Y.Fu.html/?ref=foxypanda.me">Yun Fu</a>).</p>
<p>The first website I made was a simple showcase of some of the things I made in Photoshop. It wasn&apos;t anything particularly impressive, but I still think it was kinda cool - I spent a lot of time experimenting with <code>&lt;frame&gt;</code> tags, trying to avoid code duplication as much as possible. Of course, I had to use <code>Comic Sans</code> as the font of my choice. You can see screencaps of selected pages below.</p>
<link rel="stylesheet" href="https://cdn.rawgit.com/fengyuanchen/viewerjs/7d17cf627ac3c6817d8eaa46fbb9c9a3f82a5b05/dist/viewer.min.css">
<script type="text/javascript" src="https://cdn.rawgit.com/fengyuanchen/viewerjs/7d17cf627ac3c6817d8eaa46fbb9c9a3f82a5b05/dist/viewer.min.js"></script>
<style>
.images {
    display: flex;
    align-items: center;
    justify-content: center;
}
.images .image-wrapper {
    padding: 5px;
}
.images img {
    max-width: 100%;
}
</style>
<div class="images" id="blog-v1-images">
  <div class="image-wrapper" style="flex: 0.776"><img title="The first website I made." src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/01/v11.png" alt="How I got into coding"></div>
  <div class="image-wrapper" style="flex: 1.639"><img title="The first website I made." src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/01/v12.png" alt="How I got into coding"></div>
  <div class="image-wrapper" style="flex: 1.36"><img title="The first website I made." src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/01/v15.png" alt="How I got into coding"></div>
</div>
<script>
var options = {};
var viewer = new Viewer(document.getElementById('blog-v1-images'), options);
</script>
<p>Considering I&apos;m 20 now, this must&apos;ve been 9 years ago so the whole thing begun in 2009. Since then my blog went through several iterations of complete redesigns until I finally ended up with what you see now. Here&apos;s an overview I made several years ago, which covers the 2009-2012 (approx.) period:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/01/aboutpic1.png" alt="How I got into coding" loading="lazy"></p>
<h1 id="wrapping-up">Wrapping up</h1>
<p>This pretty much sums up how I got into coding. Some time later, I got into PHP which vastly increased the amount of things I could do. I stuck with PHP+HTML+CSS stack for a year or so, and then I discovered jQuery which helped me get into JavaScript. Funnily enough, for quite a long time I was using the good ol&apos; MS Notepad for all of the coding I did. That&apos;s right - no syntax highlighting, no auto-indentation, no auto-completion, just bare text. You can&apos;t imagine how excited I was to use <a href="https://www.eclipse.org/?ref=foxypanda.me">Eclipse</a> for the first time (eventually switching to <a href="https://www.jetbrains.com/?ref=foxypanda.me">JetBrains&apos; IDEs</a>, which I still use today).</p>
<p>I&apos;d like to wrap up with a passage I wrote for my blog in 2012. I preserved the original grammar and syntax, enjoy!</p>
<blockquote>
<h1 id="web-design">Web Design</h1>
<p>The idea of creating websites came to my mind long time ago, when I saw cool websites made by non-professionals. I found out, that they were using <a href="http://www.ucoz.com/?ref=foxypanda.me">uCoz</a> system. I got an account, then registered my first website... That&apos;s how I became involved in web-design.</p>
<p>My first website was very basic and non-customized - I just used typical uCoz preset. It looked exactly as millions of other websites, because preset I used was popular. Then I explored the possibility of editing HTML and CSS with <a href="http://en.wikipedia.org/wiki/WYSIWYG?ref=foxypanda.me">WYSIWYG</a>. It was a big step forward for me. As uCoz&apos;s editor had had not enough functionality for me, I begun learning HTML by myself.</p>
<p>My first tutorial taught me how to make a simple websites using standard <a href="https://en.wikipedia.org/wiki/Windows_Notepad?ref=foxypanda.me">MS Notepad</a>. Believe me or not, but this website and it&apos;s previous version are/were written in Notepad! So I would like you to make a conclusion - the result doesn&apos;t depend on facilities. My first website was written in MS Notepad and had 3 strings of code, and this page, also written in Notepad, has 300 HTML strings, 25 PHP strings and 297 CSS strings written by me.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Responsive placeholders for lazy-loading images]]></title><description><![CDATA[<p>I was adding bits and pieces to a small project of mine (I wrote about it <a href="https://foxypanda.me/2018-so-far/">here</a>) when I came across an interesting problem. I had a bunch of images that were aligned in one row and resized using <code>display: flex;</code>, so that all of them would have the same</p>]]></description><link>https://foxypanda.me/responsive-placeholders-for-lazy-loading-images-that-use-flex/</link><guid isPermaLink="false">6592c984872ab6db7481a172</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Tue, 26 Dec 2017 00:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/12/css-flex-placeholder.png" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/12/css-flex-placeholder.png" alt="Responsive placeholders for lazy-loading images"><p>I was adding bits and pieces to a small project of mine (I wrote about it <a href="https://foxypanda.me/2018-so-far/">here</a>) when I came across an interesting problem. I had a bunch of images that were aligned in one row and resized using <code>display: flex;</code>, so that all of them would have the same height. This setup can be seen below.</p>
<iframe height="225" scrolling="no" title="CSS flex images" src="https://codepen.io/Timbo_KZ/embed/MrJyBm/?height=265&amp;theme-id=0&amp;default-tab=result&amp;embed-version=2" frameborder="no" allowtransparency="true" allowfullscreen="true" style="width: 100%;">See the Pen <a href="https://codepen.io/Timbo_KZ/pen/MrJyBm/?ref=foxypanda.me">CSS flex images</a> by Timur Kuzhagaliyev (<a href="https://codepen.io/Timbo_KZ?ref=foxypanda.me">@Timbo_KZ</a>) on <a href="https://codepen.io/?ref=foxypanda.me">CodePen</a>.
</iframe>
<p>These images were loaded lazily, which meant that the height of the page and the layout in general changed as more pictures were loaded. In this article I&apos;ll talk about creating responsive placeholder for images when you know the dimensions of the image beforehand.</p>
<h1 id="problem-statement">Problem statement</h1>
<p>As seen in the embed above, we have a row of responsive pictures. We want to replace these images with placeholders until loading is completed, and we want these placeholders to look and behave exactly like the original images. We want this because</p>
<p>We don&apos;t have any thumbnails available. We also want our solution to work correctly even when perfect alignment wasn&apos;t possible (seen below). This happens when the total width of all images is smaller than the width of the container, since we don&apos;t stretch our images.</p>
<iframe height="365" scrolling="no" title="CSS flex images edge cases" src="https://codepen.io/Timbo_KZ/embed/vpgGzM/?height=365&amp;theme-id=0&amp;default-tab=result&amp;embed-version=2" frameborder="no" allowtransparency="true" allowfullscreen="true" style="width: 100%;">See the Pen <a href="https://codepen.io/Timbo_KZ/pen/vpgGzM/?ref=foxypanda.me">CSS flex images edge cases</a> by Timur Kuzhagaliyev (<a href="https://codepen.io/Timbo_KZ?ref=foxypanda.me">@Timbo_KZ</a>) on <a href="https://codepen.io/?ref=foxypanda.me">CodePen</a>.
</iframe>
<h1 id="solution">Solution</h1>
<p>The placeholder that satisfies our requirements can be built using two <code>div</code>s and some CSS. The basic concept is as follows:</p>
<pre><code class="language-markup">&lt;div class=&quot;placeholder-wrapper&quot; style=&quot;max-width: {IMAGE_WIDTH}px;&quot;&gt;
    &lt;div class=&quot;placeholder&quot; style=&quot;padding-top: {RELATIVE_IMAGE_HEIGHT}%;&quot;&gt;
        &lt;img src=&quot;./image.png&quot; /&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<pre><code class="language-css">.placeholder-wrapper {
    margin: 0 auto;
    padding: 0;
}
.placeholder img {
    max-width: 100%;
}
</code></pre>
<p>Where <code>{IMAGE_WIDTH}</code> is the original width of the image, and <code>{RELATIVE_IMAGE_HEIGHT}</code> is defined as <code>IMAGE_HEIGHT / IMAGE_WIDTH * 100</code>. Obviously, this requires you to know the dimensions of the image beforehand.</p>
<p>The reason we need two <code>div</code>s instead of one is that <code>padding-top</code> ignores any margin induced by <code>margin: 0 auto;</code>, which means that in cases when the total width of all images is less than the width of the container, our placeholder can behave incorrectly.</p>
<h1 id="demo">Demo</h1>
<p>Applying this idea to our initial example:</p>
<iframe height="235" scrolling="no" title="CSS flex images with placeholders" src="https://codepen.io/Timbo_KZ/embed/preview/YYNqRM/?height=235&amp;theme-id=0&amp;default-tab=result&amp;embed-version=2" frameborder="no" allowtransparency="true" allowfullscreen="true" style="width: 100%;">See the Pen <a href="https://codepen.io/Timbo_KZ/pen/YYNqRM/?ref=foxypanda.me">CSS flex images with placeholders</a> by Timur Kuzhagaliyev (<a href="https://codepen.io/Timbo_KZ?ref=foxypanda.me">@Timbo_KZ</a>) on <a href="https://codepen.io/?ref=foxypanda.me">CodePen</a>.
</iframe>]]></content:encoded></item><item><title><![CDATA[Better native logging in Node.js]]></title><description><![CDATA[<p>I often have to read through piles of logs for different programs. Levels of sophistication and approaches to logging differ from program to program, but I think everyone would agree that there is a certain bare minimum of data your logs should contain to be useful. Not only that, I&</p>]]></description><link>https://foxypanda.me/better-native-logging-in-node-js/</link><guid isPermaLink="false">6592c920872ab6db7481a166</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Tue, 21 Nov 2017 00:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/node-article.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/node-article.jpg" alt="Better native logging in Node.js"><p>I often have to read through piles of logs for different programs. Levels of sophistication and approaches to logging differ from program to program, but I think everyone would agree that there is a certain bare minimum of data your logs should contain to be useful. Not only that, I&apos;d go further and say that it&apos;s your obligation as a developer of an open-source/public project to make your logs look nice.</p>
<h1 id="better-native-nodejs-logs">Better <em>native</em> Node.js logs</h1>
<p>There are a lot of amazing logging libraries out there (<a href="https://github.com/pinojs/pino?ref=foxypanda.me">pino</a>, <a href="https://github.com/trentm/node-bunyan?ref=foxypanda.me">bunyan</a>, <a href="https://www.npmjs.com/package/winston?ref=foxypanda.me">winston</a>, etc.). The only issue is that these libraries tend to require some time to setup and understand, and it&apos;s totally alright if you&apos;re not willing to put in the effort.</p>
<p>That said, there are still ways you can improve your console output without using external libraries. Here&apos;s a nice 13 line function that makes your logs look much nicer:</p>
<pre><code class="language-javascript">let log = () =&gt; {
    let now = new Date();
    let options = {
        hour12: false,
        year: &apos;numeric&apos;,
        month: &apos;short&apos;,
        day: &apos;numeric&apos;,
        hour: &apos;2-digit&apos;,
        minute: &apos;2-digit&apos;
    };
    let timeString = now.toLocaleString(&apos;en-us&apos;, options);
    let args = [].slice.call(arguments);
    console.log.apply(null, [`[${timeString}]`].concat(args));
}
</code></pre>
<p>This function is simply a wrapper for <code>console.log(...)</code>. It prepends a (configurable) timestamp to your console output and makes it easier to follow the timeline of your logs. Some usage examples:</p>
<pre><code class="language-javascript">log(&apos;Logging is cool!&apos;);
// [Nov 21, 2017, 00:46] Logging is cool!

log(&apos;Hello&apos;, {a: 1, b: 2, c: 3});
// [Nov 21, 2017, 00:47] Hello { a: 1, b: 2, c: 3 }

log(new Error(&apos;I am an error.&apos;));
// [Nov 21, 2017, 00:48] Error: I am an error.
//     at Glitter.connect (...)
//     at Object.&lt;anonymous&gt; (...)
//     at Module._compile (...)
//     ...

log();
// [Nov 21, 2017, 00:49]
</code></pre>
<p>That&apos;s not much, but it&apos;s already a step in the right direction. Replace your plain logging with my function and I&apos;ll love you forever.</p>
<p>For bonus points, export this function as a part of your <code>Util</code> module to make it easier to use. For even more bonus extra points, use a <a href="https://www.loggly.com/blog/node-js-libraries-make-sophisticated-logging-simpler/?ref=foxypanda.me">proper logging library</a>.</p>
]]></content:encoded></item><item><title><![CDATA["Always use single quotes in JS" or how to protect yourself from XSS using SQL injections]]></title><description><![CDATA[<p>Now that I&apos;m enjoying my time as an exchange student at Caltech, I decided to look back at some of the great things that happened to me in UCL. This particular time I&apos;ll tell you about an effective way to shield your website from various exploits:</p>]]></description><link>https://foxypanda.me/always-use-single-quotes-in-your-js-or-how-to-protect-yourself-from-xss-using-sql-injections/</link><guid isPermaLink="false">6592ca87872ab6db7481a190</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Fri, 17 Nov 2017 00:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/quotes-vs.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/quotes-vs.jpg" alt="&quot;Always use single quotes in JS&quot; or how to protect yourself from XSS using SQL injections"><p>Now that I&apos;m enjoying my time as an exchange student at Caltech, I decided to look back at some of the great things that happened to me in UCL. This particular time I&apos;ll tell you about an effective way to shield your website from various exploits: by introducing more vulnerabilities.</p>
<h1 id="scenario-weeks">Scenario weeks</h1>
<p>Being a 2nd year computer scientist at UCL means that several times a year you&apos;ll have so-called <em>scenario weeks</em>. During a scenario week, all lectures are cancelled (<em>woooo</em>) and you have a week long computer science and/or software engineering challenge (<em><strong>wooooooo</strong></em>).</p>
<p>To help you get started, the first couple of scenario weeks are reasonably tame. One of them involves learning about web app security and popular exploits by implementing two versions of the same website - the first version is vulnerable to popular exploits (namely <a href="https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project?ref=foxypanda.me">OWASP Top Ten</a>) and the second version is as bullet-proof as you can get.</p>
<p>Once you&apos;re done with the implementation, you produce videos and reports of all of the exploits you managed to use on your first web app, and then produce a similar report to show how every single one was prevented in your second web app. I had a bunch of web design experience prior to that scenario week so I personally didn&apos;t learn anything mind-blowing, but it was certainly a fun challenge to tackle.</p>
<blockquote>
<p><em>Aside:</em> I mentioned having some web design experience - I&apos;ve actually been making websites since I was 11. The designs for some of them were hilarious (I didn&apos;t immediately discover CSS) so I will most likely make a separate post about them too. (<em>4th Feb 2018 edit:</em> <a href="https://foxypanda.me/web/20230127165551/https://foxypanda.me/how-i-got-into-coding/">Here&apos;s the post</a>!)</p>
</blockquote>
<h1 id="implicit-cross-site-scripting-protection">&quot;Implicit&quot; cross-site scripting protection</h1>
<p>We finished making the &quot;bad&quot; version of the web app reasonably quickly (doesn&apos;t take long to break things), you can find the final &quot;masterpiece&quot; in <a href="https://github.com/culshoefer/build-your-own-gruyere?ref=foxypanda.me">Chris Ul&apos;s GitHub repo</a>. We moved on to producing reports and videos shortly after, and everything was going smoothly until we got to <a href="https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)?ref=foxypanda.me">cross-site scripting</a> (XSS).</p>
<p>It was weird and funny, of course, that every single exploit we tried before that worked successfully: cookie hijacking, SQL injections, cross-site request forgery, you name it. Every exploit worked like clockwork because we purposefully made our website vulnerable. But that did not apply to XSS - did we mis-engineer our web app and unknowingly made it more secure than it should have been?</p>
<p>As funny as it sounds, it wasn&apos;t completely out of the question. A quick reminder on how XSS works: On most websites, when you register you specify your name and other details in your profile. These details are then shown to other users when you, for example, post comments. Remember that everything you see is just HTML source code that your browser has parsed, and your browser normally can&apos;t tell the difference between proper, readable text and HTML tags that should be parsed. If I were to set my name to <code>&lt;script&gt;alert(&apos;Hello!&apos;);&lt;/script&gt;</code> and the website wouldn&apos;t have proper XSS protection in place, every user who opens a page with my comment would get an annoying <code>Hello!</code> popup. Now this example is relatively harmless, but people can execute all sorts of nasty scripts using XSS.</p>
<p>With this knowledge in mind, we went through the source code for our app to see if we&apos;re performing sanitisation or any other &quot;unnecessary&quot; checks. Looking at our <code>SnippetAPI.php</code>:</p>
<pre><code class="language-php">// ...
$q = &quot;INSERT INTO snippets (owner_id, content) VALUES (&apos;&quot;.$_GET[&apos;user_id&apos;].&quot;&apos;, &apos;&quot;.$_GET[&apos;content&apos;].&quot;&apos;)&quot;;
$res = mysqli_query($conn, $q);
// ...
</code></pre>
<p>Looks pretty insecure to me - so the issue wasn&apos;t in sanitisation. We brainstormed for a second and then it dawned on us.</p>
<h1 id="eureka">Eureka!</h1>
<p>All the scripts we used for our XSS injections had a structure similar to <code>&lt;script&gt;alert(&apos;Hello!&apos;);&lt;/script&gt;</code> - you might notice that it contains some single quotes (<code>&apos;</code>). These single quotes broke the SQL query you saw above, so nothing was inserted into the database and our uses were safe. In essence, the fact that our system was vulnerable to SQL injections saved our users from specific XSS attacks.</p>
<p>Now that&apos;s not some obscure bug that requires decades of computer science experience to pinpoint. In fact, this is probably the simplest yet silliest &quot;bug&quot; I&apos;ve ever encountered in my life, which is why we spent good 10 minutes laughing at the very idea.</p>
<h1 id="vs"><code>&apos;</code> vs <code>&quot;</code></h1>
<p>Of course, if we were to use double quotes instead (i.e. <code>&lt;script&gt;alert(&quot;Hello!&quot;);&lt;/script&gt;</code>) our XSS attack would succeed and someone could steal our users&apos; data. This is where the second punchline comes in - it&apos;s safe to say to that any meaningful JS script would contain at least one string. Same goes for scripts used in XSS attacks. Unless you&apos;re doing something <a href="http://www.jsfuck.com/?ref=foxypanda.me">weird</a>, this means the script would have to contain at least one quote symbol.</p>
<p>I personally always use single quotes in my JS code (and sometimes backticks for <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals?ref=foxypanda.me">template literals</a>, but that&apos;s unavoidable). Naturally, I would want to convince you to switch to the dark side single quotes too. There are <a href="https://stackoverflow.com/questions/242813/when-to-use-double-or-single-quotes-in-javascript?ref=foxypanda.me">many ways</a> to achieve that, and thanks to this bug here&apos;s a brand new take on the problem:</p>
<blockquote>
<p>Imagine you begin religiously using single quotes in your JavaScript, converting all of your friends. They convert their friends, and soon enough we have everyone using single-quotes.</p>
<p>Now, a friend of yours, who&apos;s not very good with webapp security, makes a site everyone begins to use. By convention, they&apos;ll use single quotes in their SQL queries.</p>
<p>Eventually someone would try to perform an XSS attack to spy on you. Remember that in our <em>perfect world</em> everyone uses single-quotes, meaning that the attacker will fail since their code won&apos;t even get inserted into the database thanks to the bug we&apos;ve just covered. Thus the world is safe.</p>
</blockquote>
<p>If this doesn&apos;t make you want to only ever use single quotes in your JS code, nothing ever will. Either way, thanks for your attention.</p>
]]></content:encoded></item><item><title><![CDATA[Blitz: Yet Another Static Site Generator]]></title><description><![CDATA[<blockquote>
<p><strong>A small disclaimer:</strong> I was planning to write this article for a long time now, and somewhere between now and the time I released Blitz I&apos;ve realised that this project was doomed from the start. Initially, this post was going to be about me telling you how cool</p></blockquote>]]></description><link>https://foxypanda.me/blitz-static-site-generator/</link><guid isPermaLink="false">6590516da84a2ca7df12d3f2</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Sat, 14 Oct 2017 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/blitz.jpg" medium="image"/><content:encoded><![CDATA[<blockquote>
<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/blitz.jpg" alt="Blitz: Yet Another Static Site Generator"><p><strong>A small disclaimer:</strong> I was planning to write this article for a long time now, and somewhere between now and the time I released Blitz I&apos;ve realised that this project was doomed from the start. Initially, this post was going to be about me telling you how cool Blitz is, but that didn&apos;t work. So now I&apos;m just gonna tell you that Blitz happened, and it was great while it lasted.</p>
</blockquote>
<p>There are times when I suffer from the <a href="https://en.wikipedia.org/wiki/Not_invented_here?ref=foxypanda.me">NIH syndrome</a>. Back in my high school days I used to write everything from scratch - I once even attempted to reinvent Photoshop using PHP (don&apos;t ask me how that went).</p>
<p>Throughout the years, my programming experience was growing and the amount of free time I had was shrinking, so I had to (forcefully) contain any NIH tendencies that I had. Luckily, that didn&apos;t always work! When I was working on the <a href="https://foxypanda.me/ucl-peach-reality-with-microsoft-hololens/">PEACH Reality</a> project one of the requirements was to make a simple static website. That website would contain an overview of what we&apos;ve done, user manual, and some other things, practically a bunch simple HTML pages with basic styling.</p>
<p>If you know me personally, you&apos;d know that I&apos;d never be satisfied with completing such a simple task without adding a bunch of all-nighter-worthy extra features. After trying a bunch of existing static site generators, I decided that none of them were worthy of being used by me. And this, ladies and gentlemen, is precisely how Blitz was born.</p>
<h1 id="blitz">Blitz</h1>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/blitz-logo.png" alt="Blitz: Yet Another Static Site Generator" loading="lazy"></p>
<p>The first version of Blitz was published <a href="https://github.com/TimboKZ/blitz?ref=foxypanda.me">on its GitHub</a> on November 12, 2016. Since then Blitz got its own website - <a href="https://getblitz.io/?ref=foxypanda.me">https://getblitz.io/</a> (which is now dead, obviously) and a bunch of updates. All <code>0.1.*</code> releases had some nice basic functionality to them, but I never got to publishing <code>0.1.6+</code> which had all the cool features like advanced menus, IDs and so on. Instead, I was just keeping <code>0.1.6+</code> to myself and reaping all of its perks, <em>har har har</em>. You might also enjoy some trivia:</p>
<ul>
<li>Blitz was my first published NPM package (I now have 3, <em>wooo</em>).</li>
<li>Blitz was developed in a series of all-nighters amidst university deadlines and upcoming <a href="https://foxypanda.me/presenting-peach-reality-to-steve-guggenheimer-corporate-vp-of-microsoft/">presentation to corporate VP of Microsoft</a>.</li>
<li>A bit of background on how Blitz generated pages:
<ul>
<li>You create some <code>.pug</code> files to define the &quot;skeleton&quot; of your website. <a href="https://pugjs.org/api/getting-started.html?ref=foxypanda.me">Pug</a> is just a very nice HTML templating engine which makes your HTML pages smart.</li>
<li>You write up some content in <code>.md</code> files because Markdown is awesome. You can also augment your <code>.md</code> files with <a href="https://jekyllrb.com/docs/frontmatter/?ref=foxypanda.me">front matter</a> (concept shamelessly stolen from Jekyll). Front matter can define all sorts of useful things for your templates, like page ID, page title, menu title, etc.</li>
<li>You create <code>blitz.yml</code> that tells Blitz how to map your content to your Pug templates. Here you would specify menus, page URLs, etc. What I personally find cool is that you could choose a single template, and then pipe all <code>.md</code> files from some folder into it, without having to create a setting for each page separately. For example, you could create a <code>blog-post.pug</code>, then make it use all <code>.md</code> files from <code>posts/</code> effectively generating a page for every blog post you had.</li>
</ul>
</li>
<li>Blitz had an amazing feature of generating &quot;offline&quot; websites. As you might know, if you open a website locally (i.e. by simply clicking on <code>.html</code> files, without a server) most of the links tend to break because they rely on absolute URLs with respect to the website (e.g. <code>https://foxypanda.me/some/path/</code>). Blitz could generate websites where all images, stylesheets, JavaScript files and (!!!) menus would use relative paths (e.g. <code>../../image.png</code>) so your website would work anywhere. &quot;Menus&quot; is emphasized because <em>damn</em> is it hard to work with multiple menus while generating HTML files in different folders.</li>
<li>...because Blitz is absolutely awesome, it didn&apos;t stop on offline websites. As you might also know, server software (like Apache) usually serve the <code>path/index.html</code> file automatically if you make a request to <code>path/</code>. Blitz made use of that (and generated tons of directories) so you could get pretty, user-friendly URLs even if your website is static: <code>http://&lt;name&gt;/about/</code>, <code>http://&lt;name&gt;/blog/post-1/</code>, <code>http://&lt;name&gt;/blog/post-2/</code>, etc.</li>
<li>A small but nice feature - Blitz actually told you what element in the menu was the &quot;active&quot; element, so you could add some nice styling to it.</li>
<li>Now a <strong>killer feature</strong>: &quot;Killer&quot; not necessarily because it&apos;s cool, but because it was damn near impossible to implement. In fact, one of the main reasons <code>v2.0</code> came to be was to solve this issue. Anyway - Blitz allows you to reference pages by IDs on any other page. This means if on your <code>/blog/category-1/post-3/sidenote-1/comments</code> page you&apos;d want to have a link to the &quot;About&quot; page, you could simple use one of the Handlebars helpers Blitz provides. Now remember, as a developer, I had to deal with the case of you using relative URLs.</li>
<li>Blitz strived to differ from other static site generators, but for some reason it chose to use YAML for its config although <em>there was really no reason to use it, Tim</em>. Because YAML isn&apos;t very smart (unless you mod it) configs got very repetitive for big websites:</li>
</ul>
<pre><code class="language-yaml"># Snippet from `blitz.yml` of a big website
pages:
  - uri: &apos;/&apos;
    content: &apos;overview.md&apos;
    template: &apos;overview.pug&apos;
    menus:
      - name: &apos;main&apos;
        title: &apos;Overview&apos;
      - name: &apos;overview&apos;
        title: &apos;Overview&apos;
    child_directories:
      - uri: &apos;/&apos;
        name: &apos;overview_pages&apos;
        template: &apos;overview.pug&apos;
        directory: &apos;overview&apos;
        menus:
          - name: &apos;overview&apos;
  - content: &apos;research.md&apos;
    template: &apos;research.pug&apos;
    menus:
      - name: &apos;main&apos;
        title: &apos;Research&apos;
      - name: &apos;research&apos;
        title: &apos;Research Overview&apos;
    child_directories:
      - uri: &apos;/&apos;
        name: &apos;research_topics&apos;
        template: &apos;research.pug&apos;
        directory: &apos;research&apos;
        menus:
          - name: &apos;research&apos;
# ... and it kept going for 100 more lines.
</code></pre>
<ul>
<li>Blitz had a super cool <code>2.0</code> version in the works, development of which begun in December of 2016. It had tons of bug fixes and tons of improvements, including real-time updates, preview server, config validation...</li>
</ul>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/blitz-2.0-short.png" alt="Blitz: Yet Another Static Site Generator" loading="lazy"></p>
<ul>
<li>...but version <code>2.0</code> was never released. In fact, <a href="https://github.com/TimboKZ/blitz/pull/12?ref=foxypanda.me">the pull request is still open</a>.</li>
</ul>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/blitz-2.0-end.png" alt="Blitz: Yet Another Static Site Generator" loading="lazy"></p>
<h1 id="conclusion">Conclusion</h1>
<p>I absolutely loved writing Blitz. BUT I really, really, <em>really</em> had to limit the scope. My software engineering lecturer, Graham Roberts, would be disappointed by the amount of scope creep I have allowed. I would also definitely not use YAML again - if anything, I&apos;d stick to JS-based config files so my users would get all of the perks of an actual programming language.</p>
<p>These 2 things aside, my main mistake was to allow the architecture to get super complex. I should&apos;ve developed a nice, simple abstraction first and then develop the project around it, instead of doing it the other way around. All in all, Blitz was a great programming exercise.</p>
<p>Lesson learned, <em>project abandoned</em>.</p>
]]></content:encoded></item><item><title><![CDATA[MyAnimeTimeline and Kuristina]]></title><description><![CDATA[<blockquote>
<p>This article has been sitting in my drafts for a while now, I decided to add some minor details and publish it. Apologies if it might seem incomplete.</p>
</blockquote>
<p>One day I was looking through the MyAnimeList (MAL) account of mine and I&apos;ve noticed that I&apos;ve specified</p>]]></description><link>https://foxypanda.me/my-anime-timeline-and-kuristina/</link><guid isPermaLink="false">6590516da84a2ca7df12d3e6</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Sat, 14 Oct 2017 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/my-anime-timeline.jpg" medium="image"/><content:encoded><![CDATA[<blockquote>
<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/my-anime-timeline.jpg" alt="MyAnimeTimeline and Kuristina"><p>This article has been sitting in my drafts for a while now, I decided to add some minor details and publish it. Apologies if it might seem incomplete.</p>
</blockquote>
<p>One day I was looking through the MyAnimeList (MAL) account of mine and I&apos;ve noticed that I&apos;ve specified the start and finish for every single title I&apos;ve watched. Now that isn&apos;t really impressive, but noone else using MAL actually does that.</p>
<p>I immediately thought that it would be neat to map all of the titles I&apos;ve watched on a timeline to see how long it took me to complete each one (I&apos;m known to take up to a year to complete certain series). Once I got the basic demo running, I decided to add a couple more features to the prototype and make it public. This is how <a href="https://hosting.kawaiidesu.me/MyAnimeTimeline/?username=Timbo_KZ&amp;list_type=anime&amp;display_type=box&amp;ref=foxypanda.me">MyAnimeTimeline</a> was born and, as a side product, I made a small API tool called Kuristina. Find out more about both of them below.</p>
<h1 id="myanimetimeline">MyAnimeTimeline</h1>
<p>You can try the <a href="https://hosting.kawaiidesu.me/MyAnimeTimeline/?ref=foxypanda.me">MyAnimeTimeline webapp</a> to see the tool in action. You will need a MyAnimeList account of course, or you could just try my username: <code>Timbo_KZ</code>. The source code can be found <a href="https://github.com/TimboKZ/MyAnimeTimeline?ref=foxypanda.me">on its GitHub</a>. Below you can see a screencap of what it looks like when you use my account.</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/my-anime-timeline-preview.png" alt="MyAnimeTimeline and Kuristina" loading="lazy"></p>
<p>Since most people don&apos;t actually specify their start and finish dates, the app has to fallback to some less accurate timestamps, such as the date of last update. This makes the timeline look a bit wonky if the user has made a lot of updates on a single day, or, a more likely scenario, if they added a bunch of titles when they&apos;ve created their account.</p>
<h1 id="fetching-anime-and-manga-lists-using-kuristina">Fetching anime and manga lists using Kuristina</h1>
<p>While developing MyAnimeTimeline, I realised that MAL website doesn&apos;t support CORS. To help aid my own projects and help out other developers, I hosted a simple API on Heroku that takes raw data from MAL and makes it a bit more structured. The API is called Kuristina and you can find more information, including usage instructions, <a href="https://github.com/TimboKZ/kuristina?ref=foxypanda.me">on its GitHub page</a>. Kuristina also has <a href="https://www.npmjs.com/package/kuristina?ref=foxypanda.me">a Node wrapper</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Akko Music Visualisation Framework]]></title><description><![CDATA[<p>It&apos;s a life-long dream of mine to invent a robust yet accessible music visualisation framework. I&apos;ve tried a bunch of times in the past using Java (<a href="https://github.com/TimboKZ/ActiveVisualiser?ref=foxypanda.me">ActiveVisualiser</a>, <a href="https://github.com/TimboKZ/Envision?ref=foxypanda.me">Envision</a>) but the solutions were very obviously imperfect: first of all, I had no idea what I was doing,</p>]]></description><link>https://foxypanda.me/akko/</link><guid isPermaLink="false">6592c7f3872ab6db7481a142</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Fri, 13 Oct 2017 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/akko-post.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/akko-post.jpg" alt="Akko Music Visualisation Framework"><p>It&apos;s a life-long dream of mine to invent a robust yet accessible music visualisation framework. I&apos;ve tried a bunch of times in the past using Java (<a href="https://github.com/TimboKZ/ActiveVisualiser?ref=foxypanda.me">ActiveVisualiser</a>, <a href="https://github.com/TimboKZ/Envision?ref=foxypanda.me">Envision</a>) but the solutions were very obviously imperfect: first of all, I had no idea what I was doing, and secondly, they were written in Java.</p>
<p>As we all know, browsers are becoming more and more powerful every day with Web Audio API and WebGL being some of the cool additions. Seeing how perfectly these 2 technologies could work together I decided to combine them to produce yet another music visualisation framework, while eliminating 2 major flaws of previous implementations:</p>
<ul>
<li>The framework no longer uses Java but the beautiful language of JavaScript.</li>
<li>Users can run visualisers in their browsers, so developing/sharing visualisers doesn&apos;t require much setup. (Or any setup if you just wanna check out someone else&apos;s visualiser, just click on a link.)</li>
</ul>
<p>With this mind, I made <strong>Akko</strong> (<a href="https://github.com/TimboKZ/Akko?ref=foxypanda.me">GitHub repository</a>, <a href="https://demos.kawaiidesu.me/akko/?ref=foxypanda.me">online demos</a>).</p>
<h1 id="introducing-akko">Introducing Akko</h1>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/akko.png" alt="Akko Music Visualisation Framework" loading="lazy"></p>
<p>Akko is a JavaScript music visualisation framework that runs in your browser. It uses Web Audio API and WebGL to provide play music and render 2D or 3D graphics in to the browser window.</p>
<p>Akko exposes a set of APIs that allow you to implement a visualiser, ranging from simple 2D graphics to complex 3D compositions with shadows and shaders. Below you can see a screenshot from a simple visualiser implemented in 45 lines of JS (with proper whitespace). The picture is clickable, in case you want to see the visualiser in action.</p>
<p><a href="https://demos.kawaiidesu.me/akko/?ref=foxypanda.me"><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/Akko-Custom-Visualiser.png" alt="Akko Music Visualisation Framework" loading="lazy"></a></p>
<p>As usual, you can find more information on <a href="https://github.com/TimboKZ/Akko?ref=foxypanda.me">Akko&apos;s GitHub page</a>. Check out some <a href="https://demos.kawaiidesu.me/akko/?ref=foxypanda.me">online demos</a>, but keep in mind that Akko provides the framework for visualisation - how <em>cool</em> the visualisation looks is up to the author.</p>
]]></content:encoded></item><item><title><![CDATA[Readability buff]]></title><description><![CDATA[<p>If you&apos;ve ever visited this website before October 2017, you might&apos;ve seen some cool animations and a dynamic grid, just like the one on the gif.</p>
<div class="center-content border-child">
<iframe src="https://giphy.com/embed/3o6nUYb73LP21VLF5K" width="480" height="336" frameborder="0" class="giphy-embed" allowfullscreen></iframe>
</div>
<p>It looked nice and was certainly fun to implement, but god was it not readable at all. I decided to</p>]]></description><link>https://foxypanda.me/readability-buff/</link><guid isPermaLink="false">6592caf2872ab6db7481a1a0</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Fri, 13 Oct 2017 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/grid-vs-list.png" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/grid-vs-list.png" alt="Readability buff"><p>If you&apos;ve ever visited this website before October 2017, you might&apos;ve seen some cool animations and a dynamic grid, just like the one on the gif.</p>
<div class="center-content border-child">
<iframe src="https://giphy.com/embed/3o6nUYb73LP21VLF5K" width="480" height="336" frameborder="0" class="giphy-embed" allowfullscreen></iframe>
</div>
<p>It looked nice and was certainly fun to implement, but god was it not readable at all. I decided to fix that.</p>
<h1 id="home-page-update">Home page update</h1>
<p>As you&apos;ve probably seen by now, the home page now has a nice list of posts. You can still filter them by Projects or Blog categories, but they&apos;re much more readable now in a sense that all titles and excerpts are now nicely aligned in a vertical line.</p>
<p>I&apos;ve also added some thumbnails (well, they were already there, I just <em>revealed</em> them) so the home page also looks prettier now. Just in case I will update it in the future, here&apos;s a snapshot of what the home page looks like at the time of writing:</p>
<div class="center-content border-child"><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/10/foxypanda-readable.PNG" alt="Readability buff" style="max-width: 480px"></div>
<p>Hope you like it! Tim out.</p>
]]></content:encoded></item><item><title><![CDATA[Panoramas from my trip to Japan in 2016]]></title><description><![CDATA[<p>As you might&apos;ve found out from <a href="https://foxypanda.me/my-trip-to-japan-in-summer-2016/">a previous post of mine</a>, in Summer of 2016 I spent a couple of weeks travelling around Japan. The previous post focused on the pictures I&apos;ve taken with my camera, while this one has a bunch of panoramas I&apos;</p>]]></description><link>https://foxypanda.me/panoramas-from-a-trip-to-japan/</link><guid isPermaLink="false">65905170a84a2ca7df12d426</guid><category><![CDATA[Blog]]></category><dc:creator><![CDATA[Timur Kuzhagaliyev]]></dc:creator><pubDate>Mon, 04 Sep 2017 23:00:00 GMT</pubDate><media:content url="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/japan-panoramas-2.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2018/06/japan-panoramas-2.jpg" alt="Panoramas from my trip to Japan in 2016"><p>As you might&apos;ve found out from <a href="https://foxypanda.me/my-trip-to-japan-in-summer-2016/">a previous post of mine</a>, in Summer of 2016 I spent a couple of weeks travelling around Japan. The previous post focused on the pictures I&apos;ve taken with my camera, while this one has a bunch of panoramas I&apos;ve taken during the trip using my phone.</p>
<h1 id="panoramas">Panoramas</h1>
<p>All of these were taken on iPhone, so apologies for the quality. I wanted to post them because I think they give quite a good idea of what the atmosphere there felt like.</p>
<p>View from the balcony of the apartment I stayed in, Osaka:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/nxvzlUp.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>View from a ferry to Miyajima island, Hiroshima:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/6gT6y7D.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>Miyajima island shore, Hiroshima:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/UlZIGuZ.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>A hidden room in an anime cafe, Hiroshima:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/0EVsl8H.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>The famous rock garden in Ryoanji temple, Kyoto:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/7YQBoUY.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>World&apos;s largest wooden building, Daibutsuden in Todaiji temple, Nara:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/zinvVk1.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>Iconic bridge from the Fate series, Kobe:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/TSzdlRZ.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>Room in a ryokan, Atami:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/Mg8s7vp.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>Bath on the roof of a ryokan, Atami:</p>
<p><img src="https://s3-eu-central-1.amazonaws.com/foxypanda-ghost/2017/09/EmORvZi.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
<p>A street in Akihabara, Tokyo:</p>
<p><img src="https://foxypanda-ghost.s3.amazonaws.com/2017/May/6VnmYWQ-1494109789295.jpg" alt="Panoramas from my trip to Japan in 2016" loading="lazy"></p>
]]></content:encoded></item></channel></rss>