CodePen Blog
404: Preventing Infinite Loops from Crashing the Browser
Stephen and Chris hop on to talk about how we’re saving everyone from crashed browser tabs in CodePen’s 2.0 editor. One simple:
Executing JavaScript can cause a browser tab to entirely lock up, preventing you from doing anything, like potentially saving your work. It can even crash other same-domain tabs. But not on our watch! CodePen is now using a “heartbeat” technique to report up from the preview iframe to the parent page, and if we don’t hear the heartbeat, we can rip out the iframe and stop the crash. But it was very tricky to get working and not too jumpy.
Fortunately, we got it all working, because our previous technique of instrumenting your JavaScript wasn’t going to scale well to the 2.0 editor.
Time Jumps
* 00:05 404 error
* 00:45 Dealing with infinite loops for the new editor
* 02:48 What happens when a browser tab freezes?
* 06:51 Why instrumenting worked
* 09:24 Alex’s heartbeat solution
* 14:59 How the UI works
* 19:10 Dealing with JavaScript alert, confirm, and prompt messages
* 20:34 Dealing with tab visibility
404: Preventing Infinite Loops from Crashing the Browser
Stephen and Chris hop on to talk about how we’re saving everyone from crashed browser tabs in CodePen’s 2.0 editor. One simple:
while(true) { } Executing JavaScript can cause a browser tab to entirely lock up, preventing you from doing anything, like potentially saving your work. It can even crash other same-domain tabs. But not on our watch! CodePen is now using a “heartbeat” technique to report up from the preview iframe to the parent page, and if we don’t hear the heartbeat, we can rip out the iframe and stop the crash. But it was very tricky to get working and not too jumpy.
Fortunately, we got it all working, because our previous technique of instrumenting your JavaScript wasn’t going to scale well to the 2.0 editor.
Time Jumps
* 00:05 404 error
* 00:45 Dealing with infinite loops for the new editor
* 02:48 What happens when a browser tab freezes?
* 06:51 Why instrumenting worked
* 09:24 Alex’s heartbeat solution
* 14:59 How the UI works
* 19:10 Dealing with JavaScript alert, confirm, and prompt messages
* 20:34 Dealing with tab visibility
📛 Scam Alert – Fake Telegram Crypto Bots!
🚫 Beware of Telegram bots promising "95 USDT bonus" for entering a "special code". These bots:
0.00300 BNB withdrawal fee (often in crypto like BNB)
Never pay anything
Have no official website or verified source
Are often promoted in fake YouTube videos with scripted comments
🎥 Even if the video shows a "successful withdrawal", it's most likely fake!
❗ Example of such a scam bot message:
✅ NEVER send money to claim rewards from unknown bots.
📢 Report such bots and warn others.
🔐 Stay safe in the crypto space.
#ScamAlert #CryptoScam #TelegramScam #BNBScam #StaySafe
🚫 Beware of Telegram bots promising "95 USDT bonus" for entering a "special code". These bots:
0.00300 BNB withdrawal fee (often in crypto like BNB)
Never pay anything
Have no official website or verified source
Are often promoted in fake YouTube videos with scripted comments
🎥 Even if the video shows a "successful withdrawal", it's most likely fake!
❗ Example of such a scam bot message:
Your Balance - 95 USDT 🔸 Min. Withdraw - 2 USDT 📮 Fee - 0.00300 BNB 📤 Enter The Amount Of You Want To Send 👇 ✅ NEVER send money to claim rewards from unknown bots.
📢 Report such bots and warn others.
🔐 Stay safe in the crypto space.
#ScamAlert #CryptoScam #TelegramScam #BNBScam #StaySafe
# Install required packages first:
# pip install pytube moviepy
from pytube import YouTube
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip
# --------------------------
# 1. Download YouTube video
# --------------------------
video_url = "https://www.youtube.com/watch?v=YOUR_VIDEO_ID"
yt = YouTube(video_url)
stream = yt.streams.filter(file_extension='mp4', progressive=True).order_by('resolution').desc().first()
video_path = "original_video.mp4"
stream.download(filename=video_path)
# --------------------------
# 2. Load the video
# --------------------------
video_clip = VideoFileClip(video_path)
# --------------------------
# 3. Get original audio & reduce volume
# --------------------------
original_audio = video_clip.audio.volumex(0.3) # 0.3 = 30% volume
# --------------------------
# 4. Load your translation/dub audio
# --------------------------
# IMPORTANT: The dub audio must already have the same timing as the original (with pauses where needed)
dub_audio_path = "dub_translation.mp3" # Your recorded translation file
dub_audio = AudioFileClip(dub_audio_path)
# --------------------------
# 5. Combine both audios (in sync)
# --------------------------
final_audio = CompositeAudioClip([original_audio, dub_audio])
# --------------------------
# 6. Set final audio to video & export
# --------------------------
final_video = video_clip.set_audio(final_audio)
final_video.write_videofile("dubbed_video.mp4", codec="libx264", audio_codec="aac")
print("✅ Dubbed video created successfully!")
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Metallic Button Demo</title>
<style>
:root{
--metal-base: #b8bcc0; /* main metal tone */
--metal-dark: #6b6f74; /* edge/shadow */
--metal-light: #e9ebed; /* highlight */
--accent: #f6f7f8; /* small top sheen */
--text: #0b1220;
--padding: 14px 30px;
--radius: 12px;
}
body{
display:flex;
gap:24px;
min-height:100vh;
align-items:center;
justify-content:center;
background: linear-gradient(180deg, #111217 0%, #1b1f25 100%);
font-family: Inter, Roboto, -apple-system, "Segoe UI", sans-serif;
padding:40px;
}
.metal-btn{
--gloss-opacity: 0.55;
appearance:none;
border: none;
cursor: pointer;
display:inline-block;
padding: var(--padding);
border-radius: var(--radius);
color: var(--text);
font-weight: 600;
font-size: 16px;
letter-spacing: .6px;
position: relative;
user-select: none;
outline: none;
transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
/* outer rim + subtle drop */
box-shadow:
0 6px 18px rgba(2,8,20,.6),
inset 0 1px 0 rgba(255,255,255,.06);
/* layered gradients to form metallic surface */
background-image:
linear-gradient(180deg, rgba(0,0,0,.18), rgba(255,255,255,.02)),
radial-gradient(120% 60% at 10% 12%, rgba(255,255,255,.12), transparent 18%),
linear-gradient(180deg, var(--metal-light), var(--metal-base) 45%, var(--metal-dark) 100%);
color: #071018;
padding: 12px 28px;
}
/* bevel edge using :before */
.metal-btn::before{
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
/* fine rim and inner-dark rim for depth */
box-shadow:
inset 0 1px 0 rgba(255,255,255,.6),
inset 0 -6px 18px rgba(0,0,0,.35);
mix-blend-mode: normal;
opacity: .95;
}
/* glossy cut (shine) */
.metal-btn::after{
content: "";
position: absolute;
left: 8%;
right: 8%;
top: 8%;
height: 36%;
border-radius: 999px;
transform: skewX(-6deg);
background: linear-gradient(180deg,
rgba(255,255,255,var(--gloss-opacity)) 0%,
rgba(255,255,255,.18) 35%,
rgba(255,255,255,0) 100%);
filter: blur(.6px);
pointer-events: none;
mix-blend-mode: screen;
}
/* pressed / active state */
.metal-btn:active{
transform: translateY(2px) scale(.997);
box-shadow:
0 3px 10px rgba(2,8,20,.55),
inset 0 1px 0 rgba(255,255,255,.03);
filter: brightness(.94);
}
/* hover gives a brighter sheen + slight lift */
.metal-btn:hover{
transform: translateY(-3px);
box-shadow:
0 18px 36px rgba(2,8,20,.55),
inset 0 1px 0 rgba(255,255,255,.06);
filter: saturate(1.04) brightness(1.03);
}
/* focus for accessibility */
.metal-btn:focus{
box-shadow:
0 12px 28px rgba(0,80,160,.18),
inset 0 1px 0 rgba(255,255,255,.06);
outline: 3px solid rgba(30,144,255,.12);
}
/* small label variant */
.metal-btn.small{ padding:8px 18px; font-size:13px; border-radius:10px; }
/* alternate metal tones — bronze and dark steel */
.metal-btn.bronze{
--metal-base: #c28a45;
--metal-dark: #6b3f1a;
--metal-light: #f1d7b2;
color: #2b1608;
}
.metal-btn.steel{
--metal-base: #cfd7df;
--metal-dark: #86949e;
--metal-light: #f7fbfe;
color: #081226;
}
</style>
</head>
<body>
<button class="metal-btn">Metallic Button</button>
<button class="metal-btn bronze">Bronze</button>
<button class="metal-btn steel small">Steel Small</button>
</body>
</html>
CodePen Blog
Chris’ Corner: Browser Wars Micro Edition
Ages ago, Firefox shipped “masonry layout” where you simply
There has been renewed interest in this the past year or so, where Google and Apple have competing ideas on how to do this style of layout better.
Google-folk think
Apple-folk think we could keep it in
Three browsers, three different situations, including shipped implementations.
Let’s hope this shakes out cleanly.
Chris’ Corner: Browser Wars Micro Edition
Ages ago, Firefox shipped “masonry layout” where you simply
grid-template-rows: masonry; instead of defining specific rows in a grid or letting them auto-create. It wasn’t terribly powerful but it did the trick for a lot of use cases. There has been renewed interest in this the past year or so, where Google and Apple have competing ideas on how to do this style of layout better.
Google-folk think
display: masonry; is best, with a bunch of other properties controlling how it works. They’ve now shipped that behind a flag, so I think we know where their head is at. Apple-folk think we could keep it in
display: grid; but introduce “Item Flow” properties, including one called item-pack which would accomplish the layout and perhaps open the door for other useful behaviors across layout styles.Three browsers, three different situations, including shipped implementations.
Let’s hope this shakes out cleanly.
❤1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Alert Box</title>
<style>
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #081021;
font-family: Arial, sans-serif;
}
.alert-btn {
padding: 12px 24px;
background: #ff9a9e;
border: none;
border-radius: 6px;
color: #fff;
font-size: 18px;
cursor: pointer;
transition: transform 0.2s;
}
.alert-btn:hover {
transform: scale(1.05);
}
.alert {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%) translateY(-50px);
background: #fad0c4;
color: #081021;
padding: 16px 24px;
border-radius: 8px;
font-weight: bold;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease, transform 0.5s ease;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.alert.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
pointer-events: auto;
}
</style>
</head>
<body>
<button class="alert-btn">Show Alert</button>
<div class="alert" id="alertBox">This is a CSS Alert!</div>
<script>
const btn = document.querySelector('.alert-btn');
const alertBox = document.getElementById('alertBox');
btn.addEventListener('click', () => {
alertBox.classList.add('show');
setTimeout(() => {
alertBox.classList.remove('show');
}, 3000);
});
</script>
</body>
</html>
Html codes
Photo
CodePen Blog
Chris’ Corner: Faces
It’s one of those weeks where I just feel like posting some specimen screenshots of new-to-me typefaces that I like and have saved.
https://blog.codepen.io/wp-content/uploads/2025/08/haffer.gif Haffer XH
https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.44.28-PM-1024x575.png Babcock https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.40.27-PM-1024x740.png Bernie https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.41.26-PM-1024x730.png Dumpling https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.42.23-PM-1024x417.png Ultramega https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.43.06-PM-1024x660.png Gottak https://blog.codepen.io/wp-content/uploads/2025/08/ampersands-1024x521.webp Pixel Imperfect https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.45.38-PM-1024x475.png Scorekard
Chris’ Corner: Faces
It’s one of those weeks where I just feel like posting some specimen screenshots of new-to-me typefaces that I like and have saved.
https://blog.codepen.io/wp-content/uploads/2025/08/haffer.gif Haffer XH
https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.44.28-PM-1024x575.png Babcock https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.40.27-PM-1024x740.png Bernie https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.41.26-PM-1024x730.png Dumpling https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.42.23-PM-1024x417.png Ultramega https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.43.06-PM-1024x660.png Gottak https://blog.codepen.io/wp-content/uploads/2025/08/ampersands-1024x521.webp Pixel Imperfect https://blog.codepen.io/wp-content/uploads/2025/08/Screenshot-2025-08-15-at-2.45.38-PM-1024x475.png Scorekard
CodePen Blog
405: Elasticsearch → Postgres Search
Alex & Chris get into a fairly recent technological change at CodePen where we ditched our Elasticsearch implementation for just using our own Postgres database for search. Sometimes choices like this are more about team expertise, dev environment practicalities, and complexity tradeoffs. We found this change to be much better for us, which matters! For the most part search is better and faster. Postgres is not nearly as fancy and capable as Elasticsearch, but we werent taking advantage of what Elasticsearch had to offer anyway.
For the power users out there: it’s true that we’ve lost the ability to do in-code search for now. But it’s temporary and will be coming back in time.
Time Jumps
* 00:07 Alex is back!
* 01:10 The history of search at CodePen
* 02:15 Why was Elasticsearch not the right choice for CodePen?
* 09:36 Why we’re using PostGres instead
* 15:18 What does triggers have to do with search?
* 21:02 Figuring out what people are actually searching for
* 24:19 Some of the tradeoffs in changing search
405: Elasticsearch → Postgres Search
Alex & Chris get into a fairly recent technological change at CodePen where we ditched our Elasticsearch implementation for just using our own Postgres database for search. Sometimes choices like this are more about team expertise, dev environment practicalities, and complexity tradeoffs. We found this change to be much better for us, which matters! For the most part search is better and faster. Postgres is not nearly as fancy and capable as Elasticsearch, but we werent taking advantage of what Elasticsearch had to offer anyway.
For the power users out there: it’s true that we’ve lost the ability to do in-code search for now. But it’s temporary and will be coming back in time.
Time Jumps
* 00:07 Alex is back!
* 01:10 The history of search at CodePen
* 02:15 Why was Elasticsearch not the right choice for CodePen?
* 09:36 Why we’re using PostGres instead
* 15:18 What does triggers have to do with search?
* 21:02 Figuring out what people are actually searching for
* 24:19 Some of the tradeoffs in changing search