<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Vihanga nivarthana | Personal Blog]]></title><description><![CDATA[working on the front end to create amazing UI stuff and provide an excellent user experience]]></description><link>https://blog.vihanga.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1748109947605/a5dd5057-5e03-4a13-93a1-136166d67065.png</url><title>Vihanga nivarthana | Personal Blog</title><link>https://blog.vihanga.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 09 Oct 2026 06:54:53 GMT</lastBuildDate><atom:link href="https://blog.vihanga.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The CSS light-dark() Function ]]></title><description><![CDATA[Modern websites are expected to support both light mode and dark mode. Users switch between themes depending on their environment, personal preference, or operating system settings. For years, develop]]></description><link>https://blog.vihanga.dev/the-css-light-dark-function</link><guid isPermaLink="true">https://blog.vihanga.dev/the-css-light-dark-function</guid><category><![CDATA[CSS]]></category><category><![CDATA[light-dark]]></category><category><![CDATA[theming]]></category><category><![CDATA[CSS Theming]]></category><category><![CDATA[React]]></category><category><![CDATA[Lighthouse]]></category><dc:creator><![CDATA[Vihanga nivarthana]]></dc:creator><pubDate>Tue, 09 Jun 2026 18:21:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63175f09961d2507e75fb9b6/54546ada-b976-46f8-8f70-4114991280ae.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern websites are expected to support both light mode and dark mode. Users switch between themes depending on their environment, personal preference, or operating system settings. For years, developers relied on media queries, JavaScript, or complex theme variables to make this work.</p>
<p>Now CSS provides a cleaner solution with the <code>light-dark()</code> function.</p>
<h2>What is <code>light-dark()</code>?</h2>
<p>The <code>light-dark()</code> function allows you to define two values directly inside a CSS property.</p>
<p>The first value is used when the page is in light mode.</p>
<p>The second value is used when the page is in dark mode.</p>
<pre><code class="language-css">color: light-dark(#000, #fff);
</code></pre>
<p>In this example, the text becomes black in light mode and white in dark mode.</p>
<p>Instead of writing separate styles for each theme, everything can be defined in a single line.</p>
<h2>Why Was It Introduced?</h2>
<p>Before <code>light-dark()</code>, developers commonly used media queries.</p>
<pre><code class="language-css">.card {
  background: white;
  color: black;
}

@media (prefers-color-scheme: dark) {
  .card {
    background: #1a1a1a;
    color: white;
  }
}
</code></pre>
<p>While this works, it becomes difficult to maintain when a project grows. Every component may need its own dark mode overrides.</p>
<p>With <code>light-dark()</code>, the same styles can be written more compactly.</p>
<pre><code class="language-css">.card {
  background: light-dark(white, #1a1a1a);
  color: light-dark(black, white);
}
</code></pre>
<p>The result is easier to read and maintain.</p>
<h2>Setting Up <code>light-dark()</code></h2>
<p>For the function to work correctly, the browser needs to know that your page supports both color schemes.</p>
<pre><code class="language-css">:root {
  color-scheme: light dark;
}
</code></pre>
<p>Without this declaration, the browser may not switch values as expected.</p>
<p>A complete example looks like this:</p>
<pre><code class="language-css">:root {
  color-scheme: light dark;
}

body {
  background: light-dark(#ffffff, #121212);
  color: light-dark(#222222, #f5f5f5);
}
</code></pre>
<p>The browser automatically chooses the appropriate value based on the active theme.</p>
<h2>Using CSS Variables Together</h2>
<p>The real power appears when <code>light-dark()</code> is combined with custom properties.</p>
<pre><code class="language-css">:root {
  color-scheme: light dark;

  --surface: light-dark(#ffffff, #1e1e1e);
  --text: light-dark(#222222, #f5f5f5);
  --border: light-dark(#dcdcdc, #444444);
}
</code></pre>
<p>Then components can simply use the variables.</p>
<pre><code class="language-css">.card {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}
</code></pre>
<p>This creates a clean design system with minimal duplication.</p>
<h2>Real World Example</h2>
<p>Imagine a button that should adapt to the current theme.</p>
<pre><code class="language-css">.button {
  background: light-dark(#2563eb, #3b82f6);
  color: light-dark(white, white);
  border: 1px solid light-dark(#1d4ed8, #60a5fa);
}
</code></pre>
<p>The button automatically adjusts without additional theme specific styles.</p>
<h2>Browser Support</h2>
<p>The <code>light-dark()</code> function is supported in modern browsers including recent versions of Chrome, Edge, Safari, and Firefox.</p>
<p>If your audience uses up to date browsers, it is generally safe to use. For applications that must support older browsers, consider testing carefully or providing fallbacks.</p>
<h2>Advantages of <code>light-dark()</code></h2>
<h3>Less Code</h3>
<p>Theme specific values can be defined directly where they are used.</p>
<h3>Better Readability</h3>
<p>Developers can immediately see both light and dark values in one place.</p>
<h3>Easier Maintenance</h3>
<p>No need to hunt through multiple media queries when updating colors.</p>
<h3>Works Well With Design Systems</h3>
<p>It integrates naturally with CSS variables and token based styling approaches.</p>
<h2>Things to Keep in Mind</h2>
<p>The function is designed primarily for values that change between light and dark themes. It should not replace all uses of media queries.</p>
<p>For example, if an entire layout changes based on screen size or theme, traditional media queries may still be the better choice.</p>
<p>Also remember that <code>light-dark()</code> depends on the <code>color-scheme</code> property being configured correctly.</p>
<h2>Final Thoughts</h2>
<p>The <code>light-dark()</code> function is one of those CSS features that feels surprisingly simple once you start using it. It reduces boilerplate, keeps theme related logic close to the styles that use it, and makes dark mode support much easier to maintain.</p>
<p>As browser support continues to improve, <code>light-dark()</code> is likely to become a standard part of modern CSS workflows. If you are building applications that support both light and dark themes, it is definitely worth adding to your toolkit.</p>
]]></content:encoded></item><item><title><![CDATA[How I Built a Free Package Tracking Notifier with Cloudflare Workers]]></title><description><![CDATA[I recently ordered Starlink. It shipped via DHL, and then got forwarded to a local courier that had no push notifications. I didn’t want to keep refreshing their tracking page, so I built a free notifier with Cloudflare Workers, KV, and IFTTT. This p...]]></description><link>https://blog.vihanga.dev/how-i-built-a-free-package-tracking-notifier-with-cloudflare-workers</link><guid isPermaLink="true">https://blog.vihanga.dev/how-i-built-a-free-package-tracking-notifier-with-cloudflare-workers</guid><category><![CDATA[automation]]></category><category><![CDATA[package tracking]]></category><category><![CDATA[Cloudflare Workers]]></category><dc:creator><![CDATA[Vihanga nivarthana]]></dc:creator><pubDate>Sat, 16 Aug 2025 07:50:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/g4W3SLjcvMA/upload/f47718b394f3548afc2d5ccad030e1a5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently ordered <strong>Starlink</strong>. It shipped via <strong>DHL</strong>, and then got forwarded to a <strong>local courier</strong> that had <strong>no push notifications</strong>. I didn’t want to keep refreshing their tracking page, so I built a free notifier with <strong>Cloudflare Workers</strong>, <strong>KV</strong>, and <strong>IFTTT</strong>. This post explains the approach and gives a <strong>step-by-step guide</strong> you can follow.</p>
<hr />
<h2 id="heading-how-it-works-quick-recap">How It Works (Quick Recap)</h2>
<ol>
<li><p>A Cloudflare Worker fetches the courier’s tracking API (JSON).</p>
</li>
<li><p>It compares the latest response with the last one stored in <strong>KV</strong>.</p>
</li>
<li><p>If there’s a change, it posts a webhook to <strong>IFTTT</strong> (which sends me a push/email).</p>
</li>
<li><p>A scheduler (cron) runs the Worker every 30 minutes.</p>
</li>
</ol>
<hr />
<h2 id="heading-final-worker-script-with-secrets-timeout-readable-timeline">Final Worker Script (with secrets + timeout + readable timeline)</h2>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-keyword">async</span> fetch(request, env, ctx) {
    <span class="hljs-comment">// respond immediately to avoid cron timeouts</span>
    ctx.waitUntil(handleTracking(env));
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">status</span>: <span class="hljs-string">"Queued"</span> }), {
      <span class="hljs-attr">headers</span>: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
    });
  },
};

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleTracking</span>(<span class="hljs-params">env</span>) </span>{
  <span class="hljs-keyword">const</span> API_URL = env.TRACKING_API_URL; <span class="hljs-comment">// store in wrangler.toml/env</span>
  <span class="hljs-keyword">const</span> IFTTT_WEBHOOK_URL = env.IFTTT_WEBHOOK_URL; <span class="hljs-comment">// stored as a secret</span>

  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// fetch with an explicit timeout</span>
    <span class="hljs-keyword">const</span> controller = <span class="hljs-keyword">new</span> AbortController();
    <span class="hljs-keyword">const</span> timer = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> controller.abort(), <span class="hljs-number">15000</span>);

    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(API_URL, { <span class="hljs-attr">signal</span>: controller.signal });
    <span class="hljs-built_in">clearTimeout</span>(timer);

    <span class="hljs-keyword">if</span> (!response.ok) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`API error: <span class="hljs-subst">${response.status}</span>`</span>);
    <span class="hljs-keyword">const</span> current = <span class="hljs-keyword">await</span> response.json();

    <span class="hljs-comment">// read previous snapshot</span>
    <span class="hljs-keyword">const</span> previousRaw = <span class="hljs-keyword">await</span> env.TRACKING_CACHE.get(<span class="hljs-string">"previous_response"</span>);
    <span class="hljs-keyword">const</span> previous = previousRaw ? <span class="hljs-built_in">JSON</span>.parse(previousRaw) : <span class="hljs-literal">null</span>;

    <span class="hljs-comment">// format timeline into readable text (limit to latest 5 entries to keep push short)</span>
    <span class="hljs-keyword">const</span> timeline = <span class="hljs-built_in">Array</span>.isArray(current.timeline) ? current.timeline : [];
    <span class="hljs-keyword">const</span> latest = timeline.slice(<span class="hljs-number">-5</span>);
    <span class="hljs-keyword">const</span> timelineText = latest
      .map(
        <span class="hljs-function">(<span class="hljs-params">item, i</span>) =&gt;</span>
          <span class="hljs-string">`<span class="hljs-subst">${i + <span class="hljs-number">1</span>}</span>. [<span class="hljs-subst">${item.date_time}</span>] <span class="hljs-subst">${item.message}</span> at <span class="hljs-subst">${item.location}</span> (Status: <span class="hljs-subst">${item.status}</span>)`</span>
      )
      .join(<span class="hljs-string">"\n"</span>);

    <span class="hljs-comment">// only notify if changed (or if nothing stored yet)</span>
    <span class="hljs-keyword">const</span> changed = !previous || <span class="hljs-built_in">JSON</span>.stringify(previous) !== <span class="hljs-built_in">JSON</span>.stringify(current);
    <span class="hljs-keyword">if</span> (changed) {
      <span class="hljs-keyword">await</span> fetch(IFTTT_WEBHOOK_URL, {
        <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
        <span class="hljs-attr">headers</span>: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">update</span>: timelineText }),
      });
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Change detected → IFTTT notified"</span>);
    }

    <span class="hljs-comment">// persist latest snapshot</span>
    <span class="hljs-keyword">await</span> env.TRACKING_CACHE.put(<span class="hljs-string">"previous_response"</span>, <span class="hljs-built_in">JSON</span>.stringify(current));
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"handleTracking error:"</span>, err);
  }
}
</code></pre>
<hr />
<h2 id="heading-step-by-step-setup-guide">Step-by-Step Setup Guide</h2>
<h3 id="heading-1-prerequisites">1) Prerequisites</h3>
<ul>
<li><p>A Cloudflare account (Workers + KV enabled on the free tier)</p>
</li>
<li><p>Node.js + npm installed locally</p>
</li>
<li><p>An <strong>IFTTT</strong> account with the <strong>Webhooks</strong> service enabled</p>
</li>
<li><p>A courier tracking API URL (replace with yours)</p>
</li>
</ul>
<hr />
<h3 id="heading-2-initialize-the-worker-project">2) Initialize the Worker Project</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># install wrangler globally (or use npx wrangler)</span>
npm i -g wrangler

<span class="hljs-comment"># create a new worker</span>
wrangler init tracking-notifier
<span class="hljs-built_in">cd</span> tracking-notifier
</code></pre>
<hr />
<h3 id="heading-3-create-a-kv-namespace">3) Create a KV Namespace</h3>
<blockquote>
<p>Wrangler v3 syntax uses spaces, not colons.</p>
</blockquote>
<pre><code class="lang-bash">wrangler kv namespace create TRACKING_CACHE
</code></pre>
<p>Copy the returned <code>id</code>. If you use environments (dev/prod), also create a preview namespace:</p>
<pre><code class="lang-bash">wrangler kv namespace create TRACKING_CACHE --preview
</code></pre>
<p>Then add to <code>wrangler.toml</code>:</p>
<pre><code class="lang-toml"><span class="hljs-attr">name</span> = <span class="hljs-string">"tracking-notifier"</span>
<span class="hljs-attr">main</span> = <span class="hljs-string">"src/index.js"</span>
<span class="hljs-attr">compatibility_date</span> = <span class="hljs-string">"2024-01-01"</span>

<span class="hljs-attr">kv_namespaces</span> = [
  { binding = <span class="hljs-string">"TRACKING_CACHE"</span>, id = <span class="hljs-string">"YOUR_NAMESPACE_ID"</span> }
]

<span class="hljs-comment"># Optional: built-in Cloudflare cron (every 30 minutes)</span>
<span class="hljs-attr">triggers</span> = { crons = [<span class="hljs-string">"*/30 * * * *"</span>] }

<span class="hljs-section">[vars]</span>
<span class="hljs-attr">TRACKING_API_URL</span> = <span class="hljs-string">"https://&lt;&gt;.lk:3001/api/tracking/&lt;number&gt;"</span>
</code></pre>
<blockquote>
<p>If you prefer environment sections:</p>
</blockquote>
<pre><code class="lang-toml"><span class="hljs-section">[env.production]</span>
<span class="hljs-attr">kv_namespaces</span> = [
  { binding = <span class="hljs-string">"TRACKING_CACHE"</span>, id = <span class="hljs-string">"PROD_NAMESPACE_ID"</span> }
]

<span class="hljs-section">[env.preview]</span>
<span class="hljs-attr">kv_namespaces</span> = [
  { binding = <span class="hljs-string">"TRACKING_CACHE"</span>, id = <span class="hljs-string">"PREVIEW_NAMESPACE_ID"</span> }
]
</code></pre>
<hr />
<h3 id="heading-4-store-secrets-dont-hardcode-your-ifttt-key">4) Store Secrets (Don’t hardcode your IFTTT key)</h3>
<p>Create the IFTTT webhook URL like:</p>
<pre><code class="lang-plaintext">https://maker.ifttt.com/trigger/ship/json/with/key/&lt;REPLACE_WITH_YOUR_KEY&gt;
</code></pre>
<p>Save it as a Worker secret:</p>
<pre><code class="lang-bash">wrangler secret put IFTTT_WEBHOOK_URL
<span class="hljs-comment"># paste your full webhook URL when prompted</span>
</code></pre>
<p>Now the code can read <code>env.IFTTT_WEBHOOK_URL</code>.</p>
<hr />
<h3 id="heading-5-wire-up-ifttt">5) Wire Up IFTTT</h3>
<ol>
<li><p>In IFTTT, create a new <strong>Applet</strong>.</p>
</li>
<li><p><strong>If This</strong> → choose <strong>Webhooks</strong>, event name: <code>ship</code>.</p>
</li>
<li><p><strong>Then That</strong> → choose <strong>Notifications</strong> (or Email/Telegram/Slack).</p>
</li>
<li><p>In the message body, include <code>{{update}}</code> to display the timeline text.</p>
</li>
<li><p>Go to the Webhooks <strong>Documentation</strong> page in IFTTT to confirm your key and test.</p>
</li>
</ol>
<p><strong>Manual test (optional):</strong></p>
<pre><code class="lang-bash">curl -X POST <span class="hljs-string">"<span class="hljs-variable">$IFTTT_WEBHOOK_URL</span>"</span> \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{"update":"IFTTT test from curl"}'</span>
</code></pre>
<hr />
<h3 id="heading-6-add-the-worker-code">6) Add the Worker Code</h3>
<ul>
<li><p>Place the JavaScript from <strong>“Final Worker Script”</strong> into <code>src/index.js</code>.</p>
</li>
<li><p>Make sure the imports/structure match your <code>wrangler.toml</code> (the <code>main</code> field).</p>
</li>
</ul>
<hr />
<h3 id="heading-7-test-locally-amp-deploy">7) Test Locally &amp; Deploy</h3>
<p><strong>Local dev (uses a local preview KV binding):</strong></p>
<pre><code class="lang-bash">wrangler dev
</code></pre>
<p>Open the local URL shown in the terminal to trigger the fetch once.</p>
<p><strong>Deploy:</strong></p>
<pre><code class="lang-bash">wrangler deploy
</code></pre>
<p><strong>Tail logs (great for debugging):</strong></p>
<pre><code class="lang-bash">wrangler tail
</code></pre>
<hr />
<h3 id="heading-8-schedule-it">8) Schedule It</h3>
<p><strong>Option A — Cloudflare Cron Triggers (recommended)</strong></p>
<ul>
<li><p>We already added:</p>
<pre><code class="lang-toml">  <span class="hljs-attr">triggers</span> = { crons = [<span class="hljs-string">"*/30 * * * *"</span>] }
</code></pre>
</li>
<li><p>After deploy, Cloudflare runs your Worker every 30 minutes automatically.</p>
</li>
</ul>
<p><strong>Option B — External Cron (</strong><a target="_blank" href="http://cron-job.org"><strong>cron-job.org</strong></a><strong>)</strong></p>
<ul>
<li><p>Create a cron job that hits your Worker URL every 30 minutes.</p>
</li>
<li><p>Use method <code>GET</code> (or <code>POST</code>) and a timeout ≥ 10–15 seconds.</p>
</li>
<li><p>Since our Worker responds immediately and does the work in the background (<code>ctx.waitUntil</code>), cron timeouts are unlikely.</p>
</li>
</ul>
<hr />
<h3 id="heading-9-troubleshooting-tips">9) Troubleshooting Tips</h3>
<ul>
<li><p><strong>IFTTT not firing?</strong> Ensure you send <code>{"update":"...text..."}</code> in the body and your event name matches (<code>/trigger/ship/...</code>).</p>
</li>
<li><p><a target="_blank" href="http://cron-job.org"><strong>cron-job.org</strong></a> <strong>timeout?</strong> We respond immediately and process in the background; verify you deployed that <code>ctx.waitUntil</code> version.</p>
</li>
<li><p><strong>Large notifications truncated?</strong> Limit timeline to the last few entries (as in the sample).</p>
</li>
<li><p><strong>KV not updating?</strong> Check your <code>kv_namespaces</code> binding name matches your code (<code>TRACKING_CACHE</code>).</p>
</li>
<li><p><strong>API errors or slow courier API?</strong> Increase the timeout slightly or add retry logic.</p>
</li>
</ul>
<hr />
<h2 id="heading-result">Result</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755411755285/f311e7e9-3c5d-49a8-874c-7fad0d1c80d6.jpeg" alt class="image--center mx-auto" /></p>
<p>Now I get a neat push/email every time the local courier posts an update, even though they don’t offer native notifications. The stack is <strong>free</strong>, <strong>reliable</strong>, and easy to adapt for any other API you want to watch.</p>
<p>Happy automating! 🚀📦</p>
]]></content:encoded></item><item><title><![CDATA[Make your applications more attractive using SVG animations]]></title><description><![CDATA[What is SVG?
Scalable Vector Graphics (SVG) is an Extensible Markup Language (XML)-based vector image format for two-dimensional graphics with support for interactivity and animation. [ from Wikipedia ]
Every image created has two types. They are Ras...]]></description><link>https://blog.vihanga.dev/svg-animations-part01</link><guid isPermaLink="true">https://blog.vihanga.dev/svg-animations-part01</guid><category><![CDATA[SVG]]></category><category><![CDATA[SVG Animation]]></category><category><![CDATA[UI]]></category><category><![CDATA[UX]]></category><dc:creator><![CDATA[Vihanga nivarthana]]></dc:creator><pubDate>Wed, 02 Nov 2022 18:51:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667414517864/AM0gZ7GDq.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-svg"><strong>What is SVG?</strong></h2>
<p><strong>Scalable Vector Graphics</strong> (<strong>SVG</strong>) is an <a target="_blank" href="https://en.wikipedia.org/wiki/Extensible_Markup_Language">Extensible Markup Language</a> (XML)-based <a target="_blank" href="https://en.wikipedia.org/wiki/Vector_image_format">vector image format</a> for <a target="_blank" href="https://en.wikipedia.org/wiki/Two-dimensional">two-dimensional</a> graphics with support for interactivity and animation. [ <a target="_blank" href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics">from Wikipedia</a> ]</p>
<p>Every image created has two types. They are Raster images and Vector images. A raster image is created using pixels, and a combination of these pixels makes a single image. Vector images use a different method which uses paths and shapes to create an image. Because of that, Vector images can be enlarged without affecting the quality of the images. But in Raster images, the quality of the image can decrease when the image is enlarged. It takes a blur-like effect when enhancing. The reason for this is when the image size is increased, pixel size also increases.</p>
<p>We can view vector images as code. Because, when a Vector image is created using any software, it transforms any given shape that we insert into an XML code and store it.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="4126fe5f4281425bed1939849adae671"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/VihangaN/4126fe5f4281425bed1939849adae671" class="embed-card">https://gist.github.com/VihangaN/4126fe5f4281425bed1939849adae671</a></div><p>You can get a clear idea of the differences between Raster and Vector images from below.</p>
<h2 id="heading-raster-image"><strong>Raster image</strong></h2>
<p><img src="https://cdn-images-1.medium.com/max/3760/1*dB6Xs7oxV18XPOUY0fu3Yw.gif" alt="Raster image ( scaling )" /></p>
<h2 id="heading-vector-image"><strong>Vector image</strong></h2>
<p><img src="https://cdn-images-1.medium.com/max/5516/1*Qis-Tsg6Rp-WA5lgDRSSGg.gif" alt="vector image ( scaling )" /></p>
<h2 id="heading-how-do-svg-animations-work"><strong>How do SVG Animations work?</strong></h2>
<p>As I mentioned before, SVG is created by combining paths and shapes. We can change these paths and shapes as we like and create animations. The advantage of creating animations like this is that there will be no quality loss.</p>
<h2 id="heading-how-to-create-svg-animations"><strong>How to create SVG Animations</strong></h2>
<p>I will separate this topic into two sections. The software which we can use to create SVGs and the software needed to create SVG Animations.</p>
<p>To create SVGs, software like Illustrator, and Inkscape can be used.</p>
<p>And to create SVG Animations, you can use specialized software such as SVG Gator.</p>
<p>Even without any software, we can create either SVGs or SVG animations. In the next part, I will discuss it further.</p>
<h2 id="heading-svg-animation-vs-gif-animations-comparison"><strong>SVG Animation VS GIF Animations ( comparison )</strong></h2>
<p>SVG animation can be displayed without any quality loss, but, since Gif animations fall under the Raster category which uses pixels, it has a lower quality when displayed.</p>
<p>SVG animation can even be changed dynamically through code at runtime, but we cannot do that for a Gif animation.</p>
<h2 id="heading-svg-animation-example">SVG Animation Example</h2>
<p>This is an Example SVG animation that I created for the codepen challenge called “insects”, in this example I have animated SVG paths and shapes to work together and create a beautiful scene.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/vihanga/pen/GRWmeva">https://codepen.io/vihanga/pen/GRWmeva</a></div>
<p>In the next article (part2), I will discuss SVG animations in both ways ( manual way and software based way ).</p>
<p>Thank you for reading 🙂 .</p>
]]></content:encoded></item><item><title><![CDATA[How to create your own duotone web icon font using Glyphs App]]></title><description><![CDATA[What are the duotone icons ?
We all know what a traditional single color web icon font is, but a duotone font is a completely different type of font that has two contrasting colors instead of one color. Fontawesome recently introduced their duotone i...]]></description><link>https://blog.vihanga.dev/duotone</link><guid isPermaLink="true">https://blog.vihanga.dev/duotone</guid><category><![CDATA[UIUX]]></category><category><![CDATA[icon]]></category><category><![CDATA[duotone]]></category><category><![CDATA[icon font]]></category><category><![CDATA[duotone font]]></category><dc:creator><![CDATA[Vihanga nivarthana]]></dc:creator><pubDate>Wed, 07 Sep 2022 07:50:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662482728796/EIrYzbJqB.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-are-the-duotone-icons"><strong>What are the duotone icons ?</strong></h2>
<p>We all know what a traditional single color web icon font is, but a <strong>duotone</strong> font is a completely different type of font that has two contrasting colors instead of one color. Fontawesome recently introduced their <strong>duotone</strong> icon font check it out <a target="_blank" href="https://fontawesome.com/docs/web/style/duotone">here</a> for full details about <strong>duotone</strong> icon fonts</p>
<h3 id="heading-the-main-technique-behind-duotone-icons">The main technique behind duotone icons</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662542051434/AqJ77LvGj.png" alt="duotone_science.png" class="image--center mx-auto" /></p>
<p>For instance, <code>A + B = AB</code> . So the main technique to create this is to layer two Unicode characters on top of each other using CSS.</p>
<h3 id="heading-lets-start-creating-our-first-duotone-icon-font">Let's start creating our first duotone icon font</h3>
<p>I found a perfect duotone design on Dribbble to start making our font.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662489487400/Bbn-H8MXr.png" alt="Screenshot 2022-09-07 at 12.07.46 AM.png" /></p>
<h3 id="heading-the-very-first-step"><strong>The very first step</strong></h3>
<p>The first step is to convert some selected icons from the icons in the Dribbble shot to vector format using illustrator.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662521718517/z2EyT9ePI.png" alt="illustrator_preview" class="image--center mx-auto" /></p>
<h3 id="heading-second-step-glyphs-app"><strong>Second step (Glyphs app)</strong></h3>
<p>From now on, all things should be done through the Glyphs app. Open the Glyphs app and remove all digits, numbers and special characters.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662522371363/Ca41vxqGs.png" alt="remove_all_glyphs.png" class="image--center mx-auto" /></p>
<p>After removing the digits and numbers, we need to create a new glyph.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662522545335/WdO5EYjdy.png" alt="new_glyphs.png" class="image--center mx-auto" /></p>
<p>Next, copy one of the icons designed by Illustrator and paste it into the glyph.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662523727508/tvm2Ufako.png" alt="illustrator_to_glyphs.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662523763304/0mpz2PcNW.png" alt="glyphs_pasted.png" class="image--center mx-auto" /></p>
<p>Now we need to duplicate our Glyph</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662523929839/ZeS57OHNO.png" alt="duplicate.png" class="image--center mx-auto" /></p>
<p>This part should be done very carefully. We need to carefully separate the two parts that we want to show in two tones.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662543219340/rPyVMM_Kd.png" alt="breakdown@2x-8.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662524418846/KwhLZQuxv.png" alt="duo1.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662524482117/fKQyEq7AV.png" alt="duo2.png" class="image--center mx-auto" /></p>
<p>After removing the unwanted tone, it should look like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662524591542/LtFqXT8Kq.png" alt="duo3.png" class="image--right mx-auto mr-0" /></p>
<h3 id="heading-private-use-areas-pua">Private Use Areas (PUA)</h3>
<p>A private use area is a freely available Unicode range that is not assigned. We can use this for personal use. Read more about this <a target="_blank" href="https://en.wikipedia.org/wiki/Private_Use_Areas">here</a>.</p>
<p>You can know about Unicode range of Private Areas from <a target="_blank" href="https://unicode-table.com/en/blocks/private-use-area/">here</a></p>
<p>The private use areas range is from U+E000 to U+F8FF</p>
<h3 id="heading-unicode-assigning"><strong>Unicode assigning</strong></h3>
<p>Since the range of Private Use Areas starts at E000, we assign E000 Unicode to our first glyph.</p>
<p>And for the second glyph, let's assign Unicode E001. 👇</p>
<p>If you added more icons, assign the Unicodes accordingly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662529231466/EUkQZkrmA.png" alt="Screenshot 2022-09-07 at 11.07.48 AM.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662529228042/D3Gsfu6wG.png" alt="Screenshot 2022-09-07 at 11.07.54 AM.png" class="image--center mx-auto" /></p>
<p><strong>Now let's export the font</strong></p>
<p>Add a proper family name to the font first to export.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662530863060/vQVS7iqFK.png" alt="Screenshot 2022-09-06 at 9.22.21 PM.png" class="image--center mx-auto" /></p>
<p>then let's export the font. Make sure to add a check mark to the <strong>WOFF</strong> and <strong>WOFF2</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662531182847/ckRQzTpKk.png" alt="Screenshot 2022-09-07 at 11.42.19 AM.png" /></p>
<h3 id="heading-the-third-step-implementation">The third step ( implementation)</h3>
<p>Now we are ready to implement our icon font After exporting the font, we can see three files like this</p>
<ul>
<li><p><strong>WOFF</strong></p>
</li>
<li><p><strong>WOFF2</strong></p>
</li>
<li><p><strong>TTF</strong></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662532944607/ZsvbaN7GF.png" alt="Screenshot 2022-09-07 at 12.10.38 PM.png" class="image--center mx-auto" /></p>
<p>Let's open these three files with vs code to implement the icon. Then two files should be created as index.html and main.css .</p>
<p>We need to add the exported font to the CSS file we can use css @font-face</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662533347952/d7dBMhyX5.png" alt="Screenshot 2022-09-07 at 12.18.33 PM.png" class="image--center mx-auto" /></p>
<p>After adding the font files, we need to add our font family to the icon class.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662533500802/ZK3t7MrIH.png" alt="Screenshot 2022-09-07 at 12.20.24 PM.png" class="image--center mx-auto" /></p>
<p>This is another important step. Now we need to use the Unicode used when creating the font. What we're going to do in this step is layer our separated icon pieces on top of each other. For that, we are going to use absolute positioning.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662533835468/mTOWLlmGq.png" alt="Screenshot 2022-09-07 at 12.22.44 PM.png" class="image--center mx-auto" /></p>
<p>Here we use CSS variables to assign the colors and opacity is used to achieve the duotone effect. Full CSS source code 👇</p>
<pre><code class="lang-css"> <span class="hljs-keyword">@font-face</span> {
     <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"duotone"</span>;
     <span class="hljs-attribute">src</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">"duotone-Regular.ttf"</span>) <span class="hljs-built_in">format</span>(<span class="hljs-string">"truetype"</span>), <span class="hljs-built_in">url</span>(<span class="hljs-string">"duotone-Regular.woff"</span>) <span class="hljs-built_in">format</span>(<span class="hljs-string">"woff"</span>);
     <span class="hljs-attribute">font-weight</span>: normal;
     <span class="hljs-attribute">font-style</span>: normal;
     <span class="hljs-attribute">font-display</span>: block;
}
 <span class="hljs-selector-attr">[class^=<span class="hljs-string">"icon-"</span>]</span>, <span class="hljs-selector-attr">[class*=<span class="hljs-string">" icon-"</span>]</span> {
     <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"duotone"</span> <span class="hljs-meta">!important</span>;
     <span class="hljs-attribute">font-style</span>: normal;
     <span class="hljs-attribute">font-weight</span>: normal;
     <span class="hljs-attribute">font-variant</span>: normal;
     <span class="hljs-attribute">text-transform</span>: none;
     <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1</span>;
     <span class="hljs-attribute">-webkit-font-smoothing</span>: antialiased;
     <span class="hljs-attribute">-moz-osx-font-smoothing</span>: grayscale;
}
<span class="hljs-comment">/* Calender */</span>
 <span class="hljs-selector-class">.icon-calender</span> {
     <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--primary);
}
 <span class="hljs-selector-class">.icon-calender</span><span class="hljs-selector-pseudo">::before</span> {
     <span class="hljs-attribute">content</span>: <span class="hljs-string">"\E000"</span>;
     <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.4</span>;
     <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--secondary);
     <span class="hljs-attribute">position</span>: absolute;
}
 <span class="hljs-selector-class">.icon-calender</span><span class="hljs-selector-pseudo">::after</span> {
     <span class="hljs-attribute">content</span>: <span class="hljs-string">"\E001"</span>;
}
<span class="hljs-comment">/* Note */</span>
 <span class="hljs-selector-class">.icon-note</span> {
     <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--primary);
}
 <span class="hljs-selector-class">.icon-note</span><span class="hljs-selector-pseudo">::before</span> {
     <span class="hljs-attribute">content</span>: <span class="hljs-string">"\E002"</span>;
     <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.4</span>;
     <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--secondary);
     <span class="hljs-attribute">position</span>: absolute;
}
 <span class="hljs-selector-class">.icon-note</span><span class="hljs-selector-pseudo">::after</span> {
     <span class="hljs-attribute">content</span>: <span class="hljs-string">"\E003"</span>;
}
</code></pre>
<p>now let's move on to the html part We can define our icon font like any other icon font, but we can apply the desired color with an inline style, so we can easily achieve the duotone effect.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662535121022/rvGr5MVuB.png" alt="Screenshot 2022-09-07 at 12.48.25 PM.png" class="image--center mx-auto" /></p>
<p>Full HTML source code 👇</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Duotone icon-font<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"icon.css"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon-calender"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"--primary: #17A171"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon-note"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"--primary: #17A171"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This is the Final preview of our <strong>duotone</strong> icon font</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662536471999/4NbCkfq8A.png" alt="Screenshot 2022-09-06 at 9.44.06 PM.png" class="image--center mx-auto" /></p>
<h3 id="heading-summary"><strong>Summary</strong></h3>
<p>In this article, we have created our own duotone icon font using Glyphs App and CSS. We use Illustrator to vectorize the icons, and then use the Glyphs App to generate those icons into a font. By using CSS, we easily achieve the duotone effect.</p>
<p>you can find the full source code from <a target="_blank" href="https://github.com/VihangaN/duotone-web-icon-font">here</a></p>
<h3 id="heading-thank-you-for-reading">Thank you for reading 🙂 !</h3>
]]></content:encoded></item></channel></rss>