vibe coded this. nostr firehose stream, media only, scroll to pause, tap media to get to profile
https://nostrstream.replit.app
nice in full screen.
(you may see things you don't want to see)
Login to reply
Replies (70)
this is the online equivalent of skinny dipping
Dont know what vibe coding is and im afraid to ask at this point. 🫠
Cool
💜! wanna slow scroll this so bad
this is unfortunately really cool
nevent1qqsdx7crpdl7s2myeuc04exgxt2wgyea9v0e8xqqaa58dpdfrdvgp4q08gneq
hate when an oomfie that hates me serves
How does this work exactly? Is it a “livestream” of all the media posted to nostr as it gets uploaded? Is it in chronological order?
just scroll and it pauses
Oops 😬 

source:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nostr Media Feed</title>
<style>
:root {
--bg-color: #ffffff;
--text-color: #333333;
}
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #ffffff;
}
body {
font-family: -apple-system, system-ui, sans-serif;
margin: 0;
padding: 0;
background: var(--bg-color);
color: var(--text-color);
}
#header {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 15px 20px;
background: var(--bg-color);
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
font-size: 14px;
}
#feed {
margin-top: 52px;
}
.note {
margin-bottom: 0;
}
.media-container {
background: #000;
line-height: 0;
width: 100%;
}
.media-container a {
display: block;
line-height: 0;
}
.media-container img,
.media-container video {
width: 100%;
height: auto;
object-fit: contain;
opacity: 0;
transition: opacity 0.5s ease-in;
}
.media-container img.loaded,
.media-container video.loaded {
opacity: 1;
}
#status {
display: flex;
align-items: center;
gap: 6px;
opacity: 0.7;
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
.status-live .status-dot {
background: #4CAF50;
}
.status-paused .status-dot {
background: #ff9800;
}
@media (min-width: 800px) {
.media-container img,
.media-container video {
max-height: 100vh;
}
}
@media (max-width: 799px) {
.media-container img,
.media-container video {
max-height: none;
}
}
</style>
</head>
<body>
<div id="header">
<div id="status" class="status-live">
<div class="status-dot"></div>
<span>Live</span>
</div>
</div>
<div id="feed"></div>
<script>
// Auto dark mode
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.setAttribute('data-theme', 'dark');
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
});
// Initialize
const feed = document.getElementById('feed');
const status = document.getElementById('status');
const seenNotes = new Set();
const seenMedia = new Set();
let isPaused = false;
// Relays
const RELAYS = [
'wss://relay.damus.io',
'wss://relay.nostr.band',
'wss://nos.lol',
'wss://relay.nostr.info'
];
const relayPool = new Map();
// Function to update status display
function updateStatus(paused) {
status.className = paused ? 'status-paused' : 'status-live';
status.querySelector('span').textContent = paused ? 'Paused' : 'Live';
}
// Pause/Resume based on scroll position
let lastScrollTop = 0;
window.addEventListener('scroll', () => {
const st = window.pageYOffset || document.documentElement.scrollTop;
if (st > lastScrollTop && st > 100) {
// Scrolling down
if (!isPaused) {
isPaused = true;
updateStatus(true);
}
} else if (st === 0) {
// At top
if (isPaused) {
isPaused = false;
updateStatus(false);
}
}
lastScrollTop = st;
});
// Connect to relays
function connect() {
let connectedRelays = 0;
RELAYS.forEach(relayUrl => {
const socket = new WebSocket(relayUrl);
relayPool.set(relayUrl, socket);
socket.onopen = () => {
connectedRelays++;
if (connectedRelays === 1) {
updateStatus(false);
}
// Subscribe to notes with media
const recentSub = JSON.stringify([
"REQ",
"recent_" + relayUrl,
{
"kinds": [1],
"limit": 500
}
]);
socket.send(recentSub);
};
socket.onclose = () => {
relayPool.delete(relayUrl);
connectedRelays--;
if (connectedRelays === 0) {
setTimeout(() => connect(), 2000);
}
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
// Handle incoming messages
socket.onmessage = async (event) => {
if (isPaused) return;
const data = JSON.parse(event.data);
if (data[0] !== 'EVENT') return;
const msg = data[2];
// Handle notes
if (msg.kind !== 1) return;
if (seenNotes.has(msg.id)) return;
seenNotes.add(msg.id);
// Look for media URLs
const mediaUrls = [];
const urlRegex = /(https?:\/\/[^\s<]+\.(jpg|jpeg|png|gif|mp4|webm))/gi;
let match;
while ((match = urlRegex.exec(msg.content)) !== null) {
mediaUrls.push(match[0]);
}
if (mediaUrls.length === 0) return;
// Check for duplicate media
const mediaKey = mediaUrls.sort().join(',');
if (seenMedia.has(mediaKey)) return;
seenMedia.add(mediaKey);
try {
// Create note element
const noteEl = document.createElement('div');
noteEl.className = 'note';
// Add media
const mediaContainer = document.createElement('div');
mediaContainer.className = 'media-container';
// Make media container clickable
const mediaLink = document.createElement('a');
mediaLink.href = `https://njump.me/${msg.id}`;
mediaLink.target = '_blank';
mediaLink.style.cursor = 'pointer';
mediaContainer.appendChild(mediaLink);
for (const url of mediaUrls) {
if (url.match(/\.(jpg|jpeg|png|gif)$/i)) {
try {
// Create and preload image
const img = document.createElement('img');
img.style.opacity = '0';
img.src = url;
img.loading = 'lazy';
// Wait for image to load
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
// Add to container and fade in
mediaLink.appendChild(img);
requestAnimationFrame(() => {
img.style.opacity = '1';
});
} catch (e) {
console.error('Failed to load image:', url);
}
} else {
const video = document.createElement('video');
video.src = url;
video.controls = true;
video.autoplay = true;
video.muted = true;
video.loop = true;
video.playsInline = true;
video.style.opacity = '0';
// Fade in once video starts playing
video.addEventListener('playing', () => {
requestAnimationFrame(() => {
video.style.opacity = '1';
});
}, { once: true });
video.onerror = () => {
video.remove();
};
mediaLink.appendChild(video);
// Try to start playing
video.play().catch(e => console.log('Auto-play prevented:', e));
}
}
noteEl.appendChild(mediaContainer);
feed.insertBefore(noteEl, feed.firstChild);
} catch (e) {
console.error('Error creating note element:', e);
}
};
});
}
// Initial connection
connect();
</script>
</body>
</html>
thank you. this is great!
Letsgoo
nevent1qqsdx7crpdl7s2myeuc04exgxt2wgyea9v0e8xqqaa58dpdfrdvgp4q08gneq
"Ela quer massacrar os olhos delas com incessantes imagens de telejornais" -- Fausto Fawcett
I want to see those things obvi
This is wild lol
As I understand it’s when you have an idea and talk to Ai to code it for you.
Is it the beginning of tiktok for nostr?
¯\_(ツ)_/¯
It works on other browsers but tor says:
error:forbidden
Weird.
I don’t get what it is. I just saw a bunch of images going by. Please explain, thank you.
@jack is talented too. Thanks for sharing. ✨
nevent1qqsdx7crpdl7s2myeuc04exgxt2wgyea9v0e8xqqaa58dpdfrdvgp4q08gneq
tell em don’t hate, appreci-roya-te
Nice. Kinda funny, amethyst thinks the CSS colors are hashtags and lists them at the bottom of this post 😅
It's like a Tiktok FYP 👍
Weird but very cool. Very nostr.
You created what leeloo was looking at .


Multipass?
“May see things you don’t want to see” is an understatement
This is some of the best code I've seen in a nostr client. Unlike every other client, it uses pure javascript, which is native everywhere. It doesnt require a compiler. It's blazingly fast. And it just works. Well done Jack, this is a gold standard in how to write client apps. Keep going!
nah, i definitely annoy the old man, prob got me muted
Will there be filters if don't want to see the unwanted stuff?
nope
Oh that’s great. Thank you leadbyexample.
What unwanted stuff ppl asking to get censored on this platform???
So pleasing to use..
nevent1qqsdx7crpdl7s2myeuc04exgxt2wgyea9v0e8xqqaa58dpdfrdvgp4q08gneq
maybe learn
So much hentai 😅
Did you “maybe learn” because I asked a question? Was that really necessary?
Are you okay?
Are you sure? For me it's not stopping.
I love that this is vanilla js.
This is nostr.TV ! ..
A remote with just one button - click to see the profile :-)
😂 heard you’re into chaos

you can just make things
nevent1qqsdx7crpdl7s2myeuc04exgxt2wgyea9v0e8xqqaa58dpdfrdvgp4q08gneq

LFG vibes
lol I've seen exactly what I wanted to see...
Anything produced by Jack is defintely something I don't want to see.
View quoted note →
glad we’re up to speed
vibecoding and nostr are a match made in heaven
😈 wild sht! 😁
nevent1qqsdx7crpdl7s2myeuc04exgxt2wgyea9v0e8xqqaa58dpdfrdvgp4q08gneq
Replit is cool & Lovable is awesome
View quoted note →
It's not vibe coding unless your using the free versions of Ai.
If you pay for an Ai to code for you, you've hired a digital worker!
ジャックが Nostr クライアント作ってる👀
Sweet.
I kind of wish I had the exact opposite. Like instead of something to just view. Something that just posts notes.
I'm picturing myself driving and having a fire note to drop. . .
I hold down a button voice to text opens up I say my note and then when I release it sends.
No other interface needed other than maybe a text editor.
Could do the same with a camera app...
Productivity gains would be nice but I'm also thinking of people in protest trying to get information out right before they get gassed.
I have a dev position open btw
not working on Tor and Ironfox browsers.
Gorgeous on Chrome so far.
doesn't show anything for me
simp
you know, the type of shit hitler saw every time he closed his eyes
i love the media only, simplicity to use and portrayal of the divergent information. it feels so internet-y that you can’t help but smile.
you should commit to an app per week attempt (like your reading goal) and get the juices flowing.
A few years ago, I saw more eerie than this.
No. It happened several times at Facebook.
Computational Intelligence?
Spiral mind down.
An endless labyrinth.
It’s still happening at Twitter/X.
A peer into the human conscious of the world.
Amazing!! Can you share the code of it please as a base to fork for Replit?
👀
This is awesome.
(I did not see anything too smutty.)
BITCH SLUT @npub1jmq8...yzuz You CHEAP WHORE 😂😂
You reposted this AFTER @jack SLUT-FUCKED you for your birthday?
Or BEFORE he BOUGHT YOUR CHEAP WHORE ASS to FUCK?
😂😂😂