Skip to content

Instantly share code, notes, and snippets.

@affandhia
Last active May 11, 2026 10:11
Show Gist options
  • Select an option

  • Save affandhia/b5df54b8d10699dfdc0fecabafaea5cb to your computer and use it in GitHub Desktop.

Select an option

Save affandhia/b5df54b8d10699dfdc0fecabafaea5cb to your computer and use it in GitHub Desktop.
Portal for all gist standalone apps and related stuff.
<!doctype html>
<html lang="en">
<head>
<!-- Core document metadata -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gist App Portal</title>
<meta
name="description"
content="A standalone Gist App Portal built with React, Tailwind CSS, Framer Motion, and Lucide React."
/>
<meta name="keywords" content="React, Tailwind CSS, Framer Motion, Lucide, Gist App Portal" />
<meta name="author" content="Your Name" />
<meta name="robots" content="index, follow" />
<!-- Canonical URL -->
<link rel="canonical" href="https://example.com/" />
<!-- Inline SVG favicon: fully standalone, no external favicon file needed -->
<link
rel="icon"
type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='16' fill='%230b1224'/%3E%3Cpath d='M18 39c7-15 21-15 28 0' fill='none' stroke='%233dd5f3' stroke-width='5' stroke-linecap='round'/%3E%3Ccircle cx='24' cy='25' r='4' fill='white'/%3E%3Ccircle cx='40' cy='25' r='4' fill='white'/%3E%3Cpath d='M22 46h20' stroke='white' stroke-width='4' stroke-linecap='round' opacity='.8'/%3E%3C/svg%3E"
/>
<!-- Theme and mobile metadata -->
<meta name="theme-color" content="#0b1224" />
<meta name="color-scheme" content="dark light" />
<meta name="application-name" content="Gist App Portal" />
<meta name="apple-mobile-web-app-title" content="Gist App Portal" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!-- Open Graph metadata -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Gist App Portal" />
<meta property="og:title" content="Gist App Portal" />
<meta
property="og:description"
content="A standalone Gist App Portal built with React, Tailwind CSS, Framer Motion, and Lucide React."
/>
<meta property="og:url" content="https://example.com/" />
<meta
property="og:image"
content="https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=1200&h=630&q=80"
/>
<meta property="og:image:alt" content="Laptop showing code on a desk" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:locale" content="en_US" />
<!-- Twitter/X card metadata -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Gist App Portal" />
<meta
name="twitter:description"
content="A standalone Gist App Portal built with React, Tailwind CSS, Framer Motion, and Lucide React."
/>
<meta
name="twitter:image"
content="https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=1200&h=630&q=80"
/>
<meta name="twitter:image:alt" content="Laptop showing code on a desk" />
<!-- Optional: set these if you have Twitter/X handles -->
<meta name="twitter:site" content="@yourhandle" />
<meta name="twitter:creator" content="@yourhandle" />
<!-- Tailwind Play CDN: quick dev only, not for production -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<!-- Babel standalone to compile JSX in-browser: dev only -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Import map: all bare imports resolve from here -->
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18.2.0?dev",
"react-dom": "https://esm.sh/react-dom@18.2.0?dev",
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev",
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime?dev",
"react/jsx-dev-runtime": "https://esm.sh/react@18.2.0/jsx-dev-runtime?dev",
"framer-motion": "https://esm.sh/framer-motion@12.26.0?dev&external=react,react-dom",
"lucide-react": "https://esm.sh/lucide-react@0.268.0?dev&external=react",
"clsx": "https://esm.sh/clsx@2.1.0?dev",
"tailwind-merge": "https://esm.sh/tailwind-merge@2.5.2?dev",
"fuse.js": "https://esm.sh/fuse.js@7.0.0?dev"
}
}
</script>
<style type="text/tailwindcss">
@theme {
--color-ink: #0b1224;
--color-brand: #3dd5f3;
--color-userscript: #a855f7;
}
@layer base {
body {
@apply antialiased;
}
}
</style>
</head>
<body class="min-h-screen bg-ink text-white">
<div id="app"></div>
<script type="text/babel" data-type="module" data-presets="react">
import React, { useState, useMemo, useRef, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { motion, AnimatePresence } from 'framer-motion';
import { Search, ExternalLink, FileCode2, AppWindow, Github } from 'lucide-react';
import clsx from 'clsx';
import { twMerge } from 'tailwind-merge';
import Fuse from 'fuse.js';
const cn = (...args) => twMerge(clsx(args));
// Utility function to highlight text
const highlightText = (text, matches, highlightClass) => {
if (!matches || matches.length === 0) {
return text;
}
const result = [];
let lastIndex = 0;
matches.forEach(match => {
const [start, end] = match; // indices are inclusive start, exclusive end for Fuse.js
// Add text before the match
if (start > lastIndex) {
result.push(text.substring(lastIndex, start));
}
// Add the highlighted text
result.push(<span className={highlightClass} key={start + '-' + end}>{text.substring(start, end + 1)}</span>);
lastIndex = end + 1;
});
// Add any remaining text after the last match
if (lastIndex < text.length) {
result.push(text.substring(lastIndex));
}
return result;
};
const gistData = [
{
id: '1',
title: 'Env File Merger',
description: 'Merge multiple .env files and add overrides.',
url: 'https://gist.githack.com/affanpg/1b9d60e16a0e1109ffb41db186dcafb8/raw/f694e9d787cc50edacb47d0d711c549879dc658b/env-merger.html',
gistUrl: 'https://gist.github.com/affanpg/1b9d60e16a0e1109ffb41db186dcafb8'
},
{
id: '2',
title: 'SMTP Copy Button Userscript',
description: "Adds a 'Copy OTP' button to SMTP pages.",
url: 'https://gist.githack.com/affanpg/9d822b707ce89848237b0a9736e681f9/raw/77665b361dcf900eae6b8ca10de0a6d2b61e053a/smtp-copy-button-script.user.js',
gistUrl: 'https://gist.github.com/affanpg/9d822b707ce89848237b0a9736e681f9'
},
{
id: '3',
title: 'GitHub Resolve All Conversations Userscript',
description: 'Adds a button to resolve all conversations in a GitHub PR.',
url: 'https://gist.githack.com/affanpg/97151fb0659214aa31776df063f3a710/raw/1d5930262af0022eaa8954427692457aff2de5e1/resolve-all-conversations.user.js',
gistUrl: 'https://gist.github.com/affanpg/97151fb0659214aa31776df063f3a710'
},
{
id: '4',
title: 'React + Tailwind + Framer Motion + Lucide standalone starter kit',
description: 'A standalone starter kit for building web apps with React, Tailwind CSS, Framer Motion, and Lucide.',
url: 'https://gist.githack.com/affanpg/0aac4c511edcb22b15571f3bb44a9ab4/raw/a6b139a0399554a4755913e6488a033f923b320e/react-tailwind-framer-lucide-starter.html',
gistUrl: 'https://gist.github.com/affanpg/0aac4c511edcb22b15571f3bb44a9ab4'
},
{
id: '5',
title: 'JSON Field Picker',
description: 'An interactive tool to pick, extract, and filter specific fields from JSON objects using path selectors.',
url: 'https://gist.githack.com/affanpg/84003ccf7d595f251e24d9ccfa0bad93/raw/0c17e3f8a6142c6c39540b79367468535a907727/json-field-picker.html',
gistUrl: 'https://gist.github.com/affanpg/84003ccf7d595f251e24d9ccfa0bad93'
},
{
id: '6',
title: 'navigator.modelContext Demo',
description: 'A demonstration of the navigator.modelContext API for providing context to the browser\'s built-in AI assistant.',
url: 'https://gist.githack.com/affanpg/2d50d683258e9ef656973133159bead8/raw/26e492215f72671520146c641d8e12613241b1d3/navigator-model-context-demo.html',
gistUrl: 'https://gist.github.com/affanpg/2d50d683258e9ef656973133159bead8'
}
];
const GistCard = React.forwardRef(({ result }, forwardedRef) => {
const cardRef = useRef(null);
const [isHovered, setIsHovered] = useState(false);
// This callback ref merges the forwardedRef with the local cardRef
const setRefs = React.useCallback(
(node) => {
// Ref's from useRef are mutable objects that we can assign to
cardRef.current = node;
// However, the forwardedRef can be either a ref object or a callback function
if (typeof forwardedRef === 'function') {
forwardedRef(node);
} else if (forwardedRef) {
forwardedRef.current = node;
}
},
[forwardedRef]
);
const handleMouseMove = (e) => {
if (!cardRef.current) return;
const rect = cardRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
cardRef.current.style.setProperty("--mouse-x", `${x}px`);
cardRef.current.style.setProperty("--mouse-y", `${y}px`);
};
const isUserscript = result.item.url.endsWith('.user.js') || result.item.title.toLowerCase().includes('userscript');
const typeLabel = isUserscript ? 'Userscript' : 'Web App';
const TypeIcon = isUserscript ? FileCode2 : AppWindow;
return (
<motion.div
ref={setRefs}
layout
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -10 }}
transition={{ duration: 0.3, ease: "easeOut" }}
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
whileHover={{ y: -4, scale: 1.01 }}
className={cn(
"relative w-full rounded-2xl bg-white/5 border border-white/10 overflow-hidden group cursor-pointer shadow-lg",
isUserscript ? "hover:shadow-userscript/5" : "hover:shadow-brand/5"
)}
onClick={() => window.open(result.item.url, '_blank')}
>
{/* Spotlight background effect */}
<div
className="pointer-events-none absolute -inset-px rounded-2xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
style={{
background: `radial-gradient(600px circle at var(--mouse-x) var(--mouse-y), ${isUserscript ? 'rgba(168, 85, 247, 0.08)' : 'rgba(61, 213, 243, 0.08)'}, transparent 40%)`,
}}
/>
{/* Spotlight border effect */}
<div
className={cn(
"pointer-events-none absolute inset-0 rounded-2xl border opacity-0 transition-opacity duration-300 group-hover:opacity-100",
isUserscript ? "border-userscript" : "border-brand"
)}
style={{
maskImage: `radial-gradient(300px circle at var(--mouse-x) var(--mouse-y), black, transparent)`,
WebkitMaskImage: `radial-gradient(300px circle at var(--mouse-x) var(--mouse-y), black, transparent)`,
}}
/>
<div className="relative p-6 z-10 flex flex-col h-full">
<div className="flex justify-between items-start mb-4">
<div className="flex items-center space-x-3">
<div className={cn(
"p-2 rounded-xl shadow-inner border",
isUserscript ? "bg-userscript/10 border-userscript/20 text-userscript" : "bg-white/5 border-white/10 text-brand"
)}>
<TypeIcon className="w-4 h-4" />
</div>
<span className="text-xs font-semibold tracking-widest uppercase text-white/50">
{typeLabel}
</span>
</div>
<div className="relative w-8 h-8 flex items-center justify-center">
<motion.div
initial={false}
animate={{
opacity: isHovered ? 1 : 0,
x: isHovered ? 0 : -10,
scale: isHovered ? 1 : 0.8
}}
transition={{ duration: 0.2, ease: "easeOut" }}
className={cn(
"absolute inset-0 rounded-full flex items-center justify-center",
isUserscript ? "bg-userscript/20 text-userscript" : "bg-brand/20 text-brand"
)}
>
<ExternalLink className="w-4 h-4" />
</motion.div>
<motion.div
initial={false}
animate={{
opacity: isHovered ? 0 : 1,
scale: isHovered ? 0.8 : 1
}}
transition={{ duration: 0.2 }}
className="absolute inset-0 flex items-center justify-center text-white/20"
>
<ExternalLink className="w-4 h-4" />
</motion.div>
</div>
</div>
<div className="flex-1 py-2">
<h2 className={cn(
"text-2xl font-bold text-white mb-3 tracking-tight transition-colors duration-300",
isUserscript ? "group-hover:text-userscript" : "group-hover:text-brand"
)}>
{highlightText(
result.item.title,
result.matches?.filter(m => m.key === 'title').flatMap(m => m.indices),
'bg-yellow-300 text-ink rounded px-0.5'
)}
</h2>
<p className="text-white/60 text-sm leading-relaxed line-clamp-2">
{highlightText(
result.item.description,
result.matches?.filter(m => m.key === 'description').flatMap(m => m.indices),
'bg-yellow-300 text-ink rounded px-0.5'
)}
</p>
</div>
<div className="pt-5 mt-4 border-t border-white/10 flex items-center justify-between">
<a
href={result.item.gistUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center space-x-2 text-xs font-medium text-white/40 hover:text-white transition-colors z-20 px-3 py-1.5 -ml-3 rounded-md hover:bg-white/5"
onClick={(e) => e.stopPropagation()}
>
<Github className="w-4 h-4" />
<span>View Source on GitHub</span>
</a>
</div>
</div>
</motion.div>
);
});
const App = () => {
const [query, setQuery] = useState('');
const searchRef = useRef(null);
const [shortcut, setShortcut] = useState('');
useEffect(() => {
const isMac = navigator.userAgent.includes('Mac');
setShortcut(isMac ? '⌘K' : 'Ctrl+K');
const handler = (event) => {
// Ignore if any modifier other than meta/ctrl with K is used for slash
const key = event.key;
const isSlash = key === '/';
const isCmdK = (key === 'k' || key === 'K') && (event.metaKey || event.ctrlKey);
if (!isSlash && !isCmdK) return;
// Don't hijack typing in other inputs or editable areas
const active = document.activeElement;
const tag = active && active.tagName ? active.tagName.toLowerCase() : '';
const isEditable = active && (active.isContentEditable || tag === 'input' || tag === 'textarea' || tag === 'select');
if (isEditable) return;
event.preventDefault();
if (searchRef.current && typeof searchRef.current.focus === 'function') {
searchRef.current.focus();
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, []);
const fuse = useMemo(() => new Fuse(gistData, {
keys: ['title', 'description'],
threshold: 0.4,
ignoreLocation: true,
includeMatches: true // Added for highlighting
}), []);
const filteredGists = useMemo(() => {
if (!query) return gistData.map(item => ({ item })); // Ensure consistent structure for non-search results
return fuse.search(query); // Return full results with matches
}, [query, fuse]);
return (
<main
className={cn(
'flex min-h-screen flex-col items-center py-16 px-6',
'bg-gradient-to-br from-ink via-slate-900 to-cyan-950',
)}
>
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
className="w-full max-w-2xl"
>
<h1 className="text-4xl font-bold tracking-tight text-center mb-10 text-white">
Gist App Portal
</h1>
<div className="relative mb-10 group">
<input
type="text"
className="w-full bg-white/5 border border-white/10 rounded-2xl py-4 pl-14 pr-20 text-white placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-brand/50 focus:border-brand/50 focus:bg-white/10 backdrop-blur-md transition-all duration-300 shadow-lg hover:bg-white/10"
placeholder="Search gists by title or description..."
value={query}
onChange={(e) => setQuery(e.target.value)}
ref={searchRef}
/>
<div className="absolute inset-y-0 left-0 pl-5 flex items-center pointer-events-none z-10">
<Search className="h-5 w-5 text-white/40 group-focus-within:text-brand transition-colors duration-300" />
</div>
{shortcut && (
<div className="absolute inset-y-0 right-0 pr-4 flex items-center pointer-events-none z-10">
<span className="bg-white/10 rounded-lg px-3 py-1.5 text-sm font-medium text-white/70 border border-white/20 shadow-sm backdrop-blur-md flex items-center justify-center gap-0.5 transition-colors duration-300 group-focus-within:border-brand/30 group-focus-within:text-brand/90">
{shortcut === '⌘K' ? (
<>
<span className="text-base leading-none">⌘</span>
<span className="leading-none">K</span>
</>
) : (
<span>{shortcut}</span>
)}
</span>
</div>
)}
</div>
<div className="space-y-6">
<AnimatePresence mode="popLayout">
{filteredGists.length > 0 ? (
filteredGists.map((result) => (
<GistCard key={result.item.id} result={result} />
))
) : (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="text-center py-16 text-white/50 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm"
>
<Search className="h-8 w-8 mx-auto mb-4 opacity-50" />
<p>No gists found matching "{query}"</p>
</motion.div>
)}
</AnimatePresence>
</div>
</motion.div>
</main>
);
};
const rootElement = document.getElementById('app');
const root = createRoot(rootElement);
root.render(<App />);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment