https://blog.tommyku.com/Tommy Ku's Blog2023-11-27T16:00:00ZTommy Kuhttps://blog.tommyku.comtag:blog.tommyku.com,2023-11-27:/blog/enjoying-diy-watch-building/Enjoying DIY watch building2023-11-27T16:00:00Z2023-11-27T16:00:00Z<p>DIY watch making has became my hobby when my body and my work prevented me from going under the sun and shoot some film photos, which I also enjoyed.</p>
<p>As such a great amount of time this summer was spent building watches. DIY watch making is a hobby that takes forever for those pricy parts to arrive and they may not even fit at the end, particular the steel bracelet end links!</p>
<p>No worries though, it doesn’t require all the 9 planets to line up for a hobbyist to build a watch one likes. In fact, the many endeavor outside of building the actual watch is accumulatively more rewarding then the actual building!</p>
<p>Below are some ways one can enjoy the hobby while waiting for the right parts to arrive.</p>
<h2 id="be-inspired">Be inspired</h2>
<p>While I stray away from participating in the community to disengage my sensitive side from social influence, I’m certainly enjoying seeing what others are doing in read-only mode. The reddit community r/Watches, r/SeikoMods, r/WatchExchange and r/ChineseWatches are subs that I frequent. These are places people share cool and crazy designs, or simply seek social approval via “my first build” or “latest purchase” posts.</p>
<p>Once I’ve seen enough, I can tell what a watch design is inspired by in a glance, with a side effect of amping up my internal conflict regarding homage vs. fakes, which are the same thing, but feel very different.</p>
<p>That’s how my Bubble Back build came to be and how it went a slightly direction just to distinguish it from the original.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./inspire.jpg" alt="Inspiration">
<figcaption>Part of my dress watch build inspirations collection, I bet my Seiko 5 build came from one of these</figcaption>
</figure>
<h2 id="watch-videos">Watch videos</h2>
<p>At first YouTube was suggesting video version of watch recommendation listicles, which I enjoyed and then detested rather quickly because a $100 sub looks little different from a $5000 sub to me, those are just for folks to feed to their confirmation bias before/after their purchases.</p>
<p>Then the truly informative and interesting form of watch building video emerged: restoration videos.</p>
<p>Those who created restoration videos are the pros, they know what they are dealing with and what they are doing. The narrated ones are best because the narration educates the viewers the history of the watch being restored, the technique and reasoning behind their decisions of say replacing a part versus just cleaning them. These video are relaxing to watch somehow as well, seeing a junk watch disassembled into parts and reassembled back together as a shiny piece of precision machine feels so satisfying at the end.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./repair.jpg" alt="Repair">
<figcaption>The time setting wheels of my NH36 were knock out of alignment and online video helped me navigate through the repair</figcaption>
</figure>
<h2 id="disassemble-watches">Disassemble watches</h2>
<p>To get any sort of hands on experience it’s easier to just disassemble an old or junk watch. I’ve done so with a defective quartz to practice hand setting before attempting my first build, and I’ve went on to do a few other experiments to enable my VH31 build.</p>
<p>Sometimes disassembling watches is a cheap way to obtain parts as well, particularly for the quartz builds I’m doing.</p>
<p>One time I was opening up my Timex Expedition Field to fix the chronograph pushers, so that the watch becomes useful again, all thanks to what I learned disassembling other parts watches.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./timex.jpg" alt="Timex chronograph pusher repair">
<figcaption>Opening up the Timex and fix the stuck pushers</figcaption>
</figure>
<h2 id="modding-watches">Modding watches</h2>
<p>When I am tired of the look of a watch I own, I don’t mind to swap out a dial, or the handset, or to completely remix the parts from two different watches, mostly with quartz watches because there are more parts lying around.</p>
<p>Most of my watch cases have gone through at least one dial swaps, and countless strap changes until I believe it’s reached a state I like the combination so much I don’t want to change it anymore—until I’ve eventually grown tired of it some time in the future.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./stealth.jpg" alt="Stealth build">
<figcaption>Last photo of this watch before I gutted it for parts</figcaption>
</figure>
<h2 id="shoot-photos">Shoot photos</h2>
<p>I have bought a lot of macro photography equipments that can finally be of use because watches are these small pieces of machines laiden with design details that can only be captured in their full glory with macro photography. Using the macro adaptor on my XT-20 and some lighting aid I was able to capture every little details that are easy to miss like, the smudge on the case or the wavy patterns on my Seiko 5 dial.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./seiko5.jpg" alt="Seiko 5 build">
<figcaption>The smudge on the case or the wavy patterns on my Seiko 5 dial</figcaption>
</figure>
<p>Good for some showing off on Instagram, or <a href="https://photostream.tommyku.com/">my photo stream</a> too.</p>
<h2 id="go-shopping">Go shopping</h2>
<p>I’m certainly guilty for this. The online stores for parts, albeit lacking creativity, has certainty no lack of homage material. To imagine how different combinations will work out is an exciting past time that I enjoyed.</p>
<p>For the low budget builds that I do, I started by gathering a few parts I believe will work and just mix and match, while also checking elsewhere for parts with similar vibe or cheaper price. At one point I will commit to a build, then took a 180 to swap to an entirely different style. Say my field watch build became an Aqua Terra build after realizing there exists those AT dials that worked way better with the case during one of my long browse of parts.</p>
<figure>
<img style="max-width: 470px; width: 100%;" src="./shopping.jpg" alt="Shopping list">
<figcaption>Shopping list for a California dial bubble back build</figcaption>
</figure>
<h2 id="build-unbuild-and-rebuild">Build, unbuild and rebuild</h2>
<p>And that’s how I was able to enjoy an evening of watch building without breaking the back. For the price of all 9 watches I bought or built I could have easily afforded a Seiko Presage or Tissot PRX or something of that caliber (pun intended), yet I wouldn’t have been as happy, not having any choice over what and how my watches are going to look.</p>
tag:blog.tommyku.com,2023-10-28:/blog/regulating-nh36/Regulating NH362023-10-28T16:00:00Z2023-10-28T16:00:00Z<p>Due to the mechanical nature of mechanical watches, there are many many moving parts susceptible to shock and wear. A watch may be running but if it’s off by +200 seconds a day it’ll quickly be off by over 23 minutes in a week.</p>
<p>Watchmakers use a device called timegrapher to measure the accuracy of a mechanical watch and each individual watch need to be regulated out of factory. And after years of usage, servicing will be required as well.</p>
<p>For amateurs like myself the free mobile timegrapher app is a godsend. When I first received my NH36 the movement measured +2s rate a day with beat error 0.2ms. This is fairly good for NH36, having a specified tolerance up to -20s~+40s.</p>
<p>The thing with new straps is they are quite stiff and not fitting the curvature of my wrist. Slippage is a common place and when it did, the watch may drop. First time it dropped from 0.5m above the ground, that actually caused the +2s daily rate to become 0s. Right when I was complementing the accuracy it fell 1m to the ground, this time with an rate of +65s a day.</p>
<p>I was frantically looking up how to fix this. With my skill I wouldn’t have been able to do a full reassembly if I took it apart. Luckily, there are two regulator arms on the balance cock that can be used to regulate a watch and bring it back to a reasonable accuracy. The one near the end of the hairspring is the stud carrier and the other one is the regulator.</p>
<p>Stud carrier controls beat error, or the time difference between the tick and tock of the movement as the balance swings back and forth. If the movement has a reasonably low beat error (say < 0.4ms) this shouldn’t be touched, otherwise, it’s worth adjusting the stud to avoid extra wear as the balance swings with unevenly.</p>
<p>Regulator controls the accuracy or rate of the movement. Moving this back and forth slightly changes how fast the balance moves and therefore how much faster or slower the rate per day will be.</p>
<p>Being in a rush I omitted that the stud can be left alone, and went on fiddling with both the studs and the regulator while putting the watch next to my phone as a timegrapher. The resultant 1.1ms beat error and +273 daily rate is fully expected. First time I’ve ever seen it “climb stairs” in the timegrapher.</p>
<figure>
<img style="max-width: 480px; width: 100%;" src="./fake_omega.jpg" alt="Timegrapher output of a fake Chinese Omega">
<figcaption>Timegrapher example of climbing stairs</figcaption>
</figure>
<p>Very carefully, wearing my loupe on one eye, I used a flat head screwdriver to slightly push around the stud until the beat error is down to around 0.7ms, not perfect, so after several attempts over a day I managed to bring it down to 0.2ms. There’s no “slight” adjustment as the stud is fiction fitted, it’s more like moving it around an area and see if I could hit the sweet spot.</p>
<p>Next is the regulator. Moving the stud resulted in the daily rate to fly up to +400s-ish and that’s normal. Referencing to the +/- mark on the balance cock I again slightly moved the regulator around such that the rate is down to +3s, which is well within margin of the movement’s specification.</p>
<figure>
<img style="max-width: 480px; width: 100%;" src="./nh36.jpg" alt="Timegrapher output regulated NH36">
<figcaption>After regulating</figcaption>
</figure>
<p>Before learning that I could regulate my movement I for a time freaked out thinking I’ll have to buy another movement. As it turned out regulating an NH36 isn’t difficult. The experience reveals to me an inconvenient fact though—when the time to service this movement comes in 5 or 10 years of time, it’s probably best buying a new movement than to spend the time and effort servicing this industrial looking, mass produced mechanical movement. Or to think of it the other way, the cost of failing to service this movement is low, as replacement can be bought, hopefully easily, in 10 years.</p>
tag:blog.tommyku.com,2023-10-27:/blog/diy-watch-making/DIY watch making2023-10-27T16:00:00Z2023-10-27T16:00:00Z<p>In the past few months I have built enough watches to say something about DIY watch making.</p>
<p>DIY watch making is hella fun. It can be cheap if you know where to source parts and cannibalize parts from earlier builds. There are lots of opportunities for one’s creative expression or one can simply feel good about wearing a self-assembled watch.</p>
<p>Before moving on I want to say watch making = building = assembling = buy ready-made parts online and put a watch together. The spectrum of watch making the simple act of strap changing all the way to milling each individual gear for a mechanical movement. You may have a different definition, I don’t care.</p>
<p>There are a lot of hate online about not using OEM parts or building an expensive <em>looking</em> branded watch out of cheaper parts. Again I don’t care. While I am unwilling to use a dial that says “spring drive” or “Grand Seiko” on my builds, I certainly don’t mind whatever design other people come up with. Like sub-par cosplays, I appreciate the effort and the love people put into their works.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./love.jpg" alt="Cosplayers in AOD 2016">
<figcaption>Appreciate the effort and the love put they into their cosplays</figcaption>
</figure>
<p>The world is stressful as it is. Let the man have their fun.</p>
<p>Ok, back to watch building.</p>
<p>My interest in watch making formed when I first saw the parts laid out in a shop and couples were picking parts, engraving names on a watch that’ll surely go to the bin upon their eventual breakups. The memory must have been fond, but any token of reminder is more likely to remind more pain than sweetness. Also they charge an exorbitant and discriminating amount of over HKD$2,000. Discriminating in which couples enjoy a discount whereas loners have to pay full price. To quote my uni humanities class instructor “Consider that they’ll have to endure the PDA of couples it’s right to charge extra”—when they don’t, all I can imagine is that smells like lures for couples that are susceptible to upselling such as engraving.</p>
<p>All these sinister thoughts may have been a simple sign of enviousness. Anyways the idea of DIY watch making had already been planted.</p>
<p>Fast forward a couple years I was in a very low point. My agonizing skin issue (itching eczema and painful loss of skin on my hands) has prohibited me from going out with my camera (out of fear they may slip from my greasy hand and not really in the mood). In searching for an alternative to pass time I began looking at watches.</p>
<p>By then I had already owned a Casio MTP-1381, Timex Expedition Chronograph, and a SNK789 from my parents. I wanted a different mechanical dress watch that sweeps. (normal quartz watch ticks once per second, most of time not pointing exactly at the second marks; whereas mechanical watch ticks 6 times, in a sweeping motion) At first I was shopping for the Seiko 5 field watch series which are distant homage to the original field watches, albeit looking more metallic and modern. I was split between the SNK705 and the newer SRPJs and SRPGs in price and feature—none of them is 100% what I wanted. I was so close to buying one but then I saw that the same kind of dial is available online.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./dial.jpg" alt="Looks like a SNK705">
<figcaption>Same kind of dial looking like an SNK705 availabile online</figcaption>
</figure>
<p>Then I realized:</p>
<blockquote>
<p>If I can’t buy one I like, I could build one.</p>
</blockquote>
<p>How? Check out SeikoMod community on Reddit, with many people eager to show off their builds. Many divers, lots of Oyster builds and mods, some completely unique and a few mind-blowing ones from time to time.</p>
<p>YouTube is also a valuable resource for beginners like me as various YouTubers documented how they build and what tools they use to build a watch. Not long after consulting a few videos I’ve compiled a list of minimal tools I’d need: a watch building set, a cushion, hand pusher, and a movement holder.</p>
<p>My first design was inspired by a Seiko Baby Alpinist, except with olive green dial, green nylon strap, golden hand reassembling the triangles on the hour marks, and 36mm round edge oyster case. Looking back it’s a weird choice, it’s too far away from a homage yet also too out of balance to be good looking. The anticipation of building my first watch however clouded my judgement and I went with it, with confidence, and excitement.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./side.jpg" alt="Olive alpinist dial in Oyster Perpetual case and questional hand choices">
<figcaption>Hard to get the golden shines without the crystal reflecting like hell, dull olive green and questionable hand choices</figcaption>
</figure>
<p>The tools arrived first. I disassembled and scratched the dial of the Feiko quartz watch I got from some uncle in China. With the Feiko I practiced hand setting several times and finally got the hang of it. Not really, as the hands were installed, they are not as tight as new hands. The holes on new hands are tighter and slightly harder to just set, not to mention press.</p>
<p>Then the parts arrived. I had to take a bus to get them as they were sent to a random collection point 3 stops away. On the same night, despite a working day ahead of me, I pointed a LED light source at my table and started building it.</p>
<p>Case, easy. Needed to use a rubber ball to open it, because Oyster case doesn’t have the indented holes for my case opener to fit into.</p>
<p>Movement, easy, NH36. Turn the crown to wind the watch and gentle shake to get the balance started. It’s ticking.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./movement.jpg" alt="NH36 movement">
<figcaption>NH36 movement</figcaption>
</figure>
<p>Dial, test fit and cut the extra pair of legs for 3.8 crown position. My cutter at home is very dull so it’s half pulling off the legs. The remaining bits needed to be filed down. Took me 30min to do that, gently. There’s no need to be gentle as I later learned, as long as the side and the the wielding spots were not damaged by violent filing.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./test_fit.jpg" alt="Test fitting the dial onto the movement">
<figcaption>Took me long enough to trim down the dial feet residue</figcaption>
</figure>
<p>Hands, once the dial is installed onto the movement nicely, hands can be set. Hour hand and minute hand went in nice and easily. However the pressure and balance needed to be just right or the hands can be easily bent, and start interfering with each other, or the hour marks on the dial. Second hand setting took me the most time, because it couldn’t seem to see where the pinion is and the second hand kept flying away from my tweezers. Later experience taught me to shine bright light from the opposite side (usually that’d be my PC screen) and use a piece of rodico, poke the end of the hand into it to secure the hand. Anyway, it only took 40 minutes to set the hands properly. 2 watches later I could easily do the same in 5 minutes. At the time however, it felt like an enormous achievement as this part is mostly where people online struggled with.</p>
<p>Stem, oh yea, it’s too long. Cutting and filing the stem down took another 30 minutes as I had only 1 stem (I learned to buy spares later), but it went in eventually, the most successful stem cutting I should say, as I got quite lazy in later builds.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./stem.jpg" alt="Stem is too long">
<figcaption>Stem is too long and begging to be trimmed</figcaption>
</figure>
<p>Strap, a nylon top and artificial leather bottom strap didn’t quite fit right on my hand. It’s either too loose or too tight, typical between the holes problem, but I was happy. NATO strap later proved to be a better match, and artificial leather strap ended up to be the best fit as it is stretchable.</p>
<figure>
<img style="max-width: 640px; width: 100%;" src="./cover.jpg" alt="Strap on and ready to go">
<figcaption>Strap on and ready to go</figcaption>
</figure>
<p>As such, after hours of struggle and anticipated sleepiness at work the next day, I finished my first watch build! Happy? Yes! Thrilled!</p>
<p>As I proudly wore my new watch to work, when nobody bet an eye on my new watch, my mind was racing hunt for inspiration for the next design and where I could source the parts to build them.</p>
<p>I knew this feeling. It’s when I first bought a new kind of camera or tried out a new trick such as Infrared Trichrome process. This feeling will fade and the brain demands the next novelty. What do people call this? An addiction. Running too fast too deep into a hobby would prove to be a mistake, but that’s a story for another time.</p>
tag:blog.tommyku.com,2023-07-08:/blog/humans-txt/Humans.txt2023-07-08T14:07:00Z2023-07-08T14:07:00Z<p>One little footprint I left as an intern web developer at EventXtra from 2016.</p>
<p>I am surprized to find that the <a href="https://humanstxt.org/">humans.txt</a> we left at EventXtra (now EventX) is still <a href="https://app.eventxtra.com/humans.txt">there</a>.</p>
<figure>
<img style="max-width: 100%;" src="./humans.jpg" alt="Humans.txt">
<figcaption>All have left. The file is still there.</figcaption>
</figure>
tag:blog.tommyku.com,2023-06-17:/blog/goodbye-x230/Goodbye, X2302023-06-17T16:00:00Z2023-06-17T16:00:00Z<!--
This line is 80 characters long
01234567890123456789012345678901234567890123456789012345678901234567890123456789
-->
<p>Nearly 12 years ago, I collected my brand new ThinkPad X230 from a factory building somewhere in Kowloon Bay, and
returned home to boot up Windows 7 for the first time. Windows 7! The aero theme works! SO COOL!</p>
<blockquote>
<p>If you are buying a laptop, get a ThinkPad or Mac, nothing else.
— My summer job boss</p>
</blockquote>
<p>A good chunk of my hard-earned money from the pre-university summer job went into this laptop. There were many cheaper
laptops, but I listened to my boss, it paied off.</p>
<p>In retrospect, X230’s spec may seem like nothing compared to the latest and greatest that technology can offer. It has
came a long way and it’s suprising that my ThinkPad X230 was still in active service after all these years. Planned
obsolescence had lost its magic touch in this instance.</p>
<p>During my university years I would host Minecraft server, watch YouTube/niconico video, emulate games, run Linux, work
on freelance projects, run Emacs in Windows 7, train machine learning model…</p>
<p>Alas, should have mined some Bitcoin too.</p>
<p>During my intern years I used X230 to work on all kinds of projects: Docker, nodejs, Android, or use as thin client to
simply SSH into a powerful server CLI it all the way.</p>
<p>After 4-5 years it felt slow and lacking that I considered <a href="../looking-for-a-laptop/">looking for a new laptop</a>. No
problem, it could <a href="../do-i-need-a-new-laptop/">be upgraded</a> rather easily. At that time Lenovo laptops was still
serviceable. Chuck an SSD and 4 more GBs or RAM and it’s good as new.</p>
<p>I bought a ThinkPad E540 to play games in student hall and it was nowhere close to be as durable as X230. The edge of
screen would have backlight bleeding and keyboard went haywire. Only T and X series can be called true ThinkPad.</p>
<figure>
<img style="max-width: 500px; width: 100%;" src="./hall.jpg" alt="My desk in student hall with a 4:3 old monitor between my legs">
<figcaption>Because of lack of room not because it’s a good angle</figcaption>
</figure>
<p>After 8 years, it’s still an <a href="../not-looking-for-a-laptop-anymore/">irreplaceable piece of equipment</a> that I heavily
rely on for my project works. When it ain’t broke, don’t <s>fix</s> replace it!</p>
<p>Frankly speaking, it was quite broken. <a href="../remapping-insert-to-delete-key-on-ubuntu-20-04/">Delete key</a> just chipped
off like it’s tired of living. I punched a small slit somewhere on the screen with a flat head screwdriver. The
magnesium alloy case cracked. The battery was replaced to get like 2 hours of run time, if not too much load. A fair
bit of tweaking to <a href="../i-switched-from-gnome-to-i3wm/">accomondate the WXGA resolution</a> while everyone was already
going FHD or QHD or 4K, or whatever. Bluetooth has stopped working for a couple years already. The keys, touchpad and
palm rest all became shiny after my hand had rubbed on them <em>for years</em>, and somehow they still feel great on touch.
The bumps on the touchpad is something I love that’s missing in all modern laptops, anmong other things such as
dedicated volume control, <em>lack</em> of Fn light, PgUp/PgDn key next to arrow keys…</p>
<figure>
<img style="max-width: 500px; width: 100%;" src="./smooth.jpg" alt="The palm rest, touchpad and keys are rubbed smooth">
<figcaption>Manually polished by hand</figcaption>
</figure>
<p>It works though. Good enough is good enough.</p>
<p>Maybe my dream laptop is just a modern laptop in a X230 shell!</p>
<figure>
<img style="max-width: 100%;" src="./boot_error.jpg" alt="Lenovo recovery screen">
<figcaption>Soft-kicking the bucket, Lenovo recovery DVDs service is very helpful</figcaption>
</figure>
<blockquote>
<p>I can’t imagine how my ThinkPad X230 kicks the bucket in the foreseeable future, or how it becomes so unusable I have
to switch to another laptop.
—Me@<a href="../not-looking-for-a-laptop-anymore/">Not looking for a laptop anymore</a></p>
</blockquote>
<p>In 2021 I could get reimbursed WFH equipment because COVID, so I bought a Yoga silm 7i carbon. There was a shortage of
T-series ThinkPad and X-series is no longer the kind of workhorse they used to be. That sales person was so good I fell
for it.</p>
<p>Originally I planned to use this as a leisure laptop, while slowly migrating my Linux workflow from ThinkPad X230 to
the Razer Blade 14. Then Razer Blade got so hot it burned its own graphics card. It could deal with a cup of water
poured all over it, inside and out, but can’t stand the heat from its own graphics card, with fan roaring like an
attack helicopter already.</p>
<p>Alright, both E540 and Razer Blade are gaming laptops, and both were short-lived.</p>
<p>Still, X230 was good enough for me to continue using into June 2023, nearly 12 years since I have purchased the laptop.
Now, however, it’s suffering from another damage, where the power button no longer function. I could still swipe my
finger over the fingerprint scanner to boot it.</p>
<p>A replacement keyboard is cheap, as well. Yet this is the point where I ask myself, is it a better choice to keep using
X230 for sentimental value, or migrate to the new Yoga slim 7i carbon, running the latest Windows 11 and sporting a
much faster CPU.</p>
<p>Yoga came with a dongle that expands one of its 3 USB-C port into 3 USB-A ports, a SD card reader, and an HDMI output,
which greatly enhances its usability for desktop work. Plug an additional ethernet adapter into one of the USB-A port
gives me LAN—a bare minimum for desktop and programming work.</p>
<p>While dual booting Linux is always an option, running WSL 2 on Windows 11 has proven to be a more pleasant experience.
Leisure, desktop and programming work can all be combined into one environment with zero switching cost. Upon trying
out WSL 2, my mind has been set, I am going to migrate off from X230 once my current project concludes.</p>
<p>Goodbye, X230. You have served me well and you deserve a special place in my heart.</p>
<figure>
<img style="max-width: 500px; width: 100%;" src="./together.jpg" alt="Yoga slim 7i carbon and X230 together">
<figcaption>Goodbye X230</figcaption>
</figure>
tag:blog.tommyku.com,2023-05-16:/blog/i-cant-draw-but-ai-can/I can't draw but AI can2023-05-16T16:00:00Z2023-05-16T16:00:00Z<p><em>Note: Unlike most other posts, this one is not instructional. There’re many tutorials online that are way better than any tutorial I could’ve ever produced.</em></p>
<p>When I was little my parents sent me to a drawing class which taught nothing but gave us empty papers and paints to draw on. Then was an age where creativity was appreciated over accuracy, thus my results were passable at the time.</p>
<p>Fast forward to secondary school years, my art score hadn’t been any better, especially when accuracy and design were concerned. Self-learning wasn’t a thing until I after form 4, when I realized the school teaches the mere basics while putting an unreasonably high bar on students.</p>
<p>Fine, had I came to that realization earlier in life, I wouldn’t have had an easier time re-learning everything in my adulthood.</p>
<p>Then came 2022, amongst the COVID-19 pandemic came the exciting boom of image diffusion model: DALL-E2, Midjourney, Stable Diffusion…</p>
<h2 id="my-love-for-impressionism">My love for impressionism</h2>
<p>For a period I was hooked by the impressionism works by Claude Monet.</p>
<p>In an attempt to re-create the impressionistic look, I dug fairly deep into the GIMPressionist filter of GIMP.</p>
<figure>
<img style="max-width: 300px;" src="./imp.jpg" alt="Impressionistic painting converted from photo via GIMPressionism">
<figcaption>Impressionistic painting converted from photo via GIMPressionism</figcaption>
</figure>
<p>Not good. There’s a hint of impressionism yet by simply placing fake strokes on top of a digital image, the details are appearing too clearly and the brushstrokes too regular for the impressionistic touch.</p>
<h2 id="first-time-trying-out-ai">First time trying out AI</h2>
<p>Therefore I turned to AI, to Stable Diffusion, when it first came out as an open source project. At that point people were still using the 1.5 checkpoint and it’s quite lacking compared to what we the community were able to produce now in May 2023.</p>
<p>On my underpowered machine Razer Blade 14 with a meager 3GB of VRAM, I was able to produce a few 512x512 at 15 steps over a run time of 5-10 minutes, <em>on command line</em>. Nowadays there’s an easy to use UI that makes both learning and using way easier.</p>
<p>I was very disappointed. The img2img output failed to retain the original look of my photo and went fairly creative. Yet, the biggest issue was the resolution. At 512x512 these are not good enough to excite me.</p>
<figure>
<img style="max-width: 100%;" src="./919183_00001.png" alt="Impressionistic painting imagined by Stable Diffusion via img2img">
<figcaption>Impressionistic painting imagined by Stable Diffusion via img2img</figcaption>
</figure>
<h2 id="my-other-artistic-interests">My other artistic interests</h2>
<p>Before continuing, I wish to talk about my other artistic interests. Aside from impressionism I am also passionate block printing works, in particular the many Ex Libris (bookplate) almost completely faded into history.</p>
<p>So I created my own that’s not exactly fancy.</p>
<figure>
<img style="max-width: 100%;" src="./192713.jpg" alt="Left: draft. Middle: print. Right: stamp.">
<figcaption>Left: draft. Middle: print. Right: stamp.</figcaption>
</figure>
<p>I told you I am not good at art. Maybe AI could help me fix this.</p>
<h2 id="second-time-trying-out-ai">Second time trying out AI</h2>
<p>Months later, I paid for Midjourney hoping to give AI generative art another try. On its own, Midjourney is capable of producing sufficiently good image given a well-crafted text to image prompt. Except it can’t do hands properly and I lack critical control over placement, style and size of elements.</p>
<p>Still, one leap forward beyond GIMP and early Stable Diffusion.</p>
<figure>
<img style="max-width: 300px;" src="./tti.png" alt="Text to image Rabbit">
<figcaption>Text to image via Midjourney</figcaption>
</figure>
<figure>
<img style="max-width: 500px;" src="./photo2img.png" alt="Photo to image via Midjourney">
<figcaption>Photo to image via Midjourney</figcaption>
</figure>
<p>By blending images Midjourney, it’s possible to use reference images to specify the style to convert the image into. This time, block printing.</p>
<figure>
<img style="max-width: 300px;" src="./rabbit.png" alt="Rabbit">
<figcaption>Sketch to image via Midjourney</figcaption>
</figure>
<p>One thing I like Midjourney over other generative art AIs is the way they run the community. Except for work generated in private mode, which is only available in the most expensive plan, all work and their promptes are made visible publicly in the community feeds. Beginners can easily learn from the good works produced by others and build on top of them.</p>
<p>However, there’s still the problem of a private company profiting off weights trained from publicly available sources and keeping a tight grip over the content generated.</p>
<h2 id="third-time-trying-out-ai">Third time trying out AI</h2>
<p>I didn’t want to continue paying for Midjourney and to be honest it could only take me that far. So the next thing to do was to setup my own instance of Stable Diffusion at home.</p>
<p>Luckily, my main PC sports a 7-year old GTX 1060 GPU with 6GB of VRAM. After several months of effort by the community, Stable Diffusion had became heaps more approachable, with one-click installer and various high quality checkpoints/loras.</p>
<p>Simply swapping the checkpoint from the default one I was able to generate pretty good output. Hands are still bad though.</p>
<figure>
<img style="max-width: 100%;" src="./2959607628.png" alt="Rabbit with unicorn horn holding a spear">
<figcaption>Rabbit with unicorn horn holding a spear but slightly too cute</figcaption>
</figure>
<p>Then ControlNet came around changing everything. For generative AI, by default there is little control, because once the text had been converted into latent space vectors it’s at the mercy of what the model already knows and lots of randomness.</p>
<figure>
<img style="max-width: 100%;" src="./3404297023.png" alt="Rabbit with unicorn horn holding a spear">
<figcaption>Rabbit with unicorn horn holding a spear, closer to original sketch, bit scary</figcaption>
</figure>
<figure>
<img style="max-width: 100%;" src="./3095961692.png" alt="Rabbit with unicorn horn holding a spear">
<figcaption>Rabbit with unicorn horn holding a spear(?), too much creative freedom, quite scary</figcaption>
</figure>
<p>With careful control over the parameters and trial and erros, I am now able to create the impressionistic work that pleases me.</p>
<figure>
<img style="max-width: 100%;" src="./2747851235.jpg" alt="Impressionistic painting convered from IMAX film shot on medium format camera">
<figcaption>IMAX film shot on medium format camera, converted into impressionistic work via Stable Diffusion</figcaption>
</figure>
<figure>
<img style="max-width: 100%;" src="./2157833255.png" alt="Impressionistic painting convered from GIMPressionist output">
<figcaption>Impressionistic painting convered from GIMPressionist output via Stable Diffusion</figcaption>
</figure>
<p>By importing Loras trained on works of a particular artist, the AI is able to replicate the artist’s style to a believable degree. And I can also blend my photo and with their drawing style. This one is based on the style of an artist I like on Pixiv.</p>
<figure>
<img style="max-width: 100%;" src="./1626995868.jpg" alt="Girl standing under flowers">
<figcaption>Photo blended with AI-generated drawing via Stable Diffusion</figcaption>
</figure>
<p>With ControlNet, there’s more control over how an image can be. I don’t get the block printing outcome I wanted because the checkpoint I used were better at creating cartoons. Sometimes (most of the time), I create monsters unintentionally.</p>
<figure>
<img style="max-width: 300px;" src="./3529768930.png" alt="Rabbit with unicorn horn holding a spear or some random creature">
<figcaption>??????</figcaption>
</figure>
<p>Use Inpainting + ControlNet img2img to preserve text while converting my friend into something that my friend isn’t.</p>
<figure>
<img style="max-width: 100%;" src="./4028222514.png" alt="Vibe">
<figcaption>Vibe</figcaption>
</figure>
<p>Oh and, it’s easy to upscale output I got from Midjourney with Stable Diffusion.</p>
<figure>
<img style="max-width: 300px;" src="./3091167260.jpg" alt="Girl holding camera">
<figcaption>For the lack of a life-sized model</figcaption>
</figure>
<h2 id="ai-is-a-tool">AI is a tool</h2>
<p>With the advent of diffusion model and GPT boom in late 2022, they have shown a lot of promises. Although they may be able to project an image of apparent intelligence, they still require a lot of guidance, say in the form of ControlNet, Inpainting, or preprocessing. Like a hammer, it only works when hitting on the nail head correctly.</p>
<p>Just like tools, a lot of experimentation and study are needed to understand how it works, and how to make these AI tools do what I want in the way I want.</p>
<p>So these AIs are probably not the silver bullet many came to believe or many are trying to advertise as.</p>
<h2 id="thoughts-on-future-of-ai">Thoughts on future of AI</h2>
<p>Imagine the amount of energy wasted on crypto could have been better used to train more sophisticated models, provided that the entity behind each and every model and algorithm make the relevant tools and model weights public for the community to build on top of, like Stable Diffusion.</p>
<p>Oh, the same amount of wasted energy (plus all the externalized side effect in hardware procurement) will be diverted to training AI models, then the companies will paywall them and begin charging a monthly subscription fee—just so that someone can have an agreeable AI conversation…</p>
<p>Pretty grim, but what in life isn’t.</p>
<p>By the way, is you job still secure?</p>
tag:blog.tommyku.com,2022-08-04:/blog/why-add-to-home-screen-links-to-incorrect-page/Why "Add to Home screen" links to the incorrect page2022-08-04T16:00:00Z2022-08-04T16:00:00Z<h2 id="background-skippable">Background (skippable)</h2>
<p><a href="https://github.com/monicahq/monica">Monica</a> is a personal CRM for managing contact list and journey entries. Its interface is desktop-first and somewhat responsive on mobile. The two-column layout stacks on mobile screen.</p>
<figure>
<a href="monica.jpg" target="_blank">
<img src="monica.jpg" alt="Add journey button">
</a>
<figcaption>Add journey button appearing at bottom of the page</figcaption>
</figure>
<p>Scrolling to the bottom is ok when there are limited numbers of entries. On day 3 it already began feeling troublesome having to scroll all the way down.</p>
<p>The add journal page <code>/journal/add</code>, so I thought if I use the “Add to Home screen” (A2HS) feature of my mobile browser I should be able to open the add journey page.</p>
<p>Except not really. When tapping the icon on home screen, the homepage of Monica is opened.</p>
<h2 id="what-add-to-home-screen-a2hs-actually-does">What “Add to Home screen” (A2HS) actually does</h2>
<p>When a website doesn’t have a web app <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">manifest</a> containing the <code>start_url</code> property, A2HS actually takes that URL instead of URL of the page you were viewing when you added the website to homescreen.</p>
<p>This is actually a feature for Progressive Web App (PWA) to have an app-like fullscreen shell and consistent start page when opened. The problem is mobile browsers do not offer an option to choose between the current page or the page pointed to by <code>start_url</code>.</p>
<p>Monica has this <a href="https://github.com/monicahq/monica/blob/6a0af31efc66eb49222e6328fa454c2d7d038e43/resources/views/layouts/skeleton.blade.php#L15">manifest</a> defined, so I am not able to open the <code>/journal/add</code> page directly.</p>
<h2 id="how-to-add-abritrary-page-to-homescreen">How to add abritrary page to homescreen</h2>
<p>Ok, A2HS doesn’t work, and I don’t want to write a separate app calling Monica’s <a href="https://www.monicahq.com/api">API</a> to create journal entries.</p>
<p>There are many ways to add to homescreen bypassing the forced <code>start_url</code> and link to whatever page you want. You can redirect with a meta tag on a static page. You can also write a serverless function that simply redirects when called.</p>
<p>Make sure your redirection isn’t instant. You need some time to press that “Add to Homescreen” button before the page actually redirects into Monica.</p>
<p>At the end, I created a Cloudflare worker (because it’s free) returning a HTML page that contains the redirection target.</p>
<p>That’s it! Nothing complex about adding arbitrary page to the homescreen. I have been creating PWAs for years, and only today do I realize that A2HS has to give way to make PWA work. Perhaps not enough people have <a href="https://android.stackexchange.com/questions/225002/chrome-and-firefox-cannot-add-specific-url-to-home-screen">complained</a> <a href="https://apple.stackexchange.com/questions/385417/add-to-home-screen-saves-wrong-url">about</a> this for browser vendors to improve the experience.</p>
tag:blog.tommyku.com,2022-02-02:/blog/be-lazy-and-still-respect-dnt-and-gpc/Be lazy and still respect DNT and GPC2022-02-02T16:00:00Z2022-02-02T16:00:00Z<p>This is a (rather late) follow-up to my previous posts to <a href="https://indieweb.org/selfdogfood">self-dogfooding</a> the <a href="http://u02.tommyku.com:3000/blog/website-analytics-for-lazy-people/">use tracking pixel</a> to <a href="http://u02.tommyku.com:3000/blog/website-analytics-for-lazy-people-again--with-cloudflare-worker/">track the number of page views to my site</a> while being as lazy as I can.</p>
<p>My method is known to bypass (my own) adblocker and does a good job double-counting all repeated page visits from everyone, meaning I <em>really</em> isn’t tracking anyone except for the page view. Even Nginx logs out more information than I do.</p>
<figure>
<a href="nginxlog.jpg" target="_blank">
<img src="nginxlog.jpg" style="max-width: 100%;" alt="Log from Nginx">
</a>
<figcaption>Hmm…legit traffic?</figcaption>
</figure>
<p>Oh yeah that…I should subscribe to some more IP blocklists.</p>
<figure>
<a href="abuse.jpg" target="_blank">
<img src="abuse.jpg" style="width: 100%; max-width: 500px;" alt="AbuseIPDB">
</a>
<figcaption>Well, not legit traffic</figcaption>
</figure>
<p>But I am lazy and poor so I didn’t want to setup a Nginx reverse proxy somewhere on the cloud just to get some logs. And if you don’t want to be tracked I don’t want to track you. In fact, I simply don’t want to track myself because it pollutes my statistics.</p>
<p>Being lazy, the simplest way is to add my own website to my adblocker’s custom list. That’ll cover the desktops that I use. However Google is <a href="https://en.wikipedia.org/wiki/Don%27t_be_evil">evil enough</a> to disallow Chrome extension on Chrome for Android. Blocking traffic to the tracking pixel endpoint using adblocker isn’t enough.</p>
<p>DNT stands for “Do Not Track”, a long neglected <em>wanna-be</em> standard that failed to become mainstream <a href="http://u02.tommyku.com:3000/blog/appcache-revisited/">like other things that came before</a> because websites decided that they don’t care.</p>
<p>GPC stands for “Global Privacy Control” that seems to be backed by <a href="https://globalprivacycontrol.org/">a legal standpoint</a> but I bet still nobody cares enough about (<a href="https://blog.freeradical.zone/post/ccpa-scam-2021-12/">except</a>), especially the small web.</p>
<p>Since I am getting bored about this already, here is the code. The only interesting part is the <code>if</code> conditions about DNT and GPC headers.</p>
<pre><code class="language-javascript">addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
/**
* Respond to the request
* @param {Request} request
*/
async function handleRequest(request) {
const page = decodeURIComponent(new URL(request.url).search.replace(/^\?/, ''));
// Try to respect DNT and GPC---
const dnt = request.headers.get('dnt');
const secGpc = request.headers.get('Sec-GPC');
if (dnt === '1' || secGpc === '1') {
return Response.redirect('https://blog.tommyku.com/assets/images/pixel.png?ignored', 301);
}
// ---Try to respect DNT and GPC
if (page && page.substr(0, 4) === 'http') {
let views = parseInt(await BLOG_VIEW.get(page));
if (Number.isNaN(views)) {
views = 0;
}
views++;
await BLOG_VIEW.put(page, views);
}
return Response.redirect('https://blog.tommyku.com/assets/images/pixel.png', 301);
}
</code></pre>
tag:blog.tommyku.com,2021-12-23:/blog/self-saving-web-app/Self-saving web app2021-12-23T13:19:40Z2021-12-23T13:19:40Z<p>I don’t remember when did I first learn about <a href="https://tiddlywiki.com/">TiddlyWiki</a>. 10 years ago perhaps, when <a href="https://classic.tiddlywiki.com/">TiddlyWiki 2</a> was still the major version. 10 years ago I was still using free hosting and cPanel to run small self-developed PHP 4 note-taking programs.</p>
<p>When I first saw TiddlyWiki I was blown away by the idea that a website replicates itself in its entirety to the disk on save. That means no backend is required to host a full-blown Wiki software, whereas <a href="https://en.wikipedia.org/wiki/List_of_wiki_software">most of rest of the world</a> relied on some sort of backend and database to work.</p>
<p>Now come to think about it, this is quite straight forward to implement:</p>
<pre><code class="language-plaintext">html
|-- head
|-- body
|---- script (saved state)
|---- templates
|---- main
|---- script (application logic)
</code></pre>
<p><code>html</code>, <code>head</code>, <code>templates</code>, <code>main</code> and <code>script</code> with application logic can simply be replicated on save. <code>script</code> with saved state is updated with application state at the time of saving. Of course, application state needs not be script only, it could also be HTML, or plaintext, or script, or script that loads external data, or any other format.</p>
<h2 id="why-self-saving-web-app">Why self-saving web app</h2>
<p>You may have seen lots of applications having front/backend architecture, yet there are few self-saving web apps like Tiddlywiki. Nowadays with free tier cloud hosting in abundance, it seems that “no backend required” argument is no longer justifyable.</p>
<p>Here let me try naming a few reasons why self-saving web app is still needed:</p>
<p><strong>Easy to backup/share</strong> - I maintain documentation of a freelance project using Tiddlywiki, so I can easily version control and share the documentation, with attachments and images, in a portable format (HTML vs. Word document)</p>
<p><strong>Simple development</strong> - simple, small app can be encapsulated into a single HTML document and edited on the spot with basic text editor without setting up a development environment</p>
<p><strong>Minimal backend</strong> - self-saving web app can be saved to a very thin backend such as WebDAV that saves the entire copy of the app to a server location, or simply save a copy to local machine without the need of a backend even</p>
<p><strong>App is also app builder</strong> - users can save a copy of the app locally after having configured it to their liking and host it themselves</p>
<h2 id="example-pastebin">Example: Pastebin</h2>
<p>Over the years I have tried out many different hosted pastebin solution. All of which require a heavy backend and most require some sort of database.</p>
<p>SQLite would have been fine, yet I can’t understand why a full-blown separate DBMS is required. Scalability? No one but me is using it.</p>
<p>Below shows a self-saving pastebin application I quickly created using the aforementioned architecture. In this case, the template and data are contained within the <code><textarea></code>.</p>
<pre><code class="language-html"><html>
<head>
<title>Pastebin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<style>
main {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
position: relative;
}
.bin-textarea {
margin-bottom: 1em;
height: 15vh;
line-height: 1.4em;
font-size: small;
}
.bin-textarea:focus {
position: absolute;
width: 100%;
height: 100%;
}
.bin-save {
display: block;
margin-bottom: 1em;
width: 100%;
}
</style>
</head>
<body>
<h1>Pastebin</h1>
<button class="bin-save">Save</button>
<main>
<textarea class="bin-textarea"></textarea>
<textarea class="bin-textarea"></textarea>
<textarea class="bin-textarea"></textarea>
<textarea class="bin-textarea"></textarea>
<textarea class="bin-textarea"></textarea>
<textarea class="bin-textarea"></textarea>
<textarea class="bin-textarea"></textarea>
<textarea class="bin-textarea"></textarea>
</main>
<script>
const save = data => {
const html =
`<html>
${document.querySelector('head').outerHTML}
<body>
<h1>Pastebin</h1>
<button class="bin-save">Save</button>
<main>
${data.map(d => `
<textarea class="bin-textarea">${
d.replace("<", "&lt;").replace(">", "&gt;")
}</textarea>
`).join("")}
</main>
${document.querySelector('script').outerHTML}
</body>
</html>`;
console.debug(html);
saveToFile(html);
};
const saveToFile = html => {
const $a = document.createElement('a');
$a.href = URL.createObjectURL(new Blob([html], { type: 'text/html' }));
$a.setAttribute('download', 'pastebin.html');
document.body.appendChild($a);
$a.click();
document.body.removeChild($a);
};
/* Will cover this later
const saveToWebDAV = html => {
fetch(`/pastebin.html`, {
method: "PUT",
credentials: "include",
body: html
})
.then(() => window.location = '/pastebin.html?saved')
.catch(console.error);
};
*/
const $save = document.querySelector('button');
const $textarea = document.querySelectorAll('textarea');
$save.addEventListener('click', e => {
const data = Array.from($textarea).map(t => t.value);
save(data);
});
/* Will cover this later
if ((new URLSearchParams(window.location.search)).has('saved')) {
$save.classList.add('animated', 'bounce');
}
*/
</script>
</body>
</html>
</code></pre>
<p>When the page loads, the <code><script></code> tag at the bottom is ran and click event listener is added to the save button.</p>
<pre><code class="language-javascript">$save.addEventListener('click', e => {
const data = Array.from($textarea).map(t => t.value);
save(data);
});
</code></pre>
<p>User edits the textareas and eventually click on “Save” button. After which, <code>save</code> method is called and the app’s full HTML content is recreated.</p>
<p><code><head></code> and <code><script></code> are simply copied over. The app shell such as app title and save button are statically written. And internal value of <code><textarea></code> is dynamically written to inner HTML of tag, so that the state is retained.</p>
<pre><code class="language-javascript">const html =
`<html>
${document.querySelector('head').outerHTML}
<body>
<h1>Pastebin</h1>
<button class="bin-save">Save</button>
<main>
${data.map(d => `
<textarea class="bin-textarea">${
d.replace("<", "&lt;").replace(">", "&gt;")
}</textarea>
`).join("")}
</main>
${document.querySelector('script').outerHTML}
</body>
</html>`;
</code></pre>
<p>Then <code>save</code> method will call <code>saveToFile</code> to auto-download the file to local. It could also save to WebDAV, or some sort of backend.</p>
<pre><code class="language-javascript">const saveToFile = html => {
const $a = document.createElement('a');
$a.href = URL.createObjectURL(new Blob([html], { type: 'text/html' }));
$a.setAttribute('download', 'pastebin.html');
document.body.appendChild($a);
$a.click();
document.body.removeChild($a);
};
</code></pre>
<p>As you can see, a lot of conerns (component initialization/saving) are mixed into the same <code><script></code> tag. I wouldn’t recommend this approval for app having multiple views.</p>
<p>Careful cleaning can help clear this up. Imagine view-model using React + action-reducer-store using Redux, yet the app would end up framework-heavy. Browser-native Web compoment might be a good middle ground.</p>
<h2 id="saving-with-webdav">Saving with WebDAV</h2>
<p>In the pastebin example above, there are some commented out code which are for saving the app to a thin backend such as WebDAV. I say WebDAV is thin because user need not write any custome to make this backend work.</p>
<p>In fact, the WebDAV server I run behind my personal pastebin instance is simply a docker container. My choice of WebDAV server is rclone, and there are many other images available.</p>
<pre><code class="language-bash">docker run --name pastebin \
-u $(id -u):$(id -g) \
-v /path/to/pastebin/dir:/data \
--log-opt max-size=32k \
-p 8080:8080 \
rclone/rclone serve webdav --addr :8080 --dir-cache-time 0 /data
</code></pre>
<p>To update content on a WebDAV server, the WebDAV PUT method (like HTTP PUT) can be used to overwrite resource on the server.</p>
<pre><code class="language-javascript">const saveToWebDAV = html => {
fetch(`/pastebin.html`, {
method: "PUT",
credentials: "include",
body: html
})
.then(() => window.location = '/pastebin.html?saved')
.catch(console.error);
};
</code></pre>
<p>We use <code>fetch</code> to make a PUT call to WebDAV server, with <code>credentials</code> set to “include” to include any authentication header in the call, and simply put the web app’s html into request body. On the otherside the content of <code>pastebin.html</code> will be overwritten.</p>
<p>Once the call is finished, the page is refreshed with an additional <code>saved</code> query parameter. This has nothing to with WebDAV but simply a cherry-on-top that we want to present some visual feedbacks that the page is saved and reloaded.</p>
<pre><code class="language-javascript">// Add animate.css or something that implements these 2 CSS classes
if ((new URLSearchParams(window.location.search)).has('saved')) {
$save.classList.add('animated', 'bounce');
}
</code></pre>
<p>Since the page is getting reloaded right after saving, <code>--dir-cache-time 0</code> is added to the docker run comment above to disable caching.</p>
<h2 id="example-startpage-generator">Example: Startpage generator</h2>
<p>The pastebin example covers the first 3 reasons: <strong>Easy to backup/share</strong>, <strong>Simple development</strong> and <strong>Simple development</strong>.</p>
<p>Now let me throw in another example to showcase the reason <strong>App is also app builder</strong>.</p>
<p>Below is a simple (lazily built) startpage + startpage generator that mutates itself when new link is added. That means the HTML structure is used to persist its state.</p>
<p>When save button is clicked, the entire page is simply re-created and saved locally. This may be used to distribute copies of the site to other people after they have customized it to their own likings. The copies can then make copies after further customization.</p>
<pre><code class="language-html"><html>
<head>
<title>Startpage</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<style>
body {
max-width: 960px;
margin: auto;
}
main {
display: grid;
grid-template-columns: repeat(4, minmax(200px, 1fr));
gap: 1em;
position: relative;
margin-bottom: 1em;
}
.btn-save {
display: block;
margin-bottom: 1em;
width: 100%;
}
main a {
padding: 1em;
border: dotted 1px grey;
}
main a:hover {
border: solid 1px grey;
}
</style>
</head>
<body>
<h1>Startpage</h1>
<button class="btn-save">Save</button>
<main></main>
<button class="btn-add">Add</button>
<script>
const save = () => {
const html =
`<html>
${document.querySelector('head').outerHTML}
${document.querySelector('body').outerHTML}
</html>`;
console.debug(html);
saveToFile(html);
};
const saveToFile = html => {
const $a = document.createElement('a');
$a.href = URL.createObjectURL(new Blob([html], { type: 'text/html' }));
$a.setAttribute('download', 'startpage.html');
document.body.appendChild($a);
$a.click();
document.body.removeChild($a);
};
const $save = document.querySelector('button.btn-save');
const $add = document.querySelector('button.btn-add');
$save.addEventListener('click', e => save());
$add.addEventListener('click', e => {
const url = prompt("URL?");
const title = prompt("Title?");
if (url && title) {
const $a = document.createElement('a');
const $main = document.querySelector('main');
$a.href = url;
$a.textContent = title;
$main.appendChild($a);
}
});
</script>
</body>
</html>
</code></pre>
<h2 id="source-pollution-due-to-browser-extensions">Source pollution due to browser extensions</h2>
<p>One thing to note, when using a self-saving web page, is that the simple ones like above are susceptible to pollution by browser extensions. If the extensions add/remove certain element on the page, the change in HTML will end up reflecting on the saved copy.</p>
<p>This may be worked around by using web components (with HTML template) and adding integrity checks to the static content to be saved. The only thing that should be changed on save is the state.</p>
<h2 id="why-write-about-self-saving-web-app">Why write about self-saving web app</h2>
<p>That’s all about self-saving web app I have to write about. This post is my answer for a Hacker News post <a href="http://web.archive.org/web/20211125010339/https://news.ycombinator.com/item?Id=29311761">Ask HN: Simplest stack to build web apps in 2021?</a></p>
<p>While this approach is not realistic for full-blown apps on production, this is a fast and cheap way to build small apps with persisted state with a shared, thin WebDAV backend.</p>
tag:blog.tommyku.com,2021-12-22:/blog/2021-was-like/2021 was like2021-12-22T13:19:40Z2021-12-22T13:19:40Z<h2 id="in-bullet-points">In bullet points</h2>
<ul>
<li>Became single</li>
<li>Returned to office</li>
<li>Grew in project management skillset at work</li>
<li>Ran 3 times a week comfortably</li>
<li>Realized head-over-water breaststroke</li>
<li>Did some work on the side</li>
<li>Matured home NAS setup</li>
<li>Attended first aid training</li>
<li>Continued posting to Instagram <a href="https://www.instagram.com/tommy_ku/">@tommy_ku</a> daily</li>
<li>Renewed (part of) the wardrobe with fashionable clothes</li>
<li>Family and friends are forever</li>
<li>Hoarded film cameras and film</li>
</ul>
<h2 id="in-short-tips">In short tips</h2>
<p>Some recurring themes this year are self-empowerment, assertiveness and letting go.</p>
<blockquote>
<p>Time spent in fixing mistakes is time wasted. When you could have spent less time avoiding the mistake in the first place. Take controlled risk. — Somewhere online</p>
</blockquote>
<blockquote>
<p>I have the tendency to hoard for backup so I have the option when the primary one fails (buy the same camera/headphones) but that means I’m forgoing a better future for a comfortable present. — Probably Me</p>
</blockquote>
<blockquote>
<p>Life never runs out of waves to shake you. But while it isn’t easy, you do learn to surf better. — <a href="https://www.reddit.com/user/charlie_wonka/">charlie_wonka on Reddit</a>, probably</p>
</blockquote>
<blockquote>
<p>Hatred is a curse that does not affect the hated. It only poisons the hater. Release a grudge as if it was a poison. — Probably Reddit</p>
</blockquote>
<blockquote>
<p>If you can move on and not care about them, it is good for your personal development. Your life is about you. Life without mistakes isn’t life, but a fairy tale. — Maybe me?</p>
</blockquote>
<blockquote>
<p>Fuck-ups happen. Some people make it their mission in life to make sure that whenever a fuck-up happens, somebody will suffer. Don’t be that person, and don’t let that person get to you. They’re an ass. — Probably Reddit</p>
</blockquote>
<blockquote>
<p>Spend time avoiding mistakes, not fixing them. — Probably Me</p>
</blockquote>
<blockquote>
<p>You don’t have to be young to feel young. — Probably Me</p>
</blockquote>
<blockquote>
<p>擅自期待,又因期望落空而擅自嬲怒,實在難看。 — Probably Me</p>
</blockquote>
<blockquote>
<p>他说,很多时候人们从同一点出发,有人会勉强自己做一些事,有了第一次还有第二次;而有人一开始就别扭,坚持初心,有了第一次,也会有第二次,时间一长,必然越走越远。 — 京都古书店风景</p>
</blockquote>
<p>By the way, I should get better in recording sources of these tips.</p>
<h2 id="in-unsolicited-advice">In unsolicited advice</h2>
<ul>
<li>Use virtual card</li>
<li>Make use of reminders</li>
<li>Exercise is a necessity</li>
<li>Most things don’t deserve immediate attention</li>
<li>Set up scheduled automatic investment</li>
<li>Some friendships can really last</li>
<li>Document how you solved a problem</li>
<li>Backup: two is one, one is none</li>
<li>Don’t just buy cameras, use them</li>
<li>Don’t just buy books, read them</li>
<li>Don’t just buy games, play them</li>
<li>When <a href="https://12ft.io/">12ft ladder</a> doesn’t work, try <a href="https://archive.is/">archive.today</a>
</li>
</ul>
<h2 id="in-photos">In photos</h2>
<style>
#photogrid {
display: grid;
grid-template-columns: 50% 50%;
grid-template-rows: auto;
align-items: stretch;
column-gap: 4px;
row-gap: 4px;
}
#photogrid img {
margin: 0;
}
@media screen and (max-width: 30em) {
#photogrid img {
grid-column-start: 1;
grid-column-end: 3;
}
}
</style>
<section id="photogrid">
<img src="./01-sq.jpg" alt="01.jpg">
<img src="./02-sq.jpg" alt="02.jpg">
<img src="./03.jpg" alt="03.jpg">
<img src="./04.jpg" alt="04.jpg">
<img src="./05.jpg" alt="05.jpg">
<img src="./06.jpg" alt="06.jpg">
<img src="./07.jpg" alt="07.jpg">
<img src="./08.jpg" alt="08.jpg">
<img src="./09.jpg" alt="09.jpg">
<img src="./10.jpg" alt="10.jpg">
<img src="./11.jpg" style="grid-column-start: 1; grid-column-end: 3;" alt="11.jpg">
</section>
tag:blog.tommyku.com,2021-01-15:/blog/website-analytics-for-lazy-people-again--with-cloudflare-worker/Website analytics for lazy people again, with Cloudflare Worker2021-01-15T15:15:28Z2021-01-15T15:15:28Z<p>In <a href="../website-analytics-for-lazy-people">Website analytics for lazy people</a> I have shown that you can use bit.ly to load a tracking pixel from your page, and thus capture the view count of the page that works even when JavaScript is disabled, and can fly under your ad blocker’s radar.</p>
<p>For those who finds the previous post on this topic TL;DR, basically I have added a tracking pixel to my site, which loads with the page like any image, and by using a middleman such as URL shortener to capture that request I am able to aggregate the view counts of my page.</p>
<p>After some time I began using Cloudflare worker, which is a serverless service that has a free tier and key-value storage without extra cost. Using a serverless worker I am able to do tiny tasks and write the results back to the storage entirely for free and without the need of additional infrastructure.</p>
<p>Then I thought, hey what if I ditch bit.ly and instead use Cloudflare worker to track my page view counts? Given a serverless function what I have more control over how the request sent to it is converted into page view record. I can even add hard-coded parameters to the worker URL in different page to get page-wise view counts.</p>
<p>I have implemented a quick and dirty page view counter as a Cloudflare worker like below and replaced the bit.ly tracking pixel with this worker tracking pixel. They work exactly the same in high-level, but I am able to track per-page view counts by adding different page parameter (e.g. <code>?http://blog.tommyku.com</code>) to the URL and have the worker save the counts by page.</p>
<pre><code class="language-javascript">addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
/**
* Respond to the request
* @param {Request} request
*/
async function handleRequest(request) {
const page = decodeURIComponent(new URL(request.url).search.replace(/^\?/, ''));
if (page && page.substr(0, 4) === 'http') {
let views = parseInt(await BLOG_VIEW.get(page));
if (Number.isNaN(views)) {
views = 0;
}
views++;
await BLOG_VIEW.put(page, views);
}
return Response.redirect('https://blog.tommyku.com/assets/images/pixel.png', 301);
}
</code></pre>
<p><small><center><i>Terribly written code that works ok</i></center></small></p>
tag:blog.tommyku.com,2021-01-03:/blog/2020-was-like/2020 was like2021-01-03T09:01:40Z2021-01-03T09:01:40Z<h2 id="in-bullet-points">In bullet points</h2>
<ul>
<li>Worked from home</li>
<li>Ran twice a week, swam while had chance</li>
<li>Did more work outside BAU</li>
<li>Organized a few photowalks among friends</li>
<li>Wrote more unit tests and regression tests</li>
<li>Saw a few friends left for another company</li>
<li>Missed my cousin’s wedding due to travel restriction</li>
<li>Became Certified ScrumMaster</li>
<li>Began film photography and got several friends started</li>
<li>Began posting to Instagram <a href="https://www.instagram.com/tommy_ku/">@tommy_ku</a> daily</li>
<li>Experimented taking color photos with black and white film</li>
<li>Resumed building plastic models</li>
<li>Spent the most money on eating outside</li>
<li>Set up FreeNAS at home</li>
<li>Hoarded books and old film cameras</li>
<li>Tried to form habits with todo reminders</li>
<li>Spent a bit more time with friends and family</li>
<li>Did a health screening</li>
<li>Read a few books, did a sharing</li>
</ul>
<h2 id="in-short-tips">In short tips</h2>
<blockquote>
<p>It is possible to commit no mistakes and still lose. That is not a weakness. That is life. — Jean-Luc Picard (Star Trek: TNG)</p>
</blockquote>
<blockquote>
<p>We suffer more in imagination than in reality. — Seneca</p>
</blockquote>
<blockquote>
<p>Will I get over it? Mmm…No. But life goes on. — Dwight Schrute (The Office)’s quote cut short to mean differently</p>
</blockquote>
<blockquote>
<p>Life is short and full of pain. — Me</p>
</blockquote>
<blockquote>
<p>Why not? Life is short, life is dull, life is full of pain - and this is a chance for something special. — Woody Allen</p>
</blockquote>
<blockquote>
<p>Everyone has something that makes them adorable and everyone has their fears. Everyone is vulnerable inside no matter how tough they look from the outside. — Somewhere online or Me</p>
</blockquote>
<blockquote>
<p>You do not rise to the level of your goals. You fall to the level of your systems.
<br>
Your goal is your desired outcome. Your system is the collection of daily habits that will get you there.
<br>
This year, spend less time focusing on outcomes and more time focusing on the habits that precede the results. — James Clear in Atomic Habits</p>
</blockquote>
<blockquote>
<p>I came to that a long time ago.
I have a olympic swimming pool of hatred inside me.
<br>
But it’s usually behind a closed door.
That’s why I’m calm and peaceful, because I don’t deny what I think and feel.
<br>
Doesn’t mean I swim in it all day long — Somewhere online or Me</p>
</blockquote>
<h2 id="in-photos">In photos</h2>
<style>
#photogrid {
display: grid;
grid-template-columns: 50% 50%;
grid-template-rows: auto;
align-items: stretch;
column-gap: 4px;
row-gap: 4px;
}
#photogrid img {
margin: 0;
}
@media screen and (max-width: 30em) {
#photogrid img {
grid-column-start: 1;
grid-column-end: 3;
}
}
</style>
<section id="photogrid">
<img src="./5ff1795f7369601.jpg" alt="01.jpg">
<img src="./5ff1795fb2aa102.jpg" alt="02.jpg">
<img src="./5ff179600620c03.jpg" alt="03.jpg">
<img src="./5ff179604e43404.jpg" alt="04.jpg">
<img src="./5ff179608f6f905.jpg" alt="05.jpg">
<img src="./5ff17960c151806.jpg" alt="06.jpg">
<img src="./5ff1796101c3907.jpg" alt="07.jpg">
<img src="./5ff179612accb08.jpg" alt="08.jpg">
<img src="./5ff179616c47f09.jpg" alt="09.jpg">
<img src="./5ff17961c1d1f10.jpg" alt="10.jpg">
</section>
tag:blog.tommyku.com,2020-12-30:/blog/website-analytics-for-lazy-people/Website analytics for lazy people2020-12-30T01:35:58Z2020-12-30T01:35:58Z<p>Imagine shouting into the void where there’s absolutely no echo. This is what it’s like writing this blog after I have <a href="../no-more-google-analytics/">removed Google Analytics</a>. I got organic traffic from time to time back when Google Analytics was enabled, and now I have zero idea.</p>
<p>Pretty sure I am not going back to over-tracking my visitors—but I do want to have an idea at least view counts to my site. Knowing th view count motivates me to continue writing things that someone might find useful.</p>
<p>Being lazy and prefer doing things <a href="../doing-things-the-layman-way/">as simply as possible</a>, I know that most of the web traffic analytics services out there won’t fit. I want something I can drop in, and get out of at any time.</p>
<blockquote>
<p>Building a program to automate and perform a computationally expensive task is one thing that all software engineers are more than prepared for. But doing things the layman way and achieve virtually the same result in lower cost is much much better.</p>
</blockquote>
<h2 id="solution-bitly--tracking-pixel">Solution: bit.ly + tracking pixel</h2>
<figure>
<a href="./5feacee4935d9tracking.png" target="_blank">
<img src="./5feacee4935d9tracking.png" style="max-width: 100%;" alt="bit.ly link click dashboard">
</a>
<figcaption>bit.ly link click dashboard tracking a bit.ly link to a…pixel.png?</figcaption>
</figure>
<p>The solution I opted for is easy to set up (took me 10 minutes to get first tracking in) without any configuration. I first created a transparent, 1x1 pixel PNG file. Then, I created a bit.ly link to the pixel.png’s supposed URL.</p>
<p>Then, I inserted the bit.ly link as an <code class="language-"><img></code> to my homepage’s <code class="language-">index.html</code>.</p>
<pre><code class="language-html"><!-- Tracking pixel -->
<img src="https://bit.ly/3aP0xkp"
style="width: 0px; height: 0px; margin: 0; padding: 0; visibility: hidden;" />
</code></pre>
<p>If you have used bit.ly before, you’d have noticed that the URL translation from bit.ly URL to the original URL happens transparently to the user. This is done my a <code class="language-">HTTP 301 Moved Permanently</code> redirection returned by bit.ly, which most browser would automatically follow to the redirected URL.</p>
<p>Therefore, from the users perspective loading this 1x1 pixel, 564 bytes, invisible image won’t make any difference in terms of experience and load speed. To the browser, it’s equally transparent because it’s a <code class="language-">HTTP 301</code> redirection. Ad blockers usually don’t block frequently used link shortener such as bit.ly (my uBlock Origin didn’t block it), so I am more or less sure I am not missing any visitor counts.</p>
<p>The benefit I get from this extra tracking pixel is that every time the bit.ly link is hit, bit.ly would increase its counter and I am able to see the visitor counts to these pages from bit.ly. One caveat to this approach is that I am not able to distinguish repeating visitors, which is fine by me.</p>
<p>When the browser is making a request to the bit.ly URL, it includes a header entry <code class="language-">referrer: https://tommyku.com</code> <s>, which means for the same site you can reuse the same bit.ly URL across pages. On the bit.ly dashboard you are able to see the breakdown of requests by referrer page.</s> <i>Edit: You cannot. Only the website’s domain, not full page URL, is included in referrer field (2020-12-30)</i></p>
<p>bit.ly doesn’t forget to sneak in a cookie, but it can be blocked if user disables third-party cookie.</p>
<figure>
<a href="./5ff306fe95e963rd_party_cookie.jpg" target="_blank">
<img src="./5ff306fe95e963rd_party_cookie.jpg" style="max-width: 100%;" alt="Cookie from bit.ly getting blocked">
</a>
<figcaption>bit.ly, don’t</figcaption>
</figure>
<p>If you want strict segregation between tracking URLs, you can create one bit.ly per page, and link to the same image file. However bit.ly would give you the same bit.ly URL if you’re linking to the same URL, so you need to add an extra query parameter (<code class="language-">?now</code>) to make them look different (bit still resolve to the same image file).</p>
<pre><code class="language-text">https://bit.ly/urlTokenA => https://tommyku.com/assets/pixel.png
https://bit.ly/urlTokenB => https://tommyku.com/assets/pixel.png?now
</code></pre>
<p>bit.ly please don’t block me <s>even though I misused you</s>.</p>
<h2 id="user-friendliness">User-friendliness</h2>
<p>Just to sweeten it a bit for the users to <s>swallow</s> accept this visitor counting approach, I have added an event listener to the tracking pixel <code class="language-"><img></code> tag which changes from a 🙈 (see no evil monkey) to 👀 (eyes) emoji after the tracking pixel has finished loading.</p>
<figure>
<a href="./5feb2b0f3aaaetracking-eye-eye.png" target="_blank">
<img src="./5feb2b0f3aaaetracking-eye-eye.png" style="max-width: 100%;" alt="Eyes emoji after tracking pixel is loaded">
</a>
<figcaption>“I see you” (on bit.ly)</figcaption>
</figure>
<p>Clicking on the icon leads users to this article. Visitors will then realize that their visit is being counted.</p>
<pre><code class="language-html"><style>
a#tracking-status {
display: block;
width: 0;
line-height: 0;
margin: 0 auto;
padding: 0;
text-decoration: none;
}
a#tracking-status.unloaded::before {
content: "\1F648";
}
a#tracking-status.loaded::before {
content: "\1F440";
}
</style>
<!-- Tracking pixel -->
<a href="https://blog.tommyku.com/blog/website-analytics-for-lazy-people" id="tracking-status" class="unloaded" title="Tracking Indicator"></a>
<img src="https://bit.ly/3aP0xkp"
style="width: 0px; height: 0px; margin: 0; padding: 0; visibility: hidden;"
onload="javascript: document.querySelector('#tracking-status').classList.replace('unloaded', 'loaded')" />
</code></pre>
<h2 id="why-not-self-hostuse-other-analytics-services">Why not self-host/use other analytics services</h2>
<p>The market is saturated with web analytics services, most of them have free tier, many available for self-hosting. I could have used any of them instead of going through this approach.</p>
<p>Aside from ordinary web analytics solutions, I could have also analyzed the web server’s <code class="language-">access.log</code> for page view counts.</p>
<p>The reason I didn’t take the <code class="language-">access.log</code> analysis approach is because the site is hosted on GitHub Pages. Despite the middle layer Cloudflare provides analytics service, it comes with a cost. Visit count needs to happen on client side somehow.</p>
<p>As for self-hosted analytics service, I simply don’t want to pay/setup a service on cloud simply for analytics which I need to maintain over the years. At such a small scale I like to do things <a href="../doing-things-the-layman-way/">the layman way</a>.</p>
<p>Lastly, the reason I am not using other hosted analytics service is because this approach is so simple I can easily swap out bit.ly to something else later if I want to.</p>
<h2 id="bonus-reactions">Bonus: reactions</h2>
<p>Loading an image means adding a count to the tracking. If image loading can be controlled by JavaScript, users can selectively load images to record their reactions to the page, such as leaving “Like” on the page.</p>
tag:blog.tommyku.com,2020-12-28:/blog/not-looking-for-a-laptop-anymore/Not looking for a laptop anymore2020-12-28T14:55:38Z2020-12-28T14:55:38Z<figure>
<a href="./5fe9e6c747276thinkpadx230.jpg" target="_blank">
<img src="./5fe9e6c747276thinkpadx230.jpg" style="max-width: 100%; max-height: 600px;" alt="">
</a>
<figcaption>This is my ThinkPad X230. There are many like it, but this one is mine</figcaption>
</figure>
<p>On September 2012 I bought my first laptop after relentlessly comparing the several educational offerings at my university. It was a ThinkPad X230. In the following 8 years it has been my primary laptop for study, leisure and work.</p>
<p>I bought my ThinkPad X230 for HK$6,180 in 2012, which is HK$ 7,005 in 2020’s money. For the same price, it is easy to buy a laptop with way superior spec today but impossible to sell this for anywhere higher than HKD$1,000.</p>
<p>Despite I have ranted about <a href="../do-i-need-a-new-laptop/">this laptop growing old</a>, and <a href="../looking-for-a-laptop/">looking for a new laptop</a>, this laptop stays on my desk. I did switch to a Razer Blade 14 2015 for some time before spilling a glass of water over its keyboard, causing it to sometimes refuse to charge the battery. For serious work though, it’s always been the ThinkPad X230.</p>
<p>This laptop was built like a tank. Its magnesium alloy case cracked and <a href="../remapping-insert-to-delete-key-on-ubuntu-20-04">Delete key</a> chipped away. I left a cut on the screen with a screw driver (which recovered on its own over the years) and partially submerged it in rain water. All the while it worked flawlessly and reliably for me.</p>
<p>These days I kept hearing complaines about poor laptop build-qualities from my peer, made worse by non-serviceable or service-unfriendly design. Planned obsolescence plus the drive to pack more powerful hardware in smaller package causes laptops these days to break even before warranty ends and beyond self-repair more easily. I cannot reasonably expect a shiny expensive laptop I buy today last more than say, 3 years without developing any annoying glitch which I cannot repair by myself or costing almost as much as a new laptop to fix!</p>
<p>When I stumbled upon a post on LOW-TECH MAGAZINE <a href="https://www.lowtechmagazine.com/2020/12/how-and-why-i-stopped-buying-new-laptops.html">about buying and using old laptops</a> from the days when they were built to last, I could instantly sympathize with the author. Instead of having half-working digital junk piling up at my house when the older ones work but glitch irritatingly, I want working laptops that I can repair for cheap.</p>
<p>While a laptop from the 2000s may seem too old for my need, something from the ThinkPad X 200 series seem like a good fit for me. They are portable, sufficiently performant and come with replaceable battery so battery life is less of a concern. I could upgrade them with an SSD or an extra stick of RAM, and they’ll work just fine.</p>
<p>I can’t imagine how my ThinkPad X230 kicks the bucket in the foreseeable future, or how it becomes so unusable I have to switch to another laptop. So for the time being I am staying with it, not looking for a laptop anymore.</p>
tag:blog.tommyku.com,2020-12-24:/blog/a-silent-night-in-the-indieweb-district-of-the-web-town/A silent night in the IndieWeb district of the Web town2020-12-24T16:53:29Z2020-12-24T16:53:29Z<p>In a corner of the populous Web town lies the IndieWeb district. A small district which lives the people who still remember a time when Web town were the wild west—when new buildings is built every minute in the maze like, almost chaotic town where the only map were manually populated indexes.</p>
<p>Next to the IndieWeb district, an once vibrant community of the past now largely abandoned, with a small part of it preserved for visiting only. Nothing new is being made in that corner of the town. A majority of it, however, were demolished, forgotten, and never see the sunlight again.</p>
<p>Against the backdrop of the monolithic constructions, these little districts are almost invisible. People mostly gather in the squares, the bars, the concert halls, the theatres, the markets and the malls where they no longer have control over how their own places are designed or how they work. A lot of what they do is to create content that fits into a limited length, or a square frame, or a tape which all could get censored by the place’s owner, or taken down, or demonetized for an arbitrary “terms of service” the moment they entered the place.</p>
<p>Oh yeah, there’s no window to peek inside, the bouncer ensures you sign the lengthy “term of service” before you could even enter. Most of them are free to enter, but all content you create stays with the house, even if it changes hand or become “for view only” or they decide to show you ads based on your behavior in the house.</p>
<p>Ads are ubiquitous, and the houses could have sold all information about you without you noticing it. They know where you’ve been and what you’ve consumed from the dozen wristbands they put on you when you visit. Removing them is troublesome and denying them sometimes means no entry. And you can’t just hit a delete button to remove this profile of yourself.</p>
<p>On the day of Christmas Eve I moved into the IndieWeb district, or at least tried to. I have a little house of my own. (where you are reading this is called the “blog” room)</p>
<p>I made a few changes, added signs here and there to make it fit into the community. Most of which pretty hidden, such as linking my place in those big “social places” and my house using “rel=me”. I made sure everyone (even robots) knows who I am using a “h-card”. I ensure everyone, human or robot, understands exactly every piece of decoration in my house, be they a “h-feed” or “h-entry”, suitably supplemented with “e-content” and “dt-published”.</p>
<p>After moving my cozy little place into the IndieWeb district of the Web town, it’s almost dusk of the Christmas Eve. So I sat by the fireplace, and began writing articles, whilst waiting for visitors to come.</p>
<p>Nobody came. And I couldn’t finish the bottle of red wine myself.</p>
<p>The festive decorations and activities of the big social communities in town reminded me. How could someone know about what I put up in my house, if all they see is inside their own communities? Sure, organic traffic sometimes bring lost souls here for a quick glance, before moving on to the popular forums and Q&A boards for the answers they want.</p>
<p>I still occupy a small corner of the social communities, so I made sure to put up a copy of whatever I have at my house, and include a “permalink”—a map to my house. This is still a manual proces to date, before I can build a robot to do it for me.</p>
<p>Some folks in the IndieWeb district seem to embrace the work of rebuilding the wheel, in many different ways, shapes or forms and feel proud of their work. I do too, sometimes.</p>
<p>Oh, a community hall was burned to the ground, gone with it all the creations people have spent countless hours creating and posting there. If one day such thing happens to the community building I frequent, at least I am going to have a golden copy at home. What if my home gots burned down? Boy, go read the <Zen and the Art of IT Backup>.</p>
<p>The Fediverse? I was there for a while. But it’s still a community hall people gather, not a place they all own and control like my very own house.</p>
<p>Sipping the last drop of wine in my wineglass, I stood up and stretched my back, knowing that this place, and the district, will remain largely quiet and neglected tonight. But the people of our kind will remain, moving from one place to another, yet staying true to the old days, the old ways when the possibilities and creativities were limitless.</p>
tag:blog.tommyku.com,2020-12-24:/blog/remapping-insert-to-delete-key-on-ubuntu-20-04/Remapping Insert to Delete key on Ubuntu 20.042020-12-24T10:51:19Z2020-12-24T10:51:19Z<figure>
<a href="./5fe46042198abdelete_key_null.jpg" target="_blank">
<img src="./5fe46042198abdelete_key_null.jpg" style="max-width: 100%; max-height: 500px;" alt="Delete key has lot a keycap, replaced by tape">
</a>
<figcaption>Filmsy keycap of Lenovo ThinkPad’s Delete key has broken</figcaption>
</figure>
<p>I tried a few out-dated methods using <code class="language-">xmodmap</code> and getting <code class="language-">~/.xinitrc</code> to run on startup but even if <code class="language-">.xinitrc</code> is executed, the key is still not mapped.</p>
<p>At the end I stumbled upon <a href="https://askubuntu.com/questions/325272/permanent-xmodmap-in-ubuntu-13-04/347382">this answer</a> when I was about to give up, and it worked.</p>
<p>I needed to change <code class="language-">/usr/share/X11/xkb/symbols/pc</code> with as root to modify the behavior of Insert key to Delete. Saved the file, reboot and it worked like a charm.</p>
<pre><code class="language-diff">- key <INS> { [ Insert ] };
+ key <INS> { [ Delete ] };
</code></pre>
tag:blog.tommyku.com,2020-12-23:/blog/how-the-agat-18k-turns-out/How the AGAT 18K turns out2020-12-23T15:33:40Z2020-12-23T15:33:40Z<p>Sometime in November I bought an AGAT 18K on eBay. This camera was produced in Soviet Union by BelOMO (not LOMO) in 1988-1991. AGAT 18K has an unusual vertical configuration like the Yashica Rapide, with viewfinder on one corner. Therefore, Unlike most other half-frame cameras, photo taken with AGAT 18K is in landscape instead of portrait.</p>
<figure>
<a href="5fe34b185322f48420029.jpg" target="_blank">
<img src="5fe34b185322f48420029.jpg" style="max-width: 100%; max-height: 750px;" alt="The Chinese University of Hong Kong">
</a>
<figcaption>The Chinese University of Hong Kong (Fuji C200, ISO 200, AGAT 18K)</figcaption>
</figure>
<figure>
<a href="5fe34b17d79dc48420015.jpg" target="_blank">
<img src="5fe34b17d79dc48420015.jpg" style="max-width: 100%; max-height: 750px;" alt="Above: red flowers; Below: selfie on a bike's rear-view mirror">
</a>
<figcaption>Flowers and selfie how (Fuji C200, ISO 200, AGAT 18K)</figcaption>
</figure>
<p>Given its samll size and mostly rectangular design, with a reliable lens cap protecting both the lens and the shutter release, AGAT 18K is extremely portable. I can slide it into my pocket without feeling or looking awkward.</p>
<p>In contrast to the all-plastic body, the Industar-104 f/2.8 28mm (a half-frame is cropped to ~40mm) lens is surprisingly sharp. Though I don’t have much to compare this with except for a few 60+ years old Solina’s Color Apotar or a similarly aged plastic lens of the Isoly 100.</p>
<p>AGAT 18K isn’t targeted to film photography beginners because it has limited control over the exposure. User can only select the film speed and aperture by rotating the 2 rings around the lens. There is no shutter speed control. According to the manual, the shutter speed is selected automatically by the combination of film speed and aperture setting, which is supposed to match the light condition symbols opposite to the aperture numbers.</p>
<figure>
<a href="5fdc6b0d1170eagat-18k.jpg" target="_blank">
<img src="5fdc6b0d1170eagat-18k.jpg" style="max-width: 100%; max-height: 500px;" alt="AGAT 18K in hand">
</a>
<figcaption>AGAT 18K fits right into my palm</figcaption>
</figure>
<p>No, AGAT 18K isn’t for beginner, it’s for the <em>noobs</em> who doesn’t even know about the exposure triangle. You have little control over depth of field for a given light condition because compensating by shutter speed isn’t reliably available (available, though, by fiddling with the film speed ring). Just shoot and hope for the best.</p>
<p>Other than hoping the shot turns out well, another thing one can do is to bracket the shot by under- and overexposing by a stop. Out of the 3 shots you are likely to get at least a good one. For example, the 2 half-frames below show a correctly exposed and an underexposed shot—an Instagram poll showed that 90% of the people replied prefer the underexposed shot.</p>
<p>Well, who said shooting half-frame means you get 2 times the images of shooting full frame?</p>
<figure>
<a href="5fe34fd9be33548420032.jpg" target="_blank">
<img src="5fe34fd9be33548420032.jpg" style="max-width: 100%; max-height: 750px;" alt="Above: properly exposed; Below: underexposed">
</a>
<figcaption>Different aperture setting resulting in different outcomes (Fuji C200, ISO 200, AGAT 18K)</figcaption>
</figure>
<p>Sometimes unrelated shots may seem related and create a story-telling opportunity out of nowhere, in Duane Michals style, but that’s rare unless you actively work towards it. Below is a almost-has-a-story-shot for example.</p>
<figure>
<a href="5fe35f0b01f5548420017.jpg" target="_blank">
<img src="5fe35f0b01f5548420017.jpg" style="max-width: 100%; max-height: 750px;" alt="Man holding his son over the fence">
</a>
<figcaption>Kid, there won’t be a witness (Fuji C200, ISO 200, AGAT 18K)</figcaption>
</figure>
<p>If you are doing panorama, make sure you take them right to left, with the camera rotated along the viewfinder counter-clockwise by 90° because the frames are by default landscape and films are winded right-to-left. Or at least that’s how my lab likes to process them.</p>
<figure>
<a href="5fe35ccfa82b905940018_pano.jpg" target="_blank">
<img src="5fe35ccfa82b905940018_pano.jpg" style="max-width: 100%; max-height: 750px;" alt="Panoramic shot in 4 half-frames">
</a>
<figcaption>Half-frames reordered to form a panoramic shot (Hitchcock 250D, ISO 250, AGAT 18K)</figcaption>
</figure>
<p>Framing is unreliable. The viewfinder of my AGAT 18K does have a barely visible set of framelines but is invisible 100% when I shoot from it. At some point someone may have opened it up and washed away paints with some weird chemicals, or maybe it degrades naturally.</p>
<p>Either case, trust your gut not framelines. My gut failed me only once out of 144 shots, and the framelines failed me all the time.</p>
<figure>
<a href="5fe3603bed08aframeline.jpg" target="_blank">
<img src="5fe3603bed08aframeline.jpg" style="max-width: 100%; max-height: 750px;" alt="Barely visible framelines">
</a>
<figcaption>Barely visible framelines, with inner frame for close shots</figcaption>
</figure>
<p>Oh and light leaks. Lots of light leaks.</p>
<p>AGAT 18K’s predecessor AGAT 18 has the same problem and perhaps due to cost-saving BelOMO didn’t address this problem at all. The plastic behind the lens reflects light when the camera is pointing at a light source at some angles, leaving an unpleasant light spot on the frame or even another frame.</p>
<p>It is possible to apply non-reflective material to the interior of the chamber to eliminate this reflective light leaks, just as what <a href="https://blog.xuite.net/liquormania/wretch/130594167-AGAT+18k+%E5%85%A7%E9%83%A8%E5%8F%8D%E5%B0%84%E9%98%B2%E6%AD%A2%E8%99%95%E7%90%86">someone has done before</a>.</p>
<figure>
<a href="5fe352caae09448420008.jpg" target="_blank">
<img src="5fe352caae09448420008.jpg" style="max-width: 100%; max-height: 750px;" alt="Light leaks from the shot below affecting the image above">
</a>
<figcaption>Light leaking to another frame (Fuji C200, ISO 200, AGAT 18K)</figcaption>
</figure>
<p>With all the shortcomings of AGAT 18K aside, I still think this little half-frame plastic box with a good glass lens to be a nice camera to carray in my pocket for scratching my film photography itch every day, as all other alternatives too bulky or too complex for a pleasant, impromptu, perhaps stealthy street shoot.</p>
tag:blog.tommyku.com,2020-11-21:/blog/some-more-tricks-on-kodak-funsaver/Some more tricks on Kodak FunSaver2020-11-21T01:13:52Z2020-11-21T01:13:52Z<p>In a <a href="../disposable-camera-for-budget-entry-level-film-photography/">previous post</a> I have mentioned a disposable camera can be reloaded if you aren’t ready to commit to buying a more functional camera. I used to reload my Kodak FunSaver by rolling the film onto the spool under a bed sheet in the dark.</p>
<p>Like whatever dubious things teens do under a bed sheet, and in the dark.</p>
<p>And later when I was Googling on an irrelevent topic I bumped into a <a href="https://www.lomography.com/magazine/248878-the-complete-guide-to-reloading-disposable-cameras">lomography blog post</a> with instructions on how to reload a disposable camera in the light.</p>
<p>Since they’ve explained it that well. I am instead writing to share some tricks I tried knowing that now we can rewind the film without opening the camera.</p>
<h2 id="reloading-without-a-dark-bag">Reloading without a dark bag</h2>
<p>Somebody has already done a detailed <a href="https://www.lomography.com/magazine/248878-the-complete-guide-to-reloading-disposable-cameras">walkthrough of the how</a>, so what else should I say about this?</p>
<ol>
<li>Open the camear up</li>
<li>Roll only enough film for it to latch on the take-up spool</li>
<li>Put the film in</li>
<li>Close the camera (tight)</li>
<li>Plunk a toothpick or something sharp to lift the arm stopping the wheel from turning backward</li>
<li>Use a flat head screwdriver to roll the film until you can’t</li>
<li>Good to go</li>
</ol>
<p>What’s different about this model of FunSaver is that the gear which is lifting the shutter mechanism up (the gear along the film guide rail) is free to move to and fro. Therefore the only thing stopping our film from moving freely is the arm at the film advance wheel, which we use a toothpick to bypass.</p>
<figure>
<a href="./5fb7dcbe600b4funsaver_wheel.jpg" target="_blank">
<img src="./5fb7dcbe600b4funsaver_wheel.jpg" style="max-width: 100%; max-height: 500px;" alt="FunSaver internal with emphasis on the film advance wheel">
</a>
<figcaption>There is an arm that only allows the film advance wheel to turn in one direction</figcaption>
</figure>
<p>If you are interested in how a simple camera works, pop open the front panel as well. Make sure you’ve fired the flash and taken the battery out though, it might get scary when you accidentally discharge the capacitor with your finger tip.</p>
<p>But once opened, voila is it simple. Just a couple of springs and gears, no battery or electronics. Those are only for the flash.</p>
<h2 id="double-exposure-using-kodak-funsaver">Double exposure using Kodak FunSaver</h2>
<p>It’s always possible to double expose on any sort of camera. Because it’s essential to rewind after finishing a roll, you could take some shots, rewind, and continue to take shots. New exposures on already exposed films. The overlapping has to be right though, or you end up with overlapping frames.</p>
<p>On Kodak FunSaver it’s no different except the rolling order is reversed. You can take a couple shots first, then rewind using the trick mentioned above and if you’re lucky, shots are going to line up and you can do some double exposures. Hope they are good.</p>
<figure>
<a href="./5fb7d0d0e1da8funsaver_double_exp.jpg" target="_blank">
<img src="./5fb7d0d0e1da8funsaver_double_exp.jpg" style="max-width: 100%; max-height: 500px;" alt="Double exposed shot on a FunSaver featuring me, psyduck and slowpoke">
</a>
<figcaption>If the two shots line up well</figcaption>
</figure>
<h2 id="pushing-is-not-always-required">Pushing is not always required</h2>
<p>Kodak FunSaver originally comes with a ISO 800 film, which is unusually fast compared to the ISO 200 ColorPlus or ISO 400 UltraMax that I often use. This makes sense because FunSaver has fixed aperture and shutter speed. A layperson wouldn’t know under what lighting condition would the camera under/over expose.</p>
<p>I think this is a fair design decision, as film is more tolerant to overexposure than under. You may overexpose a few stops without affecting the quality of image much. But when you underexpose, you lose shadow details almost immediately. Using film with faster speed as default means there’s less underexposure, and more salvagable photos out of that roll.</p>
<p>I used to push an ISO 400 UltraMax to ISO 800 on a FunSaver, until when the lab refused to push and digitally brightened the image instead, they came out ok. In fact, pushing probably won’t add anything to your already overexposed daylight shots, but instead increase grain on an already unsharp plastic lens.</p>
<p>Pushing isn’t always required unless you know you definitly underexposed most of the shots.</p>
<h2 id="you-might-use-it-as-a-very-cheap-flash">You <em>might</em> use it as a very cheap flash</h2>
<p>Thought no a FunSaver, <a href="https://www.instructables.com/DIY-Hot-shoe-Triggered-Off-Camera-Flash-Out-of-a-D/">someone</a> did take a disposable camera and hook the wires to a hotshoe and got the flash it to fire. If you come from an electronic engineering background or don’t mind getting your hands dirty tinkering with it, you could make yourself a flash from a salvaged disposable camera body.</p>
<p>For me, I have only gotten as far as triggering the flash by touching the two contacts. I simply don’t have the time and knowledge to do this properly without the risk of damaging the triggering camera’s hotshoe.</p>
<figure>
<a href="5fb7cc0317e0dfunsaver_flash_switch.jpg" target="_blank">
<img src="5fb7cc0317e0dfunsaver_flash_switch.jpg" style="width: 100%; max-width: 500px;" alt="Front of FunSaver, circuit board, switch contacts">
</a>
<figcaption>Two ends of FunSaver’s flash switch that can be triggered by the leaf shutter’s movement</figcaption>
</figure>
<figure>
<a href="5fb7cc02c88d7funsaver_flash_fire.jpg" target="_blank">
<img src="5fb7cc02c88d7funsaver_flash_fire.jpg" style="width: 100%; max-width: 500px;" alt="Front of FunSaver, circuit board, flash switch closed by shutter movement">
</a>
<figcaption>The shutter leaf handle moves, closing the switch and fire the flash</figcaption>
</figure>
<h2 id="pass-it-on-to-someone">Pass it on to someone</h2>
<p>I started out with nothing but a Fuji Simple Ace disposable film camera, then reloaded Kodar FunSaver for quite some time (I’ve reloaded the same <em>disposable</em> FunSaver at least 8 times).</p>
<p>Now my collection has grown to 5 AGFA viewfinders, with an AGAT 18K being shipped my way, a FunSaver can no longer satisfy my daily film photography need. I still reload a second FunSaver which I share with my sister and bring it with me when I simply want something to throw into my pocket, though much less often than before.</p>
<p>That doesn’t mean the FunSaver can be thrown away or stashed somewhere waiting to be forgotten. Instead, it should be passed on. Clean it up (those sticker gunk…), decorate it thoughtfully and make it a special <s>cheap</s> present to a friend who may also be interested. Or even better donate it to kids with instruction how to reload!</p>
<p>Even a mass-produced, cheap and simple camera deserves to live on. I believe only then, people can truly appreciate its predecessors and protect the legacy of a century of photographic ingenuities — simple point-and-shoot and feature-rich SLRs alike.</p>
tag:blog.tommyku.com,2020-07-12:/blog/no-more-google-analytics/No more Google Analytics2020-07-12T11:58:01Z2020-07-12T11:58:01Z<p>It was done in commit <a href="https://github.com/tommyku/blog3/commit/781edb5e523d5a6e08339d58035806f073a4a131">781edb5e523d5a6e08339d58035806f073a4a131</a>. I don’t need it anymore.</p>
<p>Still keeping disqus as some people did ask my questions or gave me feedbacks.</p>
tag:blog.tommyku.com,2020-07-04:/blog/7-additional-tips-on-using-disposable-camera/7 additional tips on using disposable camera2020-07-04T16:00:00Z2020-07-04T16:00:00Z<p>In my previous post <a href="../disposable-camera-for-budget-entry-level-film-photography/">Disposable camera for budget entry-level film photography</a> I have advocated the use of disposable cameras as the starting point of film photography.</p>
<p>Fast-forward 3 months, I had reloaded the same Kodak FunSaver 4 times and is currently on my 5th roll of Kodak UltraMax 400. The camera works perfectly fine despite branded to be “single-use” and “disposable”.</p>
<p>In this post I am going to share some tips I gathered after 3 months of usage. A disposable camera can serve as a portable secondary camera in addition to the your main camera once you have passed the entry-level stage. Before moving onto the tips, below are some pictures I have taken using my reloaded FunSaver.</p>
<figure>
<a href="./5f01cd0fb58185f0096bded0faIMG_0003.jpg" target="_blank">
<img src="./5f01cd0fb58185f0096bded0faIMG_0003.jpg" style="width: 100%; max-width: 480px;" alt="Cloth hangers">
</a>
<figcaption>Taken with reloaded Kodak FunSaver (UltraMax 400 pushed+1, ISO 800, f/11, 1/100, 30mm)</figcaption>
</figure>
<figure>
<a href="./5f01cd110ac435f0096f0b0c4e000043180022.jpg" target="_blank">
<img src="./5f01cd110ac435f0096f0b0c4e000043180022.jpg" style="width: 100%; max-width: 480px;" alt="Street of Mong Kok">
</a>
<figcaption>Taken with reloaded Kodak FunSaver (UltraMax 400, ISO 400, f/11, 1/100, 30mm)</figcaption>
</figure>
<figure>
<a href="./5f01cd10b0c8f5f00971beae0503660034.jpg" target="_blank">
<img src="./5f01cd10b0c8f5f00971beae0503660034.jpg" style="width: 100%; max-width: 480px;" alt="Chinese restaurant">
</a>
<figcaption>Taken with reloaded Kodak FunSaver (UltraMax 400 pushed+1, ISO 800, f/11, 1/100, 30mm)</figcaption>
</figure>
<p>I hope these photos are enough to persuade you to read on — they aren’t as good as what I took using my main camera, but they are good enough.</p>
<h2 id="tip-1-detatch-the-hidden-latch">Tip 1: Detatch the hidden latch</h2>
<p>When opening up the FunSaver, it is obvious that latches on the sides should be detatched first, right after removing the film roll and battery caps.</p>
<p>This can easily be done by sliding something thin into the gap and push the back plate off.</p>
<p>And it may be tempting to now pull the back plate off by forcing the top latch off. This is difficult and the top latch was perhaps added to deceive people.</p>
<!-- photo of the bottom latch -->
<p>Because if one detatches the hidden latch within the battery compartment instead, the top latch can easily be detatched by rotating the now mostly free back plate.</p>
<h2 id="tip-2-cock-the-shutter-first">Tip 2: Cock the shutter first</h2>
<p>Unlike conventional film camera where films are rolled out from the film canister as photos are taken, on FunSaver the entire roll of film is unrolled onto the spool, and rolled back into the canister as photos are taken.</p>
<!-- photo with which gear to turn -->
<p>This means that your last few shots may be shot on already exposed film segment. Every shot right after reloading counts up until the last 2-3 shots. To not waste any shot, make sure you turn the gear to cock the shutter before closing the case.</p>
<p>Otherwise, you’ll need to advance the film to cock the shutter and end up with one shot of unused film.</p>
<h2 id="tip-3-ensure-the-film-can-be-advanced">Tip 3: Ensure the film can be advanced</h2>
<p>Before closing the case run your finger along the top square holes of the film. Feel with your finger tip that the film advance gear is properly going through the square holes on the film.</p>
<p>If this is not done properly, no matter how many times your turn the film advance wheel you won’t be able to advance the film and prime for the next shot. In such case, repoen the camera (in the dark, of course) and check again.</p>
<h2 id="tip-4-keep-the-tension-on-the-spool">Tip 4: Keep the tension on the spool</h2>
<p>The compartment of most disposable cameras are designed to house a roll of film with 27 shots, while most of the 135 film available have 36 shots. This means if your film is only loosely rolled onto the spool it won’t fit back into the compartment.</p>
<p>As you pull the film out of the film canister and onto the spool, it is important to keep the tension on the spool by pressing on the film on spool and pull routinely to keep the tension on the spool.</p>
<p>Just make sure you don’t tear the film strip when applying tension.</p>
<h2 id="tip-5-wash-hands-before-loading-film">Tip 5: Wash hands before loading film</h2>
<p>I used to wear some rubber finger cots while rolling the film onto the spool until I found out that if the reloading is done properly my fingers wouldn’t even touch the center of the film.</p>
<p>However, I’ve always washed my hands before touching the film. Oil and dirt on finger could accidentally get onto the light sensitive side of the film and ruin your next million-dollar shot. Better safe than sorry.</p>
<h2 id="tip-6-make-conscious-choice-about-pushing">Tip 6: Make conscious choice about pushing</h2>
<p>In a previous lab I’ve always asked them to push my UltraMax 400 by 1 stop, so I get ISO 800, the same as FunSaver’s original film. However they refused to do it one time, rather impolitely I might add, and the scans still turned out somewhat ok.</p>
<figure>
<a href="./5f01cd1062d725f0175149e619000043180009.jpg" target="_blank">
<img src="./5f01cd1062d725f0175149e619000043180009.jpg" style="width: 100%; max-width: 480px;" alt="Sai Wan pier">
</a>
<figcaption>Taken with reloaded Kodak FunSaver (UltraMax 400, ISO 400, f/11, 1/100, 30mm)</figcaption>
</figure>
<p>It was because they digitally brightened the scans and cranked the saturation all the way up. I could have done the same post-processing myself if my photos ended up overexposed, which is rare. In other words, it’s ok not to push, and it should remain an option. A lab that impolitely refuse to push is one I will never go again.</p>
<figure>
<a href="./5f01cd10121315f01754f8313803660030.jpg" target="_blank">
<img src="./5f01cd10121315f01754f8313803660030.jpg" style="width: 100%; max-width: 480px;" alt="Chinese restaurant">
</a>
<figcaption>Taken with reloaded Kodak FunSaver (UltraMax 400 pushed+1, ISO 800, f/11, 1/100, 30mm)</figcaption>
</figure>
<p>The takeaway I got is that film is surprisingly tolerant to different exposure conditions. Under/overexposing the film by 1 stop is probably salvageable. And one doesn’t have to match the ISO speed of the original film coming with the disposable camera.</p>
<p>This is important because labs generally charge extra for pushing films. Pushing film could also introduces color shift and increased grains. If you are comfortable with not pushing, go ahead, take photo at your film’s box speed and save the bucks.</p>
<h2 id="tip-7-fuji-simpleace-may-kill-you">Tip 7: Fuji SimpleAce may kill you</h2>
<p>One day when buying a spare FunSaver I came cross a box of used Fuji SimpleAce. Their films have been taken out for development and without reloading they either go back to Fuji for recycling or to the landfill.</p>
<p>I got one of those for free. Popping up the front plate was easy. But unlike FunSaver where opening up the camera doesn’t reveal the electronics under the hood, SimpleAce simply (ahem) gives you access to the circuit board.</p>
<p>Since the flash was charged, I thought it was a good idea (spoiler alert: it wasn’t) to discharge the capacitor holding charge for the next flash by shorting the two pins connecting the capacitor to the board.</p>
<figure>
<a href="./5f01addb656d7IMG_20200703_223215_626.jpg" target="_blank">
<img src="./5f01addb656d7IMG_20200703_223215_626.jpg" style="width: 100%; max-width: 480px;" alt="Fuji SimpleAce with front plate removed">
</a>
<figcaption>Look at the size of that capacitor and what kind of evil designed to expose it once opened</figcaption>
</figure>
<p>There was a loud and bright spark, and stunned people in the living room. See, if someone accidentally touches the two pins with bare hand, it is electric shock ensued.</p>
<p>Just don’t risk it reloading a SimpleAce. It is not simple ┐( ˘_˘)┌ whereas FunSaver is easier and safer.</p>
<p><small>If several SimpleAce flashes are connected in serial does that make a make-shift stun gun? Let me know in the comment…</small></p>