📌 Cyberpunk neon sign canvas: flicker e glitch (PHP/CSS/JS)

Cyberpunk neon sign canvas: tutorial completo per costruire un pannello arcade con testo neon su HTML5 Canvas, flicker realistico e glitch cromatico. Struttura pulita in PHP, CSS e JavaScript, con gestione HiDPI e prefers-reduced-motion.

Insegna luminosa cyberpunk con un’onda a forma di infinito nei colori ciano e magenta dietro una lastra di vetro.

Autore: alessiopuppi · Creato: 26/02/2026 22:47

Cyberpunk Neon Sign: pannello arcade con Canvas, flicker e glitch (PHP + CSS + JS)

In questo articolo realizziamo un pannello "arcade cyberpunk" con un'insegna al neon disegnata su HTML5 Canvas, completa di glow multi-pass, flicker realistico, glitch cromatico, scanlines e riflesso "vetro" animato. La pagina e divisa in tre file separati: PHP (markup + testo iniziale sanitizzato), CSS (look cyberpunk) e JS (render e animazioni).

Struttura dei file

  • neon-cyberpunk.php - genera la pagina e imposta il testo iniziale in modo sicuro
  • neon-cyberpunk.css - costruisce pannello, cornice, scanlines, griglia "holo" e atmosfera
  • neon-cyberpunk.js - disegna il neon su canvas e gestisce flicker/glitch/respirazione

Come funziona (in breve, ma serio)

  • Canvas: il testo neon non e un semplice effetto CSS; viene disegnato e stratificato (profondita + glow + core bianco).
  • Flicker: non e un on/off banale; usa burst e micro-drop casuali (stile neon reale).
  • Glitch: brevi separazioni cromatiche (chroma split) per un look arcade aggressivo.
  • Sincronizzazione: il JS aggiorna la variabile CSS --I per far reagire cornice e sfondo alla luminosita del neon.
  • Accessibilita: con prefers-reduced-motion attivo, riduce le animazioni.

Installazione

  1. Crea i tre file con i nomi indicati e mettili nella stessa cartella.
  2. Apri neon-cyberpunk.php tramite server PHP (non via file statico), in locale o sul tuo hosting.
  3. Opzionale: puoi impostare un testo iniziale via URL: ?t=hello (sanitizzato, max 18 caratteri).

Personalizzazioni rapide

  • Palette: modifica --cyan e --magenta in neon-cyberpunk.css.
  • Testo default: cambia $defaultText in neon-cyberpunk.php.
  • Flicker piu aggressivo: in JS alza le probabilita in stepEvents() (soglie tipo 0.06, 0.10, 0.22).

Codice completo

1) neon-cyberpunk.php

<?php
// neon-cyberpunk.php

// Testo iniziale (puoi cambiarlo qui)
$defaultText = "WELCOME";

// Se vuoi permettere override via URL: ?t=hello
// Sanitizzazione: solo lettere/numeri/spazi/.-_ e lunghezza max 18
$raw = isset($_GET['t']) ? (string)$_GET['t'] : '';
$raw = trim($raw);
if ($raw !== '') {
    $raw = preg_replace('/[^a-zA-Z0-9 \.\-\_]/', '', $raw);
    $raw = mb_substr($raw, 0, 18, 'UTF-8');
    $text = strtoupper($raw);
} else {
    $text = $defaultText;
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Cyberpunk Neon Sign</title>
  <link rel="stylesheet" href="neon-cyberpunk.css" />
</head>
<body>

  <div id="board" data-text="<?php echo htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); ?>">
    <div id="top">
      <div id="screwTL" aria-hidden="true"></div>
      <div id="screwTR" aria-hidden="true"></div>
      <div id="screwBL" aria-hidden="true"></div>
      <div id="screwBR" aria-hidden="true"></div>

      <div id="plate">
        <div id="hint">CYBER SIGN - Canvas Neon - Flicker + Glitch</div>
        <canvas id="neonCanvas"></canvas>
        <div id="grid" aria-hidden="true"></div>
        <div id="glass" aria-hidden="true"></div>
      </div>
    </div>

    <div id="bottom">
      <form id="signForm" autocomplete="off">
        <input id="signInput" type="text" maxlength="18" placeholder="Scrivi (max 18) e premi SEND..." />
        <button type="submit">SEND</button>
      </form>
    </div>
  </div>

  <script src="neon-cyberpunk.js"></script>
</body>
</html>

2) neon-cyberpunk.css

:root{
  --I: 1;                      /* intensita neon 0.35..1 (JS) */
  --cyan: 0,255,255;
  --magenta: 255,0,255;
  --bg0: #03010a;
  --bg1: #070a18;
}

html,body{height:100%}
body{
  margin:0;
  display:grid;
  place-items:center;
  padding:24px;
  color:#fff;
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  overflow:hidden;

  background:
    radial-gradient(1200px 700px at 50% 10%, rgba(120,40,255,0.22), rgba(0,0,0,0) 60%),
    radial-gradient(900px 600px at 20% 80%, rgba(0,255,255,0.10), rgba(0,0,0,0) 55%),
    radial-gradient(900px 600px at 85% 75%, rgba(255,0,255,0.10), rgba(0,0,0,0) 55%),
    linear-gradient(120deg, var(--bg0), var(--bg1));
}

body::before{
  content:"";
  position:fixed;
  inset:-40%;
  background:
    conic-gradient(from 180deg at 50% 50%,
      rgba(0,255,255,0.05),
      rgba(255,0,255,0.06),
      rgba(0,255,255,0.05));
  filter: blur(60px);
  opacity: 0.7;
  animation: haze 10s linear infinite;
  pointer-events:none;
}
@keyframes haze{ to{ transform: rotate(360deg) } }

/* ===== BOARD ===== */
#board{
  width:min(820px, 94vw);
  border-radius:18px;
  overflow:hidden;
  position:relative;
  transform: perspective(1200px) rotateX(1.3deg);
  border:1px solid rgba(255,255,255,0.08);
  background: linear-gradient(135deg, #11131a, #080a0f 55%, #0f121a);

  box-shadow:
    0 35px 90px rgba(0,0,0,0.70),
    0 0 calc(45px * var(--I)) rgba(var(--cyan), 0.14),
    0 0 calc(28px * var(--I)) rgba(var(--magenta), 0.12);
}

#board::before{
  content:"";
  position:absolute;
  inset:8px;
  border-radius:14px;
  pointer-events:none;
  box-shadow:
    0 0 calc(26px * var(--I)) rgba(var(--cyan), 0.25),
    0 0 calc(18px * var(--I)) rgba(var(--magenta), 0.18),
    inset 0 0 0 1px rgba(255,255,255,0.06),
    inset 0 18px 24px rgba(255,255,255,0.04),
    inset 0 -22px 30px rgba(0,0,0,0.60);
  mix-blend-mode: screen;
  opacity: 0.9;
}

/* scanlines overlay */
#board::after{
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background:
    repeating-linear-gradient(
      0deg,
      rgba(255,255,255,0.020) 0px,
      rgba(255,255,255,0.020) 1px,
      rgba(0,0,0,0) 4px,
      rgba(0,0,0,0) 9px
    ),
    radial-gradient(900px 260px at 50% 0%, rgba(255,255,255,0.08), rgba(0,0,0,0) 60%),
    radial-gradient(700px 220px at 50% 110%, rgba(var(--cyan), 0.08), rgba(0,0,0,0) 60%);
  opacity: 0.70;
  animation: scan 2.2s linear infinite;
}
@keyframes scan{
  0%{ transform: translateY(-2px) }
  100%{ transform: translateY(2px) }
}

#top{
  position:relative;
  padding:18px;
  background:
    radial-gradient(1200px 260px at 50% 0%, rgba(255,255,255,0.08), rgba(255,255,255,0) 55%),
    linear-gradient(180deg, #0b0d13, #070910);
  border-bottom: 1px solid rgba(255,255,255,0.08);
}

/* screws */
#screwTL,#screwTR,#screwBL,#screwBR{
  position:absolute;
  width:14px;height:14px;border-radius:50%;
  background:
    radial-gradient(circle at 30% 30%, rgba(255,255,255,0.35), rgba(255,255,255,0) 45%),
    radial-gradient(circle at 50% 55%, #2b2f35 0%, #15181c 60%, #0b0d10 100%);
  border:1px solid rgba(255,255,255,0.08);
  box-shadow:
    inset 0 1px 1px rgba(255,255,255,0.15),
    inset 0 -1px 2px rgba(0,0,0,0.7),
    0 2px 6px rgba(0,0,0,0.6);
  opacity:0.9;
}
#screwTL{top:10px;left:10px}
#screwTR{top:10px;right:10px}
#screwBL{bottom:10px;left:10px}
#screwBR{bottom:10px;right:10px}

/* plate */
#plate{
  position:relative;
  border-radius:14px;
  overflow:hidden;
  border:1px solid rgba(255,255,255,0.08);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    inset 0 -22px 34px rgba(0,0,0,0.60),
    0 12px 28px rgba(0,0,0,0.60),
    0 0 calc(24px * var(--I)) rgba(var(--cyan), 0.18),
    0 0 calc(20px * var(--I)) rgba(var(--magenta), 0.14);

  background:
    radial-gradient(700px 180px at 50% 45%, rgba(255,255,255,0.06), rgba(255,255,255,0) 65%),
    repeating-linear-gradient(
      0deg,
      rgba(255,255,255,0.018) 0px,
      rgba(255,255,255,0.018) 1px,
      rgba(0,0,0,0) 3px,
      rgba(0,0,0,0) 7px
    ),
    linear-gradient(180deg, #090a10, #04050a);
}

#hint{
  position:absolute;
  top:10px; left:14px; right:14px;
  font-size:12px;
  opacity:0.65;
  text-align:left;
  letter-spacing:0.2px;
  user-select:none;
  z-index:3;
}

#neonCanvas{
  display:block;
  width:100%;
  height:140px;
}

/* glass sweep */
#glass{
  position:absolute;
  inset:0;
  pointer-events:none;
  background:
    linear-gradient(115deg,
      rgba(255,255,255,0.18) 0%,
      rgba(255,255,255,0.06) 18%,
      rgba(255,255,255,0.00) 42%,
      rgba(255,255,255,0.10) 62%,
      rgba(255,255,255,0.00) 88%
    );
  mix-blend-mode: screen;
  opacity:0.82;
  transform: translateX(-35%);
  animation: sweep 3.2s ease-in-out infinite;
  filter: blur(0.25px);
}
@keyframes sweep{
  0%   { transform: translateX(-45%) skewX(-10deg) }
  50%  { transform: translateX( 25%) skewX(-10deg) }
  100% { transform: translateX(-45%) skewX(-10deg) }
}

/* holo grid */
#grid{
  position:absolute;
  inset:0;
  pointer-events:none;
  background:
    linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.55)),
    repeating-linear-gradient(90deg, rgba(var(--cyan),0.05) 0 1px, rgba(0,0,0,0) 1px 34px),
    repeating-linear-gradient(0deg,  rgba(var(--magenta),0.04) 0 1px, rgba(0,0,0,0) 1px 28px);
  opacity: calc(0.35 + 0.25 * var(--I));
  mix-blend-mode: screen;
  animation: drift 7s linear infinite;
}
@keyframes drift{ to{ transform: translate(28px, 22px) } }

/* bottom haze + form */
#bottom{
  position:relative;
  height:320px;
  background:
    radial-gradient(900px 260px at 50% 0%,
      rgba(var(--cyan), calc(0.10 + 0.30 * var(--I))) 0%,
      rgba(var(--magenta), calc(0.06 + 0.18 * var(--I))) 40%,
      rgba(0,0,0,0) 65%
    ),
    linear-gradient(180deg,
      rgba(var(--cyan), calc(0.35 + 0.55 * var(--I))) 0%,
      rgba(10,10,14,0.65) 70%,
      rgba(0,0,0,0.80) 100%
    );
  box-shadow: inset 0 18px 30px rgba(0,0,0,0.35);
}
#bottom::before{
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background:
    radial-gradient(800px 260px at 50% 20%, rgba(255,255,255,0.06), rgba(0,0,0,0) 60%),
    repeating-linear-gradient(0deg, rgba(255,255,255,0.016) 0 1px, rgba(0,0,0,0) 1px 10px);
  opacity: 0.7;
}

#signForm{
  position:absolute;
  left:5%;
  width:90%;
  bottom:22px;
  display:flex;
  gap:10px;
  z-index:2;
}

#signInput, #signForm button{
  font-size:16px;
  padding:12px 14px;
  border-radius:12px;
  border:1px solid rgba(255,255,255,0.10);
  color:#fff;
  background: rgba(0,0,0,0.22);
  outline:none;
  box-shadow: inset 0 10px 20px rgba(0,0,0,0.35);
}

#signInput{
  flex:1;
}

#signInput:focus{
  border-color: rgba(var(--cyan), 0.45);
  box-shadow:
    inset 0 10px 20px rgba(0,0,0,0.35),
    0 0 20px rgba(var(--cyan), 0.18),
    0 0 18px rgba(var(--magenta), 0.10);
}

#signForm button{
  cursor:pointer;
  background: linear-gradient(180deg, rgba(255,255,255,0.12), rgba(0,0,0,0.25));
  box-shadow: 0 12px 28px rgba(0,0,0,0.40);
  transition: transform .15s ease, filter .15s ease;
  white-space:nowrap;
}
#signForm button:hover{
  transform: translateY(-1px);
  filter: brightness(1.08);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce){
  body::before, #glass, #grid, #board::after{ animation: none !important; }
  #board{ transform:none; }
}

3) neon-cyberpunk.js

const board = document.getElementById("board");
const canvas = document.getElementById("neonCanvas");
const ctx = canvas.getContext("2d");

const prefersReduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

let neonText = (board.getAttribute("data-text") || "WELCOME").toUpperCase().slice(0,18);

let I = 1;
let targetI = 1;
let glitch = 0;
let nextEvent = 0;

function clamp(x,a,b){ return Math.max(a, Math.min(b,x)); }

function setupHiDPI(){
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();
  const cssW = Math.max(320, Math.round(rect.width));
  const cssH = Math.max(120, Math.round(rect.height));

  canvas.width  = Math.round(cssW * dpr);
  canvas.height = Math.round(cssH * dpr);

  // Disegno in coordinate CSS
  ctx.setTransform(dpr,0,0,dpr,0,0);

  return { W: cssW, H: cssH };
}

let dims = setupHiDPI();
window.addEventListener("resize", () => { dims = setupHiDPI(); });

function stepEvents(){
  if (prefersReduced){
    targetI = 1;
    glitch = 0;
    return;
  }

  const r = Math.random();

  // glitch cromatico breve (arcade)
  if (r < 0.06){
    glitch = 1;
    setTimeout(() => { glitch = 0; }, 60 + Math.random()*90);
  }

  // flicker: burst o drop
  if (r < 0.10){
    const hits = 3 + Math.floor(Math.random()*4); // 3-6
    let k = 0;
    const burst = () => {
      const off = (k % 2 === 0);
      targetI = off ? 0.42 : 1;
      k++;
      if (k < hits) setTimeout(burst, 28 + Math.random()*45);
      else targetI = 1;
    };
    burst();
  } else if (r < 0.22){
    targetI = 0.45;
    setTimeout(() => { targetI = 1; }, 55 + Math.random()*90);
  } else {
    targetI = 0.88 + Math.random()*0.12;
  }
}

function draw(){
  const W = dims.W, H = dims.H;
  ctx.clearRect(0,0,W,H);

  // vignette interna
  const g = ctx.createRadialGradient(W*0.5,H*0.5, 30, W*0.5,H*0.5, W*0.62);
  g.addColorStop(0, "rgba(255,255,255,0.04)");
  g.addColorStop(1, "rgba(0,0,0,0.45)");
  ctx.fillStyle = g;
  ctx.fillRect(0,0,W,H);

  const cx = W/2, cy = H/2 + 2;

  const fontSize = Math.round(Math.min(74, Math.max(46, W/12)));
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.font = `900 ${fontSize}px Arial`;

  // profondita pseudo-3D
  const depth = 10;
  for (let i=depth; i>0; i--){
    const k = i/depth;
    ctx.fillStyle = `rgba(0,0,0,${0.48 + 0.40*k})`;
    ctx.fillText(neonText, cx + i*1.25, cy + i*1.15);
  }

  // glow esterno multipass (cyan + magenta)
  ctx.save();
  ctx.globalCompositeOperation = "lighter";

  ctx.shadowBlur = 32 * I;
  ctx.shadowColor = "rgba(0,255,255,1)";
  for (let w=18; w>=8; w-=5){
    ctx.lineWidth = w;
    ctx.strokeStyle = `rgba(0,255,255,${0.18*I})`;
    ctx.strokeText(neonText, cx, cy);
  }

  ctx.shadowColor = "rgba(255,0,255,1)";
  ctx.shadowBlur = 26 * I;
  for (let w=16; w>=7; w-=4){
    ctx.lineWidth = w;
    ctx.strokeStyle = `rgba(255,0,255,${0.12*I})`;
    ctx.strokeText(neonText, cx, cy);
  }
  ctx.restore();

  // corpo tubo
  ctx.save();
  ctx.shadowBlur = 26 * I;
  ctx.shadowColor = "rgba(0,255,255,1)";
  ctx.fillStyle = `rgba(0,255,255,${0.70*I})`;
  ctx.fillText(neonText, cx, cy);
  ctx.restore();

  // core bianco
  ctx.save();
  ctx.globalCompositeOperation = "screen";
  ctx.shadowBlur = 12 * I;
  ctx.shadowColor = "rgba(255,255,255,1)";
  ctx.fillStyle = `rgba(255,255,255,${0.55*I})`;
  ctx.font = `900 ${Math.max(10, fontSize-2)}px Arial`;
  ctx.fillText(neonText, cx, cy);
  ctx.restore();

  // outline
  ctx.save();
  ctx.lineWidth = 2;
  ctx.strokeStyle = `rgba(0,255,255,${0.95*I})`;
  ctx.strokeText(neonText, cx, cy);
  ctx.restore();

  // glitch cromatico (chroma split)
  if (glitch > 0){
    const dx = (Math.random()*10 - 5) * glitch;
    const dy = (Math.random()*4 - 2) * glitch;

    ctx.save();
    ctx.globalCompositeOperation = "screen";
    ctx.font = `900 ${fontSize}px Arial`;

    ctx.fillStyle = `rgba(255,0,255,${0.30*I})`;
    ctx.fillText(neonText, cx + dx, cy + dy);

    ctx.fillStyle = `rgba(0,255,255,${0.30*I})`;
    ctx.fillText(neonText, cx - dx*0.8, cy - dy*0.8);

    ctx.fillStyle = `rgba(255,255,255,${0.15*I})`;
    ctx.fillText(neonText, cx + dx*0.3, cy - dy*0.3);
    ctx.restore();
  }

  // scintille
  if (!prefersReduced && Math.random() < 0.10 * (1 - I + 0.15)){
    const sx = cx + (Math.random()*W*0.7 - W*0.35);
    const sy = cy + (Math.random()*H*0.35 - H*0.18);
    ctx.save();
    ctx.globalCompositeOperation = "lighter";
    ctx.fillStyle = `rgba(255,255,255,${0.22*I})`;
    ctx.shadowBlur = 14*I;
    ctx.shadowColor = "rgba(0,255,255,1)";
    ctx.beginPath();
    ctx.arc(sx, sy, 1.2 + Math.random()*1.4, 0, Math.PI*2);
    ctx.fill();
    ctx.restore();
  }
}

function tick(ts){
  // eventi ogni ~120-280ms
  if (!prefersReduced && ts > nextEvent){
    stepEvents();
    nextEvent = ts + (120 + Math.random()*220);
  }

  // smoothing intensita (inerzia)
  I += (targetI - I) * 0.25;
  I = clamp(I, 0.35, 1);

  // esporta a CSS var
  board.style.setProperty("--I", I.toFixed(3));

  draw();
  requestAnimationFrame(tick);
}

requestAnimationFrame(tick);

// Form: cambia testo
document.getElementById("signForm").addEventListener("submit", (e) => {
  e.preventDefault();
  const inp = document.getElementById("signInput");
  const v = (inp.value || "").trim();
  if (!v) return;

  neonText = v.toUpperCase().slice(0,18);
  inp.value = "";

  // colpo di luce + glitch breve
  targetI = 1;
  if (!prefersReduced){
    glitch = 1;
    setTimeout(() => { glitch = 0; }, 90);
  }
});

Chiusura

Questo pannello non e un "effettino": e una mini-interfaccia cyberpunk completa, riutilizzabile in hero section, pagine demo o componenti UI. La separazione PHP/CSS/JS mantiene il progetto pulito e pronto da integrare nel tuo flusso editoriale.

CYBER SIGN · Canvas Neon · Flicker + Glitch
Messaggio sponsorizzato
Pubblicità

🔗 Condividi l'articolo:

Autore: alessiopuppi · Creato: 26/02/2026 22:47 · Ultima modifica: 03/09/2026 12:18