<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Thea</title>
    <description>The latest articles on Forem by Thea (@highflyer910).</description>
    <link>https://forem.com/highflyer910</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2491%2F2e334b3d-5af0-43e5-95c0-e3ededf148f7.png</url>
      <title>Forem: Thea</title>
      <link>https://forem.com/highflyer910</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/highflyer910"/>
    <language>en</language>
    <item>
      <title>git blame --emotions: No Solutions. Just Vibes.</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Sat, 04 Apr 2026 12:04:22 +0000</pubDate>
      <link>https://forem.com/highflyer910/git-blame-emotions-no-solutions-just-vibes-3gbc</link>
      <guid>https://forem.com/highflyer910/git-blame-emotions-no-solutions-just-vibes-3gbc</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built a tool that does absolutely nothing useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;git blame --emotions&lt;/strong&gt; is a Shakespearean error therapy app. You paste your error message. It gives you a sonnet. No solutions. No Stack Overflow links. No rubber duck. Just iambic pentameter and the gentle acknowledgment that your &lt;code&gt;undefined is not a function&lt;/code&gt; is &lt;em&gt;grieving&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The app lives at the intersection of two deeply important things: the emotional intelligence of Elizabethan poetry, and the complete uselessness of a tool that refuses to help you debug anything. It will not fix your code.&lt;/p&gt;

&lt;p&gt;It also looks like a 1999 Geocities fan site - Comic Sans, pastel chaos, and a visitor counter stuck at 000418.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://git-blame-emotions.vercel.app/" rel="noopener noreferrer"&gt;git-blame--emotions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what happens when you use it:&lt;br&gt;
Paste an error. Click the button.&lt;br&gt;
You get a Shakespearean sonnet about your suffering.&lt;br&gt;
Your code remains broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; If you paste anything containing &lt;code&gt;418&lt;/code&gt; or &lt;code&gt;teapot&lt;/code&gt;, you get a special Easter egg. I won't spoil it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/highflyer910" rel="noopener noreferrer"&gt;
        highflyer910
      &lt;/a&gt; / &lt;a href="https://github.com/highflyer910/git-blame--emotions" rel="noopener noreferrer"&gt;
        git-blame--emotions
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git blame --emotions 💔&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;No solutions. Just vibes.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/d1f4dba716690f42d3152316a1985757a70505b898c5d76a6bb1d32af3e0e419/68747470733a2f2f68696768666c7965723931302e736972762e636f6d2f676974626c616d652e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/d1f4dba716690f42d3152316a1985757a70505b898c5d76a6bb1d32af3e0e419/68747470733a2f2f68696768666c7965723931302e736972762e636f6d2f676974626c616d652e706e67" alt="git blame --emotions in action"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A Shakespearean error therapy app powered by Google Gemini AI and a complete disregard for productivity.&lt;/p&gt;
&lt;p&gt;You paste your error message. It writes you a sonnet. The error remains. You feel seen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://git-blame-emotions.vercel.app" rel="nofollow noopener noreferrer"&gt;git-blame--emotions&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What It Does&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;You paste an error message, or stack trace&lt;/li&gt;
&lt;li&gt;Google Gemini AI reads it and writes a &lt;strong&gt;Shakespearean sonnet&lt;/strong&gt; - 14 lines, ABAB CDCD EFEF GG rhyme scheme, iambic pentameter&lt;/li&gt;
&lt;li&gt;Confetti fires&lt;/li&gt;
&lt;li&gt;You feel emotionally validated&lt;/li&gt;
&lt;li&gt;Your code is still broken&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This tool will not fix your bugs. It will not suggest a solution. It will not link you to Stack Overflow. It will, however, tell you that your &lt;code&gt;NullPointerException&lt;/code&gt; is &lt;em&gt;"a void where love should be"&lt;/em&gt; - and sometimes that's enough.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Bonus:&lt;/strong&gt; Paste anything containing &lt;code&gt;418&lt;/code&gt; or &lt;code&gt;teapot&lt;/code&gt; for a special surprise. RFC 2324 compliant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla HTML, CSS, JavaScript&lt;/strong&gt; - zero…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/highflyer910/git-blame--emotions" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Tech stack is intentionally minimal:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pure vanilla HTML, CSS, JavaScript - zero frameworks, zero build tools&lt;/li&gt;
&lt;li&gt;One serverless function &lt;code&gt;api/poem.js&lt;/code&gt; on Vercel&lt;/li&gt;
&lt;li&gt;Google Gemini API for the sonnet generation&lt;/li&gt;
&lt;li&gt;canvas-confetti for emotional release&lt;/li&gt;
&lt;li&gt;Google Fonts: UnifrakturMaguntia + Patrick Hand&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Honestly, this started as a joke. It stayed a joke. I read "build something completely useless," and thought: what if instead of fixing errors, we just... felt them.&lt;/p&gt;

&lt;p&gt;The frontend is a single &lt;code&gt;index.html&lt;/code&gt; + &lt;code&gt;style.css&lt;/code&gt; + &lt;code&gt;script.js&lt;/code&gt;. The Geocities aesthetic isn't laziness; it's a &lt;em&gt;commitment&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The API lives in &lt;code&gt;api/poem.js&lt;/code&gt;, a Vercel serverless function that calls Gemini and keeps the API key server-side.&lt;/p&gt;

&lt;p&gt;Most of the work went into the prompt. I force Gemini to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;write a proper Shakespearean sonnet (ABAB CDCD EFEF GG)&lt;/li&gt;
&lt;li&gt;use iambic pentameter&lt;/li&gt;
&lt;li&gt;treat the error as a character with feelings&lt;/li&gt;
&lt;li&gt;and absolutely refuse to help in any technical way&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 418 Easter egg was non-negotiable. HTTP 418 "I'm a Teapot" gets special treatment: confetti, a wobbling teapot overlay, and a dramatic sonnet that treats the RFC like high tragedy.&lt;/p&gt;

&lt;p&gt;On mobile, the site politely asks you to rotate your CRT monitor. There is also a vertical label that says &lt;code&gt;EMPTY SPACE FOR YOUR TEARS&lt;/code&gt;. These are features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ☕ Best Ode to Larry Masinter
&lt;/h3&gt;

&lt;p&gt;The visitor counter stops at 000418. The site claims RFC 2324 compliance. The teapot Easter egg turns the most absurd HTTP status code into a Shakespearean tragedy. It felt only right.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 Best Google AI Usage
&lt;/h3&gt;

&lt;p&gt;Google Gemini powers every poem. It reads raw error messages and turns them into structured Shakespearean sonnets with dramatic tone and zero usefulness. The 418 path uses a separate prompt to elevate the RFC into full tragedy.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌟 Community Favorite
&lt;/h3&gt;

&lt;p&gt;We’ve all been there at 2am, staring at a stack trace and questioning everything. Sometimes you don’t need a fix. You need someone to say: &lt;em&gt;"thy memory, shattered like my heart."&lt;/em&gt;&lt;br&gt;
That’s this app.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;em&gt;No bugs were fixed during the making of this website.&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>DevStretch: The Antiburnout Protocol for Devs Who Forgot They Have Bodies</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Sun, 01 Mar 2026 21:03:41 +0000</pubDate>
      <link>https://forem.com/highflyer910/devstretch-the-antiburnout-protocol-for-devs-who-forgot-they-have-bodies-3am</link>
      <guid>https://forem.com/highflyer910/devstretch-the-antiburnout-protocol-for-devs-who-forgot-they-have-bodies-3am</guid>
      <description>&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;Let’s be honest: most of us treat our physical bodies like a deprecated legacy dependency. It’s still running, it’s technically functional, but it hasn't had an update in years, and we’ve been ignoring the &lt;code&gt;STIFF_NECK_WARNING&lt;/code&gt; in the logs for six hours.&lt;/p&gt;

&lt;p&gt;I built this for the community of developers, specifically the ones who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sit at a 45-degree angle until they merge with their chair.&lt;/li&gt;
&lt;li&gt;Make a "crunchy" sound when they finally stand up at 3 AM.&lt;/li&gt;
&lt;li&gt;Treat "Hydration" as just another cup of coffee.
Burnout isn't just a mental state; it’s a physical bug report. DevStretch is the patch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://devstretch.vercel.app" rel="noopener noreferrer"&gt;DevStretch&lt;/a&gt;&lt;/strong&gt; is a terminal-themed PWA designed to interrupt your "flow state" before it permanently wrecks your posture.&lt;/p&gt;

&lt;p&gt;It’s an 11-step maintenance protocol. We’re not "stretching"; we’re refactoring our spines. I gave every movement a proper developer rebrand because let’s face it - you’re more likely to "Clear Cache" than "Rest your eyes."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Protocol Name&lt;/th&gt;
&lt;th&gt;System Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Review That Code&lt;/td&gt;
&lt;td&gt;Neck Stretch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Roll Back&lt;/td&gt;
&lt;td&gt;Shoulder Rolls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Prevent Carpal Tunnel PR&lt;/td&gt;
&lt;td&gt;Wrist Stretches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Deploy to Standing Position&lt;/td&gt;
&lt;td&gt;Sit to Stand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Clear Cache&lt;/td&gt;
&lt;td&gt;Eye Break&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Refactor Your Spine&lt;/td&gt;
&lt;td&gt;Seated Back Twist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Offline Mode&lt;/td&gt;
&lt;td&gt;Walk Away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Memory Garbage Collection&lt;/td&gt;
&lt;td&gt;Box Breathing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Extend Your Reach&lt;/td&gt;
&lt;td&gt;Overhead Arm Stretch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Lint Your Posture&lt;/td&gt;
&lt;td&gt;Posture Check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;git commit --water&lt;/td&gt;
&lt;td&gt;Hydration Reminder&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The UI is a dark mode terminal aesthetic - phosphor green on near-black, JetBrains Mono font, scanlines, a flickering timer with a blinking cursor, and a startup boot sequence that makes you feel like you’re initializing a mainframe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://devstretch.vercel.app" rel="noopener noreferrer"&gt;devstretch.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Open it on your phone and "Add to Home Screen." It’s a PWA, so it works offline when your Wi-Fi goes down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The project is entirely dependency-free. No React, no Vite, no node_modules folder larger than the project itself. Just clean, modular Vanilla JS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/highflyer910/devstretch" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I chose a deliberately "boring" stack in the best way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web Speech API&lt;/strong&gt;: Provides hands-free voice guidance. No need to look at the screen while you're "Refactoring your spine."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen Wake Lock API&lt;/strong&gt;: This was crucial. It prevents the phone screen from dimming or locking mid-stretch, ensuring the timer doesn't throttle while you're away from the keyboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Notifications API&lt;/strong&gt;: Background stand-up reminders that stay active even if you close the tab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Worker&lt;/strong&gt;:Full offline support. If your internet dies, your health protocol shouldn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The "Bug" Log: Notification Hell&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Browser notifications were humbling. I learned the hard way that new Notification() called from the main thread is often silently blocked; the "Senior" move is rerouting everything through the Service Worker via registration.showNotification().&lt;/p&gt;

&lt;p&gt;Even then, OS-level notification layers (Focus Assist on Windows, battery optimization on Android) can swallow notifications entirely. Permission shows as &lt;code&gt;granted&lt;/code&gt;, the Service Worker fires without errors... and nothing appears. Still actively debugging. Sometimes shipping means shipping with a 'Known Issue' 🙃&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deeper platform integration for background notifications&lt;/li&gt;
&lt;li&gt;Custom exercise editor - add your own stretches&lt;/li&gt;
&lt;li&gt;Configurable rest time&lt;/li&gt;
&lt;li&gt;Dedicated wrist and eye exercise sets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;git commit -m "took care of myself today"&lt;/code&gt;&lt;br&gt;
&lt;em&gt;// It's a feature, not a bug.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
      <category>pwa</category>
    </item>
    <item>
      <title>Smooth Page Transitions with Zero Libraries: The View Transitions API</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Tue, 11 Nov 2025 13:04:58 +0000</pubDate>
      <link>https://forem.com/highflyer910/smooth-page-transitions-with-zero-libraries-the-view-transitions-api-3o0m</link>
      <guid>https://forem.com/highflyer910/smooth-page-transitions-with-zero-libraries-the-view-transitions-api-3o0m</guid>
      <description>&lt;p&gt;I recently started using the View Transitions API in one of my projects, and honestly, it felt like unlocking a hidden superpower for the web. I had to share it.&lt;/p&gt;

&lt;p&gt;Not long ago, this API was just a Chrome experiment. But as of 2025, it’s officially everywhere, supported in Chrome, Edge, Safari, and Firefox, covering over 85% of browsers. It’s now powering everything from e-commerce sites to dashboards and blogs, making transitions feel native and seamless.&lt;/p&gt;

&lt;p&gt;Here’s what I’ve learned using it, and why you’ll probably want to add it to your toolkit too.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is the View Transitions API?
&lt;/h3&gt;

&lt;p&gt;The View Transitions API animates between two page states, toggling a UI mode, opening a modal, or moving between pages. It works whether you're updating part of a page or navigating between entirely different pages.&lt;/p&gt;

&lt;p&gt;The trick: give an element the same &lt;code&gt;view-transition-name&lt;/code&gt; before and after the update. The browser retains the element’s identity and morphs it, automatically handling position, size, and content blending.&lt;/p&gt;

&lt;p&gt;No libraries. No keyframes. Just native transitions that are easy to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Pattern (Only 3 Steps!)
&lt;/h3&gt;

&lt;p&gt;To apply a transition to an in-page element (like a card changing from a grid to a list view):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Name your element in CSS&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.content.active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;main-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Keep your element in the DOM (toggle its state)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content card-view active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Card content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Wrap your state change in &lt;code&gt;startViewTransition()&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Toggle state - the element keeps the same view-transition-name&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grid-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That's it! The browser handles the animation.&lt;/strong&gt;&lt;br&gt;
💡 Tip: Avoid using &lt;code&gt;element.innerHTML = '...'&lt;/code&gt; or re-rendering components that replace the DOM completely, which breaks the element’s identity and stops the smooth transition.&lt;br&gt;
Instead, just toggle classes or, if you’re using a framework like React, make sure elements keep stable keys so the DOM node stays the same.&lt;/p&gt;
&lt;h3&gt;
  
  
  See It in Action
&lt;/h3&gt;

&lt;p&gt;I built a small SPA demo to test how smooth the View Transitions API can be. It uses directional animations and custom effects, and honestly, it feels surprisingly close to a native app.&lt;/p&gt;

&lt;p&gt;Pages slide left and right, hero sections move up and down, and the back button even reverses the animation automatically.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://codepen.io/HighFlyer910/pen/WbwebvQ" rel="noopener noreferrer"&gt;View Live Demo on CodePen&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Try it yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click between pages to see forward animations (slides right-to-left)&lt;/li&gt;
&lt;li&gt;Use the back button to see reverse animations (slide left to right)&lt;/li&gt;
&lt;li&gt;Watch the hero section move up and down on its own&lt;/li&gt;
&lt;li&gt;Open DevTools and inspect the &lt;code&gt;::view-transition-*&lt;/code&gt; pseudo-elements to see how the browser does it!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Advanced Technique #1: Directional Back Navigation
&lt;/h3&gt;

&lt;p&gt;Want your back button to slide the other way? Here’s how to make your SPA navigation feel more like a native app:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The JavaScript&lt;/strong&gt;&lt;br&gt;
We listen for the popstate event (which fires on back/forward navigation) and briefly add a class to the root element to trigger reverse animations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePopState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add class to trigger reverse animations&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;back-transition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;renderPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Clean up after animation completes&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;back-transition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;renderPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;popstate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handlePopState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The CSS&lt;/strong&gt;&lt;br&gt;
We target the global transition, which is automatically assigned the name root.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Forward navigation (default) */&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-old&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-to-left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;::view-transition-new&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-from-right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Reverse navigation (back button) */&lt;/span&gt;
&lt;span class="nc"&gt;.back-transition&lt;/span&gt; &lt;span class="nd"&gt;::view-transition-old&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-to-right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.back-transition&lt;/span&gt; &lt;span class="nd"&gt;::view-transition-new&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-from-left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates that iOS/Android feeling where navigation direction matches user intent!&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Technique #2: Custom Animations for Specific Elements
&lt;/h3&gt;

&lt;p&gt;You can animate different parts of the page independently using named transitions.&lt;br&gt;
For example, while your main content slides horizontally, a hero section can move vertically or fade, and the browser syncs it all perfectly for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hero Section with Vertical Slide&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;::view-transition-old&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-down-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;::view-transition-new&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="m"&gt;0.1s&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-up-in&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;slide-down-out&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;slide-up-in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result? A smooth motion where each element moves on its own but still feels perfectly connected, just like a well-crafted native app.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use It (and When Not To)
&lt;/h3&gt;

&lt;p&gt;Great for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Toggling states (like dark mode or list ↔ grid views)&lt;/li&gt;
&lt;li&gt;Page navigation in SPAs&lt;/li&gt;
&lt;li&gt;Opening modals or dialogs&lt;/li&gt;
&lt;li&gt;Simple UI transitions that need a touch of polish
Skip it for:&lt;/li&gt;
&lt;li&gt;Complex, multi-step animations (GSAP or Framer Motion are better here)&lt;/li&gt;
&lt;li&gt;Situations that need precise timing across many elements&lt;/li&gt;
&lt;li&gt;Critical interactions where you need guaranteed fallback behavior
&lt;strong&gt;Accessibility Tip:&lt;/strong&gt;
Always respect &lt;code&gt;prefers-reduced-motion&lt;/code&gt;, as some users are sensitive to motion, and fast transitions can make them uncomfortable.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;::view-transition-old&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nd"&gt;::view-transition-new&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* Instantly complete the animation */&lt;/span&gt;
    &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Minimal Fallback (Just in Case)&lt;/strong&gt;&lt;br&gt;
While browser support is excellent, a tiny fallback never hurts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateDOM&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;updateDOM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// instant update&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use CSS &lt;code&gt;@supports&lt;/code&gt; to provide a subtle &lt;code&gt;transition&lt;/code&gt; fallback for older browsers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The View Transitions API is a surprisingly powerful tool that can turn clunky page changes into smooth, polished experiences, with minimal code.&lt;/p&gt;

&lt;p&gt;Directional transitions and custom element animations let your web app feel closer to a native app, all with just vanilla CSS and JS.&lt;/p&gt;

&lt;p&gt;So next time you update a view, just give it a &lt;code&gt;view-transition-name&lt;/code&gt;.&lt;br&gt;
It’s that simple, and the page instantly feels smoother.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>animation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Single-tenant vs Multi-tenant: What I Wish I Knew When I Started</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Thu, 16 Oct 2025 10:45:52 +0000</pubDate>
      <link>https://forem.com/highflyer910/single-tenant-vs-multi-tenant-what-i-wish-i-knew-when-i-started-1hem</link>
      <guid>https://forem.com/highflyer910/single-tenant-vs-multi-tenant-what-i-wish-i-knew-when-i-started-1hem</guid>
      <description>&lt;p&gt;I should be coding right now, but instead, I went down the single-tenant vs multi-tenant rabbit hole. Then I got lost in videos, articles, and posts that all said different things.&lt;/p&gt;

&lt;p&gt;One person says multi-tenant is the only way to scale. Another says single-tenant is the only way to be secure. And someone always warns: “If you choose wrong, your SaaS will fail.”&lt;/p&gt;

&lt;p&gt;It’s really not that serious. I just wanted a simple explanation without all the technical words and drama. So here’s the version I wish I had read earlier.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Tenant (Apartments):&lt;/strong&gt; Cheaper, easier to update, and perfect when you’re starting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-Tenant (Houses):&lt;/strong&gt; Private, secure, and flexible, but expensive and hard to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid (Schema-per-Tenant):&lt;/strong&gt; Each tenant has its own schema in the same database. It’s more complex but gives better separation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Multi-tenant apps must have strong data separation. One missing &lt;code&gt;WHERE&lt;/code&gt; clause can leak data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re building your first SaaS, multi-tenant is usually the better choice.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Analogy (Houses vs. Apartments)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Single-Tenant = Everyone Has Their Own House&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each customer has a private house, their own driveway, kitchen, and mailbox. In SaaS, that means a separate app and database for each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Very secure:&lt;/strong&gt; If one house is broken into, others are safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to customize:&lt;/strong&gt; You can change things for one client without affecting others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No noisy neighbors:&lt;/strong&gt; No performance issues between users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expensive:&lt;/strong&gt; More infrastructure means higher costs (servers, databases, monitoring)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard to update:&lt;/strong&gt; You must update each app separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More maintenance:&lt;/strong&gt; fixing one problem often means fixing it many times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you work with enterprise clients who specifically ask for it.&lt;/li&gt;
&lt;li&gt;If you’re in a highly regulated industry (healthcare, finance, etc.) that needs strong compliance (HIPAA, SOC 2, and so on).&lt;/li&gt;
&lt;li&gt;When your users want custom deployments or on-premise hosting.&lt;/li&gt;
&lt;li&gt;If you actually have funding or a big budget to handle the extra infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Multi-Tenant = One Big Apartment Building&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everyone lives in the same building, but each apartment has a lock. In SaaS, it means one app and one database, but data is separated by tenant IDs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Much cheaper:&lt;/strong&gt; Resources are shared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier to update:&lt;/strong&gt; One update affects all tenants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple to add new tenants:&lt;/strong&gt; New signups are just new rows in your database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security must be perfect:&lt;/strong&gt; one small mistake can expose another tenant’s data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "noisy neighbor" problem:&lt;/strong&gt;: A heavy user can slow down others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harder to customize:&lt;/strong&gt; Individual tenant customizations are more complex&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More complex queries:&lt;/strong&gt; Every query needs tenant filtering logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're bootstrapping or indie hacking&lt;/li&gt;
&lt;li&gt;You want to launch and iterate fast&lt;/li&gt;
&lt;li&gt;Your users have similar needs (same features, pricing tiers)&lt;/li&gt;
&lt;li&gt;You're using modern frameworks with built-in tenant isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Schema-per-Tenant = Separate Floors in the Same Building&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One database server, but each tenant gets their own schema (namespace).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each tenant has tables like &lt;code&gt;tenant_123.users&lt;/code&gt;, &lt;code&gt;tenant_123.orders&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your app switches schemas based on who's logged in&lt;/li&gt;
&lt;li&gt;Better separation than row-level filtering, cheaper than separate databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better isolation than pure multi-tenant&lt;/li&gt;
&lt;li&gt;Easier to backup/restore individual tenants&lt;/li&gt;
&lt;li&gt;Still cheaper than full single-tenant&lt;/li&gt;
&lt;li&gt;Can migrate specific tenants to their own database later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More complex than simple multi-tenant&lt;/li&gt;
&lt;li&gt;Some ORMs don't handle this well&lt;/li&gt;
&lt;li&gt;Database connection pooling gets trickier&lt;/li&gt;
&lt;li&gt;Migration scripts need to run on all schemas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Popular tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Django:&lt;/strong&gt; django-tenants (formerly django-tenant-schemas)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js:&lt;/strong&gt; Knex.js with schema switching, or Prisma with multi-schema support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruby on Rails:&lt;/strong&gt; Apartment gem&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - &lt;strong&gt;Postgres:&lt;/strong&gt; Built-in schema support
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security (The Scary Part)
&lt;/h3&gt;

&lt;p&gt;Multi-tenant security means more than just adding a &lt;code&gt;tenant_id&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three Levels of Protection:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Use row-level security or tenant views.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application:&lt;/strong&gt; Always filter queries by tenant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API:&lt;/strong&gt; Make sure tokens connect users to the right tenant.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forgetting &lt;code&gt;WHERE tenant_id = ?&lt;/code&gt; → Instant data leak&lt;/li&gt;
&lt;li&gt;Wrong JWT token → Can access another tenant's data&lt;/li&gt;
&lt;li&gt;Admin panels that bypass tenant filters → Privacy nightmare&lt;/li&gt;
&lt;li&gt;Sharing caches across tenants → Information disclosure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tools That Help:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supabase:&lt;/strong&gt; Built-in RLS, auth hooks, and tenant isolation patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prisma:&lt;/strong&gt; Row-level security middleware and tenant filtering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgREST:&lt;/strong&gt; API that enforces Postgres RLS automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Django Tenants:&lt;/strong&gt; Mature library for schema-per-tenant in Django&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PlanetScale:&lt;/strong&gt; Database branching makes tenant testing easier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security Best Practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Postgres RLS as your backup layer&lt;/li&gt;
&lt;li&gt;Choose an ORM that enforces tenant filtering by default&lt;/li&gt;
&lt;li&gt;Include tenant_id in all JWT tokens&lt;/li&gt;
&lt;li&gt;Create separate admin roles with explicit permissions&lt;/li&gt;
&lt;li&gt;Test with multiple tenants in every environment&lt;/li&gt;
&lt;li&gt;Log all cross-tenant access attempts&lt;/li&gt;
&lt;li&gt;Use database-level constraints where possible&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why I chose multi-tenant
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;I'm bootstrapping.&lt;/strong&gt; Single-tenant would be too expensive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My users don't need custom setups.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I want to move fast.&lt;/strong&gt; One update for all tenants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can always switch later&lt;/strong&gt; Big clients could get single-tenant setups.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  What Should You Do?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Go multi-tenant if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're bootstrapping or self-funded.&lt;/li&gt;
&lt;li&gt;You want to launch fast.&lt;/li&gt;
&lt;li&gt;Your users have similar needs.&lt;/li&gt;
&lt;li&gt;You don’t deal with heavy security rules.&lt;/li&gt;
&lt;li&gt;Infrastructure costs matter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Go Schema-per-Tenant If:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want better isolation than pure multi-tenant&lt;/li&gt;
&lt;li&gt;You plan to offer data export/backup per tenant&lt;/li&gt;
&lt;li&gt;You might need to migrate large clients later&lt;/li&gt;
&lt;li&gt;Your users have moderately different needs&lt;/li&gt;
&lt;li&gt;You're okay with more complex migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Go single-tenant if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You work with enterprise clients.&lt;/li&gt;
&lt;li&gt;You have funding for infrastructure.&lt;/li&gt;
&lt;li&gt;You need strict data separation.&lt;/li&gt;
&lt;li&gt;You offer custom setups.&lt;/li&gt;
&lt;li&gt;Clients demand it (and pay for it)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;I spent too much time reading about this when I should have been coding.&lt;/p&gt;

&lt;p&gt;Big SaaS companies like Notion, Vercel, Shopify, and Zoom also started simple and are still multi-tenant. Slack started multi-tenant, now hybrid.&lt;/p&gt;

&lt;p&gt;The real problem isn’t “choosing wrong”, it’s never launching at all.&lt;/p&gt;

&lt;p&gt;Many companies eventually use both: multi-tenant for most users and single-tenant for large clients like Stripe or GitHub.&lt;/p&gt;

&lt;p&gt;If you’re still deciding, start simple. Build, launch, and learn. You can always improve later.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>beginners</category>
      <category>saas</category>
    </item>
    <item>
      <title>I Thought My Backups Were Safe - Until I Tried Restoring One</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Mon, 06 Oct 2025 13:27:40 +0000</pubDate>
      <link>https://forem.com/highflyer910/i-thought-my-backups-were-safe-until-i-tried-restoring-one-387i</link>
      <guid>https://forem.com/highflyer910/i-thought-my-backups-were-safe-until-i-tried-restoring-one-387i</guid>
      <description>&lt;p&gt;Imagine this: you wake up one morning, open your app, and something's wrong. Your database is gone. Your files are corrupted. Or a simple bug deleted everything clean.&lt;/p&gt;

&lt;p&gt;You breathe a little easier because you have backups, right? Except… when you try to restore them, they don't work.&lt;br&gt;
They're empty. Or the format is broken. Or restoring takes forever.&lt;/p&gt;

&lt;p&gt;That's the painful truth I learned recently:&lt;br&gt;
&lt;strong&gt;A backup is worthless until you've restored it.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  My Wake-Up Call
&lt;/h2&gt;

&lt;p&gt;Last month, I was refactoring my side project's database schema.&lt;br&gt;
Something went wrong during migration, and I corrupted my SQLite database.&lt;/p&gt;

&lt;p&gt;No problem, I thought, I will just restore my weekly backup.&lt;br&gt;
But when I tried, the backup file was also corrupted.&lt;br&gt;
Later I found out I had been copying the database while the app was still running, catching it middle of writing.&lt;br&gt;
So my “backup system” was creating broken files for weeks, and I didn’t know because I never tested restoring one.&lt;/p&gt;

&lt;p&gt;I was lucky it was only dev data, but it scared me.&lt;br&gt;
If that had been production?&lt;br&gt;
If that had been production, I could have lost real users’ data, their work, and my project’s reputation.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Learned About Backups While Building Side Projects
&lt;/h2&gt;

&lt;p&gt;Before this, I thought backups were just a checklist: make a copy, sleep peacefully.&lt;br&gt;
But a backup is not magic protection.&lt;br&gt;
It’s just a copy of important things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;strong&gt;Your codebase&lt;/strong&gt; - GitHub or GitLab usually handle this&lt;/li&gt;
&lt;li&gt;🗄️ &lt;strong&gt;Your database&lt;/strong&gt; - Postgres, MySQL, Supabase, Firebase, SQLite…
&lt;/li&gt;
&lt;li&gt;🖼️ &lt;strong&gt;User files&lt;/strong&gt; - images, uploads, documents
&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;Configs &amp;amp; secrets&lt;/strong&gt; - the &lt;code&gt;.env&lt;/code&gt; file, API keys, deployment settings &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real problem:&lt;br&gt;
You don't know if those backups are good until you test restoring them.&lt;br&gt;
Otherwise, it’s like having an emergency plan locked in a drawer.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Bigger Teams Handle It
&lt;/h2&gt;

&lt;p&gt;I checked how bigger companies do backups.&lt;br&gt;
&lt;strong&gt;GitLab&lt;/strong&gt;, for example, runs restore tests every day and tracks success rates.&lt;br&gt;
&lt;strong&gt;Basecamp&lt;/strong&gt; even does “disaster tests,” pretending their main datacenter disappears.&lt;/p&gt;

&lt;p&gt;Usually, their process looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make backups automatically (nightly dumps, snapshots, etc)
&lt;/li&gt;
&lt;li&gt;Restore them into a test environment (not production)
&lt;/li&gt;
&lt;li&gt;Check if everything works: database starts, files open, users can log in&lt;/li&gt;
&lt;li&gt;Get alerts when something fails&lt;/li&gt;
&lt;li&gt;Simulate disasters to see how fast they can recover
Then I realized:
I don't need a datacenter, just a smaller version that fits my side project setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And actually, many platforms like Supabase, Neon, and Vercel Postgres now have &lt;strong&gt;point-in-time recovery (PITR)&lt;/strong&gt; built in. Sometimes even free. So before you create your own backup scripts, check your dashboard first.&lt;/p&gt;
&lt;h3&gt;
  
  
  🧠 What to Back Up
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Backup Method&lt;/th&gt;
&lt;th&gt;Test Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Push to GitHub/GitLab (with 2FA!)&lt;/td&gt;
&lt;td&gt;Clone repo to a fresh folder: &lt;code&gt;git clone &amp;lt;repo&amp;gt; test-restore &amp;amp;&amp;amp; cd test-restore &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm run dev&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• SQLite: &lt;code&gt;sqlite3 mydb.db ".backup backup.db"&lt;/code&gt;&lt;br&gt;• Postgres: &lt;code&gt;pg_dump mydb &amp;gt; backup.sql&lt;/code&gt;&lt;br&gt;• Managed DBs: Use built-in exports or PITR&lt;/td&gt;
&lt;td&gt;Restore to test DB: &lt;code&gt;sqlite3 test.db &amp;lt; backup.sql&lt;/code&gt; then run a query to verify data. For Supabase/Neon, use their one-click export + local restore.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User Files&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Store in &lt;strong&gt;Backblaze B2&lt;/strong&gt;, &lt;strong&gt;S3&lt;/strong&gt;, or &lt;strong&gt;Google Cloud Storage&lt;/strong&gt; (all offer free tiers). Avoid consumer Dropbox for production data.&lt;/td&gt;
&lt;td&gt;Download 3–5 random files monthly and verify they open. Bonus: enable &lt;strong&gt;object lock&lt;/strong&gt; or &lt;strong&gt;versioning&lt;/strong&gt; to prevent accidental deletion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configs &amp;amp; Secrets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Store in a &lt;strong&gt;password manager&lt;/strong&gt; (1Password, Bitwarden) or &lt;strong&gt;encrypted offline vault&lt;/strong&gt; (VeraCrypt). &lt;strong&gt;Never in Git—even private repos!&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Run project locally using only saved configs. Use GitHub Secrets or &lt;code&gt;.env.vault&lt;/code&gt; for CI/CD, not raw &lt;code&gt;.env&lt;/code&gt; files.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;⚠️ &lt;em&gt;Important SQLite Tip:&lt;/em&gt;&lt;br&gt;
** Copying a live &lt;code&gt;.db&lt;/code&gt; file can damage it. Use &lt;code&gt;.backup&lt;/code&gt; or &lt;code&gt;VACUUM INTO&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; sqlite3 mydb.db &lt;span class="s2"&gt;"VACUUM INTO backup.db"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a clean, safe copy even while your app is running.&lt;br&gt;
💡 &lt;em&gt;Pro tip:&lt;/em&gt; Automate your test restore with a small script:&lt;br&gt;
For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
   &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;  &lt;span class="c"&gt;# Exit on any error&lt;/span&gt;
   &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; test.db
   sqlite3 test.db &lt;span class="s2"&gt;".read latest_backup.sql"&lt;/span&gt;
   sqlite3 test.db &lt;span class="s2"&gt;"SELECT count(*) FROM users;"&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ Restore test passed!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can even run it weekly on GitHub Actions, if it fails, you’ll get an alert.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⏰ When to Test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Monthly (15–30 minutes)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restore one random backup locally
&lt;/li&gt;
&lt;li&gt;Download a few random user files&lt;/li&gt;
&lt;li&gt;Write down anything strange or failed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quarterly (1–2 hours)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full local restore: DB + files + app&lt;/li&gt;
&lt;li&gt;Time it - how long does recovery take?&lt;/li&gt;
&lt;li&gt;Update your notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Yearly (half day)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pretend your laptop died
&lt;/li&gt;
&lt;li&gt;Can you restore &lt;em&gt;everything&lt;/em&gt; from your backups and docs?
&lt;/li&gt;
&lt;li&gt;It’s a great test for your memory and process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;em&gt;Privacy reminder:&lt;/em&gt; &lt;br&gt;
&lt;code&gt;If your app stores user data, make sure backups are encrypted at rest.&lt;br&gt;
Most cloud providers do this by default.&lt;br&gt;
And never keep plain emails or passwords in backups.&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters
&lt;/h2&gt;

&lt;p&gt;It’s easy to think backups are only for big companies.&lt;br&gt;
But if you're building side projects or working solo, losing data can kill your project one day. Users won’t wait while you say, &lt;em&gt;“Oops, I thought I had backups.”&lt;/em&gt;&lt;br&gt;
Now, backups aren’t about peace of mind when I make them;&lt;br&gt;
They’re about peace of mind when I &lt;strong&gt;restore&lt;/strong&gt; them.&lt;br&gt;
Because backups aren’t about paranoia, they’re about love for your future self. ❤️&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>backup</category>
      <category>database</category>
      <category>devops</category>
    </item>
    <item>
      <title>Passkeys Without the Pain: A Frontend Dev’s Guide</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Thu, 25 Sep 2025 11:08:56 +0000</pubDate>
      <link>https://forem.com/highflyer910/passkeys-without-the-pain-a-frontend-devs-guide-276k</link>
      <guid>https://forem.com/highflyer910/passkeys-without-the-pain-a-frontend-devs-guide-276k</guid>
      <description>&lt;p&gt;I was building my SaaS side project when I realized I needed to add passkeys. Everyone talks about better security and user experience, but I had no idea where to start. Would I need to rebuild my entire auth system? How complicated is this stuff?&lt;/p&gt;

&lt;p&gt;Turns out, it’s way simpler than I thought. You don’t need to break your existing login flow to add passkeys. Let me walk you through how I added them in a single weekend, kept everything working smoothly, and got ready for when users start signing up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Passkeys, Anyway?
&lt;/h2&gt;

&lt;p&gt;Passkeys are a secure, passwordless login method using biometrics (like your fingerprint or face scan) or device-based authentication. They’re basically the user-friendly names for the FIDO2 and WebAuthn standards. &lt;/p&gt;

&lt;p&gt;Here’s the gist: when you create a passkey, your device makes a unique cryptographic key pair. The private key stays locked on your device (protected by your fingerprint or PIN), while the public key goes to the server. During login, your device signs a challenge with the private key, and the server checks it with the public key.&lt;/p&gt;

&lt;p&gt;No passwords. No phishing. No credential stuffing (that’s when attackers reuse leaked passwords across multiple sites, hoping one works).&lt;/p&gt;

&lt;p&gt;Think of them as a futuristic “Login with Google” button, minus the third-party dependency and with top-notch security. Plus, they can sync across devices through providers like Apple iCloud Keychain, Google Password Manager, and 1Password. Cross-device sync is a game-changer.&lt;br&gt;
Important note: the PIN or biometric you use doesn’t sync. That’s just the local way your device unlocks its stored keys. The actual passkey itself is what gets synced through your Apple/Google/1Password account.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Simplest Way to Add Passkeys (That Actually Works)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Add One Button
&lt;/h3&gt;

&lt;p&gt;I added a single passkey button to my login form—no database chaos, no 3 a.m. panic sessions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Only show if browser supports passkeys */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;supportsPasskeys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handlePasskeyLogin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full border-2 border-dashed border-gray-300 p-3 rounded-lg hover:border-gray-500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="err"&gt;🔐&lt;/span&gt; &lt;span class="nx"&gt;Try&lt;/span&gt; &lt;span class="nx"&gt;Passkey&lt;/span&gt; &lt;span class="nx"&gt;Login&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Let Users Opt In (But Encourage It)
&lt;/h3&gt;

&lt;p&gt;I don’t force passkeys on signup, but I nudge users after a few password logins: "Use a passkey. Skip the password."&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Use a Service (You've Got Features to Ship)
&lt;/h3&gt;

&lt;p&gt;I could’ve spent months wrestling with WebAuthn docs, but I’m pre-revenue and need to ship. After some research, I picked Clerk; their Next.js integration is smooth, and the passkey setup is painless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other solid options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stytch&lt;/strong&gt;:  Great for startups, generous free tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth0&lt;/strong&gt;: The enterprise choice, now with much better passkey UX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corbado&lt;/strong&gt;: Super fast setup, great for MVPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descope&lt;/strong&gt;: Most flexible for custom flows, has a visual workflow builder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase Auth&lt;/strong&gt;: Passkeys now supported if you’re already on Supabase.
Integration took me ~2 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;❌ &lt;strong&gt;Forcing passkeys on everyone&lt;/strong&gt;: Don't. Make it optional but encouraged.&lt;br&gt;
❌ &lt;strong&gt;Overhauling the database&lt;/strong&gt;: Just add a table for passkey data (public key, credential ID, device info). Existing users won't notice.&lt;br&gt;
❌ &lt;strong&gt;Learning WebAuthn from scratch&lt;/strong&gt;: Use a service and ship faster.&lt;br&gt;
❌ &lt;strong&gt;Ignoring backup scenarios&lt;/strong&gt;: Plan for when users lose devices.&lt;br&gt;
⚠️ &lt;strong&gt;Keep in mind&lt;/strong&gt;: Passkeys require HTTPS (localhost works for testing). Custom local domains need proper HTTPS, or WebAuthn will throw errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Service Reliability &amp;amp; Vendor Lock-in
&lt;/h3&gt;

&lt;p&gt;If your passkey provider goes down, most have 99.9% uptime, but keep email/password as a backup. Export user data regularly so you can switch providers if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Management Features
&lt;/h3&gt;

&lt;p&gt;Users should manage passkeys like passwords. I added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;View registered devices&lt;/strong&gt;: "iPhone 17, MacBook Pro, Windows Desktop"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove old passkeys&lt;/strong&gt;: For lost/replaced devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passkey naming&lt;/strong&gt;: "Work Laptop" vs "Personal Phone"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage analytics&lt;/strong&gt;: Show last login times per device&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why You Should Add Passkeys Now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User expectation&lt;/strong&gt;: Feels modern, missing it feels outdated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive edge&lt;/strong&gt;: Still ahead of many apps, but not for long.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security story&lt;/strong&gt;: Great for investors and enterprise clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support costs&lt;/strong&gt;: Less password reset chaos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better conversion&lt;/strong&gt;: Easy login = better retention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future compliance&lt;/strong&gt;: Some industries will require strong authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your Playbook for Adding Passkeys
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start small&lt;/strong&gt;: Add the login button and basic flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a service&lt;/strong&gt;: Stytch for startups, Clerk for full-stack, Auth0 for enterprise, Corbado for speed. Don’t reinvent the wheel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make it optional but encourage it&lt;/strong&gt;: Nudges &amp;gt; forcing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test everywhere&lt;/strong&gt;: Mobile, desktop, cross-device sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan for problems&lt;/strong&gt;: Lost devices, account recovery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track adoption&lt;/strong&gt;: See what works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship it fast&lt;/strong&gt;: This isn’t just a nice-to-have anymore.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;It took me one weekend to add passkeys, and now my app is ready for users. Better security, smoother UX, and fewer support headaches.&lt;/p&gt;

&lt;p&gt;Building something cool? I’d love to hear what you’re working on. If you’ve added passkeys to your project, drop a comment with your experience. I’m always learning too.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Multi-Tab Logout Problem Nobody Warned You About</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Mon, 25 Aug 2025 11:14:42 +0000</pubDate>
      <link>https://forem.com/highflyer910/the-multi-tab-logout-problem-nobody-warned-you-about-2jil</link>
      <guid>https://forem.com/highflyer910/the-multi-tab-logout-problem-nobody-warned-you-about-2jil</guid>
      <description>&lt;p&gt;Picture this: you’re using your favorite web app. You have three tabs open — one with reports, one editing a document, and one checking settings.&lt;/p&gt;

&lt;p&gt;In the settings tab, you click Logout.&lt;br&gt;
You think you’re done… but when you switch back to the other tabs, surprise! They still look logged in. You can click buttons, type in forms, and maybe even see private data.&lt;/p&gt;

&lt;p&gt;This is the multi-tab session problem. And it’s more common than you think.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Problem Happens
&lt;/h2&gt;

&lt;p&gt;Browsers don't automatically tell every tab that you've logged out in another one.&lt;br&gt;
Yes, cookies are shared across tabs, but your app’s JavaScript in each tab doesn’t know what happened until it talks to the server again.&lt;/p&gt;

&lt;p&gt;So you end with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Tab A&lt;/strong&gt; -&amp;gt; You're officially logged out (server knows, UI knows)&lt;br&gt;
&lt;strong&gt;- Tab B&lt;/strong&gt; -&amp;gt; Your UI doesn't know what happened and hasn't updated yet&lt;/p&gt;

&lt;p&gt;Result: a weird, broken state that can confuse users or even expose private data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-Life Example
&lt;/h2&gt;

&lt;p&gt;Imagine a design tool with a subscription:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Tab 1&lt;/strong&gt;: You’re using a premium feature.&lt;br&gt;
&lt;strong&gt;- Tab 2&lt;/strong&gt;: You cancel your subscription in settings.&lt;br&gt;
&lt;strong&gt;- What happens?&lt;/strong&gt; Tab 1 still lets you use the premium feature until refresh.&lt;/p&gt;

&lt;p&gt;Bad for business (free features) and bad for the user (sudden “Access Denied” when saving). Everyone loses.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Developers Fix It
&lt;/h2&gt;

&lt;p&gt;The trick is simple: make your tabs talk to each other.&lt;br&gt;
&lt;strong&gt;Step 1&lt;/strong&gt;: When something important changes (like logging out), store that change in &lt;code&gt;localStorage&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Step 2&lt;/strong&gt;: Add a listener in every tab that watches for these changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check for our specific key and that it was set to 'loggedOut'&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authStatus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loggedOut&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Could reload, redirect, or update state&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if one tab logs out and sets &lt;code&gt;authStatus = 'loggedOut'&lt;/code&gt;, the other tabs instantly know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better approach&lt;/strong&gt;: instead of always reloading (which can be annoying), you can show a message like: &lt;strong&gt;"You’ve been logged out"&lt;/strong&gt; → redirect to login page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Remember
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This only works for the same domain (yourapp.com).&lt;/li&gt;
&lt;li&gt;The storage event fires only in other tabs, not the one that made the change.&lt;/li&gt;
&lt;li&gt;For some apps, you may also want to sync session data between tabs, but that’s extra work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;If you’re building a web app with accounts, don’t forget the multi-tab case. Users won’t thank you when it works… but they’ll notice when it doesn’t.&lt;/p&gt;

&lt;p&gt;Because the only thing worse than a broken logout is working inside a ghost session that died 20 minutes ago. 👻&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>frontend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Creating Text Shadows in CSS: Simple to Advanced Techniques</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Fri, 15 Aug 2025 13:55:06 +0000</pubDate>
      <link>https://forem.com/highflyer910/creating-text-shadows-in-css-simple-to-advanced-techniques-59j2</link>
      <guid>https://forem.com/highflyer910/creating-text-shadows-in-css-simple-to-advanced-techniques-59j2</guid>
      <description>&lt;p&gt;Ever wanted to add beautiful shadows to your text? CSS offers different methods depending on how complex you want your shadows to be. Let's explore both simple and advanced techniques!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simple Way: &lt;code&gt;text-shadow&lt;/code&gt; (Best for Most Cases)
&lt;/h2&gt;

&lt;p&gt;For basic shadows, use the built-in &lt;code&gt;text-shadow&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is awesome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Super easy to write&lt;/li&gt;
&lt;li&gt;Works in all modern browsers&lt;/li&gt;
&lt;li&gt;Best performance&lt;/li&gt;
&lt;li&gt;Perfect for standard shadows&lt;/li&gt;
&lt;li&gt;Supports multiple shadows in one rule (e.g., add a glow with &lt;code&gt;-2px -2px 4px rgba(255,255,255,0.3)&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When You Need Fancy Shadows: The &lt;code&gt;data-text&lt;/code&gt; Technique
&lt;/h2&gt;

&lt;p&gt;Sometimes you want special effects like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gradient shadows that match gradient text&lt;/li&gt;
&lt;li&gt;Multiple shadow layers for depth&lt;/li&gt;
&lt;li&gt;Advanced visual effects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem With Duplicating Text
&lt;/h2&gt;

&lt;p&gt;When creating complex shadows, you might duplicate text in HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
  I ♥ coding
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"shadow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;I ♥ coding&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works but creates messy code that's hard to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Cleaner Way: Using &lt;code&gt;data-text&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here's a better way using a custom HTML attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;data-text=&lt;/span&gt;&lt;span class="s"&gt;"I ♥ coding"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;I ♥ coding&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#667eea&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#764ba2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-background-clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* If -webkit-text-fill-color fails */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data-text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
             &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
             &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;162&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-background-clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;attr(data-text)&lt;/code&gt; means you only write the text once in HTML. If you later change the text, you don’t have to edit multiple places.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;data-text&lt;/code&gt; attribute: Stores your text in HTML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;attr(data-text)&lt;/code&gt;: CSS grabs the text from the attribute&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;::before&lt;/code&gt;: Creates a shadow layer behind the real text&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transform&lt;/code&gt;: Moves the shadow slightly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filter: blur()&lt;/code&gt;: Makes the shadow soft&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pro Tip: Use &lt;code&gt;filter: blur()&lt;/code&gt; sparingly. It’s rendered on the GPU but can cause jank on lower-end devices. Avoid animating elements with &lt;code&gt;blur&lt;/code&gt; or &lt;code&gt;backdrop-filter&lt;/code&gt;.&lt;br&gt;
Also, &lt;code&gt;backdrop-filter&lt;/code&gt; has limited support in some older browsers, especially Firefox on Android.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding a Second Shadow Layer
&lt;/h2&gt;

&lt;p&gt;For extra depth, add another layer with &lt;code&gt;::after&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data-text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
             &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
             &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;162&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-background-clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Stacking pseudo-elements like this adds a bit of rendering complexity, so reserve it for cases where &lt;code&gt;text-shadow&lt;/code&gt; isn’t enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important Things to Know
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Browser Support&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works in Chrome, Firefox, Edge, and Safari&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-webkit-&lt;/code&gt; prefixes (like &lt;code&gt;-webkit-background-clip&lt;/code&gt;) are mainly for older browsers&lt;/li&gt;
&lt;li&gt;Always test your designs in different browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid animating these effects (can slow down your page)&lt;/li&gt;
&lt;li&gt;Too many &lt;code&gt;filter: blur&lt;/code&gt; effects may lag on low-end devices&lt;/li&gt;
&lt;li&gt;For simple shadows, &lt;code&gt;text-shadow&lt;/code&gt; is always faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These effects are visual only and won't affect screen readers&lt;/li&gt;
&lt;li&gt;Make sure your text has enough contrast with the background&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternative Methods
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;drop-shadow&lt;/code&gt; Filter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For some effects, you can try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.filter-shadow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;drop-shadow&lt;/code&gt; works better than &lt;code&gt;text-shadow&lt;/code&gt; for see-through text or images, because it keeps the shadow looking clean around transparent parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple &lt;code&gt;text-shadow&lt;/code&gt; Layers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For simple gradient-like effects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.multi-shadow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;162&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Use Each Method
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Best Technique&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple shadows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text-shadow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Easy to write, best performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple solid layers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Multiple text-shadow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Good balance of simplicity and effect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradient shadows&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;data-text&lt;/code&gt; method&lt;/td&gt;
&lt;td&gt;Only way to achieve gradient shadows behind gradient text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance critical&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text-shadow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No pseudo-elements or complex rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex animations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;filter: drop-shadow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Can be GPU accelerated; works well with transforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;All methods&lt;/td&gt;
&lt;td&gt;Ensure high contrast (4.5:1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Check out all these shadow techniques with working examples and copy-paste code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://codepen.io/HighFlyer/pen/qEOpVxL?editors=1010" rel="noopener noreferrer"&gt;Demo on CodePen →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Text shadows can take your design from plain to wow. Start with the simple &lt;code&gt;text-shadow&lt;/code&gt; for most cases, and level up to the &lt;code&gt;data-text&lt;/code&gt; technique when you need those fancy gradient effects. &lt;br&gt;
Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>tutorial</category>
      <category>frontend</category>
    </item>
    <item>
      <title>SphereConnect: Creating Team Vibes for Axero's Intranet Challenge</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Tue, 22 Jul 2025 12:16:36 +0000</pubDate>
      <link>https://forem.com/highflyer910/sphereconnect-creating-team-vibes-for-axeros-intranet-challenge-41jb</link>
      <guid>https://forem.com/highflyer910/sphereconnect-creating-team-vibes-for-axeros-intranet-challenge-41jb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend/axero"&gt;Frontend Challenge: Office Edition sponsored by Axero, Holistic Webdev: Office Space&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Meet SphereConnect! It's an intranet dashboard I created for Axero's "Holistic Webdev: Office Space" challenge, and honestly, I had way too much fun building it. Think of it as your team's digital meeting place where working together feels natural instead of forced.&lt;/p&gt;

&lt;p&gt;Why SphereConnect? Because I wanted something that showed how it brings people together in one connected space, like a digital circle where ideas flow freely and everyone stays connected. Perfect match for what Axero had in mind!&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Explore SphereConnect live!&lt;br&gt;&lt;br&gt;
🌐 &lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://sphere-connect.vercel.app/" rel="noopener noreferrer"&gt;SphereConnect&lt;/a&gt;&lt;br&gt;&lt;br&gt;
📂 &lt;strong&gt;GitHub Repo&lt;/strong&gt;: &lt;a href="https://github.com/highflyer910/sphereconnect" rel="noopener noreferrer"&gt;github.com/highflyer910/sphereconnect&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dark Theme&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxpdn1oj5ps44u8aar4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxpdn1oj5ps44u8aar4o.png" alt="Dark Theme" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light Theme&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1vm24nvd5k90l44akcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1vm24nvd5k90l44akcn.png" alt="Light Theme" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile View&lt;/strong&gt;: A responsive 1-column layout, ensuring usability on the go.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxevjnfqaokbwaom46cw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxevjnfqaokbwaom46cw7.png" alt="Mobile View" width="800" height="1670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Development Adventure
&lt;/h2&gt;

&lt;p&gt;Building SphereConnect was like putting together a fun puzzle - each piece had to work perfectly with the others while making teams' lives easier. Let me walk you through how it all came together!&lt;/p&gt;

&lt;h3&gt;
  
  
  The name
&lt;/h3&gt;

&lt;p&gt;Choosing SphereConnect was pretty fun. I kept thinking about how the best workspaces feel like this connected space where everyone understands each other and ideas flow naturally. Plus, it sounds modern and friendly, exactly what I was going for!&lt;/p&gt;

&lt;h3&gt;
  
  
  Process
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Big Picture&lt;/strong&gt;: I read Axero's requirements and got excited about creating something that would make people want to use their intranet. I planned out widgets for Announcements, Events, Team Spotlight, New Hires, Quick Resources, Tasks, and even a dual-mode Chat system. Oh, and that Welcome section with live weather? That was my "let's make this personal" moment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;My Toolkit&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vite-React&lt;/strong&gt; (because life's too short for slow builds:))&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Material-UI (MUI)&lt;/strong&gt; (consistent and beautiful)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lucide-React&lt;/strong&gt; (clean icons)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WeatherAPI.com&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel Analytics&lt;/strong&gt; (gotta know if people use this thing!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Making It Pretty&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Theme toggle was a must-have since some people prefer light mode, others prefer dark mode&lt;/li&gt;
&lt;li&gt;Added small hover effects and smooth animations because details matter&lt;/li&gt;
&lt;li&gt;Used a widget-based layout that feels familiar but not boring&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Development&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Welcome Section&lt;/strong&gt;: Greets you with real weather and time (with a simple "allow location" button for privacy-conscious users)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat Widget&lt;/strong&gt;: Team chat for those "quick question" moments, plus an AI Assistant (though it's fake AI for now)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick Resources&lt;/strong&gt;: One-click access to all the tools you use - Time Tracker, Launch Pad, you name it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal Touch&lt;/strong&gt;: Resources that change based on who's logged in, including some of my dev.to posts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks Section&lt;/strong&gt;: Create, track, and remember your tasks (thanks, localStorage!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Spotlight&lt;/strong&gt;: A slideshow that makes everyone feel special&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New Hire Welcome&lt;/strong&gt;: Because starting somewhere new shouldn't feel scary&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Made sure it works well with screen readers and keyboard navigation&lt;/li&gt;
&lt;li&gt;Added alt text for avatars and icons.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Responsiveness&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responsive grid that goes from 3 columns to 2 to 1 as screens get smaller&lt;/li&gt;
&lt;li&gt;Tested on my phone, tablet, laptop, and it works everywhere!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;: Clicked every button, tried every feature, and tested it on many different browsers.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What This Taught Me
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vite is amazing&lt;/strong&gt;: The development speed is so much faster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MUI Theming is Powerful&lt;/strong&gt;: Once you learn it, you can make everything look exactly how you want&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility Matters&lt;/strong&gt;: Making sure everyone can use your site is important&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design takes time&lt;/strong&gt;: Getting those screen size changes just right took longer than expected, but it was worth it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Future Plans
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real Navigation&lt;/strong&gt;: Those navbar links need React Router to work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual AI&lt;/strong&gt;: Time to replace the fake responses with a real language model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Languages&lt;/strong&gt;: Adding language options because good design shouldn't be limited by language&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Thanks to Axero
&lt;/h3&gt;

&lt;p&gt;Huge thanks to Axero for creating such an inspiring challenge! It made me think beyond just "another dashboard" and consider what makes teams work well together. Plus, I got to build something I'm genuinely proud of, which is always great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Legal Stuff&lt;/strong&gt;: MIT License for the open-source community, and Axero gets to showcase this work while I keep ownership. Fair deal all around!&lt;/p&gt;

&lt;p&gt;Thanks for taking the time to check out SphereConnect! Building it was fun, and I hope exploring it brings a little joy to your day.✨&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Deploy Your FastAPI App on Vercel: The Complete Guide</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Mon, 30 Jun 2025 12:09:14 +0000</pubDate>
      <link>https://forem.com/highflyer910/deploy-your-fastapi-app-on-vercel-the-complete-guide-27c0</link>
      <guid>https://forem.com/highflyer910/deploy-your-fastapi-app-on-vercel-the-complete-guide-27c0</guid>
      <description>&lt;p&gt;So I was working on this FastAPI project last week and needed to deploy it somewhere. I tried a few different platforms, but Vercel turned out to be simple, much easier than I expected!&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Need
&lt;/h2&gt;

&lt;p&gt;Just basic stuff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your FastAPI app(obviously)&lt;/li&gt;
&lt;li&gt;GitHub account&lt;/li&gt;
&lt;li&gt;Vercel account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No need for complicated server setup or Docker stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Your App Ready
&lt;/h2&gt;

&lt;p&gt;First, make sure your FastAPI app is working. Here's my simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Look Ma, I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m deployed!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;healthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty straightforward, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements File
&lt;/h2&gt;

&lt;p&gt;You need a &lt;code&gt;requirements.txt&lt;/code&gt; file so Vercel knows what packages to install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fastapi==0.104.1
uvicorn==0.24.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important: Always pin your versions! Trust me, I learned this the hard way when my app broke because of package updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel configuration
&lt;/h2&gt;

&lt;p&gt;This part is a bit tricky, but not too bad. Create a &lt;code&gt;vercel.json&lt;/code&gt; file in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"builds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@vercel/python"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/(.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main.py"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Vercel, "hey, this is a Python app, run it like this".&lt;/p&gt;

&lt;h3&gt;
  
  
  Small change needed
&lt;/h3&gt;

&lt;p&gt;Vercel works with ASGI apps (FastAPI is ASGI), but you need to add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello World from FastAPI on Vercel!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;healthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# This is important for Vercel
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uvicorn&lt;/span&gt;
    &lt;span class="n"&gt;uvicorn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Push to GitHub
&lt;/h3&gt;

&lt;p&gt;Get your code on GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial FastAPI app"&lt;/span&gt;
git remote add origin https://github.com/yourusername/your-repo.git
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy time!
&lt;/h2&gt;

&lt;p&gt;Now the fun part:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Vercel dashboard&lt;/li&gt;
&lt;li&gt;Click "New Project"&lt;/li&gt;
&lt;li&gt;Connect your GitHub repo&lt;/li&gt;
&lt;li&gt;Vercel detects it's Python automatically&lt;/li&gt;
&lt;li&gt;Click "Deploy"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And... that's it! No server configuration, no SSL certificates, nothing complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using CLI instead
&lt;/h3&gt;

&lt;p&gt;If you prefer the command line (like me):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Vercel CLI&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; vercel

&lt;span class="c"&gt;# Log in via CLI&lt;/span&gt;
vercel login

&lt;span class="c"&gt;# Deploy&lt;/span&gt;
vercel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three commands and you're done!&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-deployment with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Want to deploy automatically when you push code? Here's the workflow file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Vercel&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amondnet/vercel-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;vercel-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;vercel-org-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_ORG_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;vercel-project-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_PROJECT_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing your deployment
&lt;/h2&gt;

&lt;p&gt;After deployment, check these URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https://your-app.vercel.app/&lt;/code&gt; - Main page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://your-app.vercel.app/api/health&lt;/code&gt; - Health check&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://your-app.vercel.app/docs&lt;/code&gt; - FastAPI docs (this is a cool feature!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Things I learned (the hard way)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vercel gives you HTTPS automatically - no need to worry about certificates&lt;/li&gt;
&lt;li&gt;Environment variables are easy to add in the Vercel dashboard&lt;/li&gt;
&lt;li&gt;Every push to main branch = new deployment&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;/api&lt;/code&gt; prefix for your routes. Vercel likes it better, especially when you have frontend + backend together&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When something breaks
&lt;/h2&gt;

&lt;p&gt;Don't worry, it happens to everyone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check build logs in the Vercel dashboard - they usually show what's wrong&lt;/li&gt;
&lt;li&gt;Look at your &lt;code&gt;requirements.txt&lt;/code&gt;, missing packages cause most problems&lt;/li&gt;
&lt;li&gt;Verify your &lt;code&gt;vercel.json&lt;/code&gt; configuration&lt;/li&gt;
&lt;li&gt;Test locally first. If it doesn't work on your computer, it won't work on Vercel&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;That's it! Your FastAPI app is now running on Vercel's servers worldwide. No need to manage servers or worry about hosting costs (unless you become popular, but that's a good problem to have 😄).&lt;br&gt;
The whole process takes maybe 10-15 minutes once you know what you're doing. Pretty good for getting your API online, I think!&lt;/p&gt;

</description>
      <category>fastapi</category>
      <category>vercel</category>
      <category>webdev</category>
      <category>python</category>
    </item>
    <item>
      <title>'No Awkward Breakups'? Why I Migrated From Appwrite to Supabase After a Broken Promise</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Tue, 29 Apr 2025 11:39:46 +0000</pubDate>
      <link>https://forem.com/highflyer910/no-awkward-breakups-why-i-migrated-from-appwrite-to-supabase-after-a-broken-promise-4hf8</link>
      <guid>https://forem.com/highflyer910/no-awkward-breakups-why-i-migrated-from-appwrite-to-supabase-after-a-broken-promise-4hf8</guid>
      <description>&lt;p&gt;I’m not one to write takedown blog posts. I love exploring new dev tools and I genuinely support open-source platforms. But when a company, especially its CEO, makes a clear promise and breaks it without explanation, I think the dev community deserves transparency.&lt;/p&gt;

&lt;p&gt;Here’s what happened with Appwrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Offer
&lt;/h2&gt;

&lt;p&gt;On February 28, I received an email from Appwrite’s CEO, Eldad Fux, offering what sounded like a simple gift:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Here's a free month of Appwrite Pro… no awkward breakups needed.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had been working on KitchenPal, a personal AI-powered recipe project. I thought this was the perfect opportunity to try out Appwrite’s paid features during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Did
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Proof&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feb 28&lt;/td&gt;
&lt;td&gt;Received CEO’s email offering free Pro features&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsaiwmnh96xcy7bbwie7y.png" alt="Screenshot" width="800" height="396"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;March 2&lt;/td&gt;
&lt;td&gt;Activated the $15 credit exactly as instructed&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6gflr6u5x3jgcthogvy.png" alt="Screenshot 2" width="800" height="446"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;March 31&lt;/td&gt;
&lt;td&gt;Downgraded my plan before renewal&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwypr74g17xb6fq43csi.png" alt="Screenshot 3" width="800" height="445"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I expected that if I downgraded within the credit window, I wouldn’t be charged and would simply return to the free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happened Instead
&lt;/h2&gt;

&lt;p&gt;One week after downgrading, on April 7, my Appwrite account was restricted, and I was asked to pay $15 to unlock access to my database.&lt;br&gt;
This was confusing for a few reasons:&lt;/p&gt;

&lt;p&gt;-The platform said I had Pro features “until April 30.”&lt;br&gt;
-I had no users or activity to “run out” the credits.&lt;br&gt;
-The CEO promised “no awkward breakups.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fine Print Argument
&lt;/h2&gt;

&lt;p&gt;Support later said the credits were gone and that the upgrade page included a message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“You’ll pay $0.00 now. Once credits run out, you’ll be charged $15 every 30 days.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But there was no activity to “run out” the credits.&lt;br&gt;
And I never saw that. And even if it was there, it contradicted the CEO’s message and the downgrade confirmation I received.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Timeline and Attempt to Resolve It
&lt;/h2&gt;

&lt;p&gt;I tried multiple channels to get this resolved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Direct email to the CEO on March 31,&lt;/li&gt;
&lt;li&gt;Support ticket after the restriction on April 7&lt;/li&gt;
&lt;li&gt;Follow-up emails to support on April 9 with screenshots&lt;/li&gt;
&lt;li&gt;Follow-up email to both support and the CEO on April 14&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As of April 29, there was no reply.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Invoice Tells the Real Story
&lt;/h2&gt;

&lt;p&gt;Appwrite’s invoice shows I was billed for March 1–31, yet I activated Pro on March 2.&lt;br&gt;
Their own docs state billing starts when you upgrade, but they charged me for March 1 despite activating March 2.&lt;br&gt;
That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I was charged for a day I didn’t even use.&lt;/li&gt;
&lt;li&gt;I got 29 days of “30-day” credits.&lt;/li&gt;
&lt;li&gt;My access was blocked 7 days after downgrading, not at the end of the cycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  This isn’t about $15.
&lt;/h2&gt;

&lt;p&gt;Let me be clear: this isn’t about the money.&lt;br&gt;
It’s about trust. And transparency.&lt;/p&gt;

&lt;p&gt;I didn’t mind paying for a tool that works. I just didn’t expect to be billed after downgrading early, especially when the offer was framed as “no awkward breakups.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Outcome: I Moved to Supabase
&lt;/h2&gt;

&lt;p&gt;After two weeks of silence, I decided to stop waiting.&lt;/p&gt;

&lt;p&gt;I migrated my project KitchenPal to Supabase and rebuilt everything to restore access to my data.&lt;/p&gt;

&lt;p&gt;Honestly? I’m glad I did. Supabase is working well so far, and my app is now live in beta:&lt;br&gt;
👉&lt;a href="https://kitchenpal.vercel.app" rel="noopener noreferrer"&gt;KitchenPal&lt;/a&gt;&lt;br&gt;
Feel free to try it out!&lt;/p&gt;

&lt;p&gt;Surprisingly, this became a valuable learning experience. Forcing myself to migrate to a new backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pushed me to explore Supabase's features more deeply than I might have otherwise&lt;/li&gt;
&lt;li&gt;Improved my database architecture skills through the rebuild&lt;/li&gt;
&lt;li&gt;Proved the importance of vendor flexibility in personal projects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I’d Like to See
&lt;/h2&gt;

&lt;p&gt;I still want Appwrite to succeed. But here’s what would make a difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Honor the full 30 days of credit.&lt;/li&gt;
&lt;li&gt;Don’t restrict downgraded accounts mid-cycle.&lt;/li&gt;
&lt;li&gt;Make billing terms and time limits clearer.&lt;/li&gt;
&lt;li&gt;Respond to users, especially when things go wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let’s Talk
&lt;/h2&gt;

&lt;p&gt;Have you run into similar issues with Appwrite or other platforms?&lt;br&gt;
I’m sharing this after three weeks of silence, not to attack, but to give developers a heads-up and to celebrate how challenges can spark growth.&lt;br&gt;
Let’s keep dev tools honest and dev-first 💙&lt;/p&gt;

</description>
      <category>appwrite</category>
      <category>devtools</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
    <item>
      <title>Lost Without Tech: How Our Brains Are Changing</title>
      <dc:creator>Thea</dc:creator>
      <pubDate>Wed, 26 Mar 2025 11:54:05 +0000</pubDate>
      <link>https://forem.com/highflyer910/lost-without-tech-how-our-brains-are-changing-1glm</link>
      <guid>https://forem.com/highflyer910/lost-without-tech-how-our-brains-are-changing-1glm</guid>
      <description>&lt;p&gt;Hello, my friends and family!&lt;/p&gt;

&lt;p&gt;Have you noticed how quickly technology is changing our world? It seems like every day brings something new that transforms how we live, work, and even think.&lt;/p&gt;

&lt;p&gt;Let me give you a simple example: navigation. In the past, people used paper maps to find their way, which wasn't always easy. Now, apps guide us everywhere. But it's more than just getting from one place to another - technology is changing how we experience the world.&lt;/p&gt;

&lt;p&gt;My little niece recently made me laugh. When I showed her an old paper map, she looked confused. "Why would anyone use something that doesn't show exactly where you are?" she asked. And she was right.&lt;/p&gt;

&lt;p&gt;Back in the day, we used to build mental maps. We noticed landmarks. We remembered routes. We developed a sense of direction. Now, we just follow the blue line on our phones and trust the app completely. What if our phone battery dies? We're lost.&lt;/p&gt;

&lt;p&gt;But this is about more than just finding our way. It's about how technology is reshaping our thinking.&lt;/p&gt;

&lt;p&gt;Remember when people worried calculators would make kids bad at math? Now, our phones do even more. They remember birthdays, save passwords, and even finish our sentences. We've become experts at finding information, but we might be losing our ability to remember it.&lt;/p&gt;

&lt;p&gt;Is this bad? Not really. It's just different. Think about how people felt when writing was first invented. They thought people would stop remembering stories. In a way, they were right. But look at everything we've gained.&lt;/p&gt;

&lt;p&gt;The key is choosing what to keep in our minds. Some skills are important to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to show care for someone we love&lt;/li&gt;
&lt;li&gt;How to think critically about online information&lt;/li&gt;
&lt;li&gt;How to solve problems without technology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As AI starts writing emails and virtual reality becomes more common, we need to be smart about which mental skills we keep sharp.&lt;/p&gt;

&lt;p&gt;Here's a challenge: Next time you go somewhere new, try turning off navigation for part of the trip. Look around. Notice things. Let your brain create its map.&lt;/p&gt;

&lt;p&gt;Because the most amazing technology we'll ever have is still the one between our ears.&lt;/p&gt;

</description>
      <category>futurechallenge</category>
      <category>devchallenge</category>
      <category>rippleeffects</category>
    </item>
  </channel>
</rss>
