Back to CSS Neon Synthwave Scene CSS + JS
Share
HTML
<section class="nn-syn" aria-label="Synthwave scene demo">
  <div class="scene">

    <div class="stars" data-nn-syn-stars aria-hidden="true"></div>

    <div class="sun-wrap" aria-hidden="true">
      <div class="sun"></div>
      <div class="sun-lines"></div>
    </div>

    <div class="horizon" aria-hidden="true"></div>

    <div class="mountains" aria-hidden="true">
      <svg viewBox="0 0 700 100" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
        <defs>
          <linearGradient id="nn-syn-mtnGrad" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stop-color="#1a0035"/>
            <stop offset="100%" stop-color="#0a0015"/>
          </linearGradient>
        </defs>
        <polygon points="0,100 0,65 60,30 120,58 180,18 240,52 300,5 360,48 420,22 480,55 540,28 600,60 660,35 700,48 700,100" fill="url(#nn-syn-mtnGrad)"/>
        <line x1="0" y1="65" x2="60" y2="30" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="60" y1="30" x2="120" y2="58" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="120" y1="58" x2="180" y2="18" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="180" y1="18" x2="240" y2="52" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="240" y1="52" x2="300" y2="5" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="300" y1="5" x2="360" y2="48" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="360" y1="48" x2="420" y2="22" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="420" y1="22" x2="480" y2="55" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="480" y1="55" x2="540" y2="28" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="540" y1="28" x2="600" y2="60" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="600" y1="60" x2="660" y2="35" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
        <line x1="660" y1="35" x2="700" y2="48" stroke="rgba(255,0,200,0.5)" stroke-width="0.8"/>
      </svg>
    </div>

    <div class="grid-wrap" aria-hidden="true">
      <div class="grid"></div>
      <div class="grid-fade"></div>
    </div>

    <div class="scanlines" aria-hidden="true"></div>

    <div class="title-wrap">
      <div class="title-main">SYNTHWAVE</div>
      <div class="title-sub">Retro · Future · Neon</div>
      <div class="year">◈ 1 9 8 6 ◈</div>
    </div>

  </div>
</section>
CSS
/* ─── 04 Synthwave Scene — 80s retrofuture composition ─────────── */
@import url('https://fonts.googleapis.com/css2?family=Audiowide&family=Oxanium:wght@700;800&display=swap');

.nn-syn {
  position: relative;
  width: 100%;
  min-height: 560px;
  background: #0a0015;
  font-family: 'Audiowide', cursive;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 16px;
  overflow: hidden;
  box-sizing: border-box;
}

.nn-syn *,
.nn-syn *::before,
.nn-syn *::after { box-sizing: border-box; margin: 0; padding: 0; }

.nn-syn .scene {
  position: relative;
  width: 700px;
  max-width: 100%;
  height: 480px;
  overflow: hidden;
  background: linear-gradient(
    180deg,
    #090010 0%,
    #130025 30%,
    #1a003a 55%,
    #260050 70%,
    #0f0020 100%
  );
}

.nn-syn .stars { position: absolute; inset: 0 0 50% 0; overflow: hidden; }
.nn-syn .star {
  position: absolute;
  background: #fff;
  border-radius: 50%;
  animation: nn-syn-twinkle var(--dur, 2s) var(--del, 0s) ease-in-out infinite alternate;
}
@keyframes nn-syn-twinkle {
  from { opacity: var(--min, 0.3); transform: scale(1); }
  to   { opacity: 1; transform: scale(1.4); }
}

.nn-syn .sun-wrap {
  position: absolute;
  left: 50%;
  top: 24%;
  transform: translateX(-50%);
  width: 160px;
  height: 160px;
}
.nn-syn .sun {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: linear-gradient(
    180deg,
    #ffe066 0%,
    #ff9900 20%,
    #ff2d78 45%,
    #c800ff 65%,
    #6600cc 80%,
    #2a0060 100%
  );
  box-shadow:
    0 0 30px rgba(255,150,0,0.6),
    0 0 70px rgba(255,80,150,0.5),
    0 0 120px rgba(200,0,255,0.35),
    0 0 200px rgba(100,0,200,0.2);
  animation: nn-syn-sunpulse 4s ease-in-out infinite;
}
@keyframes nn-syn-sunpulse {
  0%, 100% { box-shadow: 0 0 28px rgba(255,150,0,0.55), 0 0 65px rgba(255,80,150,0.45), 0 0 110px rgba(200,0,255,0.3); }
  50%      { box-shadow: 0 0 42px rgba(255,150,0,0.75), 0 0 90px rgba(255,80,150,0.6),  0 0 160px rgba(200,0,255,0.45); }
}

.nn-syn .sun-lines {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  pointer-events: none;
}
.nn-syn .sun-lines::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    0deg,
    transparent 0px,
    transparent 8px,
    rgba(10,0,20,0.55) 8px,
    rgba(10,0,20,0.55) 12px
  );
}

.nn-syn .horizon {
  position: absolute;
  left: 0;
  right: 0;
  top: 56%;
  height: 4px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(255,60,180,0.8) 20%,
    #ff2aff 50%,
    rgba(255,60,180,0.8) 80%,
    transparent 100%
  );
  box-shadow:
    0 0 18px rgba(255,40,255,0.8),
    0 0 45px rgba(255,40,255,0.4),
    0 0 80px rgba(255,40,255,0.2);
}

.nn-syn .grid-wrap {
  position: absolute;
  left: 0;
  right: 0;
  top: 57%;
  bottom: 0;
  overflow: hidden;
  perspective: 300px;
}
.nn-syn .grid {
  position: absolute;
  width: 200%;
  left: -50%;
  top: 0;
  height: 300%;
  transform-origin: top center;
  transform: rotateX(55deg) translateY(0);
  background-image:
    linear-gradient(rgba(255,0,200,0.7) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,0,200,0.7) 1px, transparent 1px);
  background-size: 60px 60px;
  animation: nn-syn-gridscroll 1.5s linear infinite;
}
@keyframes nn-syn-gridscroll {
  from { background-position: 0 0; }
  to   { background-position: 0 60px; }
}
.nn-syn .grid-fade {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(10,0,21,0.0) 0%, rgba(10,0,21,0.95) 100%),
    linear-gradient(90deg, rgba(10,0,21,0.8) 0%, transparent 15%, transparent 85%, rgba(10,0,21,0.8) 100%);
  pointer-events: none;
}

.nn-syn .mountains {
  position: absolute;
  left: 0;
  right: 0;
  top: 43%;
  height: 18%;
  overflow: hidden;
}
.nn-syn .mountains svg { width: 100%; height: 100%; }

.nn-syn .scanlines {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    0deg,
    transparent,
    transparent 2px,
    rgba(0,0,0,0.06) 2px,
    rgba(0,0,0,0.06) 3px
  );
  pointer-events: none;
  z-index: 10;
}

.nn-syn .title-wrap {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-bottom: 80px;
  z-index: 5;
}
.nn-syn .title-main {
  font-family: 'Oxanium', sans-serif;
  font-size: 56px;
  font-weight: 800;
  letter-spacing: 0.18em;
  color: #fff;
  text-shadow:
    0 0 6px #fff,
    0 0 16px #ff69d0,
    0 0 36px #ff2aff,
    0 0 70px rgba(200,0,255,0.7);
  animation: nn-syn-titlebreath 3s ease-in-out infinite;
}
.nn-syn .title-sub {
  font-family: 'Audiowide', cursive;
  font-size: 13px;
  letter-spacing: 0.55em;
  color: rgba(255,150,230,0.85);
  text-shadow: 0 0 8px rgba(255,50,200,0.7), 0 0 20px rgba(255,0,200,0.4);
  margin-top: 6px;
  text-transform: uppercase;
}
@keyframes nn-syn-titlebreath {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.88; }
}
.nn-syn .year {
  font-family: 'Audiowide', cursive;
  font-size: 11px;
  letter-spacing: 0.3em;
  color: rgba(255,220,0,0.8);
  text-shadow: 0 0 10px rgba(255,200,0,0.7);
  margin-top: 10px;
}

@media (max-width: 720px) {
  .nn-syn .scene { width: 100%; height: 380px; }
  .nn-syn .title-main { font-size: 40px; }
  .nn-syn .sun-wrap { width: 120px; height: 120px; }
}

@media (prefers-reduced-motion: reduce) {
  .nn-syn .star,
  .nn-syn .sun,
  .nn-syn .grid,
  .nn-syn .title-main { animation: none !important; }
}
JS
(() => {
  // Scoped star injection — finds the stars container inside .nn-syn
  // only, so the demo can render anywhere on the page without
  // colliding on a global element id.
  const root = document.querySelector('.nn-syn');
  if (!root) return;
  const stars = root.querySelector('[data-nn-syn-stars]');
  if (!stars || stars.childElementCount > 0) return;
  for (let i = 0; i < 90; i++) {
    const s = document.createElement('div');
    s.className = 'star';
    const size = Math.random() * 2.2 + 0.5;
    s.style.cssText =
      'width:' + size + 'px;' +
      'height:' + size + 'px;' +
      'left:' + (Math.random() * 100) + '%;' +
      'top:' + (Math.random() * 100) + '%;' +
      '--dur:' + (Math.random() * 2.5 + 1.2).toFixed(2) + 's;' +
      '--del:' + (Math.random() * 3).toFixed(2) + 's;' +
      '--min:' + (Math.random() * 0.3 + 0.1).toFixed(2) + ';';
    stars.appendChild(s);
  }
})();