<?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[Samir Shuman's Blog]]></title><description><![CDATA[Welcome to my blog! I build stuff for the web and write about the interesting parts. Expect posts about real development problems, smart solutions I've found, a]]></description><link>https://blog.devmansam.net</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1764061920460/ca502a2f-cb45-43b3-a5ae-d7dc2ff66078.png</url><title>Samir Shuman&apos;s Blog</title><link>https://blog.devmansam.net</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 11:35:29 GMT</lastBuildDate><atom:link href="https://blog.devmansam.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Cutting Through JavaScript RegEx 🍕]]></title><description><![CDATA[title: "Cutting Through JavaScript RegEx 🍕" subtitle: "🚫🍍 /[^pineapple]/" slug: cutting-through-javascript-regex tags: javascript, regex, tutorial cover: domain: publishAs:
It's been a while since my last post. I've gotten back into doing coding c...]]></description><link>https://blog.devmansam.net/cutting-through-javascript-regex</link><guid isPermaLink="true">https://blog.devmansam.net/cutting-through-javascript-regex</guid><category><![CDATA[Regex]]></category><category><![CDATA[Regular Expressions]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Fri, 30 Jan 2026 08:00:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769752573694/f339201b-d14a-4da6-a586-f6677788e1bf.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<hr />
<h2 id="heading-title-cutting-through-javascript-regex-subtitle-pineapple-slug-cutting-through-javascript-regex-tags-javascript-regex-tutorial-cover-domain-publishas">title: "Cutting Through JavaScript RegEx 🍕" subtitle: "🚫🍍 /[^pineapple]/" slug: cutting-through-javascript-regex tags: javascript, regex, tutorial cover: domain: publishAs:</h2>
<p>It's been a while since my last post. I've gotten back into doing coding challenges recently and although I'm not an expert on regular expressions by any means, I assumed that everyone knew about them. I've been hosting sessions every night on Discord where people meet up to do coding challenges live and much to my surprise many people had little to no experience with them.</p>
<p>I get it, regular expressions look intimidating at first glance. They look like someone smashed their keyboard and called it code. But once you understand the syntax, they're actually pretty logical and useful.</p>
<p>So what are regular expressions? They're a way to define patterns in text. You can use them to search for specific sequences of characters, validate input, or transform strings based on rules you define. Regular expressions exist in many programming languages, but we're going to focus on how they work in JavaScript.</p>
<p>In the real world, developers use regex for things like validating email addresses and passwords, finding and replacing text in documents, parsing large amounts of data, and cleaning up user input before it hits your database. I would have been at my wits' end without regular expressions while parsing data in <a target="_blank" href="https://github.com/devmansam777/yp-scraper">YP Scraper</a> and the built-in scraper for <a target="_blank" href="https://github.com/devmansam777/devleads">DevLeads</a>.</p>
<p>The key to understanding regular expressions is recognizing that they're all about patterns. Once you know how to describe the pattern you're looking for, the rest falls into place. In this post, I'm going to walk you through how to build those patterns using the most common syntax and features you'll actually use. Ready? Let's go.</p>
<h2 id="heading-creating-regular-expressions">Creating Regular Expressions</h2>
<p>A regular expression in JavaScript is created using forward slashes with your pattern in between. It looks like this: <code>/pattern/</code>. You can also create one using the RegExp constructor like <code>new RegExp('pattern')</code>, which is useful when you need to build a pattern dynamically. For example, if you're getting a search term from user input or an API response and need to create a regex from that returned value, the constructor lets you pass in variables. For most cases, though, you'll use the literal notation with slashes because it's cleaner and easier to read.</p>
<h2 id="heading-literal-patterns">Literal Patterns</h2>
<p>The simplest pattern you can match is literal text. If you want to find the word "pizza" in a string, your regex is just <code>/pizza/</code>. It matches exactly what you write. So <code>/sauce/</code> matches "sauce", <code>/SAUCE/</code> matches "SAUCE", and so on. This works great when you know exactly what you're looking for, but things get more interesting when you need flexibility.</p>
<h2 id="heading-the-or-operator">The OR Operator</h2>
<p>That's where the OR operator comes in. The pipe symbol <code>|</code> lets you match one pattern or another. So <code>/thin-crust|deep-dish/</code> will match either "thin-crust" or "deep-dish" in your string. You can chain as many options as you need, like <code>/mozzarella|provolone|parmesan|ricotta/</code> to match any of those cheeses. This is handy when you have a few specific variations you want to catch.</p>
<h2 id="heading-character-classes">Character Classes</h2>
<p>Character classes give you even more flexibility. When you put characters inside square brackets like <code>[aeiou]</code>, it matches any single character from that set. So <code>/[aeiou]/</code> matches any vowel, and <code>/[0-9]/</code> matches any single digit. You can also use ranges with a hyphen. <code>/[a-z]/</code> matches any lowercase letter, <code>/[A-Z]/</code> matches any uppercase letter, and <code>/[0-9]/</code> matches any digit from zero to nine. You can combine ranges too, like <code>/[a-zA-Z0-9]/</code> to match any letter regardless of case.</p>
<h2 id="heading-negative-character-classes">Negative Character Classes</h2>
<p>You can invert a character class by putting an up-caret <code>^</code> right after the opening bracket. So <code>[^aeiou]</code> matches any character except vowels. Notice that the up-caret here means "NOT" because it's inside the brackets. This is important because the same <code>^</code> symbol means something completely different when it's outside brackets, which we'll get to in a bit. So <code>[^0-9]</code> matches any character that isn't a digit, and <code>[^.,!?]</code> matches any character that isn't common punctuation.</p>
<h2 id="heading-escape-sequences">Escape Sequences</h2>
<p>Escape sequences are shorthand for common character classes. Instead of writing <code>[0-9]</code> every time you want to match a digit, you can use <code>\d</code>. Instead of <code>[a-zA-Z0-9_]</code> for all word (alphanumeric characters plus the underscore '_'), you use <code>\w</code>. And <code>\s</code> matches any whitespace character like spaces, tabs, carriage returns, or newlines. Each of these has an inverse too. <code>\D</code> matches anything that's not a digit, <code>\W</code> matches anything that's not a word character, and <code>\S</code> matches anything that's not whitespace. There are individual ones like <code>\n</code> for newlines, <code>\t</code> for tabs, and <code>\r</code> for carriage returns, but the digit, word, and whitespace shortcuts are the ones you'll reach for most often.</p>
<h2 id="heading-quantifiers">Quantifiers</h2>
<p>Quantifiers let you specify how many times a pattern should repeat. The asterisk <code>*</code> matches zero or more occurrences, so <code>/o*/</code> matches zero or more o's in "book". The plus sign <code>+</code> matches one or more, so <code>/o+/</code> requires at least one o. The question mark <code>?</code> makes something optional by matching zero or one occurrence. You can also be specific with curly braces. <code>/\d{3}/</code> matches exactly three digits (like an area code), <code>/\d{2,4}/</code> matches between two and four digits, and <code>/\d{2,}/</code> matches two or more digits. These quantifiers apply to whatever comes immediately before them, whether that's a single character, a character class, or a group.</p>
<p>By default, quantifiers are greedy. They match as much as possible. If you have the string <code>"&lt;span&gt;Large $18&lt;/span&gt;&lt;span&gt;Medium $14&lt;/span&gt;"</code> and use the pattern <code>/&lt;span&gt;.*&lt;\/span&gt;/</code>, it matches the entire string from the first opening tag to the last closing tag, not just the first span. This happens because <code>.*</code> grabs everything it can. You can make a quantifier lazy by adding a question mark after it. So <code>/&lt;span&gt;.*?&lt;\/span&gt;/</code> matches as little as possible, stopping at the first closing tag it finds. The lazy version matches <code>"&lt;span&gt;Large $18&lt;/span&gt;"</code> and then <code>"&lt;span&gt;Medium $14&lt;/span&gt;"</code> separately instead of treating them as one big match.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> greedy = <span class="hljs-regexp">/&lt;span&gt;.*&lt;\/span&gt;/</span>;
<span class="hljs-keyword">const</span> lazy = <span class="hljs-regexp">/&lt;span&gt;.*?&lt;\/span&gt;/</span>;
<span class="hljs-keyword">const</span> menu = <span class="hljs-string">"&lt;span&gt;Large $18&lt;/span&gt;&lt;span&gt;Medium $14&lt;/span&gt;"</span>;

menu.match(greedy); <span class="hljs-comment">// ["&lt;span&gt;Large $18&lt;/span&gt;&lt;span&gt;Medium $14&lt;/span&gt;"]</span>
menu.match(lazy); <span class="hljs-comment">// ["&lt;span&gt;Large $18&lt;/span&gt;"]</span>
</code></pre>
<h2 id="heading-anchors">Anchors</h2>
<p>Anchors let you match positions in a string rather than characters. The up-caret <code>^</code> matches the start of a string, and the bling symbol <code>$</code> matches the end. So <code>/^Large/</code> only matches "Large" if it's at the beginning of the string, and <code>/pizza$/</code> only matches "pizza" if it's at the end. You can combine them like <code>/^Special: .+ pizza$/</code> to match lines that start with "Special:" and end with "pizza". Remember, this up-caret is outside the brackets, so it means "start of string" here, not "NOT" like it does inside character classes.</p>
<h2 id="heading-capture-groups">Capture Groups</h2>
<p>Parentheses create capture groups, which do two things. First, they let you group parts of your pattern together so you can apply quantifiers to the whole group. Second, they capture the matched text so you can reference it later. So <code>/(yum)+/</code> matches "yum", "yumyum", "yumyumyum" and so on. We'll see how to use these captured groups with methods like <code>replace()</code> later on.</p>
<p>You can also reference captured groups within the regex itself using backreferences. The syntax <code>\1</code> refers to the first capture group, <code>\2</code> to the second, and so on. This is useful when you need to match repeated patterns.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(\w+)\s+\1/</span>;
pattern.test(<span class="hljs-string">"pizza pizza"</span>); <span class="hljs-comment">// true</span>
pattern.test(<span class="hljs-string">"pizza pasta"</span>); <span class="hljs-comment">// false</span>
</code></pre>
<p>You can also name your capture groups using the syntax <code>(?&lt;name&gt;pattern)</code>. This makes your regex more readable and lets you reference groups by name instead of by number. For back references with named groups, use <code>\k&lt;name&gt;</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> datePattern = <span class="hljs-regexp">/(?&lt;year&gt;\d{4})-(?&lt;month&gt;\d{2})-(?&lt;day&gt;\d{2})/</span>;
</code></pre>
<p>Sometimes you want the grouping but don't need to capture the text. That's where non-capturing groups come in. You write them as <code>(?:pattern)</code>. So <code>(?:https?)</code> groups the pattern for optional matching but doesn't save the match for later use.</p>
<h2 id="heading-lookaheads-and-lookbehinds">Lookaheads and Lookbehinds</h2>
<p>Lookaheads and lookbehinds let you match a pattern only if it's followed or preceded by another pattern, without including that other pattern in the match.</p>
<p>A positive lookahead <code>(?=pattern)</code> checks that what comes next matches the pattern. So <code>/\d+(?=oz)/</code> matches a digit only if it's followed by "oz", but the "oz" itself isn't part of the match.</p>
<p>A negative lookahead <code>(?!pattern)</code> does the opposite, matching only if what comes next doesn't match the pattern. Lookbehinds work the same way but check what comes before.</p>
<p>A positive lookbehind <code>(?&lt;=pattern)</code> matches only if preceded by the pattern, and a negative lookbehind <code>(?&lt;!pattern)</code> matches only if not preceded by it. These are useful when you need to match something based on context without consuming the surrounding characters.</p>
<h2 id="heading-flags">Flags</h2>
<p>Flags modify how the entire regex behaves. You add them after the closing slash. The <code>g</code> flag stands for global and makes the pattern match all occurrences in a string instead of stopping after the first one.</p>
<p>The <code>i</code> flag makes the match case-insensitive, so <code>/pizza/i</code> matches "pizza", "Pizza", "PIZZA", and any other variation.</p>
<p>The <code>m</code> flag is for multiline mode, which changes how the <code>^</code> and <code>$</code> anchors work. Instead of matching only the start and end of the entire string, they match the start and end of each line. So <code>/^Special:/m</code> will match "Special:" at the beginning of any line in a multiline string, not just the very first line.</p>
<p>The <code>s</code> flag enables dotAll mode, which makes the dot <code>.</code> match newline characters in addition to everything else. Normally <code>.</code> matches any character except newlines, so <code>/First.*Second/</code> wouldn't match across lines. With the <code>s</code> flag, <code>/First.*Second/s</code> will match even if "First" and "Second" are on different lines.</p>
<p>You can combine flags like <code>/pattern/gims</code> to use multiple at once.</p>
<h2 id="heading-using-regular-expressions-with-javascript-methods">Using Regular Expressions with JavaScript Methods</h2>
<p>Now that we understand how to build patterns, let's look at how to actually use them with JavaScript methods.</p>
<h3 id="heading-regexpprototypetest">RegExp.prototype.test()</h3>
<p>The <code>test()</code> method checks if a pattern exists in a string and returns true or false. It takes a string as its argument and belongs to the regex itself, not the string. Here's how it works:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\d+/</span>;
pattern.test(<span class="hljs-string">"16 inch pizza"</span>); <span class="hljs-comment">// true</span>
pattern.test(<span class="hljs-string">"Large pizza"</span>); <span class="hljs-comment">// false</span>
</code></pre>
<p>This is straightforward when you're just checking for a match, but there's a gotcha you need to know about. If you use the <code>g</code> flag with <code>test()</code>, the regex object maintains state between calls. It keeps track of where it left off using an internal <code>lastIndex</code> property on the regex itself.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\d+/g</span>;
pattern.test(<span class="hljs-string">"Deliver to 123 Main St Apt 4"</span>); <span class="hljs-comment">// true</span>
<span class="hljs-built_in">console</span>.log(pattern.lastIndex); <span class="hljs-comment">// 14</span>
pattern.test(<span class="hljs-string">"Deliver to 123 Main St Apt 4"</span>); <span class="hljs-comment">// true</span>
<span class="hljs-built_in">console</span>.log(pattern.lastIndex); <span class="hljs-comment">// 28</span>
pattern.test(<span class="hljs-string">"Deliver to 123 Main St Apt 4"</span>); <span class="hljs-comment">// false</span>
<span class="hljs-built_in">console</span>.log(pattern.lastIndex); <span class="hljs-comment">// 0 (wraps back to start)</span>
</code></pre>
<p>Each call picks up where the previous one left off. This can cause unexpected results if you're testing the same regex against different strings or reusing a regex object. If you need to reset it, set <code>lastIndex</code> back to zero manually.</p>
<h3 id="heading-regexpprototypeexec">RegExp.prototype.exec()</h3>
<p>The <code>exec()</code> method searches for a match and returns detailed information about it, including capture groups. It's called on a regex and takes a string as its argument. Like <code>test()</code>, it belongs to the regex object.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(pepperoni|mushrooms|sausage|olives|peppers)/</span>;
<span class="hljs-keyword">const</span> result = pattern.exec(<span class="hljs-string">"I want mushrooms on my pizza"</span>);
<span class="hljs-built_in">console</span>.log(result);
<span class="hljs-comment">// ["mushrooms", "mushrooms", index: 7, input: "I want mushrooms on my pizza"]</span>
</code></pre>
<p>When you have multiple capture groups, they're all included in the result array:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(large|medium|small)-(pepperoni|mushrooms|sausage)/</span>;
<span class="hljs-keyword">const</span> result = pattern.exec(<span class="hljs-string">"Order: large-pepperoni"</span>);
<span class="hljs-built_in">console</span>.log(result);
<span class="hljs-comment">// ["large-pepperoni", "large", "pepperoni", index: 7, input: "Order: large-pepperoni"]</span>
</code></pre>
<p>With named capture groups, the matched groups are accessible through the <code>groups</code> property:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\$(?&lt;discount&gt;\d+) off (?&lt;size&gt;large|medium|small)/</span>;
<span class="hljs-keyword">const</span> result = pattern.exec(<span class="hljs-string">"Save $5 off large pizzas"</span>);
<span class="hljs-built_in">console</span>.log(result.groups);
<span class="hljs-comment">// { discount: "5", size: "large" }</span>
</code></pre>
<p>Like <code>test()</code>, if you use the <code>g</code> flag with <code>exec()</code>, it maintains state and returns subsequent matches on repeated calls:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\d+/g</span>;
pattern.exec(<span class="hljs-string">"2 large 3 medium"</span>); <span class="hljs-comment">// ["2", index: 0, ...]</span>
<span class="hljs-built_in">console</span>.log(pattern.lastIndex); <span class="hljs-comment">// 1</span>
pattern.exec(<span class="hljs-string">"2 large 3 medium"</span>); <span class="hljs-comment">// ["3", index: 8, ...]</span>
<span class="hljs-built_in">console</span>.log(pattern.lastIndex); <span class="hljs-comment">// 9</span>
pattern.exec(<span class="hljs-string">"2 large 3 medium"</span>); <span class="hljs-comment">// null</span>
<span class="hljs-built_in">console</span>.log(pattern.lastIndex); <span class="hljs-comment">// 0</span>
</code></pre>
<h3 id="heading-stringprototypematch">String.prototype.match()</h3>
<p>The <code>match()</code> method searches a string for a pattern and returns information about the matches. It's called on a string and takes a regex as its argument. Without the <code>g</code> flag, it returns an array with the first match, any capture groups, the index, and the input string.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(\d+)\s+(cups?|tsp|tbsp|oz)/</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Mix 2 cups flour with other ingredients"</span>.match(pattern);
<span class="hljs-built_in">console</span>.log(result);
<span class="hljs-comment">// ["2 cups", "2", "cups", index: 4, input: "Mix 2 cups flour with other ingredients", groups: undefined]</span>
</code></pre>
<p>With the <code>g</code> flag, it returns an array of all matches but doesn't include capture groups or other details.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\d+/g</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Small $12, Medium $16, Large $20, XL $24"</span>.match(pattern);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// ["12", "16", "20", "24"]</span>
</code></pre>
<p>If you need both global matching and capture groups, use <code>matchAll()</code> instead.</p>
<h3 id="heading-stringprototypematchall">String.prototype.matchAll()</h3>
<p>The <code>matchAll()</code> method returns a Regular Expression String Iterator of all matches with their capture groups. It requires the <code>g</code> flag on the regex. This is useful when you need detailed information about every match. To use array methods like .map(), .filter(), .forEach() directly, you can convert it to an array using Array.from() or spread syntax.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(\w+):\s*(\d+)\s*oz/g</span>;
<span class="hljs-keyword">const</span> text = <span class="hljs-string">"Mozzarella: 8 oz, Parmesan: 2 oz, Provolone: 4 oz"</span>;
<span class="hljs-keyword">const</span> matches = [...text.matchAll(pattern)];

matches.forEach(<span class="hljs-function">(<span class="hljs-params">match</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Full match: <span class="hljs-subst">${match[<span class="hljs-number">0</span>]}</span>`</span>);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Cheese: <span class="hljs-subst">${match[<span class="hljs-number">1</span>]}</span>`</span>);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Amount: <span class="hljs-subst">${match[<span class="hljs-number">2</span>]}</span>`</span>);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Index: <span class="hljs-subst">${match.index}</span>`</span>);
});
</code></pre>
<p>With named capture groups, you can access them through the <code>groups</code> property.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(?&lt;cheese&gt;\w+):\s*(?&lt;amount&gt;\d+)\s*oz/g</span>;
<span class="hljs-keyword">const</span> text = <span class="hljs-string">"Mozzarella: 8 oz, Parmesan: 2 oz, Provolone: 4 oz"</span>;
<span class="hljs-keyword">const</span> matches = [...text.matchAll(pattern)];

matches.forEach(<span class="hljs-function">(<span class="hljs-params">match</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${match.groups.cheese}</span>: <span class="hljs-subst">${match.groups.amount}</span>`</span>);
});
<span class="hljs-comment">// "Mozzarella: 8"</span>
<span class="hljs-comment">// "Parmesan: 2"</span>
<span class="hljs-comment">// "Provolone: 4"</span>
</code></pre>
<h3 id="heading-stringprototypesearch">String.prototype.search()</h3>
<p>The <code>search()</code> method returns the index of the first match or -1 if there's no match. It's called on a string and takes a regex as its argument. The <code>g</code> flag doesn't affect this method since it only returns the first match's position.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/stuffed/</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Hand-tossed with a stuffed crust edge"</span>.search(pattern);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// 23</span>
</code></pre>
<p>This is useful when you just need to know where something is without caring about what was matched or any capture groups.</p>
<h3 id="heading-stringprototypereplace-and-stringprototypereplaceall">String.prototype.replace() and String.prototype.replaceAll()</h3>
<p>The <code>replace()</code> method searches for a pattern and replaces it with a new string. It's called on a string and takes two arguments: the regex pattern and the replacement. Without the <code>g</code> flag, it only replaces the first match.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/pepperoni/</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Add peppers, extra cheese, double pepperoni"</span>.replace(
  pattern,
  <span class="hljs-string">"sausage"</span>,
);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// "Add peppers, extra cheese, double sausage"</span>
</code></pre>
<p>With the <code>g</code> flag, it replaces all matches.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/medium/g</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Table 5 ordered medium, Table 8 wants medium too"</span>.replace(
  pattern,
  <span class="hljs-string">"large"</span>,
);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// "Table 5 ordered large, Table 8 wants large too"</span>
</code></pre>
<p>You can reference capture groups in the replacement string using dollar signs. <code>$1</code> refers to the first capture group, <code>$2</code> to the second, and so on.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(\d+)\s+(pizzas?|salads?)/g</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Need 3 pizzas and 2 salads"</span>.replace(pattern, <span class="hljs-string">"$2: $1"</span>);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// "Need pizzas: 3 and salads: 2"</span>
</code></pre>
<p>With named capture groups, you use <code>$&lt;name&gt;</code> syntax.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(?&lt;quantity&gt;\d+)\s+(?&lt;item&gt;pizzas?|salads?)/g</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Need 3 pizzas and 2 salads"</span>.replace(
  pattern,
  <span class="hljs-string">"$&lt;item&gt;: $&lt;quantity&gt;"</span>,
);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// "Need pizzas: 3 and salads: 2"</span>
</code></pre>
<p>The second argument can also be a callback function that gets called for each match. The function receives the full match, any capture groups, the index, and the full string. Whatever you return becomes the replacement.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\d+/g</span>;
<span class="hljs-keyword">const</span> discount = <span class="hljs-number">0.25</span>; <span class="hljs-comment">// 25% off</span>
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Pepperoni $18, Veggie $16, Supreme $22"</span>.replace(
  pattern,
  <span class="hljs-function">(<span class="hljs-params">match</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">parseInt</span>(match) * (<span class="hljs-number">1</span> - discount).toFixed(<span class="hljs-number">2</span>);
  },
);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// "Pepperoni $13.50, Veggie $12.00, Supreme $16.50"</span>
</code></pre>
<p>The <code>replaceAll()</code> method works the same way but requires the <code>g</code> flag if you're using a regex. It's there for consistency with the string version of <code>replaceAll()</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\[PENDING\]/g</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"Order 1: [PENDING], Order 2: [PENDING]"</span>.replaceAll(
  pattern,
  <span class="hljs-string">"READY"</span>,
);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// "Order 1: READY, Order 2: READY"</span>
</code></pre>
<h3 id="heading-stringprototypesplit">String.prototype.split()</h3>
<p>The <code>split()</code> method divides a string into an array based on a pattern. It's called on a string and takes a regex as its argument. This is where using regex as a delimiter really shines.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/\s+/</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"pepperoni    mushrooms   olives  peppers   sausage"</span>.split(
  pattern,
);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// ["pepperoni", "mushrooms", "olives", "peppers", "sausage"]</span>
</code></pre>
<p>Without regex, you'd have to split on a single space and deal with the empty strings from multiple spaces. With regex, you can match any amount of whitespace at once.</p>
<p>You can also split on multiple different delimiters.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/[\/\-]/</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"large-pepperoni/medium-veggie/small-cheese"</span>.split(pattern);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// ["large", "pepperoni", "medium", "veggie", "small", "cheese"]</span>
</code></pre>
<p>You can also use lookaheads and lookbehinds to split at specific positions without consuming any characters:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(?&lt;=[a-z])(?=[A-Z])/</span>;
<span class="hljs-keyword">const</span> result = <span class="hljs-string">"largePepperonimediumVeggiesmallCheese"</span>.split(pattern);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// ["large", "Pepperoni", "medium", "Veggie", "small", "Cheese"]</span>
</code></pre>
<h2 id="heading-going-further">Going Further</h2>
<p>This guide covers the most commonly used regex features and methods in JavaScript, but there's a lot more to explore. If you want to dive deeper into regular expressions, the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">MDN documentation</a> is an excellent resource with comprehensive coverage of all the features and edge cases.</p>
<h2 id="heading-thanks-for-stopping-by">Thanks For Stopping By!</h2>
<p>Before we wrap up, here's something to decode:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> decodeThis =
  <span class="hljs-string">"7nks s$ mu2h f$r r#@d%ng! 8$p# &amp;$u #nj$&amp;#d l#@rn%ng @b$ut r#g#x @nd @ll th#s# p$w#rful m#th$ds. St@&amp; 2##s#&amp;!"</span>;
<span class="hljs-built_in">console</span>.log(
  decodeThis.replace(
    <span class="hljs-regexp">/[@#$%&amp;2789]/g</span>,
    <span class="hljs-function">(<span class="hljs-params">x</span>) =&gt;</span>
      ({
        <span class="hljs-string">"@"</span>: <span class="hljs-string">"a"</span>,
        <span class="hljs-string">"#"</span>: <span class="hljs-string">"e"</span>,
        <span class="hljs-attr">$</span>: <span class="hljs-string">"o"</span>,
        <span class="hljs-string">"%"</span>: <span class="hljs-string">"i"</span>,
        <span class="hljs-string">"&amp;"</span>: <span class="hljs-string">"y"</span>,
        <span class="hljs-number">2</span>: <span class="hljs-string">"c"</span>,
        <span class="hljs-number">7</span>: <span class="hljs-string">"Tha"</span>,
        <span class="hljs-number">8</span>: <span class="hljs-string">"H"</span>,
        <span class="hljs-number">9</span>: <span class="hljs-string">""</span>,
      })[x],
  ),
);
<span class="hljs-comment">// "Thanks so much for reading! Hope you enjoyed learning about regex and all these powerful methods. Stay cheesy!"</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Detroit-Style Web Component 🍕:]]></title><description><![CDATA[Hey coders! After slicing through JavaScript's .replace() method in my last post, today I'm diving into the world of Web Components with a simple Detroit-style pizza visualization. I'll show you how to create a custom HTML element that renders a beau...]]></description><link>https://blog.devmansam.net/detroit-style-web-component</link><guid isPermaLink="true">https://blog.devmansam.net/detroit-style-web-component</guid><category><![CDATA[Web Components]]></category><category><![CDATA[ShadowDOM]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Fri, 09 May 2025 10:27:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746645211258/32e308c2-11dd-4dce-967e-e249be2439a6.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey coders! After slicing through JavaScript's <code>.replace()</code> method in my last post, today I'm diving into the world of Web Components with a simple Detroit-style pizza visualization. I'll show you how to create a custom HTML element that renders a beautiful pizza with optional toppings.</p>
<h2 id="heading-what-are-web-components">What Are Web Components?</h2>
<p>Web Components allow you create reusable custom elements that work across any framework or no framework at all. Instead of reinventing your pizza oven each time you want to make a pizza, you build it once and use it everywhere!</p>
<p>Web Components use three core technologies:</p>
<ol>
<li><p><strong>Custom Elements API</strong> - Lets you define your own HTML tags</p>
</li>
<li><p><strong>Shadow DOM</strong> - Creates an isolated DOM tree for your element</p>
</li>
<li><p><strong>HTML Templates</strong> - Defines reusable markup templates</p>
</li>
</ol>
<p>Today, we'll focus on creating a pizza component that shows off these key concepts with a delicious visual example.</p>
<h2 id="heading-why-detroit-style-pizza">Why Detroit-Style Pizza?</h2>
<p>Detroit-style pizza is the perfect metaphor for Web Components:</p>
<ul>
<li><p>It has a distinctive rectangular shape (like our component's structure)</p>
</li>
<li><p>Features edge-to-edge cheese with caramelized edges (like our component's encapsulation)</p>
</li>
<li><p>Can be customized with various toppings (custom attributes), showing component configurability</p>
</li>
<li><p>Has its own unique style that stands out from other pizza types (like web components stand out from other UI approaches)</p>
</li>
</ul>
<p>Plus, I'm on a pizza theme with my blog posts, so why stop now (I’m in too deep)?</p>
<h2 id="heading-lets-build-our-web-component">Let's Build Our Web Component!</h2>
<p>We're going to create a simple <code>&lt;detroit-pizza&gt;</code> element that can be configured with HTML attributes. The special feature? Our pizza starts as a plain cheese pizza, with three optional additions you can enable with attributes:</p>
<ol>
<li><p>Pepperoni (red)</p>
</li>
<li><p>Racing stripe sauce (deep red)</p>
</li>
<li><p>Fresh basil leaves (green)</p>
</li>
</ol>
<p>Here's the full code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/*
  Detroit-style pizza web component
*/</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DetroitPizza</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();

    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });

    <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        :host {
          display: block;
          width: 400px;
        }

        .pizza {
          width: 400px;
          height: 225px;
          background: #e8c38d;
          border: 8px solid #c49c67;
          border-radius: 4px;
          position: relative;
          box-shadow: 0 5px 15px rgba(0,0,0,0.3);
        }


        .cheese {
          position: absolute;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          border: 5px solid #b26b14;
          background: linear-gradient(45deg, #fddb6d, #fee9a0);
        }

        .sauce {
          position: absolute;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          z-index: 1;
        }

        /* three horizontal sauce stripes */
        .sauce.racing-stripes::before,
        .sauce.racing-stripes::after,
        .sauce.racing-stripes .middle-stripe {
          content: '';
          position: absolute;
          height: 12%;
          left: 5%;
          right: 5%;
          border-radius: 20px;
          background: #990000;
          box-shadow: 0 0 5px rgba(0,0,0,0.2);
        }

        .sauce.racing-stripes::before {
          top: 13%; 
        }

        .sauce.racing-stripes::after {
          bottom: 13%;
        }

        .sauce.racing-stripes .middle-stripe {
          top: 50%;
          transform: translateY(-50%);
        }

        /* toppings */
        .toppings {
          position: absolute;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          z-index: 1; 
        }

        .toppings.has-pepperoni {
          background-image: 
            radial-gradient(#d92e0e 8px, transparent 9px);
          background-size: 35px 35px;
          background-position: 8px 8px;
          background-position: 8px 8px;
          padding: 3px;
          box-sizing: border-box;
        }

        .basil-leaf {
          position: absolute;
          width: 20px;
          height: 30px;
          background: #386c0b;
          border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
          z-index: 5; 
          transform: rotate(15deg);
          box-shadow: 0 1px 2px rgba(0,0,0,0.2);
        }

        .basil-leaf:nth-child(1) { top: 15%; left: 20%; transform: rotate(32deg); }
        .basil-leaf:nth-child(2) { top: 65%; left: 30%; transform: rotate(-20deg); }
        .basil-leaf:nth-child(3) { top: 25%; left: 75%; transform: rotate(10deg); }
        .basil-leaf:nth-child(4) { top: 70%; left: 80%; transform: rotate(-35deg); }
        .basil-leaf:nth-child(5) { top: 40%; left: 60%; transform: rotate(27deg); }
        .basil-leaf:nth-child(6) { top: 75%; left: 45%; transform: rotate(15deg); }
        .basil-leaf:nth-child(7) { top: 45%; left: 15%; transform: rotate(-12deg); }
        .basil-leaf:nth-child(8) { top: 30%; left: 40%; transform: rotate(22deg); }

        .basil-leaf::after {
          content: '';
          position: absolute;
          top: 30%;
          left: 20%;
          width: 30%;
          height: 15%;
          background: rgba(255, 255, 255, 0.2);
          border-radius: 50%;
          transform: rotate(-10deg);
        }
      &lt;/style&gt;

      &lt;div class="pizza"&gt;
        &lt;div class="cheese"&gt;&lt;/div&gt;
        &lt;div class="toppings"&gt;&lt;/div&gt;
        &lt;div class="sauce"&gt;
          &lt;div class="middle-stripe"&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="basil-container"&gt;&lt;/div&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-built_in">this</span>.toppingsEl = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.toppings'</span>);
    <span class="hljs-built_in">this</span>.sauceEl = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.sauce'</span>);
    <span class="hljs-built_in">this</span>.basilContainer = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.basil-container'</span>);
  }

  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'pepperoni'</span>, <span class="hljs-string">'racing-stripes'</span>, <span class="hljs-string">'basil'</span>];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'pepperoni'</span>) {
      <span class="hljs-keyword">const</span> hasPepperoni = newValue === <span class="hljs-string">'true'</span>;
      <span class="hljs-keyword">if</span> (hasPepperoni) {
        <span class="hljs-built_in">this</span>.toppingsEl.classList.add(<span class="hljs-string">'has-pepperoni'</span>);
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">this</span>.toppingsEl.classList.remove(<span class="hljs-string">'has-pepperoni'</span>);
      }
    }

    <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'racing-stripes'</span>) {
      <span class="hljs-keyword">const</span> hasStripes = newValue === <span class="hljs-string">'true'</span>;
      <span class="hljs-keyword">if</span> (hasStripes) {
        <span class="hljs-built_in">this</span>.sauceEl.classList.add(<span class="hljs-string">'racing-stripes'</span>);
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">this</span>.sauceEl.classList.remove(<span class="hljs-string">'racing-stripes'</span>);
      }
    }

    <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'basil'</span>) {
      <span class="hljs-keyword">const</span> hasBasil = newValue === <span class="hljs-string">'true'</span>;
      <span class="hljs-keyword">if</span> (hasBasil) {
        <span class="hljs-built_in">this</span>.addBasilLeaves();
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">this</span>.basilContainer.innerHTML = <span class="hljs-string">''</span>;
      }
    }
  }


  addBasilLeaves() {
    <span class="hljs-built_in">this</span>.basilContainer.innerHTML = <span class="hljs-string">''</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt;= <span class="hljs-number">8</span>; i++) {
      <span class="hljs-keyword">const</span> leaf = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
      leaf.className = <span class="hljs-string">'basil-leaf'</span>;
      <span class="hljs-built_in">this</span>.basilContainer.appendChild(leaf);
    }
  }

  connectedCallback() {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">'pepperoni'</span>)) {
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'pepperoni'</span>, <span class="hljs-string">'false'</span>);
    }

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">'racing-stripes'</span>)) {
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'racing-stripes'</span>, <span class="hljs-string">'false'</span>);
    }

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">'basil'</span>)) {
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'basil'</span>, <span class="hljs-string">'false'</span>);
    }
  }
}

customElements.define(<span class="hljs-string">'detroit-pizza'</span>, DetroitPizza);
</code></pre>
<h2 id="heading-breaking-down-the-recipe-key-parts-explained">Breaking Down the Recipe: Key Parts Explained</h2>
<p>Let's explore each part of our component:</p>
<h3 id="heading-1-the-base-extending-htmlelement">1. The Base: Extending HTMLElement</h3>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DetroitPizza</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
</code></pre>
<p>Our component extends the built-in <code>HTMLElement</code> class to get all the standard HTML element behavior. The <code>super()</code> call initializes the base class.</p>
<h3 id="heading-2-the-secret-sauce-shadow-dom">2. The Secret Sauce: Shadow DOM</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
</code></pre>
<p>Shadow DOM creates a separate DOM tree for our component, keeping styles and structure encapsulated. Nothing leaks out, and outside styles don't mess up our pizza's appearance.</p>
<h3 id="heading-3-the-recipe-styling-and-structure">3. The Recipe: Styling and Structure</h3>
<ul>
<li><p>A golden-brown crust</p>
</li>
<li><p>A warm yellow cheese</p>
</li>
<li><p>Deep red sauce stripes (#990000)</p>
</li>
<li><p>Bright red pepperoni (#d92e0e)</p>
</li>
<li><p>Rich green basil leaves</p>
</li>
</ul>
<h3 id="heading-4-the-opt-in-toppings-strict-attribute-handling">4. The Opt-In Toppings: Strict Attribute Handling</h3>
<pre><code class="lang-javascript">attributeChangedCallback(name, oldValue, newValue) {
  <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'pepperoni'</span>) {
    <span class="hljs-keyword">const</span> hasPepperoni = newValue === <span class="hljs-string">'true'</span>;
    <span class="hljs-comment">// ...</span>
  }
}
</code></pre>
<p>We only add toppings when their respective attribute is exactly <code>"true"</code>. Any other value keeps those features turned off. The basil leaves are nicely spread across the pizza at various angles for a natural look.</p>
<h3 id="heading-5-default-plain-cheese-initial-setup">5. Default Plain Cheese: Initial Setup</h3>
<pre><code class="lang-javascript">connectedCallback() {
  <span class="hljs-comment">// All features off by default, enable with explicit "true"</span>
  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">'pepperoni'</span>)) {
    <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'pepperoni'</span>, <span class="hljs-string">'false'</span>);
  }

  <span class="hljs-comment">// Same for other attributes...</span>
}
</code></pre>
<p>When our component is added to the page, this method ensures it starts as a plain cheese pizza by setting attributes to "false" by default. Users must explicitly opt-in to get additional toppings.</p>
<h2 id="heading-using-our-pizza-component">Using Our Pizza Component</h2>
<p>To use this component, include the JavaScript code and add the tag to your HTML. All features are off by default, so if you want them, you must explicitly enable them:</p>
<pre><code class="lang-html"><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>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Detroit Pizza Demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://rawcdn.githack.com/DevManSam777/detroit-style-web-component/eea750ab212f4cc794e27ec87928d20a17b89afc/detroit-pizza.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</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">h1</span>&gt;</span>Detroit-Style Pizzas<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Plain cheese pizza (default) --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Plain Cheese<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">detroit-pizza</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">detroit-pizza</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Pizza with pepperoni only --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>With Pepperoni<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">detroit-pizza</span> <span class="hljs-attr">pepperoni</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">detroit-pizza</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Pizza with racing stripes only --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>With Racing Stripes<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">detroit-pizza</span> <span class="hljs-attr">racing-stripes</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">detroit-pizza</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Pizza with basil only --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>With Fresh Basil<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">detroit-pizza</span> <span class="hljs-attr">basil</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">detroit-pizza</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- The works - everything enabled --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>The Works<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">detroit-pizza</span> <span class="hljs-attr">pepperoni</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">racing-stripes</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">basil</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">detroit-pizza</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><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746786385486/57f349a1-b7f2-40ea-927a-ad5e26a61ea9.png" alt /></p>
<h2 id="heading-the-special-sauce-why-web-components-are-awesome">The Special Sauce: Why Web Components Are Awesome</h2>
<p>Web Components have several key advantages:</p>
<ol>
<li><p><strong>Framework-agnostic</strong> - They work in vanilla JavaScript or any framework</p>
</li>
<li><p><strong>Encapsulated styling</strong> - CSS stays contained within the component</p>
</li>
<li><p><strong>Declarative API</strong> - Configure through readable HTML attributes</p>
</li>
<li><p><strong>Opt-in complexity</strong> - Start simple, add features only when needed</p>
</li>
<li><p><strong>Future-proof</strong> - Built on web standards with long-term browser support</p>
</li>
</ol>
<h2 id="heading-when-to-use-web-components">When to Use Web Components</h2>
<p>Web Components are particularly valuable for:</p>
<ul>
<li><p>Creating design system elements that need to work consistently across projects</p>
</li>
<li><p>Building UI widgets with clean, attribute-based APIs</p>
</li>
<li><p>Developing embeddable content for third-party sites</p>
</li>
<li><p>Creating progressive enhancements for existing applications</p>
</li>
</ul>
<h2 id="heading-final-slice-what-weve-learned">Final Slice: What We've Learned</h2>
<p>In this tutorial, we've built a Detroit-style pizza web component that demonstrates several key principles:</p>
<ul>
<li><p>Creating custom HTML elements</p>
</li>
<li><p>Using Shadow DOM for style encapsulation</p>
</li>
<li><p>Styling with pure CSS (no external images needed!)</p>
</li>
<li><p>Implementing opt-in features with strict attribute checking</p>
</li>
</ul>
<p>The ability to start with a simple base and explicitly opt-in to additional features is a powerful pattern in web development. It follows the principle of progressive enhancement. We start with the essentials and let users add complexity only when needed.</p>
<p>Next time you're building a UI component, consider if the Web Components approach might be right for your project. They offer a great combination of encapsulation, reusability, and framework independence.</p>
<p>Are you using Web Components in your projects? Have you tried the opt-in approach to features? Drop a comment below and let me know!</p>
<h3 id="heading-want-to-try-it-out"><strong>Want to try it out?</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746785881774/213dcb40-9a29-4122-a000-5087162a4764.gif" alt /></p>
<h3 id="heading-add-this-script-element-to-the-head-section-of-your-html-page">Add this script element to the head section of your html page:</h3>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://rawcdn.githack.com/DevManSam777/detroit-style-web-component/eea750ab212f4cc794e27ec87928d20a17b89afc/detroit-pizza.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h3 id="heading-and-add-the-component-anywhere-within-your-html-body">And add the component anywhere within your html body:</h3>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">detroit-pizza</span> <span class="hljs-attr">pepperoni</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">racing-stripes</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">basil</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">detroit-pizza</span>&gt;</span>
</code></pre>
<p>Until next time, happy coding and happy pizza-making!</p>
<p>P.S. Follow me on <a target="_blank" href="https://bsky.app/profile/devmansam.net">bsky</a> for more web development recipes with a dash of pizza-themed fun!</p>
]]></content:encoded></item><item><title><![CDATA[Slicing Through JavaScript's .replace() Method & RegEx 🍕]]></title><description><![CDATA[Hey coders and pizza fans! In my last blog post, we learned about HTML lists with my top-secret, low-carb, pizza dough recipe. Today, we’re going to explore the powerful combination of JavaScript's .replace() method and regular expressions. And becau...]]></description><link>https://blog.devmansam.net/slicing-through-javascripts-replace-method-and-regex</link><guid isPermaLink="true">https://blog.devmansam.net/slicing-through-javascripts-replace-method-and-regex</guid><category><![CDATA[replace()]]></category><category><![CDATA[Regex]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[#100devs]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Mon, 17 Mar 2025 07:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742081092253/1e7a4d5b-a5f0-4b5d-8f53-218b16e1a1d8.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey coders and pizza fans! In my last blog post, we learned about HTML lists with my top-secret, low-carb, pizza dough recipe. Today, we’re going to explore the powerful combination of JavaScript's <code>.replace()</code> method and regular expressions. And because pizza makes everything better (you know I can't code without a slice nearby), we're going to knead our way through some string manipulation examples in JavaScript.</p>
<h2 id="heading-the-three-crust-options-of-replace">The Three Crust Options of .replace()</h2>
<p>Just like how you can choose different types of pizza crust (thin, deep dish, or stuffed?), JavaScript gives us three different ways to use <code>.replace()</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Plain Cheese: Simple String Replace</span>
<span class="hljs-comment">// "Pepperoni Pizza" - Simple direct replacement</span>
<span class="hljs-string">"Pineapple Pizza"</span>.replace(<span class="hljs-string">"Pineapple"</span>, <span class="hljs-string">"Pepperoni"</span>);  


<span class="hljs-comment">// Supreme: RegEx Replace with case-insensitive flag </span>
<span class="hljs-comment">// "Pepperoni pizza" - The 'i' flag ignores case differences</span>
<span class="hljs-string">"pINeApPlE pizza"</span>.replace(<span class="hljs-regexp">/pineapple/i</span>, <span class="hljs-string">"Pepperoni"</span>);  


<span class="hljs-comment">// Stuffed Crust: Callback Function Replace with calculation</span>
<span class="hljs-comment">// "Pizza with 5 toppings" - Converts to number, adds 2, then back to string</span>
<span class="hljs-string">"Pizza with 3 toppings"</span>.replace(<span class="hljs-regexp">/\d+/</span>, <span class="hljs-function">(<span class="hljs-params">match</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Number</span>(match) + <span class="hljs-number">2</span>;  
});
</code></pre>
<p>HOT TIP: There's an important limitation to know about <code>.replace()</code> that might save you debugging time:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// This works - regex pattern with callback function</span>
<span class="hljs-string">"Pizza Time"</span>.replace(<span class="hljs-regexp">/Time/</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">"Party"</span>);

<span class="hljs-comment">// This causes TypeError - string pattern doesn't support callbacks</span>
<span class="hljs-string">"Pizza Time"</span>.replace(<span class="hljs-string">"Time"</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">"Party"</span>);
</code></pre>
<h2 id="heading-extra-toppings-callback-parameters">Extra Toppings: Callback Parameters</h2>
<p>When you use a callback function with <code>.replace()</code>, JavaScript delivers four standard parameters:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"Extra cheesy pizza $12.99"</span>.replace(<span class="hljs-regexp">/\$(\d+\.\d+)/</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">match, group, offset, originalString</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Full match:"</span>, match);           <span class="hljs-comment">// "$12.99" - the entire text matched by the regex</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Captured group:"</span>, group);       <span class="hljs-comment">// "12.99" - just the digits captured in parentheses</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Found at position:"</span>, offset);   <span class="hljs-comment">// 18 - the character position where match starts</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Original order:"</span>, originalString); <span class="hljs-comment">// "Extra cheesy pizza $12.99" - the complete string</span>

  <span class="hljs-comment">// Apply a 25% discount to the captured price</span>
  <span class="hljs-keyword">return</span> <span class="hljs-string">"$"</span> + (<span class="hljs-built_in">Number</span>(group) * <span class="hljs-number">0.75</span>).toFixed(<span class="hljs-number">2</span>);
});
<span class="hljs-comment">// "Extra cheesy pizza $9.74"</span>
</code></pre>
<p>Let's break down each parameter:</p>
<p>First, the <strong>match</strong> parameter gives you the entire substring that matched your regex pattern, similar to grabbing a whole slice of pizza.</p>
<p>Next, the <strong>group</strong> parameter contains any content captured in parentheses from your regex, kind of like isolating just the different types of toppings on your slice. If your regex has multiple capturing groups, you'll get additional parameters (<code>group1, group2, group3...</code>).</p>
<p>The <strong>offset</strong> parameter tells you the position where the match was found, sort of like knowing exactly which slice in the box has your favorite topping.</p>
<p>Finally, the <strong>originalString</strong> parameter gives you the complete string you're performing the replacement on, just like when the delivery person hands you your entire order.</p>
<h2 id="heading-regular-expression-basics">Regular Expression Basics</h2>
<p>Regular expressions are like the pizza topping counter of JavaScript, overwhelming at first glance, but you can make some cool things happen when you learn about each ingredient.</p>
<h3 id="heading-character-classes-the-basic-ingredients">Character Classes (The Basic Ingredients)</h3>
<pre><code class="lang-plaintext">\d - Any digit (0-9)         // Like counting pizza slices
\D - NOT a digit             // Everything except the slice numbers

\w - Any word character       // The letters in P-I-Z-Z-A
\W - NOT a word character     // Everything that's not in P-I-Z-Z-A

\s - Any whitespace          // The space between pizza and perfection
\S - NOT whitespace          // Everything except those spaces

.  - Any character           // Whatever you want on your pizza
</code></pre>
<h3 id="heading-quantifiers-how-much-of-each-topping">Quantifiers (How Much of Each Topping?)</h3>
<pre><code class="lang-plaintext">*  - 0 or more              // Can match empty string or repeated characters
+  - 1 or more              // Requires at least one occurrence
?  - 0 or 1 (optional)      // Makes the previous element optional
{3} - Exactly 3             // Matches exactly 3 occurrences
{2,4} - Between 2 and 4     // Matches between 2-4 occurrences, inclusive
</code></pre>
<h3 id="heading-anchors-where-on-the-pizza">Anchors (Where on the Pizza?)</h3>
<pre><code class="lang-plaintext">^  - Start of string         // Ensures pattern starts at beginning
$  - End of string           // Ensures pattern goes to the very end
\b - Word boundary           // Matches position between word/non-word character
</code></pre>
<h2 id="heading-capturing-groups-separating-your-pizza-slices">Capturing Groups: Separating Your Pizza Slices</h2>
<p>Parentheses in regex help you grab exactly which slice you want:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pizzaOrder = <span class="hljs-string">"I want a large pepperoni pizza with extra cheese"</span>;
<span class="hljs-keyword">const</span> size = pizzaOrder.match(<span class="hljs-regexp">/a (small|medium|large) \w+ pizza/</span>)[<span class="hljs-number">1</span>];
<span class="hljs-comment">// The [1] gets just the captured group - "large"</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Size ordered: <span class="hljs-subst">${size}</span>`</span>);  <span class="hljs-comment">// "large"</span>
</code></pre>
<p>When you use parentheses <code>()</code> in a regular expression, you create "capture groups" that store matched portions separately. The <code>.match()</code> method returns an array where index <code>[0]</code> contains the entire match, while subsequent indexes <code>[1]</code>, <code>[2]</code>, etc. contain just the text matched by each set of parentheses. This lets you extract specific pieces (like "large") from a longer string without having to process the entire matched text.</p>
<p>Think of it as ordering a whole pizza but only wanting a particular slice - the capture group gives you just the part you need!</p>
<h2 id="heading-beyond-basic-groups-lookaheads-lookbehinds-and-non-capturing-groups">Beyond Basic Groups: Lookaheads, Lookbehinds, and Non-Capturing Groups</h2>
<p>While capturing groups are powerful, sometimes you need more advanced techniques to handle complex patterns without cluttering your results.</p>
<h3 id="heading-non-capturing-groups-grouping-without-storing">Non-Capturing Groups: Grouping Without Storing</h3>
<p>Non-capturing groups let you use parentheses for alternation or applying quantifiers without storing the matched content:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Regular capturing group</span>
<span class="hljs-keyword">const</span> order = <span class="hljs-string">"Order #12345"</span>.match(<span class="hljs-regexp">/(Order #)(\d+)/</span>);
<span class="hljs-built_in">console</span>.log(order);  
<span class="hljs-comment">// ["Order #12345", "Order #", "12345"]</span>

<span class="hljs-comment">// Non-capturing group for the prefix</span>
<span class="hljs-keyword">const</span> orderNonCapture = <span class="hljs-string">"Order #12345"</span>.match(<span class="hljs-regexp">/(?:Order #)(\d+)/</span>);
<span class="hljs-built_in">console</span>.log(orderNonCapture);  
<span class="hljs-comment">// ["Order #12345", "12345"] - Only the order number is captured</span>
</code></pre>
<p>Non-capturing groups use the syntax <code>(?:pattern)</code> and are especially useful when you need to group elements for matching purposes but don't need to reference that specific match later.</p>
<h3 id="heading-lookaheads-checking-what-follows">Lookaheads: Checking What Follows</h3>
<p>Lookaheads check what follows a pattern without including it in the match:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Extract prices only when followed by "discount"</span>
<span class="hljs-keyword">const</span> menuText = <span class="hljs-string">"Margherita $10.99 standard, Pepperoni $14.99 discount"</span>;

<span class="hljs-comment">// Positive lookahead - match prices that are followed by "discount"</span>
<span class="hljs-keyword">const</span> discountPrices = menuText.match(<span class="hljs-regexp">/\$\d+\.\d+(?= discount)/g</span>);
<span class="hljs-built_in">console</span>.log(discountPrices);  <span class="hljs-comment">// ["$14.99"]</span>

<span class="hljs-comment">// Negative lookahead - match prices NOT followed by "discount"</span>
<span class="hljs-keyword">const</span> regularPrices = menuText.match(<span class="hljs-regexp">/\$\d+\.\d+(?! discount)/g</span>);
<span class="hljs-built_in">console</span>.log(regularPrices);  <span class="hljs-comment">// ["$10.99"]</span>
</code></pre>
<p>Lookaheads are particularly useful for validation patterns or when you need to match text only in specific contexts.</p>
<h3 id="heading-lookbehinds-checking-what-precedes">Lookbehinds: Checking What Precedes</h3>
<p>Lookbehinds check what comes before your pattern:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> ingredients = <span class="hljs-string">"tomato sauce, mozzarella cheese, fresh basil, extra oregano"</span>;

<span class="hljs-comment">// Positive lookbehind - match ingredients preceded by "extra"</span>
<span class="hljs-keyword">const</span> extraIngredients = ingredients.match(<span class="hljs-regexp">/(?&lt;=extra )\w+/g</span>);
<span class="hljs-built_in">console</span>.log(extraIngredients);  <span class="hljs-comment">// ["oregano"]</span>

<span class="hljs-comment">// Negative lookbehind - match ingredients NOT preceded by "fresh" or "extra"</span>
<span class="hljs-keyword">const</span> standardIngredients = ingredients.match(<span class="hljs-regexp">/(?&lt;!fresh |extra )\w+(?= \w+,|$)/g</span>);
<span class="hljs-built_in">console</span>.log(standardIngredients);  <span class="hljs-comment">// ["sauce", "cheese"]</span>
</code></pre>
<p>Lookbehinds let you match based on what came before without including those preceding characters in your match.</p>
<h3 id="heading-practical-example-advanced-text-parsing">Practical Example: Advanced Text Parsing</h3>
<p>Let's see how these techniques can work together:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> customerReviews = <span class="hljs-string">`
ID: 1001 - Rating: 5 stars - "Best pizza in town! The crust was perfect."
ID: 1002 - Rating: 3 stars - "Delivery was slow but food was good."
ID: 1003 - Rating: 4 stars - "Great value for money. Will order again."
`</span>;

<span class="hljs-comment">// Extract only positive reviews (4-5 stars) with their IDs</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">extractPositiveReviews</span>(<span class="hljs-params">text</span>) </span>{
  <span class="hljs-comment">// Break down the pattern:</span>
  <span class="hljs-comment">// (?&lt;=ID: )(\d+)        - Lookbehind for "ID: " and capture the ID number</span>
  <span class="hljs-comment">// .+?Rating: [45]       - Match text up to ratings of 4 or 5</span>
  <span class="hljs-comment">// (?=.+?".+?")          - Lookahead to ensure there's a review in quotes</span>
  <span class="hljs-keyword">const</span> pattern = <span class="hljs-regexp">/(?&lt;=ID: )(\d+)(.+?Rating: [45])(?=.+?"(.+?)")/g</span>;

  <span class="hljs-keyword">const</span> matches = [];
  <span class="hljs-keyword">let</span> match;

  <span class="hljs-keyword">while</span> ((match = pattern.exec(text)) !== <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">const</span> reviewText = text.slice(match.index).match(<span class="hljs-regexp">/"(.+?)"/</span>)[<span class="hljs-number">1</span>];
    matches.push({
      <span class="hljs-attr">id</span>: match[<span class="hljs-number">1</span>],
      <span class="hljs-attr">rating</span>: match[<span class="hljs-number">2</span>].match(<span class="hljs-regexp">/(\d+)/</span>)[<span class="hljs-number">0</span>],
      <span class="hljs-attr">review</span>: reviewText
    });
  }

  <span class="hljs-keyword">return</span> matches;
}

<span class="hljs-built_in">console</span>.log(extractPositiveReviews(customerReviews));
<span class="hljs-comment">// [</span>
<span class="hljs-comment">//   { id: "1001", rating: "5", review: "Best pizza in town! The crust was perfect." },</span>
<span class="hljs-comment">//   { id: "1003", rating: "4", review: "Great value for money. Will order again." }</span>
<span class="hljs-comment">// ]</span>
</code></pre>
<p>This example demonstrates how non-capturing groups, lookaheads, and lookbehinds can work together to extract precise information from complex text patterns. By using these advanced techniques, you can create more efficient and readable regular expressions that focus on extracting exactly the data you need.</p>
<h2 id="heading-lets-cook-up-some-more-examples">Let's Cook Up Some More Examples!</h2>
<h3 id="heading-example-1-the-pizza-order-number-incrementer">Example 1: The Pizza Order Number Incrementer</h3>
<p>When your 99th pizza order becomes your 100th (this is a variation of the codewars kata I recently completed that inspired this blog post):</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">incrementString</span>(<span class="hljs-params">string</span>) </span>{
  <span class="hljs-keyword">return</span> string.replace(<span class="hljs-regexp">/(\d*)$/</span>, <span class="hljs-function"><span class="hljs-params">match</span> =&gt;</span> {
    <span class="hljs-comment">// If no digits found at the end, add "1"</span>
    <span class="hljs-keyword">if</span> (match === <span class="hljs-string">""</span>) <span class="hljs-keyword">return</span> <span class="hljs-string">"1"</span>;

    <span class="hljs-comment">// Store the original length to preserve leading zeros</span>
    <span class="hljs-keyword">const</span> length = match.length;

    <span class="hljs-comment">// Convert to number, add 1, then back to string with padding</span>
    <span class="hljs-keyword">return</span> (<span class="hljs-built_in">Number</span>(match) + <span class="hljs-number">1</span>).toString().padStart(length, <span class="hljs-string">"0"</span>);
  });
}

incrementString(<span class="hljs-string">"Pizza"</span>);     <span class="hljs-comment">// "Pizza1"</span>
incrementString(<span class="hljs-string">"Order #23"</span>); <span class="hljs-comment">// "Order #24"</span>
incrementString(<span class="hljs-string">"Receipt #099"</span>); <span class="hljs-comment">// "Receipt #100" - keeps leading zeros!</span>
</code></pre>
<h3 id="heading-example-2-pizza-recipe-parser">Example 2: Pizza Recipe Parser</h3>
<p>Extract ingredients from recipe text:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseIngredients</span>(<span class="hljs-params">recipe</span>) </span>{
  <span class="hljs-keyword">const</span> ingredients = [];
  <span class="hljs-comment">// This regex captures: amount, unit, and ingredient name</span>
  <span class="hljs-keyword">const</span> regex = <span class="hljs-regexp">/(\d+(?:\.\d+)?) (cups?|tablespoons?|teaspoons?) of ([^,\.]+)/g</span>;

  <span class="hljs-keyword">let</span> match;
  <span class="hljs-comment">// Loop through all matches in the text</span>
  <span class="hljs-keyword">while</span> ((match = regex.exec(recipe)) !== <span class="hljs-literal">null</span>) {
    ingredients.push({
      <span class="hljs-attr">amount</span>: <span class="hljs-built_in">parseFloat</span>(match[<span class="hljs-number">1</span>]),  <span class="hljs-comment">// Convert amount to number</span>
      <span class="hljs-attr">unit</span>: match[<span class="hljs-number">2</span>],                <span class="hljs-comment">// Get the unit (cup, tablespoon, etc)</span>
      <span class="hljs-attr">ingredient</span>: match[<span class="hljs-number">3</span>].trim()    <span class="hljs-comment">// Get just the ingredient name, trimmed</span>
    });
  }

  <span class="hljs-keyword">return</span> ingredients;
}

<span class="hljs-keyword">const</span> pizzaSauce = <span class="hljs-string">"Mix 2 cups of crushed tomatoes, 1.5 tablespoons of olive oil, 2 teaspoons of oregano."</span>;
<span class="hljs-built_in">console</span>.log(parseIngredients(pizzaSauce));
<span class="hljs-comment">// [{amount: 2, unit: "cups", ingredient: "crushed tomatoes"}, ...]</span>
</code></pre>
<h3 id="heading-example-3-pizza-special-formatter">Example 3: Pizza Special Formatter</h3>
<p>Format your specials menu with visual styling:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatSpecials</span>(<span class="hljs-params">menu</span>) </span>{
  <span class="hljs-comment">// Convert prices to styled spans</span>
  menu = menu.replace(<span class="hljs-regexp">/\$(\d+\.\d+)/g</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">match, price, offset, originalString</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Found price <span class="hljs-subst">${price}</span> at position <span class="hljs-subst">${offset}</span> in "<span class="hljs-subst">${originalString}</span>"`</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-string">'&lt;span class="price"&gt;$'</span> + price + <span class="hljs-string">'&lt;/span&gt;'</span>;
  });

  <span class="hljs-comment">// Make pizza names bold</span>
  menu = menu.replace(<span class="hljs-regexp">/([A-Za-z]+ Pizza)/gi</span>, <span class="hljs-string">'&lt;strong&gt;$1&lt;/strong&gt;'</span>);

  <span class="hljs-comment">// Add pizza emoji to beginning of each line using multiline flag (m)</span>
  menu = menu.replace(<span class="hljs-regexp">/^(.+)$/gm</span>, <span class="hljs-string">'🍕 $1'</span>);

  <span class="hljs-keyword">return</span> menu;
}

<span class="hljs-keyword">const</span> specials = <span class="hljs-string">"Margherita Pizza $12.99\nPepperoni Pizza $14.99"</span>;
<span class="hljs-built_in">console</span>.log(formatSpecials(specials));
<span class="hljs-comment">// "🍕 &lt;strong&gt;Margherita Pizza&lt;/strong&gt; &lt;span class="price"&gt;$12.99&lt;/span&gt;</span>
<span class="hljs-comment">// 🍕 &lt;strong&gt;Pepperoni Pizza&lt;/strong&gt; &lt;span class="price"&gt;$14.99&lt;/span&gt;"</span>
</code></pre>
<h2 id="heading-the-secret-menu-common-regex-patterns">The Secret Menu: Common RegEx Patterns</h2>
<p>Just like my secret pizza menu items, here are some regex patterns only the regulars know about:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Extract all prices from a menu</span>
<span class="hljs-keyword">const</span> prices = <span class="hljs-string">"Margherita: $12.99, Pepperoni: $14.99"</span>.match(<span class="hljs-regexp">/\$\d+\.\d+/g</span>);
<span class="hljs-comment">// ["$12.99", "$14.99"] - the 'g' flag gets all matches!</span>

<span class="hljs-comment">// Split a pizza order at commas (keeping the commas)</span>
<span class="hljs-keyword">const</span> toppings = <span class="hljs-string">"Pepperoni, Mushrooms, Extra Cheese"</span>.split(<span class="hljs-regexp">/(?&lt;=,) /</span>);
<span class="hljs-comment">// Uses a positive lookbehind (?&lt;=,) to keep the commas in the result</span>

<span class="hljs-comment">// Validate a pizza delivery email</span>
<span class="hljs-keyword">const</span> isValidEmail = <span class="hljs-regexp">/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i</span>.test(<span class="hljs-string">"order@pizzaplace.com"</span>);
<span class="hljs-comment">// Returns true if the email format is valid</span>

<span class="hljs-comment">// Turn #hashtags into links with full callback parameter usage</span>
<span class="hljs-keyword">const</span> withLinks = <span class="hljs-string">"Love this #pizza and #code combo!"</span>.replace(<span class="hljs-regexp">/#(\w+)/g</span>, 
  <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">match, tag, offset, originalString</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Found <span class="hljs-subst">${match}</span> with tag <span class="hljs-subst">${tag}</span> at position <span class="hljs-subst">${offset}</span>`</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Original string: "<span class="hljs-subst">${originalString}</span>"`</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-string">'&lt;a href="tag/'</span> + tag + <span class="hljs-string">'"&gt;'</span> + match + <span class="hljs-string">'&lt;/a&gt;'</span>;
  }
);
<span class="hljs-comment">// Shows how all callback parameters can provide useful context</span>
</code></pre>
<h2 id="heading-the-perfect-recipe-putting-it-all-together">The Perfect Recipe: Putting It All Together</h2>
<p>Just like how my perfect pizza has the exact right ratio of sauce to cheese to toppings, your RegEx skills need balance too. Let's create the ultimate pizza order parser:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parsePizzaOrder</span>(<span class="hljs-params">orderText</span>) </span>{
  <span class="hljs-comment">// Extract customer name - the 'm' flag enables multi-line mode</span>
  <span class="hljs-keyword">const</span> nameMatch = orderText.match(<span class="hljs-regexp">/^Name: (.+)$/m</span>);
  <span class="hljs-keyword">const</span> name = nameMatch ? nameMatch[<span class="hljs-number">1</span>] : <span class="hljs-string">"Anonymous"</span>;

  <span class="hljs-comment">// Extract pizza size with case-insensitive flag</span>
  <span class="hljs-keyword">const</span> sizeMatch = orderText.match(<span class="hljs-regexp">/Size: (Small|Medium|Large)/i</span>);
  <span class="hljs-keyword">const</span> size = sizeMatch ? sizeMatch[<span class="hljs-number">1</span>] : <span class="hljs-string">"Medium"</span>;  <span class="hljs-comment">// Default size if not specified</span>

  <span class="hljs-comment">// Extract all toppings from a single line</span>
  <span class="hljs-keyword">const</span> toppingsMatch = orderText.match(<span class="hljs-regexp">/Toppings: (.+)$/m</span>);
  <span class="hljs-keyword">const</span> toppings = toppingsMatch 
    ? toppingsMatch[<span class="hljs-number">1</span>].split(<span class="hljs-regexp">/,\s*/</span>).map(<span class="hljs-function"><span class="hljs-params">t</span> =&gt;</span> t.trim())  <span class="hljs-comment">// Split by comma and clean up</span>
    : [<span class="hljs-string">"Cheese"</span>];  <span class="hljs-comment">// Default topping</span>

  <span class="hljs-comment">// Calculate price based on size and number of toppings</span>
  <span class="hljs-keyword">let</span> basePrice = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">let</span> totalPrice = <span class="hljs-number">0</span>;

  <span class="hljs-comment">// Using replace with callback function parameters for price calculation</span>
  orderText.replace(<span class="hljs-regexp">/Size: (Small|Medium|Large)/i</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">match, sizeGroup, offset, original</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Found size "<span class="hljs-subst">${sizeGroup}</span>" at position <span class="hljs-subst">${offset}</span>`</span>);
    basePrice = sizeGroup.toLowerCase() === <span class="hljs-string">"small"</span> ? <span class="hljs-number">8</span> : 
               sizeGroup.toLowerCase() === <span class="hljs-string">"medium"</span> ? <span class="hljs-number">10</span> : <span class="hljs-number">12</span>;

    <span class="hljs-keyword">const</span> toppingPrice = <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0</span>, toppings.length - <span class="hljs-number">1</span>) * <span class="hljs-number">2</span>;  <span class="hljs-comment">// First topping free!</span>
    totalPrice = basePrice + toppingPrice;

    <span class="hljs-keyword">return</span> match; <span class="hljs-comment">// Keep the original text (we're just using replace for the callback)</span>
  });

  <span class="hljs-comment">// If no size was found, set default prices</span>
  <span class="hljs-keyword">if</span> (basePrice === <span class="hljs-number">0</span>) {
    basePrice = <span class="hljs-number">10</span>; <span class="hljs-comment">// Default Medium price</span>
    <span class="hljs-keyword">const</span> toppingPrice = <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0</span>, toppings.length - <span class="hljs-number">1</span>) * <span class="hljs-number">2</span>;
    totalPrice = basePrice + toppingPrice;
  }

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">customer</span>: name,
    <span class="hljs-attr">order</span>: { size, toppings },
    <span class="hljs-attr">price</span>: <span class="hljs-string">`$<span class="hljs-subst">${totalPrice.toFixed(<span class="hljs-number">2</span>)}</span>`</span>
  };
}

<span class="hljs-keyword">const</span> order = <span class="hljs-string">`
Name: Samir
Size: Large
Toppings: Pepperoni, Mushrooms, Bell Peppers
`</span>;

<span class="hljs-built_in">console</span>.log(parsePizzaOrder(order));
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//   customer: "Samir",</span>
<span class="hljs-comment">//   order: {</span>
<span class="hljs-comment">//     size: "Large",</span>
<span class="hljs-comment">//     toppings: ["Pepperoni", "Mushrooms", "Bell Peppers"]</span>
<span class="hljs-comment">//   },</span>
<span class="hljs-comment">//   price: "$16.00"</span>
<span class="hljs-comment">// }</span>
</code></pre>
<h2 id="heading-wrapping-it-up-the-pizza-box-of-knowledge">Wrapping It Up: The Pizza Box of Knowledge</h2>
<p>And there you have it, folks! We've sliced through JavaScript's <code>.replace()</code> method and regular expressions like a pizza cutter through a perfectly baked pie. Remember:</p>
<ul>
<li><p><code>.replace()</code> comes in three flavors: simple strings, regex patterns, and callback functions</p>
</li>
<li><p>Callbacks get four bonus parameters. First is match (the full text matched by your regex). Next comes group(s) (the content captured in parentheses). Third is offset (the position where the match was found). Finally, originalString gives you the complete string you're working with.</p>
</li>
<li><p>Regular expressions are weird looking but super powerful - just like experimenting with unique pizza combinations</p>
</li>
<li><p>Capturing groups let you grab specific pieces of your text - just like picking out your favorite ingredients</p>
</li>
</ul>
<p>Practice makes perfect, whether it's kneading dough or writing regex.</p>
<p>Are you team regex or team "just use string methods"? And what's your favorite pizza topping combination? Drop a comment!</p>
<p>P.S. Follow me on <a target="_blank" href="https://bsky.app/profile/devmansam.net">BlueSky</a>, or check out my previous post about <a target="_blank" href="https://blog.devmansam.net/making-lists-and-pizza-with-html-and-css">HTML lists and CSS styling</a> - I even give out my secret pizza low-carb pizza recipe!</p>
]]></content:encoded></item><item><title><![CDATA[Making Lists (and Pizza!🍕) with HTML & CSS:]]></title><description><![CDATA[Hey pizza lovers and web developers! Remember my extra cheesy posts about text gradients and React Three Fiber? Well, today we're diving into something equally as delicious, HTML lists! And because pizza makes everything better (and you know I can't ...]]></description><link>https://blog.devmansam.net/making-lists-and-pizza-with-html-and-css</link><guid isPermaLink="true">https://blog.devmansam.net/making-lists-and-pizza-with-html-and-css</guid><category><![CDATA[unorderedlist]]></category><category><![CDATA[CSS]]></category><category><![CDATA[HTML]]></category><category><![CDATA[pizza]]></category><category><![CDATA[Lists]]></category><category><![CDATA[orderedlist]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Thu, 13 Feb 2025 06:08:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739427162920/59a56c63-eb6a-4b8c-a4a7-9c8f5eebb8b9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey pizza lovers and web developers! Remember my extra cheesy posts about text gradients and React Three Fiber? Well, today we're diving into something equally as delicious, HTML lists! And because pizza makes everything better (and you know I can't write a post without mentioning it), we're going to learn by creating an amazing gluten-free, low-carb pizza recipe that'll make both your stomach and your code editor happy. Whether you're following a keto diet or just looking to cut down on carbs, this recipe is a game-changer!</p>
<h2 id="heading-a-slice-of-semantic-html">A Slice of Semantic HTML</h2>
<p>Before we dive into our recipe, let's talk about semantic HTML for a hot second. You know how a pizza has distinct layers - the crust, sauce, and toppings? Well, semantic HTML is kind of like that. Instead of just throwing everything into <code>&lt;div&gt;</code> tags (which would be like putting all your toppings in a blender... yuck!), we use specific tags that actually describe what our content is.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- Not semantic (the pizza blender approach) --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"recipe"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"recipe-title"</span>&gt;</span>Low-Carb Pizza<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"recipe-list"</span>&gt;</span>...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Semantic (the properly layered pizza approach) --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"recipe"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Low-Carb Pizza<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ingredients"</span>&gt;</span>...<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<p>We've got awesome semantic tags like <code>&lt;article&gt;</code>, <code>&lt;section&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;header&gt;</code>, <code>&lt;footer&gt;</code>, and even <code>&lt;aside&gt;</code> (for those side orders of garlic knots, obviously). But you know what's missing? <code>&lt;pizza&gt;</code>! I mean, seriously W3C, how is this not a standard HTML element yet?</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- In my dreams... --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">pizza</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">crust</span>&gt;</span>Low-carb mozzarella<span class="hljs-tag">&lt;/<span class="hljs-name">crust</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">sauce</span>&gt;</span>Crushed tomatoes<span class="hljs-tag">&lt;/<span class="hljs-name">sauce</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">toppings</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">topping</span>&gt;</span>More mozzarella<span class="hljs-tag">&lt;/<span class="hljs-name">topping</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- Because there's no such thing as too much cheese --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">toppings</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">pizza</span>&gt;</span>
</code></pre>
<p>Until my petition for pizza-semantic-tags gets approved (don't hold your breath), we'll stick with the current semantic elements for our recipe. They help screen readers understand our content better, improve SEO, and keep our code as organized as a well-managed pizzeria!</p>
<h2 id="heading-why-lists">Why Lists?</h2>
<p>Lists are everywhere on the web - navigation menus, recipe ingredients, step-by-step tutorials (like this one!), and pretty much anywhere you need to organize information. But here's the thing: they're not just about slapping some bullet points on your content. With the right HTML structure and some CSS magic, you can create lists that are both functional and fabulous!</p>
<h2 id="heading-the-basic-ingredients">The Basic Ingredients</h2>
<p>Just like our pizza recipe needs specific ingredients, HTML lists come with their own essential elements. Let's break them down:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- Unordered list (ul) - for ingredients --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Item 1<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Item 2<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Ordered list (ol) - for steps --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ol</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Step 1<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Step 2<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
</code></pre>
<p>Think of <code>&lt;ul&gt;</code> as your pizza toppings (they can go in any order) and <code>&lt;ol&gt;</code> as your recipe steps (order matters!). The <code>&lt;li&gt;</code> elements are like your individual ingredients - they're what make up the list.</p>
<h2 id="heading-lets-make-some-pizza">Let's Make Some Pizza!</h2>
<p>Now, let's put these lists to work with our actual recipe. First, our ingredients:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"recipe-card"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>🍕 Low-Carb
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>P<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span> /*see my post on text-gradients and text background images to learn how to apply this effect*/
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>I<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>Z<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>Z<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>A<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            Recipe 🍕
        <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"section"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Ingredients<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ingredients-list"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span> 4 cups low moisture mozzarella cheese
                    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>2½ cups mozzarella cheese (for crust)
                        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>1½ cups mozzarella cheese (for topping)<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>2 oz cream cheese<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>1 egg<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>½ tablespoon baking powder<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>½ teaspoon xanthan gum<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>1 teaspoon Italian seasoning<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>1 teaspoon garlic powder<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>3/4 cup crushed tomatoes<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>2 tablespoons olive oil<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"section"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Equipment Needed<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"equipment-list"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heavy bottom pan<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Parchment paper<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Microwave-safe bowl<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Let's style our lists with some pizzazz!</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">h2</span>, <span class="hljs-selector-tag">h3</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#2d3748</span>;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">1.5rem</span> <span class="hljs-number">0</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">text-align</span>: center;
}

<span class="hljs-selector-tag">h2</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">border-bottom</span>: <span class="hljs-number">2px</span> solid <span class="hljs-number">#e2e8f0</span>;
    <span class="hljs-attribute">padding-bottom</span>: <span class="hljs-number">0.5rem</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1.5rem</span>;
}

<span class="hljs-selector-class">.section</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">4px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.05</span>);
.recipe-card {
    <span class="hljs-attribute">max-width</span>: <span class="hljs-number">800px</span>;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">2rem</span> auto;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">6px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
    <span class="hljs-attribute">background</span>: <span class="hljs-number">#fff</span>;
}

<span class="hljs-selector-class">.ingredients-list</span> <span class="hljs-selector-tag">li</span> {
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">0</span>;
    <span class="hljs-attribute">padding-left</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">position</span>: relative;
    <span class="hljs-attribute">transition</span>: transform <span class="hljs-number">0.2s</span> ease;  <span class="hljs-comment">/* Smooth hover effect */</span>
}

<span class="hljs-selector-class">.ingredients-list</span> <span class="hljs-selector-tag">li</span><span class="hljs-selector-pseudo">:hover</span> {
    <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateX</span>(<span class="hljs-number">10px</span>);  <span class="hljs-comment">/* Slide effect on hover */</span>
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739424244665/73b2049a-dab0-48bd-b9ac-fd031f5d54a6.png" alt class="image--center mx-auto" /></p>
<p>Want to make your lists more appetizing? Instead of boring bullet points, you can use any emoji as a list marker! First, remove the default bullets with <code>list-style: none</code>, then use the <code>::before</code> pseudo-element to add your custom marker. The <code>content</code> property is where the magic happens - you can use a pizza emoji (or any other emoji) to spice up your lists. Just make sure to add a bit of margin to keep things nicely spaced out!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739425715179/4f827b21-cda9-48f8-8a52-8c1716544646.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-css"><span class="hljs-selector-class">.ingredients-list</span> {
 <span class="hljs-attribute">list-style</span>: none; <span class="hljs-comment">/* Remove default bullets */</span>
}

<span class="hljs-selector-class">.ingredients-list</span> <span class="hljs-selector-tag">li</span><span class="hljs-selector-pseudo">::before</span> {
 <span class="hljs-attribute">content</span>: <span class="hljs-string">"🍕"</span>; <span class="hljs-comment">/* Pizza emoji as marker */</span>
 <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">8px</span>; <span class="hljs-comment">/* Space between emoji and text */</span>
 <span class="hljs-attribute">display</span>: inline-block;
}
</code></pre>
<p>Now for the complete cooking instructions. Check out how we can style ordered lists differently:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ol</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"instructions"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Preheat oven to 375°F (190°C)<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Combine 2½ cups mozzarella with cream cheese in a microwave-safe bowl<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heat in 30-second intervals, stirring between each interval, until fully melted<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Add all dry ingredients and mix until fully incorporated<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Mix in the egg<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Oil two 18x18 pieces of parchment paper<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Place dough ball between the oiled parchment papers<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Use a rolling pin to roll dough as round and thin as possible<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Roll edges up to form crust shape<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Place in preheated pan with parchment paper under the crust<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Bake for 12 minutes or until slightly browned<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Remove from oven and add crushed tomatoes and cheese<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Increase oven temperature to 400°F (200°C)<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Bake for additional 10-12 minutes until cheese is melted and bubbling<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Let rest for 5 minutes before cutting<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739421722922/081b2da0-e8f6-4784-8521-4329e279ae01.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739423376874/cc27aedb-61b1-45b9-89f3-61e4a8a677c8.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-css"><span class="hljs-selector-class">.instructions</span> {
    <span class="hljs-attribute">counter-reset</span>: step;  <span class="hljs-comment">/* Creates custom counter */</span>
    <span class="hljs-attribute">list-style</span>: none;
    <span class="hljs-attribute">padding-left</span>: <span class="hljs-number">0</span>;
}

<span class="hljs-selector-class">.instructions</span> <span class="hljs-selector-tag">li</span> {
    <span class="hljs-attribute">position</span>: relative;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">0</span> <span class="hljs-number">1rem</span> <span class="hljs-number">3rem</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.5rem</span>;
}

<span class="hljs-selector-class">.instructions</span> <span class="hljs-selector-tag">li</span><span class="hljs-selector-pseudo">::before</span> {
    <span class="hljs-attribute">counter-increment</span>: step;
    <span class="hljs-attribute">content</span>: <span class="hljs-built_in">counter</span>(step);
    <span class="hljs-attribute">position</span>: absolute;
    <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">top</span>: <span class="hljs-number">50%</span>;
    <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateY</span>(-<span class="hljs-number">50%</span>);
    <span class="hljs-attribute">width</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">background</span>: <span class="hljs-number">#ff6b6b</span>;  <span class="hljs-comment">/* Pizza sauce red! */</span>
    <span class="hljs-attribute">color</span>: white;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">50%</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">font-weight</span>: bold;
}
</code></pre>
<p>You can absolutely omit the CSS styling, including the pseudo-elements like <code>::before</code> and counters, and your ordered and unordered lists will still function perfectly. The HTML provides the structure and content of the lists, while the CSS simply enhances their visual appearance.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739426101750/4b5ff4e2-f1d7-4670-a9a8-8d71a87ea2a7.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739422770604/fbf9a811-6172-4c3c-aea7-b102c2a0a649.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739422789844/a2081d04-d4ff-4eee-9060-f59970cca382.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739422799657/5c92239d-f5af-47fb-9eff-32665c44774c.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739422871943/74997ed5-d7de-4fc9-ade6-b60bbd57889d.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739422890913/e3176893-a449-419d-a61b-44ef544c1eee.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739422955728/0c17c928-2d1c-4daa-8f88-504aa77bf42a.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-pro-tips">Pro Tips!</h2>
<p>Let's use a nested list to share some important notes about our pizza:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"pro-tips"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Reheating instructions:
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Reheat in oven at 400°F (200°C) for 10-12 minutes<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Do NOT use microwave - trust me on this one! 🙅‍♂️<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Customization options:
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Try different cheese blends for the topping<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Add your favorite low-carb toppings<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Experiment with different herbs in the crust<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739424820910/ac705432-6953-4ead-bf64-fa77537861e4.jpeg" alt class="image--center mx-auto" /></p>
<pre><code class="lang-css"><span class="hljs-selector-class">.pro-tips</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fffaf0</span>;
    <span class="hljs-attribute">border-left</span>: <span class="hljs-number">4px</span> solid <span class="hljs-number">#ed8936</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span>;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
}

<span class="hljs-selector-class">.pro-tips</span> &gt; <span class="hljs-selector-tag">li</span> {
    <span class="hljs-attribute">list-style</span>: none;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">font-weight</span>: bold;
}

<span class="hljs-selector-class">.pro-tips</span> <span class="hljs-selector-tag">ul</span> {
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">0.5rem</span>;
    <span class="hljs-attribute">margin-left</span>: <span class="hljs-number">1.5rem</span>;
    <span class="hljs-attribute">font-weight</span>: normal;
}

<span class="hljs-selector-class">.pro-tips</span> <span class="hljs-selector-tag">ul</span> <span class="hljs-selector-tag">li</span> {
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">0</span>;
    <span class="hljs-attribute">list-style</span>: disc;
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739424855976/6dccb8f7-7922-429d-8186-59455625ec9e.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739422700089/3145dcdb-b431-412b-b205-e268474eff9b.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-making-it-mobile-friendly">Making It Mobile-Friendly</h2>
<p>Just like our pizza needs to fit in different pans, our lists need to work on different screen sizes. Here's how to make them responsive:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@media</span> (<span class="hljs-attribute">max-width:</span> <span class="hljs-number">768px</span>) {
    <span class="hljs-selector-class">.recipe-card</span> {
        <span class="hljs-attribute">margin</span>: <span class="hljs-number">1rem</span>;
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
    }

    <span class="hljs-selector-class">.instructions</span> <span class="hljs-selector-tag">li</span> {
        <span class="hljs-attribute">padding-left</span>: <span class="hljs-number">2.5rem</span>;  <span class="hljs-comment">/* Smaller padding on mobile */</span>
    }

    <span class="hljs-selector-class">.instructions</span> <span class="hljs-selector-tag">li</span><span class="hljs-selector-pseudo">::before</span> {
        <span class="hljs-attribute">width</span>: <span class="hljs-number">1.5rem</span>;  <span class="hljs-comment">/* Smaller numbers on mobile */</span>
        <span class="hljs-attribute">height</span>: <span class="hljs-number">1.5rem</span>;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.9rem</span>;
    }

    <span class="hljs-selector-class">.pro-tips</span> <span class="hljs-selector-tag">ul</span> {
        <span class="hljs-attribute">padding-left</span>: <span class="hljs-number">1rem</span>;  <span class="hljs-comment">/* Less indentation for nested lists */</span>
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739423018849/4c3a1a41-8249-404a-bb94-03d0fbbc036c.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>And there you have it, folks! Not only do you now know how to create and style HTML lists like a pro, but you've also got a killer gluten-free, low-carb pizza recipe to try out. The best part? Just like how we can nest lists inside other lists, you can stack these pizza slices right into your meal prep routine!</p>
<p>Remember, whether you're coding or cooking, it's all about experimenting and finding what works best for you. Drop a comment below if you try either the HTML tricks or the pizza recipe - I'd love to hear how it turned out!</p>
<p>P.S. Want more pizza-themed web dev tutorials? Connect with me on <a target="_blank" href="https://bsky.app/profile/devmansam.net">BlueSky</a>, or check out my other posts right here!</p>
<p><a target="_blank" href="https://www.unrwa.org/"><em>Don't</em></a> <em>forget to check out my</em> <a target="_blank" href="https://blog.devmansam.net/how-to-fill-text-with-gradients-and-images-its-easier-than-you-think"><strong><em>previous post</em></strong></a> <em>about text gradients and text background images</em></p>
]]></content:encoded></item><item><title><![CDATA[Lakers Land Big Cheese 🍕]]></title><description><![CDATA[I know we normally talk about pizza here, but today we need to discuss how the Dallas Mavericks just traded away their entire franchise. And no, I'm not talking about Dominos.
The Dallas Mavericks just traded away Luka Dončić. Let that sink in.
They ...]]></description><link>https://blog.devmansam.net/lakers-land-the-big-cheese</link><guid isPermaLink="true">https://blog.devmansam.net/lakers-land-the-big-cheese</guid><category><![CDATA[nba]]></category><category><![CDATA[LeBronJames ]]></category><category><![CDATA[lakers]]></category><category><![CDATA[basketball]]></category><category><![CDATA[ #anthonydavis ]]></category><category><![CDATA[#lukadoncic ]]></category><category><![CDATA[ #mavericks ]]></category><category><![CDATA[ #mavs ]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Sun, 02 Feb 2025 21:18:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738530247055/9b23de46-693c-471d-92a6-3c659695ca6f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I know we normally talk about pizza here, but today we need to discuss how the Dallas Mavericks just traded away their entire franchise. And no, I'm not talking about Dominos.</p>
<p>The Dallas Mavericks just traded away Luka Dončić. <strong>Let that sink in</strong>.</p>
<p>They traded a 25-year-old who's already made All-NBA five times - the only player under 30 to accomplish this. They traded away a player who just dropped 73 points against Atlanta last month. They traded away a generational talent who averages 28.6 points per game - third in NBA history behind only Jordan and Chamberlain, who led the Mavs to the NBA Finals last season, and the Western Conference Finals in 2023.</p>
<p>And while Anthony Davis is an undeniably elite defensive anchor, who is currently averaging 25.7 points and 11.9 rebounds, changing countless shots at the rim, and who also has a championship pedigree, this trade is still baffling. The silver lining for Davis is that he finally gets his wish to play primarily at power forward, with both Daniel Gafford and Dereck Lively to handle center duties. It's a setup that could maximize AD's versatility while reducing the physical toll on his body, which is something he's long advocated for during his time with the Lakers.</p>
<p>Look, if Luka enjoys an extra slice of pizza now and then, so what? That's hardly a reason to trade away a top-3 NBA player who's only 25. It's not some career-ending disability. It's a minor detail that could easily be addressed with proper conditioning, and habit building. The Mavericks essentially traded away the whole pizzeria because they were worried about the chef having a few extra slices. They just swapped their entire pizzeria for a slice of A.D.</p>
<p>This earth-shattering deal is stunning, not just for its impact, but for how secretly it was negotiated. In today's NBA, even the smallest trade rumors leak instantly. Yet, this blockbuster remained completely under wraps. There were no cryptic tweets from insiders, no "sources say" articles. In a league where Woj (we miss ya, Woj!) and now Shams, who broke the news, typically get the scoop before the teams themselves, this silence is remarkable. It speaks volumes about the discretion and commitment of both front offices.</p>
<h3 id="heading-an-extra-large-pizza-was-just-delivered-to-la"><strong>An Extra Large Pizza Was Just Delivered to LA</strong></h3>
<p>Rob Pelinka's masterful maneuvering here cannot be understated. While LeBron James remains one of the league's elite players, the Lakers' GM has secured not just their future, but their present. This isn't just planning for tomorrow, it's intriguing today. The prospect of LeBron and Luka sharing the court is enough to give Western Conference coaches nightmares. Sure, they still need a big, but come on, Luka FREAKING Dončić and King James?! You can find a center. You can’t find another Luka. Well done, Rob.</p>
<p>This is the kind of trade that gets people fired. The Lakers didn't just win this trade, they committed highway robbery. Any team in the NBA would have made this trade in the Lakers' position. You get a young superstar who could be the face of your franchise for the next decade? It's a no-brainer.</p>
<p>What's wild is Nico Harrison's explanation about "getting ahead of a tumultuous summer" regarding Luka's potential supermax extension. That's not strategy, that's panic. You don't trade away a player of Luka's caliber because you're worried about future negotiations. Even with Davis's defensive brilliance and perfect fit next to Kyrie, this is still Luka Dončić, a once-in-a-generation talent, and Anthony Davis’s injury history, along with the fact that he turns 32 next month is leaving many of the Mavericks faithful scratching their heads at the franchise-altering move.</p>
<p>The Lakers just got their next franchise cornerstone, while the Mavericks will be explaining this one for years to come. As Kevin Durant put it: "Insane. It's crazy. Crazy. Damn, would of never thought Luka Doncic would get traded."</p>
<p>Crazy indeed, KD. Crazy indeed.</p>
<p>What a wild trade... what do you think? Leave a comment with your thoughts, or connect with me on <a target="_blank" href="https://bsky.app/profile/devmansam.net">BlueSky</a>.</p>
<p>Thanks for stopping by.</p>
]]></content:encoded></item><item><title><![CDATA[Making Your First 3D Web Animation with THREE.js and Framer-Motion 
(It's as Easy as Pie!) 🍕]]></title><description><![CDATA[Remember that pizza-filled text from my last post? Well, today we're taking that delicious inspiration to a whole new dimension. The third dimension to be exact! Instead of just filling text with pizza, we're going to be cooking up an extra-large, in...]]></description><link>https://blog.devmansam.net/making-your-first-3d-web-animation-with-threejs-and-framer-motion-its-as-easy-as-pie</link><guid isPermaLink="true">https://blog.devmansam.net/making-your-first-3d-web-animation-with-threejs-and-framer-motion-its-as-easy-as-pie</guid><category><![CDATA[React]]></category><category><![CDATA[ThreeJS]]></category><category><![CDATA[reactthreefiber]]></category><category><![CDATA[framer-motion]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[3d]]></category><category><![CDATA[3d model]]></category><category><![CDATA[pizza]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Thu, 23 Jan 2025 08:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741749985545/5790674a-03ac-474c-8853-eb740b76382b.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Remember that pizza-filled text from my last post? Well, today we're taking that delicious inspiration to a whole new dimension. The third dimension to be exact! Instead of just filling text with pizza, we're going to be cooking up an extra-large, interactive, 3D pizza in React. If you thought that filling text with gradients and custom background images was cool, wait until you see what we can do with React Three Fiber!</p>
<p>React Three Fiber is a React renderer for Three.js, which lets us use JSX to build 3D scenes declaratively. It makes Three.js easier to use in React projects.</p>
<p>One of the key techniques we'll be using throughout this example is the combination of Framer Motion and React Three Fiber. Framer Motion is a powerful animation library that brings fluid motion and gestures to React applications. Think of React Three Fiber as the dough that makes our 3D effect possible, and Framer Motion as the toppings that make it delicious... I mean, animated! (Can you tell I'm still thinking about that pizza text effect?)</p>
<h3 id="heading-before-we-start">Before We Start</h3>
<p>You'll want to be familiar with:</p>
<ul>
<li><p>React basics (components, hooks)</p>
</li>
<li><p>JavaScript fundamentals</p>
</li>
<li><p>A code editor (VS Code recommended)</p>
</li>
</ul>
<p>Let's dive into making our very own animated 3D pizza!</p>
<h3 id="heading-the-basic-setup">The Basic Setup</h3>
<p>First, we need our ingredients (dependencies):</p>
<pre><code class="lang-bash">npx create-react-app pizza-3d-demo
<span class="hljs-built_in">cd</span> pizza-3d-demo
npm install three @react-three/fiber @react-three/drei framer-motion
</code></pre>
<p>Before I forget, I want to give credit where credit is due. The amazing pizza 3D model we'll be using is: Pepperoni pizza by Poly by Google [CC-BY] (<a target="_blank" href="https://creativecommons.org/licenses/by/3.0/">https://creativecommons.org/licenses/by/3.0/</a>) via Poly Pizza (<a target="_blank" href="https://poly.pizza/m/9IWGn64Fnqo">https://poly.pizza/m/9IWGn64Fnqo</a>)</p>
<h3 id="heading-the-magic-component">The Magic Component</h3>
<p>We're going to create a component that loads and animates our 3D pizza model:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useGLTF } <span class="hljs-keyword">from</span> <span class="hljs-string">'@react-three/drei'</span>;
<span class="hljs-keyword">import</span> { motion } <span class="hljs-keyword">from</span> <span class="hljs-string">'framer-motion-3d'</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Pizza3D</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> pizza = useGLTF(<span class="hljs-string">'/models/pepperoni_pizza.glb'</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (pizza) pizza.scene.dispose();
    };
  }, [pizza]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.group</span>
      <span class="hljs-attr">initial</span>=<span class="hljs-string">{{</span> 
        <span class="hljs-attr">scale:</span> <span class="hljs-attr">0</span>, 
        <span class="hljs-attr">rotateX:</span> <span class="hljs-attr">-50</span>,
        <span class="hljs-attr">x:</span> <span class="hljs-attr">0</span>,
        <span class="hljs-attr">y:</span> <span class="hljs-attr">200</span>, 
      }}
      <span class="hljs-attr">animate</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">scale:</span> <span class="hljs-attr">1</span>,
        <span class="hljs-attr">rotateX:</span> <span class="hljs-attr">0</span>,
        <span class="hljs-attr">x:</span> <span class="hljs-attr">0</span>,
        <span class="hljs-attr">y:</span> <span class="hljs-attr">0</span>,
        <span class="hljs-attr">transition:</span> {
          <span class="hljs-attr">type:</span> "<span class="hljs-attr">spring</span>",
          <span class="hljs-attr">stiffness:</span> <span class="hljs-attr">100</span>, 
          <span class="hljs-attr">damping:</span> <span class="hljs-attr">8</span>,    
          <span class="hljs-attr">mass:</span> <span class="hljs-attr">1</span>,       
        }
      }}
      <span class="hljs-attr">whileHover</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">scale:</span> <span class="hljs-attr">1.1</span>,
        <span class="hljs-attr">rotateX:</span> <span class="hljs-attr">0.2</span>,
        <span class="hljs-attr">rotateZ:</span> <span class="hljs-attr">0.2</span>,
        <span class="hljs-attr">y:</span> <span class="hljs-attr">1.5</span>,
        <span class="hljs-attr">transition:</span> {
          <span class="hljs-attr">type:</span> "<span class="hljs-attr">tween</span>",
          <span class="hljs-attr">duration:</span> <span class="hljs-attr">0.2</span>
        }
      }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">primitive</span> 
        <span class="hljs-attr">object</span>=<span class="hljs-string">{pizza.scene}</span> 
        <span class="hljs-attr">scale</span>=<span class="hljs-string">{1.5}</span>
        <span class="hljs-attr">position</span>=<span class="hljs-string">{[0,</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">0</span>]}
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">motion.group</span>&gt;</span></span>
  );
}
</code></pre>
<p>Just like how we used <code>background-clip: text</code> to create a mask for our text effects, here we're using <code>motion.group</code> to create a container for our 3D animations. It’s like a pizza box that we use to move our pizza around.</p>
<p>The <code>useEffect</code> hook is here for clean up. It makes sure we properly dispose of the 3D pizza model when we’re done with it. It’s kind of like taking out the trash after a pizza party. Without this, we could potentially have memory leaks, which nobody wants.</p>
<h3 id="heading-setting-the-stage">Setting the Stage</h3>
<p>Now, let's create the perfect environment for our pizza to perform. Just like setting up the perfect shot for your Instagram food pics, we need to create the right environment for our 3D pizza to shine. The <code>Canvas</code> component acts as our viewfinder, while <code>Stage</code> handles all the fancy lighting setup.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { Canvas } <span class="hljs-keyword">from</span> <span class="hljs-string">'@react-three/fiber'</span>;
<span class="hljs-keyword">import</span> { OrbitControls, PresentationControls, Stage } <span class="hljs-keyword">from</span> <span class="hljs-string">'@react-three/drei'</span>;
<span class="hljs-keyword">import</span> { Pizza3D } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Pizza3D'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Scene</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">width:</span> '<span class="hljs-attr">100vw</span>', <span class="hljs-attr">height:</span> '<span class="hljs-attr">100vh</span>' }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Canvas</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Stage</span> 
          <span class="hljs-attr">environment</span>=<span class="hljs-string">"city"</span> 
          <span class="hljs-attr">intensity</span>=<span class="hljs-string">{0.6}</span>
          <span class="hljs-attr">preset</span>=<span class="hljs-string">"rembrandt"</span>
          <span class="hljs-attr">adjustCamera</span>=<span class="hljs-string">{1.2}</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">PresentationControls</span>
            <span class="hljs-attr">speed</span>=<span class="hljs-string">{1.5}</span>
            <span class="hljs-attr">global</span>
            <span class="hljs-attr">zoom</span>=<span class="hljs-string">{0.7}</span>
            <span class="hljs-attr">polar</span>=<span class="hljs-string">{[-0.1,</span> <span class="hljs-attr">Math.PI</span> / <span class="hljs-attr">4</span>]}
          &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Pizza3D</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">PresentationControls</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Stage</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">OrbitControls</span> <span class="hljs-attr">enableZoom</span>=<span class="hljs-string">{false}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Canvas</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>The <code>Stage</code> component is doing all the heavy lifting behind the scenes here:</p>
<ul>
<li><p>That <code>environment="city"</code> prop adds a nice lighting effect</p>
</li>
<li><p><code>intensity={0.6}</code> keeps that lighting subtle and not too harsh</p>
</li>
<li><p><code>preset="rembrandt"</code> adds some classic artistic shadowing</p>
</li>
<li><p><code>adjustCamera={1.2}</code> makes sure our pizza is perfectly framed in view</p>
</li>
</ul>
<p>I also wrapped our pizza in <code>PresentationControls</code> to give it that smooth, responsive feel when users interact with it. You can think of it as a turntable that lets us show the pizza from all of its cheesy angles. The props here control exactly how it moves:</p>
<ul>
<li><p><code>speed={1.5}</code> gives it a smooth movement speed</p>
</li>
<li><p><code>zoom={0.7}</code> keeps us at the perfect distance to see all those toppings</p>
</li>
<li><p><code>polar={[-0.1, Math.PI / 4]}</code> makes sure users can't rotate it at weird angles</p>
</li>
</ul>
<p>And finally, I added <code>OrbitControls</code> with <code>enableZoom={false}</code> to keep folks from getting too close to those crispy pepperonis!</p>
<h3 id="heading-the-animation-magic"><strong>The Animation Magic</strong></h3>
<p>Just like how we used keyframes for our neon text effect, we're using Framer Motion's animation properties to bring our pizza to life. But instead of just changing colors, we're creating a dramatic entrance in 3D space.</p>
<p>The entrance animation has our pizza making a grand entrance:</p>
<ul>
<li><p>Starts floating high above <code>(y: 200)</code></p>
</li>
<li><p>Has a slight tilt <code>(rotateX: -50)</code></p>
</li>
<li><p>Falls gracefully with spring physics</p>
</li>
</ul>
<p>And when you hover, the pizza does a little happy dance:</p>
<ul>
<li><p>Lifts up slightly <code>(y: 1.5)</code></p>
</li>
<li><p>Grows a bit <code>(scale: 1.1)</code></p>
</li>
<li><p>Tilts playfully <code>(rotateX: 0.2, rotateZ: 0.2)</code></p>
</li>
</ul>
<p>We've also added a sliding title animation for extra pizzazz, because who doesn't love a dramatic entrance? The title starts completely off-screen to the left (<code>-100vw</code>) and slides into the middle of the viewport (<code>-50vw</code>):</p>
<pre><code class="lang-jsx">&lt;motion.h1
  className=<span class="hljs-string">"title"</span>
  initial={{ <span class="hljs-attr">x</span>: <span class="hljs-string">'-100vw'</span> }}
  animate={{ <span class="hljs-attr">x</span>: <span class="hljs-string">'-50vw'</span> }}
  transition={{
    <span class="hljs-attr">type</span>: <span class="hljs-string">'spring'</span>,
    <span class="hljs-attr">stiffness</span>: <span class="hljs-number">100</span>,
    <span class="hljs-attr">damping</span>: <span class="hljs-number">25</span>,
    <span class="hljs-attr">duration</span>: <span class="hljs-number">3</span>,
  }}
&gt;
  THREE.js and Framer-Motion &lt;br /&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"image-text"</span>&gt;</span>P<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"image-text"</span>&gt;</span>I<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"image-text"</span>&gt;</span>Z<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"image-text"</span>&gt;</span>Z<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"image-text"</span>&gt;</span>A<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> Demo
&lt;/motion.h1&gt;
</code></pre>
<p>Each letter of "PIZZA" gets its own span with the <code>image-text</code> class which we can then style separately <a target="_blank" href="https://blog.devmansam.net/how-to-fill-text-with-gradients-and-images-its-easier-than-you-think">(</a>more on that <a target="_blank" href="https://blog.devmansam.net/how-to-fill-text-with-gradients-and-images-its-easier-than-you-think">here)</a>. And just like our pizza animation, we're using spring physics for that bouncy, playful feel. The <code>stiffness</code> and <code>damping</code> values give it just the right amount of bounce without going overboard!</p>
<h3 id="heading-cowabunga-dude">Cowabunga, Dude!</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737194410490/c47094a2-138c-4f42-ad32-643297b2fccf.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-key-insights">Key Insights</h3>
<p>The 3D transformation trick: Just like how making text transparent was crucial for our gradient effects, using <a target="_blank" href="http://motion.group"><code>motion.group</code></a> is essential for 3D animations. It creates a container that can be animated in 3D space without affecting the model itself.</p>
<ul>
<li><p><strong>Physics-Based Animation</strong>: The combination of spring physics (<code>stiffness: 100, damping: 8</code>) with added mass creates a satisfyingly bouncy entrance that makes the pizza feel alive and playful.</p>
</li>
<li><p><strong>Lighting and Environment</strong>: The <code>Stage</code> component is like a photo studio. It provides perfect lighting to make the pizza look delicious from every angle. The <code>environment="city"</code> prop gives nice reflective highlights.</p>
</li>
<li><p><strong>Interactive Considerations</strong>: Remember how pseudo-elements were used for text shadows? Here, <code>PresentationControls</code> handle user interaction. It ensures the pizza rotates smoothly and naturally.</p>
</li>
</ul>
<h3 id="heading-making-it-responsive">Making It Responsive</h3>
<p>Just like our text effects, we want this to work everywhere:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.scene-container</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100vw</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-built_in">clamp</span>(<span class="hljs-number">300px</span>, <span class="hljs-number">100vh</span>, <span class="hljs-number">800px</span>);
}
</code></pre>
<h3 id="heading-lets-see-what-you-create">Let's See What You Create!</h3>
<p>Now it's your turn! Try playing with different:</p>
<ul>
<li><p>Animation timings</p>
</li>
<li><p>Spring physics settings</p>
</li>
<li><p>Camera angles</p>
</li>
<li><p>Lighting setups</p>
</li>
</ul>
<p>Drop a comment below with your creations! You can also connect with me up on <a target="_blank" href="https://bsky.app/profile/devmansam.net">BlueSky</a>, or on my website, <a target="_blank" href="https://devmansam.net">devmansam.net</a>, if you come up with something cool!</p>
<p>Thanks for stopping by.</p>
<h3 id="heading-key-tips-for-those-new-to-3d">Key Tips for Those New to 3D:</h3>
<ul>
<li><p>Keep your models lightweight</p>
</li>
<li><p>Test on mobile devices</p>
</li>
<li><p>Use <code>Suspense</code> for loading states</p>
</li>
<li><p>Remember to clean up your models with <code>useEffect</code></p>
</li>
<li><p>For more on Framer-Motion visit <a target="_blank" href="https://motion.dev/">motion.dev</a></p>
</li>
<li><p>Documentation for React Three Fiber can be found <a target="_blank" href="https://r3f.docs.pmnd.rs/getting-started/introduction">here</a></p>
</li>
</ul>
<p>Want to learn how to create that pizza-filled text effect I mentioned at the start? Check out my <a target="_blank" href="https://blog.devmansam.net/how-to-fill-text-with-gradients-and-images-its-easier-than-you-think">previous blog post</a> where I show you how to make it step by step!</p>
<p>Happy coding (and don't forget to grab a real slice of pizza when you’re done)!</p>
]]></content:encoded></item><item><title><![CDATA[Cold Pizza]]></title><description><![CDATA[Last night I wrote a proposal for a web development project requiring expedited service. I spent hours crafting it, researching market rates, and fighting the urge to undercut my prices. The whole time, a voice in my head kept saying "lower your rate...]]></description><link>https://blog.devmansam.net/cold-pizza</link><guid isPermaLink="true">https://blog.devmansam.net/cold-pizza</guid><category><![CDATA[Freelancing]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[work life balance]]></category><category><![CDATA[mentalhealth]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Tue, 21 Jan 2025 22:12:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741773903071/f575bcfb-fda8-4ae5-916e-0e3e27563c13.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last night I wrote a proposal for a web development project requiring expedited service. I spent hours crafting it, researching market rates, and fighting the urge to undercut my prices. The whole time, a voice in my head kept saying "lower your rate, get the job." I finished the proposal and hit send, rates unchanged.</p>
<p>Standing firm on my rates, despite the internal pressure, felt a bit like enjoying a slice of cold pizza, technically satisfying, but lacking the same instant gratification of that glorious, hot, melty, ooey-gooey, extra-cheesy, goodness that a signed contract brings.</p>
<p>Why share this? Because it happens and because managing stress is just as important as managing projects. There's a constant balance between taking opportunities and maintaining healthy boundaries, and it's something we need to talk about more openly in our community.</p>
<p>To me, the value I place on myself and my work is more important than a quick financial gain, especially while working with a stress-inducing, expedited timeline. While financial security is important, I feel that undervaluing my skills can have detrimental effects on my self-esteem and long-term career prospects. It can lead to self-doubt, limit my earning potential, and potentially trap me in less fulfilling career paths. By valuing myself and charging accordingly, I am investing in my long-term success, as well as my own well-being and self-image.</p>
<p>I want to discuss something crucial in the dev community: protecting our mental health while gaining experience as paid professionals. Between client deadlines, continuous learning, and the pressure to perform, it can be challenging to maintain balance. Good practices can make a difference. I myself am admittedly, as they say, still learning on the job, but I am also focused on improving daily by learning to manage my work-life balance more effectively.</p>
<p>Before taking on any project, we should consider several key factors. We need to ensure we have the systems in place to effectively manage the workload, including sufficient buffer time for unexpected challenges. We should also evaluate how the project will impact our ability to maintain a healthy routine and ensure the project timeline allows for proper planning and execution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737497678363/8adac951-f567-41b0-80af-6d8f24b84ac8.jpeg" alt class="image--center mx-auto" /></p>
<p>Writing proposals is part of the business. It's about being clear about what value we can provide. And <strong>SPOILER ALERT</strong>, proposals are sometimes declined. That's okay - it's simply part of the process. Not every opportunity is going to work out, and that's actually healthy. It means we are setting clear boundaries about our worth and our ability to deliver quality work within the requested timeline. These boundaries also ensure that we are able to provide a high-value product and exceed customer expectations.</p>
<p>The freelance journey often involves learning about sustainable practices. This can mean blocking out focused work time, reaching out to other developers for advice, maintaining regular hours, and sometimes declining rush requests that would compromise production quality, or our own well-being.</p>
<p>Sustainability requires establishing clear boundaries around work hours, staying organized with regular planning sessions, connecting with other developers for support, and ensuring there's time for both focused work and proper breaks. As freelancers, we should also leave room in our budgets to bring other developers on when we need help. We should <strong>NOT</strong> be afraid to ask for assistance when we need it. These aren't just nice-to-haves, they're essential for long-term success.</p>
<p>It's worth considering that project deadlines and client pressure aren't the only challenges we face. Imposter syndrome, the competitive landscape, and the constantly evolving industry all play a role in our stress levels.</p>
<p>We can experiment with different techniques to improve focus and manage stress. Meditation has been particularly helpful for me lately. I'm currently on a 5-day meditation streak, which feels like a major win for my mental health.</p>
<p>Finding a balance between prioritizing our well-being and ensuring our financial stability is crucial. Valuing ourselves and our mental health is not a luxury, but a necessity for long-term success in this field. I’m ok with eating cold-pizza sometimes. This is a marathon, not a race, and this marathon has no finish line. We’re in this for the long-haul, so let’s value our community, ourselves, our bodies, our heath, our skills, and our time. Balance is key.</p>
<p>I don't have all the answers, and I would love to learn from others in this community. Drop a comment below if this resonates with you, or connect with me on <a target="_blank" href="https://bsky.app/profile/devmansam.net">Bluesky</a>.</p>
<p><a target="_blank" href="https://www.unrwa.org/"><em>cover</em></a> <em>photo from:</em> <a target="_blank" href="https://images.pexels.com/photos/9353454/pexels-photo-9353454.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=400"><em>https://images.pexels.com/photos/9353454/pexels-photo-9353454.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=400</em></a></p>
]]></content:encoded></item><item><title><![CDATA[How to Fill Text with Gradients and Images (It's Easier Than You Think)]]></title><description><![CDATA[Remember learning about linear-gradients and background-images in CSS? Pretty basic stuff, right? But here's a question: did you know you can apply these same properties to actual text? I've created a collection of eye-catching typography to share wi...]]></description><link>https://blog.devmansam.net/how-to-fill-text-with-gradients-and-images-its-easier-than-you-think</link><guid isPermaLink="true">https://blog.devmansam.net/how-to-fill-text-with-gradients-and-images-its-easier-than-you-think</guid><category><![CDATA[text gradient]]></category><category><![CDATA[CSS]]></category><category><![CDATA[HTML]]></category><category><![CDATA[linear gradients]]></category><category><![CDATA[background]]></category><category><![CDATA[keyframes]]></category><category><![CDATA[typography]]></category><category><![CDATA[google fonts]]></category><dc:creator><![CDATA[Samir Shuman]]></dc:creator><pubDate>Sun, 12 Jan 2025 01:11:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736714194842/a211c756-1c4f-4d94-b157-891f9b871ed0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Remember learning about linear-gradients and background-images in CSS? Pretty basic stuff, right? But here's a question: did you know you can apply these same properties to actual text? I've created a collection of eye-catching typography to share with you. Let me show you how they work!</p>
<p>One of the key techniques we'll be using throughout these examples is the combination of <code>background-clip: text</code> and <code>color: transparent</code>. This is the magic that lets us fill our text with gradients, images, and animations. When we set <code>color: transparent</code>, we're essentially making the text invisible. Then, by setting <code>background-clip: text</code>, we're telling CSS to only show the background within the shape of our text characters.</p>
<p>Think of it like a cookie cutter. The text is the cookie cutter shape, and the background is the dough we can see through that shape. The chocolate chips are up to you and your imagination!</p>
<h2 id="heading-the-rainbow-gradient">The Rainbow Gradient</h2>
<p>Let’s start with a simple but striking rainbow gradient effect:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.gradient-text</span> {
    <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">45deg</span>, 
        #ff0000,
        #ff8700,
        #ffd300,
        #<span class="hljs-number">00</span>ff00,
        #<span class="hljs-number">0094</span>ff,
        #<span class="hljs-number">6</span>b00ff
    );
    <span class="hljs-attribute">-webkit-background-clip</span>: text;
    <span class="hljs-attribute">background-clip</span>: text;
    <span class="hljs-attribute">color</span>: transparent;
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Bagel Fat One"</span>, sans-serif;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">10rem</span>;
    <span class="hljs-attribute">background-size</span>: contain;
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736637871559/4ec64b69-062f-475a-9a6a-8efc8df723f3.png" alt class="image--center mx-auto" /></p>
<p><em>[Rainbow Gradient]</em></p>
<h2 id="heading-the-metallic-gold-effect">The Metallic Gold Effect</h2>
<p>Here's where things get interesting. We can create a realistic metallic gold effect using a carefully crafted gradient and some depth:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.depth-text</span> {
    <span class="hljs-attribute">position</span>: relative;
    <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">45deg</span>,
        #<span class="hljs-number">462523</span> <span class="hljs-number">0%</span>,
        #cb9b51 <span class="hljs-number">22%</span>, 
        #f6e27a <span class="hljs-number">45%</span>,
        #f6f2c0 <span class="hljs-number">50%</span>,
        #f6e27a <span class="hljs-number">55%</span>,
        #cb9b51 <span class="hljs-number">78%</span>,
        #<span class="hljs-number">462523</span> <span class="hljs-number">100%</span>
    );
    <span class="hljs-attribute">-webkit-background-clip</span>: text;
    <span class="hljs-attribute">background-clip</span>: text;
    <span class="hljs-attribute">color</span>: transparent;
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Monoton"</span>, sans-serif;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">10rem</span>;
}

<span class="hljs-selector-class">.depth-text</span><span class="hljs-selector-pseudo">::before</span> {
    <span class="hljs-attribute">content</span>: <span class="hljs-built_in">attr</span>(data-text);
    <span class="hljs-attribute">position</span>: absolute;
    <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">z-index</span>: -<span class="hljs-number">1</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">26</span>, <span class="hljs-number">26</span>, <span class="hljs-number">26</span>, <span class="hljs-number">0.8</span>);
    <span class="hljs-attribute">text-shadow</span>: 
        <span class="hljs-number">2px</span> <span class="hljs-number">2px</span> <span class="hljs-number">3px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>),
        <span class="hljs-number">4px</span> <span class="hljs-number">4px</span> <span class="hljs-number">6px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.3</span>),
        <span class="hljs-number">6px</span> <span class="hljs-number">6px</span> <span class="hljs-number">12px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>);
}
</code></pre>
<p>Adding a <code>text-shadow</code> directly to text with <code>background-clip: text</code> can be a bit of a disaster. The shadow color will basically flood the text, completely covering it up. To avoid this messy situation, we need to use a pseudo-element like <code>::before</code> or <code>::after</code>. This lets us apply the <code>text-shadow</code> to the element containing the text, keeping the shadow separate and looking sharp.</p>
<p>In your HTML, you'll need:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"depth-text"</span> <span class="hljs-attr">data-text</span>=<span class="hljs-string">"GOLD"</span>&gt;</span>GOLD<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736637974378/faaf15dc-2164-49d6-9b1b-275130bd1ace.png" alt class="image--center mx-auto" /></p>
<p><em>[Gold Gradient]</em></p>
<h2 id="heading-image-filled-text">Image-Filled Text</h2>
<p>Now, here's where you can really start to use your imagination. You can use actual images as text fill. Think about that for a second - your text can be filled with textures, patterns, or even photos. I chose to use an ooey gooey pizza as my background, because, well, who doesn’t like pizza?!</p>
<p>(I wrapped each individual letter in a span element with the image-text class this time)</p>
<pre><code class="lang-css">    <span class="hljs-selector-class">.image-text</span> {
        <span class="hljs-attribute">background</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">'./pizza.png'</span>);
        <span class="hljs-attribute">-webkit-background-clip</span>: text;
        <span class="hljs-attribute">background-clip</span>: text;
        <span class="hljs-attribute">color</span>: transparent;
        <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Nosifer"</span>, sans-serif;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">10rem</span>;
        <span class="hljs-attribute">background-size</span>: cover;
    }
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736638183565/289267c1-0d3f-4024-a0e3-ead74a69f79e.png" alt class="image--center mx-auto" /></p>
<p><em>[Pizza Background]</em></p>
<h2 id="heading-animated-neon-effect">Animated Neon Effect</h2>
<p>Want something more dynamic? Check out this flickering neon effect:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.neon-pulse</span> {
    <span class="hljs-attribute">position</span>: relative;
    <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">90deg</span>,
        #<span class="hljs-number">39</span>ff14,
        #d4ff00,
        #fee715,
        #d4ff00,
        #<span class="hljs-number">39</span>ff14
    );
    <span class="hljs-attribute">background-size</span>: <span class="hljs-number">200%</span> auto;
    <span class="hljs-attribute">-webkit-background-clip</span>: text;
    <span class="hljs-attribute">background-clip</span>: text;
    <span class="hljs-attribute">color</span>: transparent;
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Train One"</span>, sans-serif;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">10rem</span>;
    <span class="hljs-attribute">animation</span>: neon-flicker <span class="hljs-number">1.5s</span> infinite;
    <span class="hljs-attribute">text-shadow</span>: 
        <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">57</span>, <span class="hljs-number">255</span>, <span class="hljs-number">20</span>, <span class="hljs-number">0.7</span>),
        <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">20px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">212</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.5</span>),
        <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">30px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">254</span>, <span class="hljs-number">231</span>, <span class="hljs-number">21</span>, <span class="hljs-number">0.3</span>);
}

<span class="hljs-keyword">@keyframes</span> neon-flicker {
    0%, 100% { 
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
        <span class="hljs-attribute">text-shadow</span>: 
            <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">57</span>, <span class="hljs-number">255</span>, <span class="hljs-number">20</span>, <span class="hljs-number">0.7</span>),
            <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">20px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">212</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.5</span>),
            <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">30px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">254</span>, <span class="hljs-number">231</span>, <span class="hljs-number">21</span>, <span class="hljs-number">0.3</span>);
    }
    10% {
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.9</span>;
        <span class="hljs-attribute">text-shadow</span>: none;
    }
    10<span class="hljs-selector-class">.1</span>% {
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
        <span class="hljs-attribute">text-shadow</span>: 
            <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">57</span>, <span class="hljs-number">255</span>, <span class="hljs-number">20</span>, <span class="hljs-number">0.7</span>),
            <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">20px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">212</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.5</span>),
            <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">30px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">254</span>, <span class="hljs-number">231</span>, <span class="hljs-number">21</span>, <span class="hljs-number">0.3</span>);
    }
    20% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.8</span>; }
    30% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>; }
    30<span class="hljs-selector-class">.1</span>% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.95</span>; }
    30<span class="hljs-selector-class">.5</span>% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>; }
    85% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>; }
    85<span class="hljs-selector-class">.1</span>% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.8</span>; }
    85<span class="hljs-selector-class">.2</span>% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>; }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736638533922/9a84f433-8404-4042-814e-15a1f3a7e6b9.gif" alt class="image--center mx-auto" /></p>
<p><em>[Neon Pulse]</em></p>
<h2 id="heading-holographic-wave-effect">Holographic Wave Effect</h2>
<p>Here's a smooth, wave-like holographic effect:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.holo-text</span> {
    <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">90deg</span>,
        #<span class="hljs-number">003973</span> <span class="hljs-number">0%</span>,
        #<span class="hljs-number">0085</span>c7 <span class="hljs-number">20%</span>,
        #<span class="hljs-number">00</span>d4ff <span class="hljs-number">40%</span>,
        #<span class="hljs-number">4</span>dd0e1 <span class="hljs-number">60%</span>,
        #<span class="hljs-number">0085</span>c7 <span class="hljs-number">80%</span>,
        #<span class="hljs-number">003973</span> <span class="hljs-number">100%</span>
    );
    <span class="hljs-attribute">background-size</span>: <span class="hljs-number">200%</span> auto;
    <span class="hljs-attribute">-webkit-background-clip</span>: text;
    <span class="hljs-attribute">background-clip</span>: text;
    <span class="hljs-attribute">color</span>: transparent;
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Aladin"</span>, sans-serif;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">10rem</span>;
    <span class="hljs-attribute">animation</span>: wave <span class="hljs-number">4s</span> linear infinite;
}

<span class="hljs-keyword">@keyframes</span> wave {
    <span class="hljs-selector-tag">to</span> { <span class="hljs-attribute">background-position</span>: <span class="hljs-number">200%</span> center; }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736639905254/180f6a45-f5e2-4854-9962-8ee5704a6d2e.gif" alt class="image--center mx-auto" /></p>
<p><em>[Holographic Wave Effect]</em></p>
<h2 id="heading-sunset-gradient-effect">Sunset Gradient Effect</h2>
<p>Finally, here's a beautiful sunset-inspired gradient:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.sunset-text</span> {
    <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">180deg</span>,
        #ffef00 <span class="hljs-number">0%</span>,
        #ffd700 <span class="hljs-number">15%</span>,
        #ffa500 <span class="hljs-number">30%</span>,
        #ff8c00 <span class="hljs-number">45%</span>,
        #ff6b6b <span class="hljs-number">60%</span>,
        #ff4500 <span class="hljs-number">75%</span>,
        #<span class="hljs-number">4</span>b0082 <span class="hljs-number">100%</span>
    );
    <span class="hljs-attribute">-webkit-background-clip</span>: text;
    <span class="hljs-attribute">background-clip</span>: text;
    <span class="hljs-attribute">color</span>: transparent;
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Climate Crisis"</span>, sans-serif;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">10rem</span>;
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736638970265/fb7ecd50-0c13-4d3a-bce1-73d3781a0e6a.png" alt class="image--center mx-auto" /></p>
<p><em>[Sunset Gradient]</em></p>
<h2 id="heading-key-insights">Key Insights</h2>
<ol>
<li><p><strong>The text transparency trick</strong>: Setting <code>color: transparent</code> is crucial but can be confusing at first. You might wonder "why make the text invisible?" But this is exactly what allows our backgrounds to show through. Without this step, the text color would sit on top of our beautiful gradients and images.</p>
</li>
<li><p><strong>Background clipping</strong>: The <code>background-clip: text</code> property (and its webkit prefix version) is what creates the "mask" effect. It tells the browser "only show the background where there are text characters." This is different from a regular background that would fill the whole container. This relationship between transparent text and background clipping is what makes everything come together.</p>
</li>
<li><p><strong>Layering and depth considerations</strong>:</p>
<ul>
<li><p><strong>Positioning and z-index</strong>: Use <code>position: relative</code> when working with pseudo-elements, and pay attention to your <code>z-index</code> values. The main text element should have a higher <code>z-index</code> than any shadows or effects created with pseudo-elements (that's why we used <code>z-index: -1</code> on the <code>::before</code> element in our gold effect). This layering ensures our effects appear in the correct order and don't accidentally cover up our main text.</p>
</li>
<li><p><strong>Text shadow pitfalls</strong>: When applying <code>background-clip: text</code>, directly adding a <code>text-shadow</code> can lead to unexpected color flooding. To avoid this, use a pseudo-element (like <code>::before</code> or <code>::after</code>) and apply the <code>text-shadow</code> to that element. This keeps the shadow effect separate and prevents the text from being obscured.</p>
</li>
<li><p><strong>Text matching is critical</strong>: When using the depth effect, the <code>data-text</code> attribute must match your text verbatim.</p>
</li>
</ul>
</li>
<li><p><strong>Animation considerations</strong>: For animated effects, experiment with different durations and timing functions. This becomes especially important when creating effects like our neon flicker or holographic wave, where the timing can make or break the visual impact.</p>
</li>
</ol>
<h2 id="heading-making-it-responsive">Making It Responsive</h2>
<p>All these effects work with responsive units. Just adjust the <code>font-size</code> using viewport units or <code>clamp()</code>:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.responsive-effect</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-built_in">clamp</span>(<span class="hljs-number">2rem</span>, <span class="hljs-number">10vw</span>, <span class="hljs-number">10rem</span>);
}
</code></pre>
<h2 id="heading-the-complete-html">The Complete HTML</h2>
<p>Here's the complete HTML structure you'll need:</p>
<pre><code class="lang-html"><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">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">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preconnect"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preconnect"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.gstatic.com"</span> <span class="hljs-attr">crossorigin</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com/css2?family=Aladin&amp;family=Bagel+Fat+One&amp;family=Climate+Crisis&amp;family=Moirai+One&amp;family=Monoton&amp;family=Nosifer&amp;family=Train+One&amp;display=swap"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</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">"style.css"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Text Effects Demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</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">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"gradient-text"</span>&gt;</span>RAINBOW<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"depth-text"</span> <span class="hljs-attr">data-text</span>=<span class="hljs-string">"GOLD"</span>&gt;</span>GOLD<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>P<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>I<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>Z<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>Z<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-text"</span>&gt;</span>A<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"neon-pulse"</span>&gt;</span>NEON<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"holo-text"</span>&gt;</span>OCEAN<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sunset-text"</span>&gt;</span>SUNSET<span class="hljs-tag">&lt;/<span class="hljs-name">div</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>
<h2 id="heading-lets-see-what-you-create">Let's See What You Create!</h2>
<p>Now it's your turn! Try combining these effects or creating your own variations. Here are some ideas to get you started:</p>
<ul>
<li><p>Mix different gradient angles</p>
</li>
<li><p>Try different animation timing patterns</p>
</li>
<li><p>Experiment with various font combinations</p>
</li>
<li><p>Create theme-specific gradients</p>
</li>
</ul>
<p>Drop a comment below with your creations! You can also connect on <a target="_blank" href="https://bsky.app/profile/devmansam.net">BlueSky</a>, or on my website, <a target="_blank" href="https://devmansam.net">devmansam.net</a>, if you come up with something cool!</p>
<p>Thanks for stopping by.</p>
<p>P.S. All of the typefaces used are available free of charge at <a target="_blank" href="https://fonts.google.com/">https://fonts.google.com</a> .</p>
]]></content:encoded></item></channel></rss>