<?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: Sami Bashraheel</title>
    <description>The latest articles on Forem by Sami Bashraheel (@sami).</description>
    <link>https://forem.com/sami</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%2F29920%2F9ace8435-2226-4a6f-b920-77ef6ea0e936.png</url>
      <title>Forem: Sami Bashraheel</title>
      <link>https://forem.com/sami</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sami"/>
    <language>en</language>
    <item>
      <title>Build Your Own Developer Card with Astro and Tailwind CSS</title>
      <dc:creator>Sami Bashraheel</dc:creator>
      <pubDate>Sun, 01 Mar 2026 15:42:32 +0000</pubDate>
      <link>https://forem.com/sami/build-your-own-developer-card-with-astro-and-tailwind-css-45de</link>
      <guid>https://forem.com/sami/build-your-own-developer-card-with-astro-and-tailwind-css-45de</guid>
      <description>&lt;p&gt;Most developers maintain a portfolio site, a GitHub profile, and a LinkedIn page. A single, focused URL does something none of those do: show who you are in one screen. Your name, your role, where to find you, and your stack. No noise, no navigation, no filler.&lt;/p&gt;

&lt;p&gt;This guide walks through building a developer card from scratch with Astro, Tailwind CSS v4, and astro-icon. The end result is fully static, deploys anywhere, and stays easy to update.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 For a live reference, visit &lt;a href="http://card.sami.codes" rel="noopener noreferrer"&gt;card.sami.codes&lt;/a&gt;. The example uses the same stack, with 15 switchable themes, social links, and a copy-to-clipboard embed button.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The finished card includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name, role, and a short bio&lt;/li&gt;
&lt;li&gt;Clickable social icons for your platforms&lt;/li&gt;
&lt;li&gt;Tech stack badges rendered from a plain string array&lt;/li&gt;
&lt;li&gt;A theme switcher powered by CSS custom properties&lt;/li&gt;
&lt;li&gt;Copy Link and Embed buttons for sharing and embedding&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Astro?
&lt;/h2&gt;

&lt;p&gt;Astro builds to plain HTML by default. No client-side framework ships to the browser unless you opt in. For this type of project, the output is tiny bundles and fast loads. The only JavaScript on the page handles theme switching and clipboard interaction. Everything else is static.&lt;/p&gt;

&lt;p&gt;Astro components use a familiar HTML-first syntax. TypeScript works out of the box. Deployment targets any static host without extra configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Scaffold a new project and install the required packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest my-developer-card
&lt;span class="nb"&gt;cd &lt;/span&gt;my-developer-card

npm &lt;span class="nb"&gt;install &lt;/span&gt;astro-icon @iconify-json/simple-icons @iconify-json/lucide
npm &lt;span class="nb"&gt;install &lt;/span&gt;tailwindcss @tailwindcss/vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update &lt;code&gt;astro.config.mjs&lt;/code&gt; to register both integrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro-icon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;vite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;icon&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;
  
  
  The Theme System
&lt;/h2&gt;

&lt;p&gt;Before building any components, plan the theming approach. This card uses CSS custom properties (CSS Custom Properties) scoped to a class on the root element. Swapping the class instantly repaints the card. No JavaScript style injection, no theme context, no extra library.&lt;/p&gt;

&lt;p&gt;Define four variables and reference them throughout:&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;/* src/styles/global.css */&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@theme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--font-sans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Inter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ui-sans-serif&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--font-mono&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'JetBrains Mono'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ui-monospace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--color-brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6366F1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-surface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0F172A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-text-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#F1F5F9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-text-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94A3B8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.theme-default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--color-brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6366F1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-surface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0F172A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-text-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#F1F5F9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-text-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94A3B8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.theme-light&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--color-brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6366F1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-surface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#F8FAFC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-text-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1E293B&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-text-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#64748B&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;/* keep adding themes in this same pattern */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-sans&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-surface&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="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-text-primary&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;background-color&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease&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;Every component references &lt;code&gt;var(--color-brand)&lt;/code&gt; and the other custom properties. Hard-coded Tailwind colour classes never appear. This separation makes the whole system work.&lt;/p&gt;

&lt;p&gt;A few theme directions worth trying:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Feel&lt;/th&gt;
&lt;th&gt;Brand colour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;midnight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dark navy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#38BDF8&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terminal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Green on black&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#00FF00&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coffee&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Warm browns&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#D2691E&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retrowave&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Neon synthwave&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#F9C80E&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deep greens&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#9BC53D&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grayscale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Near-monochrome&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#555555&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pick whatever fits your personality. Two or three themes is plenty to start.&lt;/p&gt;




&lt;h2&gt;
  
  
  File Structure
&lt;/h2&gt;

&lt;p&gt;Five small components make up the full card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  components/
    DeveloperCard.astro   ← card shell, composes everything
    SocialLinks.astro     ← icon row
    TechBadges.astro      ← stack badges
    ThemeToggle.astro     ← theme cycling button
    CardActions.astro     ← copy link / embed buttons
  layouts/
    Layout.astro
  pages/
    index.astro
  styles/
    global.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  DeveloperCard.astro
&lt;/h2&gt;

&lt;p&gt;The root shell. Personal data passes in as props from the page, keeping the component clean and reusable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import ThemeToggle from "./ThemeToggle.astro";
import SocialLinks from "./SocialLinks.astro";
import TechBadges from "./TechBadges.astro";

interface Props {
  name?: string;
  title?: string;
  bio?: string;
  socials?: { platform: string; url: string }[];
  stack?: string[];
}

const {
  name    = "Your Name",
  title   = "Your Role",
  bio     = "A short line about what you build and care about.",
  socials = [],
  stack   = [],
} = Astro.props;
---

&amp;lt;section
  id="dev-card"
  class="theme-default w-full max-w-[360px] mx-auto p-6 rounded-xl
         border border-[var(--color-brand)] bg-[var(--color-surface)]
         shadow-lg transition-all relative"
  data-theme="default"
&amp;gt;
  &amp;lt;div class="space-y-4"&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;h1 class="text-2xl font-bold text-[var(--color-text-primary)] tracking-tight"&amp;gt;
        {name}
      &amp;lt;/h1&amp;gt;
      &amp;lt;h2 class="text-[var(--color-brand)] font-medium mt-1"&amp;gt;{title}&amp;lt;/h2&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;p class="text-[var(--color-text-secondary)] text-sm leading-relaxed"&amp;gt;
      {bio}
    &amp;lt;/p&amp;gt;

    &amp;lt;div class="pt-2"&amp;gt;
      &amp;lt;SocialLinks links={socials} /&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;div class="pt-4 border-t border-[var(--color-brand)]/20 flex justify-between items-end"&amp;gt;
      &amp;lt;TechBadges stack={stack} /&amp;gt;
      &amp;lt;div class="shrink-0"&amp;gt;
        &amp;lt;ThemeToggle /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  SocialLinks.astro
&lt;/h2&gt;

&lt;p&gt;A platform-to-icon mapping backed by &lt;code&gt;astro-icon&lt;/code&gt;. The &lt;code&gt;simple-icons&lt;/code&gt; pack covers most developer platforms. &lt;code&gt;lucide&lt;/code&gt; handles generic cases. Add or remove entries from the map to match your own profiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { Icon } from "astro-icon/components";

interface Props {
  links: { platform: string; url: string }[];
}

const { links = [] } = Astro.props;

const iconMap: Record&amp;lt;string, string&amp;gt; = {
  github:   "simple-icons:github",
  linkedin: "simple-icons:linkedin",
  x:        "simple-icons:x",
  twitter:  "simple-icons:x",
  dev:      "simple-icons:devdotto",
  hashnode: "simple-icons:hashnode",
  youtube:  "simple-icons:youtube",
  twitch:   "simple-icons:twitch",
  bluesky:  "simple-icons:bluesky",
  mastodon: "simple-icons:mastodon",
  website:  "lucide:globe",
  email:    "lucide:mail",
};

const getIcon = (platform: string) =&amp;gt;
  iconMap[platform.toLowerCase()] ?? "lucide:link";
---

&amp;lt;div class="flex flex-wrap gap-4 items-center mt-2"&amp;gt;
  {links.map((link) =&amp;gt; (
    &amp;lt;a
      href={link.url}
      target="_blank"
      rel="noopener noreferrer"
      class="text-[var(--color-text-primary)] hover:text-[var(--color-brand)]
             transition-transform hover:scale-110 focus:outline-none
             focus:ring-2 focus:ring-[var(--color-brand)] rounded"
      aria-label={`Visit my ${link.platform}`}
    &amp;gt;
      &amp;lt;Icon name={getIcon(link.platform)} class="w-6 h-6" /&amp;gt;
    &amp;lt;/a&amp;gt;
  ))}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browse the full icon list at &lt;a href="http://icon-sets.iconify.design/simple-icons" rel="noopener noreferrer"&gt;icon-sets.iconify.design/simple-icons&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  TechBadges.astro
&lt;/h2&gt;

&lt;p&gt;Pass in a string array. Each entry renders as a pill badge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
interface Props {
  stack: string[];
}
const { stack = [] } = Astro.props;
---

&amp;lt;div class="mt-4"&amp;gt;
  &amp;lt;p class="text-[0.65rem] font-bold tracking-widest text-[var(--color-brand)]
            font-mono mb-2 uppercase"&amp;gt;
    Stack
  &amp;lt;/p&amp;gt;
  &amp;lt;div class="flex flex-wrap gap-2"&amp;gt;
    {stack.map((item) =&amp;gt; (
      &amp;lt;span class="px-3 py-1 text-xs rounded border
                   border-[var(--color-brand)] text-[var(--color-brand)]
                   bg-transparent font-mono"&amp;gt;
        {item}
      &amp;lt;/span&amp;gt;
    ))}
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ThemeToggle.astro
&lt;/h2&gt;

&lt;p&gt;The theme list serialises as a JSON &lt;code&gt;data-*&lt;/code&gt; attribute at build time. A small inline script reads the value at runtime. No state management, no framework. One button cycles through an array and swaps a class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
const themes = [
  { name: "default",  label: "Switch Theme" },
  { name: "light",    label: "Too Bright?"  },
  { name: "terminal", label: "Too Hackery?" },
];
---

&amp;lt;button
  id="theme-toggle"
  class="text-xs px-2 py-1 rounded border border-[var(--color-brand)]
         text-[var(--color-text-primary)] hover:bg-[var(--color-brand)]
         hover:text-[var(--color-surface)] transition-colors font-mono"
  data-themes={JSON.stringify(themes)}
&amp;gt;
  Theme
&amp;lt;/button&amp;gt;

&amp;lt;script&amp;gt;
  let currentIndex = 0;

  const setup = () =&amp;gt; {
    const btn  = document.getElementById("theme-toggle");
    const card = document.getElementById("dev-card");
    if (!btn || !card) return;

    const themes = JSON.parse(btn.dataset.themes!);

    const saved = localStorage.getItem("dev-card-theme");
    if (saved) {
      const idx = themes.findIndex((t: any) =&amp;gt; t.name === saved);
      if (idx !== -1) currentIndex = idx;
    }

    const apply = (i: number) =&amp;gt; {
      const theme = themes[i];
      card.className = card.className.replace(/theme-\S+/g, "").trim();
      card.classList.add(`theme-${theme.name}`);
      card.setAttribute("data-theme", theme.name);
      btn.textContent = theme.label;
      localStorage.setItem("dev-card-theme", theme.name);
    };

    apply(currentIndex);
    btn.addEventListener("click", () =&amp;gt; {
      currentIndex = (currentIndex + 1) % themes.length;
      apply(currentIndex);
    });
  };

  setup();
  document.addEventListener("astro:page-load", setup);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The button label doubles as a hint about the current theme. Write whatever copy fits each one. &lt;a href="http://card.sami.codes" rel="noopener noreferrer"&gt;card.sami.codes&lt;/a&gt; uses slightly sarcastic labels like "Too Hackery!" and "Too Cosy!" to give the card personality.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 After calling &lt;code&gt;apply()&lt;/code&gt;, read the freshly computed CSS variables with &lt;code&gt;getComputedStyle&lt;/code&gt; and regenerate your browser tab favicon as an inline SVG. The tab icon repaints to match the active theme. A small detail with a big payoff.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CardActions.astro
&lt;/h2&gt;

&lt;p&gt;Two utility buttons sit below the card. The embed snippet appends &lt;code&gt;?embed=true&lt;/code&gt; to the URL. The page checks for this parameter and hides the buttons when loaded inside an iframe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { Icon } from "astro-icon/components";
---

&amp;lt;div class="flex gap-4 relative"&amp;gt;
  &amp;lt;button id="copy-link-btn"
    class="text-xs px-3 py-1.5 rounded border border-[var(--color-brand)]
           text-[var(--color-text-primary)] hover:bg-[var(--color-brand)]
           hover:text-[var(--color-surface)] transition-colors font-mono
           font-bold flex items-center gap-1.5"&amp;gt;
    &amp;lt;Icon name="lucide:link-2" class="w-4 h-4" /&amp;gt; Copy Link
  &amp;lt;/button&amp;gt;

  &amp;lt;button id="copy-embed-btn"
    class="text-xs px-3 py-1.5 rounded border border-[var(--color-brand)]
           text-[var(--color-text-primary)] hover:bg-[var(--color-brand)]
           hover:text-[var(--color-surface)] transition-colors font-mono
           font-bold flex items-center gap-1.5"&amp;gt;
    &amp;lt;Icon name="lucide:code" class="w-4 h-4" /&amp;gt; Embed
  &amp;lt;/button&amp;gt;

  &amp;lt;div id="toast"
    class="absolute left-1/2 -top-8 -translate-x-1/2 px-2 py-1
           bg-[var(--color-brand)] text-[var(--color-surface)] text-[0.65rem]
           font-bold uppercase tracking-wider rounded opacity-0
           pointer-events-none transition-all duration-300 font-mono"&amp;gt;
    Copied!
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  const setup = () =&amp;gt; {
    const linkBtn  = document.getElementById("copy-link-btn")!;
    const embedBtn = document.getElementById("copy-embed-btn")!;
    const toast    = document.getElementById("toast")!;
    let timer: ReturnType&amp;lt;typeof setTimeout&amp;gt;;

    const showToast = (msg: string) =&amp;gt; {
      clearTimeout(timer);
      toast.textContent = msg;
      toast.classList.replace("opacity-0", "opacity-100");
      timer = setTimeout(() =&amp;gt;
        toast.classList.replace("opacity-100", "opacity-0"), 2000
      );
    };

    linkBtn.addEventListener("click", async () =&amp;gt; {
      await navigator.clipboard.writeText(
        window.location.origin + window.location.pathname
      );
      showToast("Link Copied!");
    });

    embedBtn.addEventListener("click", async () =&amp;gt; {
      const src  = `${window.location.origin}${window.location.pathname}?embed=true`;
      const html = `&amp;lt;iframe src="${src}" width="100%" height="400"
        style="border:none;max-width:400px;display:block;"
        title="Developer Card" loading="lazy"&amp;gt;&amp;lt;/iframe&amp;gt;`;
      await navigator.clipboard.writeText(html);
      showToast("Embed Copied!");
    });
  };

  setup();
  document.addEventListener("astro:page-load", setup);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  index.astro: Wire Up Your Details
&lt;/h2&gt;

&lt;p&gt;All personal data lives here, passed as props to the card. The component stays generic. The page is where the card becomes yours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Layout from "../layouts/Layout.astro";
import DeveloperCard from "../components/DeveloperCard.astro";
import CardActions from "../components/CardActions.astro";
---

&amp;lt;Layout&amp;gt;
  &amp;lt;main class="min-h-screen flex flex-col items-center justify-center p-4 gap-6"&amp;gt;
    &amp;lt;DeveloperCard
      name="Your Name"
      title="Your Role"
      bio="One or two sentences. What you build, what you care about."
      socials={[
        { platform: "github",   url: "https://github.com/you" },
        { platform: "linkedin", url: "https://linkedin.com/in/you" },
        { platform: "website",  url: "https://yoursite.com" },
      ]}
      stack={["React", "TypeScript", "your actual stack"]}
    /&amp;gt;
    &amp;lt;div id="actions-wrapper"&amp;gt;
      &amp;lt;CardActions /&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/main&amp;gt;
&amp;lt;/Layout&amp;gt;

&amp;lt;script&amp;gt;
  const checkEmbed = () =&amp;gt; {
    const isEmbed =
      window.self !== window.top ||
      new URLSearchParams(window.location.search).has("embed");
    if (isEmbed) {
      const el = document.getElementById("actions-wrapper");
      if (el) el.style.display = "none";
    }
  };
  checkEmbed();
  document.addEventListener("astro:page-load", checkEmbed);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Going Live
&lt;/h2&gt;

&lt;p&gt;The build output is a plain static folder. No server needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build    &lt;span class="c"&gt;# outputs to ./dist&lt;/span&gt;
npm run preview  &lt;span class="c"&gt;# sanity check before pushing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push to GitHub and connect to Vercel, Netlify, or Cloudflare Pages. All three detect the Astro project and configure the build command automatically. Point a subdomain at the deployment (&lt;code&gt;card.yourdomain.com&lt;/code&gt;) and use the URL in your email signature, conference bio, or README.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ideas for Going Further
&lt;/h2&gt;

&lt;p&gt;Once the basics work, consider these extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic favicon: regenerate the browser tab icon as an inline SVG after each theme change, using the active CSS variable values (the live example at &lt;a href="http://card.sami.codes" rel="noopener noreferrer"&gt;card.sami.codes&lt;/a&gt; does this)&lt;/li&gt;
&lt;li&gt;OG image: use Satori or &lt;code&gt;@astrojs/og&lt;/code&gt; to auto-generate a social share image matching your card colours&lt;/li&gt;
&lt;li&gt;QR code: generate one from your card URL and print on a conference badge or business card&lt;/li&gt;
&lt;li&gt;System theme detection: read &lt;code&gt;prefers-color-scheme&lt;/code&gt; on first load and pick a suitable starting theme before any user interaction&lt;/li&gt;
&lt;li&gt;Analytics: a single &lt;code&gt;navigator.sendBeacon&lt;/code&gt; call is enough to track visits&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Open-source reference: &lt;a href="http://github.com/sami/developer-card" rel="noopener noreferrer"&gt;github.com/sami/developer-card&lt;/a&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>tailwindcss</category>
      <category>component</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Code Like a Librarian: A Beginner's Guide to Git and GitHub</title>
      <dc:creator>Sami Bashraheel</dc:creator>
      <pubDate>Fri, 04 Oct 2024 06:00:23 +0000</pubDate>
      <link>https://forem.com/sami/code-like-a-librarian-a-beginners-guide-to-git-and-github-g0b</link>
      <guid>https://forem.com/sami/code-like-a-librarian-a-beginners-guide-to-git-and-github-g0b</guid>
      <description>&lt;p&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%2Fdpmfye2kv2jtyfe71dne.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%2Fdpmfye2kv2jtyfe71dne.png" alt="An illustration of a person sitting at a desk, coding on a computer with Git and GitHub logos on the screen." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hello there! If you're stepping into the programming world, you've probably heard the terms Git and GitHub tossed around. They might sound slightly intimidating initially, but don't worry—I am here to break them down for you using a simple comparison: managing a library. So, sit comfortably and let's explore the world of version control using Git and GitHub!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Your Library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Building Your Library
&lt;/h3&gt;

&lt;p&gt;Imagine you're about to start your very own library. The first thing you need is a space to organise all your books. In the programming world, this space is called a repository.&lt;/p&gt;

&lt;p&gt;When you use &lt;code&gt;git init&lt;/code&gt;, you're laying the foundation for your library. You're setting up shelves to store your books (or code files).&lt;/p&gt;

&lt;h3&gt;
  
  
  Placing Books on Shelves
&lt;/h3&gt;

&lt;p&gt;Once your library is ready, you'll want to add books. Using &lt;code&gt;git add filename&lt;/code&gt; is like placing a book neatly on a shelf. You're preparing it to become part of your library's collection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Catalog Entries
&lt;/h3&gt;

&lt;p&gt;After adding books to your shelves, it's time to catalogue them so you can easily find them later. Committing changes with &lt;code&gt;git commit -m "Your commit message"&lt;/code&gt; is like creating a catalogue entry for each book. The commit message is not just a note; it's a detailed description of what the book is about, helping you track changes over time and understand the evolution of your library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigating Your Library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Viewing Commit History
&lt;/h3&gt;

&lt;p&gt;To see what books you've added over time, you need to browse through your catalogue. Using &lt;code&gt;git log&lt;/code&gt; is like flipping through the library’s catalogue to see all the books (commits) you've added and notes (commit messages) about each.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branching Out
&lt;/h3&gt;

&lt;p&gt;Sometimes, you want to explore new topics or editions without disturbing your primary collection. Creating a new branch with &lt;code&gt;git branch new-feature&lt;/code&gt; and switching to it with &lt;code&gt;git checkout new-feature&lt;/code&gt; is like creating a new section in your library for special projects. You can work on these projects independently without affecting the primary collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating New Ideas
&lt;/h2&gt;

&lt;p&gt;Once your new project is ready, you can integrate it into the main library. Merging changes with &lt;code&gt;git checkout main&lt;/code&gt; and &lt;code&gt;git merge new-feature&lt;/code&gt; is like moving books from the new section into the main library, seamlessly combining both collections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing Your Library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pushing Changes
&lt;/h3&gt;

&lt;p&gt;You can share your library with others or keep a backup in another location. Using &lt;code&gt;git push origin main&lt;/code&gt; is like sending a copy of your library’s catalogue and books to another library (the remote repository), ensuring both libraries are up-to-date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulling Changes
&lt;/h3&gt;

&lt;p&gt;You'll also want to get those updates if someone else adds new books to the other library. Pulling changes with &lt;code&gt;git pull origin main&lt;/code&gt; is like receiving new books and catalogue updates from another library, ensuring your local collection stays current.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forking
&lt;/h3&gt;

&lt;p&gt;You might want to create a personal copy of someone else’s library to work on independently. Forking a repository on GitHub is like making a photocopy of someone else's library catalogue and books. It allows you to make changes without affecting the original, and when you're ready, you can suggest those changes to the original owner through a pull request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proposing Changes
&lt;/h3&gt;

&lt;p&gt;After making changes to your copy, suggest those changes to the original library owner. Creating a pull request on GitHub is like sending a letter to the original library owner with your proposed changes, asking them to consider adding those changes to their collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keep Your Library Organised
&lt;/h3&gt;

&lt;p&gt;Like any good librarian, you'll want to keep things tidy. Make frequent commits with clear messages like 'Added new feature X' or 'Fixed bug Y' to easily track changes over time and understand your library's history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collaborate Effectively
&lt;/h3&gt;

&lt;p&gt;Use branches for new projects and pull requests for collaboration. This way, everyone can work together without stepping on each other's toes.&lt;/p&gt;

&lt;h2&gt;
  
  
  You’re Now a Librarian!
&lt;/h2&gt;

&lt;p&gt;Congratulations! You've taken your first steps into managing code with Git and GitHub. By thinking of these tools as managing a library, I hope you've found them more approachable. Now it's time to start building and sharing your own "library" of code. Happy coding!&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>versioncontrol</category>
    </item>
  </channel>
</rss>
