Home Repos Story Libs
// opensource

Libraries & Tools

Every library, framework, and tool used to build this portfolio from scratch — with working code examples side by side.

3D

Three.js

r128 3D Graphics
Docs ↗

Lightweight 3D engine for WebGL. Used to create the animated 3D particle globe, floating geometry, and real-time background effects on the hero section.

WebGL GPU Shaders 3D Rendering

Basic 3D Scene Setup

Create a scene, camera, renderer, and add a rotating cube. This is the foundation of any Three.js project — the same pattern used for the portfolio's 3D hero background.

JavaScript
import * as THREE from 'three';

// 1. Create scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });

// 2. Add geometry + material
const geo = new THREE.BoxGeometry(1, 1, 1);
const mat = new THREE.MeshStandardMaterial({
  color: 0x6C63FF,
  wireframe: true
});
const cube = new THREE.Mesh(geo, mat);
scene.add(cube);

// 3. Position camera
camera.position.z = 3;

// 4. Add light
const light = new THREE.PointLight(0xffffff, 1);
light.position.set(2, 2, 2);
scene.add(light);

// 5. Render loop
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}
animate();
Live Preview
AN

GSAP

3.12.5 Animation Engine
Docs ↗

GreenSock Animation Platform. Powers every scroll-triggered reveal, staggered entrance, morphing transition, and smooth interpolation across all pages. ScrollTrigger handles scroll-linked animations.

ScrollTrigger Timeline Easing Stagger

Scroll-Triggered Reveal

Elements fade in and slide up as you scroll to them. The once: true option means they only animate once. Used for every section on the portfolio.

JavaScript
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);

// Reveal .section on scroll
gsap.from('.section', {
  opacity: 0,
  y: 60,
  duration: 1,
  ease: 'power3.out',
  scrollTrigger: {
    trigger: '.section',
    start: 'top 85%',
    once: true
  }
});

// Parallax scroll effect
gsap.to('.hero-bg', {
  y: 100,
  scrollTrigger: {
    trigger: '.hero',
    start: 'top top',
    end: 'bottom top',
    scrub: 1
  }
});
Live Preview
Scroll Reveal
Stagger A
Stagger B
Stagger C
SM

Lenis

1.1.18 Smooth Scroll
Docs ↗

butter-smooth scroll library by Darkroom Engineering. Replaces native scroll with inertia-based scrolling that feels like native iOS. Integrates with GSAP ScrollTrigger for synced animations.

Inertia 60fps Scroll Sync

Lenis Initialization

Initialize Lenis with duration and easing options. The requestAnimationFrame loop keeps it synced with the browser's repaint cycle.

JavaScript
import Lenis from 'lenis';

// Create Lenis instance
const lenis = new Lenis({
  duration: 1.2,        // scroll duration (seconds)
  easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
  orientation: 'vertical',
  smoothWheel: true
});

// Sync with rAF
function raf(time) {
  lenis.raf(time);
  requestAnimationFrame(raf);
}
requestAnimationFrame(raf);

// Scroll to section on link click
document.querySelector('a[href="#work"]').addEventListener('click', () => {
  lenis.scrollTo('#work', { offset: -50 });
});
Live Preview
Scroll down with inertia
Butter smooth scrolling
60fps native feel
GSAP ScrollTrigger synced
End of demo
GO

Go

1.21 Backend Language
Docs ↗

The backend is written in pure Go — no framework, no dependencies beyond the database driver. Handles API routes, SQLite CRUD, static file serving, and contact form storage.

net/http SQLite REST API Docker

Go HTTP Server

The entire backend starts in main() — opens the database, creates tables, registers routes, and listens on port 8080. Zero external frameworks, just net/http.

Go
package main

import (
    "database/sql"
    "log"
    "net/http"
    _ "modernc.org/sqlite"
)

func main() {
    // Open SQLite database
    db, err := sql.Open("sqlite", "data/data.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Create tables
    createTables(db)

    // Register routes
    mux := http.NewServeMux()
    mux.HandleFunc("/api/portfolio", handlePortfolio(db))
    mux.HandleFunc("/api/projects", handleProjects(db))
    mux.HandleFunc("/api/skills", handleSkills(db))
    mux.HandleFunc("/api/contact", handleContact(db))
    mux.HandleFunc("/api/config", handleConfig(db))

    // Static files + start server
    fs := http.FileServer(http.Dir("."))
    mux.Handle("/", fs)
    log.Println("Server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", cors(mux)))
}
Terminal Output
$ go run main.go
2025/01/01 12:00:00 Server running on :8080
$ curl localhost:8080/api/projects
[{"id":1,"name":"GitViz","description":"GitHub analytics dashboard"}]
$
DK

Docker

Multi-stage Containerization
Docs ↗

Multi-stage Docker build: compile Go in golang:1.21-alpine, run in alpine:3.19. Final image is ~15MB. Used for Render deployment with persistent SQLite volume.

Multi-stage Alpine Render

Multi-stage Dockerfile

Stage 1 compiles Go binary. Stage 2 copies binary into minimal Alpine image. CGO_ENABLED=0 ensures static binary (pure Go SQLite needs no C compiler).

Dockerfile
# Stage 1: Build
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY backend/ .
RUN CGO_ENABLED=0 go build -o server .

# Stage 2: Run
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=builder /app/server .
COPY index.html repos.html story.html ./
COPY css/ css/
COPY js/ js/
COPY admin/ admin/
COPY data/ data/
RUN mkdir -p data

EXPOSE 8080
CMD ["./server"]
Build Output
$ docker build -t portfolio .
JS

Vanilla JavaScript

ES2024 Core Language

Zero React, zero frameworks. Pure ES modules, fetch API, Web Animations API, IntersectionObserver, and modern DOM APIs. Every interaction is hand-written.

ES Modules Fetch API Web APIs No Build Step

GitHub API with Fetch

Paginated fetch loop that loads ALL repos across multiple API calls. Handles errors, parses JSON, and concatenates results.

JavaScript
// Paginated fetch — gets ALL repos
async function fetchAllRepos(user) {
  let page = 1, all = [];
  while (true) {
    const res = await fetch(
      `https://api.github.com/users/${user}/repos` +
      `?per_page=100&page=${page}&type=owner`
    );
    if (!res.ok) throw new Error(res.statusText);
    const data = await res.json();
    if (data.length === 0) break;
    all = all.concat(data);
    if (data.length < 100) break;
    page++;
  }
  return all;
}
Interactive Demo
Waiting for input...
Card 1 - Scroll into view
Card 2 - Animate on enter
Card 3 - Unobserve after
CS

CSS Custom Properties

CSS3+ Styling

Dynamic theming with CSS variables. The entire color palette, fonts, spacing, and animations are defined as custom properties on :root. Supports dark/light mode toggling.

Variables Theming Gradients Grid/Flex

Theme System

Every color, font, and spacing value is a CSS variable. Change one value and the entire theme updates. Used for the dark/cyber aesthetic.

CSS
:root {
  /* Colors */
  --bg: #0a0a0f;
  --bg2: #12121a;
  --surface: #1a1a24;
  --text: #e8e8f0;
  --text-2: #a0a0b0;
  --text-3: #606070;
  --accent: #6C63FF;
  --accent-glow: rgba(108,99,255,0.3);
  --border: #222233;

  /* Fonts */
  --font-body: 'Space Grotesk', sans-serif;
  --font-mono: 'JetBrains Mono', monospace;

  /* Spacing */
  --section-pad: 7rem;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Usage */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  font-family: var(--font-body);
}
Live CSS Editor
Preview Box
Preview Text
F

Google Fonts

CDN Typography
Site ↗

Space Grotesk for headings — geometric, modern. JetBrains Mono for code blocks — designed for readability. Loaded via Google Fonts CDN with display=swap.

Space Grotesk JetBrains Mono Preconnect

Font Loading

preconnect establishes early connection to Google's CDN. display=swap shows fallback text immediately, swaps to custom font when loaded.

HTML
<link rel="preconnect"
      href="https://fonts.googleapis.com">
<link rel="preconnect"
      href="https://fonts.gstatic.com"
      crossorigin>
<link href="https://fonts.googleapis.com/css2?
  family=Space+Grotesk:wght@400;500;600;700&
  family=JetBrains+Mono:wght@400;500&
  display=swap"
      rel="stylesheet">
Font Preview
Space Grotesk Bold
Space Grotesk Regular
JetBrains Mono Medium
JetBrains Mono Regular
DB

modernc.org/sqlite

v1.29.6 Database Driver
Docs ↗

Pure Go SQLite implementation — no CGO, no C compiler needed. Transpiles SQLite C code to Go. Enables Docker builds with CGO_ENABLED=0 and cross-compilation.

Pure Go No CGO WASM-ready

Database Connection

Register the driver as "sqlite" (not "sqlite3"). Opens a file-based database. The ?_journal_mode=WAL pragma enables concurrent reads.

Go
package main

import (
    "database/sql"
    _ "modernc.org/sqlite"
)

func main() {
    // Register driver (pure Go, no CGO)
    db, err := sql.Open("sqlite",
        "data/portfolio.db?_journal_mode=WAL")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Verify connection
    if err = db.Ping(); err != nil {
        log.Fatal("DB connection failed:", err)
    }
}
Query Browser
idnamedescriptiontags
1GitVizGitHub analytics dashboardGo,Charts
GH

GitHub REST API

v3 Data Source
Docs ↗

Powers the repos page — fetches all 191 repos with metadata, stars, forks, languages, and topics. No backend caching — direct client-side API calls with CORS.

REST JSON CORS Pagination

List User Repos

Fetch all repos for a user. per_page=100 maximizes each request. Loop through pages until empty response. Each repo includes name, description, language, stars, forks, topics, and fork status.

JavaScript
// Fetch all repos with pagination
async function fetchRepos(user) {
  const API = 'https://api.github.com';
  let page = 1, repos = [];

  while (true) {
    const url = `${API}/users/${user}/repos`
      + `?per_page=100&page=${page}&type=owner`;

    const res = await fetch(url);
    const data = await res.json();

    if (data.length === 0) break;
    repos = repos.concat(data);
    if (data.length < 100) break;
    page++;
  }

  return repos; // All repos!
}

// Each repo object includes:
// name, full_name, description, fork,
// language, stargazers_count, forks_count,
// topics, html_url, created_at, updated_at
Live API Fetch
Click to fetch from GitHub API...
RN

Render

Free Tier Hosting Platform
Site ↗

Cloud hosting with Docker support. Free tier runs one web service. Persistent disk keeps SQLite data alive across deploys. Auto-deploys from GitHub pushes.

Docker Persistent Disk Auto Deploy Free Tier

Deploy to Render

Connect your GitHub repo, Render detects the render.yaml or Dockerfile. First build takes ~3min. Subsequent builds ~30s. Free tier spins down after 15min of inactivity.

Shell
# 1. Push to GitHub
git add . && git commit -m "deploy"
git push origin master

# 2. On render.com:
#    - New > Web Service
#    - Connect GitHub repo
#    - Runtime: Docker
#    - Disk: /app/data (1GB)
#    - Auto-deploy: On

# 3. First build output:
#    Step 1/8 : FROM golang:1.21-alpine
#    Step 2/8 : WORKDIR /app
#    Step 5/8 : RUN go build -o server .
#    Step 8/8 : CMD ["./server"]
#    Successfully built abcd1234

# Your site is live at:
# https://mayank-portfolio.onrender.com
Deploy Timeline
$ git push origin master
EJ

EmailJS

4.x Email Service
Docs ↗

Serverless email sending directly from the browser. No backend needed — the contact form sends emails via EmailJS's SMTP relay using a pre-configured service and template.

SMTP Serverless Contact Form

Send Contact Email

Initialize with public key, send with service ID, template ID, and template params. Zero backend — email goes directly from browser to recipient.

JavaScript
import emailjs from '@emailjs/browser';

// Initialize with public key
emailjs.init('Sm0zzKtPUyizJGKVP');

// Send email
const result = await emailjs.send(
  'service_r1wu0ne',    // Service ID
  'template_bkl8nf7',   // Template ID
  {
    from_name: name,
    from_email: email,
    message: message,
    to_name: 'Mayank',
  }
);
Contact Form Demo
MD

marked.js

15.x Markdown Parser
Docs ↗

Fast Markdown parser and compiler. Used in the AI Chat module to render streaming AI responses with full Markdown support — headings, code blocks, lists, links, and tables.

Markdown Streaming HTML Output

Markdown to HTML

Configure marked with custom renderer for syntax highlighting, then parse streaming chunks into HTML for the chat window.

JavaScript
import { marked } from 'marked';

marked.setOptions({
  highlight: (code, lang) => {
    return hljs.highlight(code, { language: lang }).value;
  },
  breaks: true,
  gfm: true
});

// Parse AI response
const html = marked.parse(aiResponse);
chatWindow.innerHTML = html;
Markdown Parser
TX

KaTeX

0.16.x LaTeX Rendering
Docs ↗

Fast math typesetting for the web. Renders LaTeX expressions in AI chat responses — formulas, equations, and mathematical notation display beautifully in the browser.

LaTeX Math Fast Render

LaTeX to HTML

Auto-render detects $...$ and $$...$$ delimiters in text, then renders them as beautiful math using KaTeX.

JavaScript
// Auto-render LaTeX in chat
renderMathInElement(chatWindow, {
  delimiters: [
    { left: "$$", right: "$$", display: true },
    { left: "$", right: "$", display: false }
  ],
  throwOnError: false
});

// Or render directly
const html = katex.renderToString(
  '\\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}',
  { displayMode: true }
);
LaTeX Renderer
HL

highlight.js

11.x Syntax Highlighting
Docs ↗

Automatic syntax highlighting for code blocks. Used in the AI Chat module to colorize code snippets in AI responses — supports 190+ languages.

190+ Languages Auto-detect Themes

Highlight Code Blocks

Auto-detect language and apply syntax highlighting to all <pre><code> blocks in the chat window.

JavaScript
// Highlight all code blocks
document.querySelectorAll('pre code').forEach((el) => {
  hljs.highlightElement(el);
});

// Or use marked integration
marked.setOptions({
  highlight: (code, lang) => {
    if (lang && hljs.getLanguage(lang)) {
      return hljs.highlight(code, { lang }).value;
    }
    return hljs.highlightAuto(code).value;
  }
});
Syntax Highlighter
CV

Canvas API

HTML5 2D Graphics
Docs ↗

Powers the shared background stars animation — 3-layer parallax starfield with shooting stars, mouse tracking, and particle effects. Injected via shared-bg.js on every page.

2D Context Particles requestAnimationFrame Starfield

3-Layer Parallax Stars

Creates a full-screen canvas with 3 depth layers of stars. Each layer moves at different speeds based on mouse position, creating a 3D parallax effect.

JavaScript
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
document.body.prepend(canvas);

// 3 layers of stars
const layers = [
  { count: 120, speed: 0.3, size: 1.2 },
  { count: 80,  speed: 0.6, size: 1.5 },
  { count: 40,  speed: 1.0, size: 2.0 }
];

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  layers.forEach(layer => {
    layer.stars.forEach(star => {
      ctx.beginPath();
      ctx.arc(star.x, star.y, layer.size, 0, Math.PI*2);
      ctx.fillStyle = `rgba(255,255,255,${star.alpha})`;
      ctx.fill();
    });
  });
  requestAnimationFrame(draw);
}
Star Field
ST

localStorage

Web API Client Storage
Docs ↗

Persistent client-side key-value storage. Used for: preloader preference, AI chat history, theme toggle state, cursor mode, and user settings — all survive page reloads.

5MB Limit Sync API Per-origin No Expiry

Store & Retrieve

Used throughout the portfolio — preloader choice, chat messages, theme, and cursor preferences all persist via localStorage.

JavaScript
// Save preloader preference
localStorage.setItem('preloader-type', 'matrix');

// Load AI chat history
const messages = JSON.parse(
  localStorage.getItem('ai-chat-history') || '[]'
);

// Save theme state
localStorage.setItem('theme', 'dark');

// Save cursor mode
localStorage.setItem('cursor-mode', 'default');
Storage Demo
Click Load to retrieve...
IO

IntersectionObserver

Web API Viewport Detection
Docs ↗

Detects when elements enter/exit the viewport. Powers scroll-triggered animations, lazy loading, section navigation highlighting, and the tutorial overlay first-visit detection.

Lazy Load Scroll Reveal Section Tracking Performance

Reveal on Scroll

Watch [data-reveal] elements and add .visible class when they enter viewport. Unobserve after first trigger for performance.

JavaScript
const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.classList.add('visible');
        observer.unobserve(entry.target);
      }
    });
  },
  { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }
);

// Observe all reveal elements
document.querySelectorAll('[data-reveal]').forEach(el => {
  observer.observe(el);
});
Scroll Reveal
Scroll down to reveal boxes...
Box 1 - Fade In
Box 2 - Slide Up
Box 3 - Animate
CS

Custom Cursor System

Custom UI Interaction

Fully custom cursor with dot, follower, glow, trail, hover labels, and text mode. Centralized in shared-bg.js — injects CSS and DOM on every page. z-index 99999 to stay above all overlays.

CSS Injection Mouse Tracking Hover Labels Trail Effect

Inject Cursor CSS + DOM

shared-bg.js dynamically injects cursor CSS and creates DOM elements, so every page gets the same cursor without duplicate code.

JavaScript
// shared-bg.js — inject cursor CSS
const style = document.createElement('style');
style.textContent = `
  .cursor {
    position: fixed; width: 8px; height: 8px;
    background: #6c63ff; border-radius: 50%;
    pointer-events: none; z-index: 99999;
    transition: transform 0.1s;
  }
  .cursor-follower {
    position: fixed; width: 40px; height: 40px;
    border: 1px solid rgba(108,99,255,0.5);
    border-radius: 50%; pointer-events: none;
    z-index: 99999; transition: all 0.15s;
  }
`;
document.head.appendChild(style);

// Create cursor elements
const cursor = document.createElement('div');
cursor.className = 'cursor';
document.body.appendChild(cursor);
Cursor Demo
Move mouse over this area
x: 0, y: 0
PL

Preloader System

10 Styles Loading Animation

10 unique animated preloaders: Matrix Rain, Glitch, Particles, Pulse, Orbit, Wave, Typewriter, Circuit, DNA Helix, and Cyber Grid. Random selection on each visit, user preference saved.

Canvas CSS Animations localStorage Random Rotation

Preloader Manager

Loads random preloader, tracks completion, fades out. User can choose their favorite via the gear icon selector.

JavaScript
// 10 preloader classes
const PRELOADERS = [
  'MatrixPreloader',
  'GlitchPreloader',
  'ParticlePreloader',
  'PulsePreloader',
  'OrbitPreloader',
  'WavePreloader',
  'TypewriterPreloader',
  'CircuitPreloader',
  'DnaPreloader',
  'CyberGridPreloader'
];

// Load saved or random
const saved = localStorage.getItem('preloader-type');
const type = saved || PRELOADERS[Math.floor(
  Math.random() * PRELOADERS.length
)];
Preloader Gallery
Spin
Pulse
Bounce
AI

AI Chat Module

Custom AI Integration

Full-featured AI chat with streaming responses, thinking display, KaTeX LaTeX, Markdown rendering, 255 tools (Code, Files, Web, System, Security, AI, DevOps), theme control, and localStorage persistence.

Streaming 255 Tools OpenRouter API localStorage

Streaming AI Response

Sends chat history to OpenRouter API, streams tokens in real-time, renders Markdown + LaTeX + code highlighting. Saves conversation to localStorage.

JavaScript
const response = await fetch('https://openrouter.ai/api/v1/chat', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'nvidia/nemotron-3-super-120b-a12b:free',
    messages: [{ role: 'user', content: msg }],
    stream: true
  })
});

// Stream tokens
const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  // Parse SSE, append to chat
}
Chat UI Mock
How do I sort an array in Go?
Use sort.Slice() from the sort package:

sort.Slice(arr, func(i, j int) bool {
  return arr[i] < arr[j]
})
AW

AI Floating Widget

Custom Floating Chat Bot

Self-contained floating chat widget — appears on every page as a bottom-right button. Expandable panel with streaming AI, Markdown, LaTeX, and localStorage persistence. Zero configuration.

Self-contained Floating UI All Pages Auto-init

Auto-Initialize Widget

Include the script, widget auto-creates its own DOM, styles, and event listeners. Works on any page with zero HTML changes.

HTML
<!-- Just include the script -->
<script src="js/ai-widget.js"></script>

<!-- Widget auto-creates: -->
<!-- - Floating button (bottom-right) -->
<!-- - Expandable chat panel -->
<!-- - Streaming AI responses -->
<!-- - Markdown + LaTeX rendering -->
<!-- - localStorage persistence -->
Floating Widget
Page content area...
AI
AD

Google AdSense

ca-pub-2384016999231870 Monetization
Site ↗

Google's ad network for monetizing the portfolio. 30 ad spaces across 7 rows — banners and responsive squares. Help Me Grow section encourages visitors to support via ad clicks.

30 Ad Spaces Responsive Auto Ads Revenue

Ad Unit Setup

Verification script in <head>, ad units with <ins> tags, and push script. 30 spaces in banner/square grid layout.

HTML
<!-- In <head> -->
<script async
  src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-2384016999231870"
  crossorigin="anonymous"></script>

<!-- Ad unit -->
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client="ca-pub-2384016999231870"
     data-ad-slot="SLOT_ID"
     data-ad-format="auto"
     data-full-width-responsive="true"></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
Ad Layout
Banner Ad (728x90)
Square (300x250)
Content Area
Banner Ad (728x90)
RD

Responsive Design

CSS3+ Layout System

Mobile-first responsive design with CSS Grid, Flexbox, and media queries. Every page adapts from 320px mobile to 4K desktop. Hamburger menu, fluid typography, and adaptive layouts.

Mobile-first CSS Grid Flexbox Fluid Typography

Responsive Breakpoints

Standard breakpoints for mobile, tablet, and desktop. Fluid typography with clamp() for smooth scaling.

CSS
/* Fluid typography */
h1 {
  font-size: clamp(2rem, 5vw, 4rem);
}

/* Mobile breakpoint */
@media (max-width: 768px) {
  .nav-links { display: none; }
  .mobile-menu { display: flex; }
}

/* Desktop breakpoint */
@media (min-width: 1200px) {
  .container { max-width: 1140px; }
}
Breakpoint Demo
[MB]
Width: 100%
TH

Theme Toggle (Dark/Light)

Custom UI System

Pill-shaped toggle with moon/stars (dark) and sun (light) SVG icons. Switches data-theme attribute on <html>. CSS variables update instantly. State saved to localStorage.

data-theme CSS Variables SVG Icons localStorage

Theme Switching

Toggle data-theme on <html>. All colors are CSS variables that change with the theme attribute.

JavaScript
const toggle = document.getElementById('themeToggle');
let theme = localStorage.getItem('theme') || 'dark';

document.documentElement.setAttribute('data-theme', theme);

toggle.addEventListener('click', () => {
  theme = theme === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
});
Theme Switch
Preview Box

This box switches between dark and light themes using CSS variables.

Card 1
Card 2
Card 3
// interactive

Code Playground

Edit the code and hit Run. Live preview renders in real-time.

editor.js
output.html