1 <!DOCTYPE html>2 <html lang="es">3 <head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Contador · +2 / −3</title>7 <link rel="preconnect" href="https://fonts.googleapis.com">8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>9 <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Unbounded:wght@500;700;800&display=swap" rel="stylesheet">10 <style>11 *{box-sizing:border-box;margin:0;padding:0}12 :root{13 --bg:#0c1210;14 --panel:#131b17;15 --panel-2:#0f1713;16 --line:rgba(190,255,220,.09);17 --text:#eaf4ee;18 --muted:#7f968c;19 --green:#2fd66b;20 --green-soft:rgba(47,214,107,.14);21 --red:#ff5d5d;22 --red-soft:rgba(255,93,93,.14);23 }24 html,body{min-height:100%}25 body{26 background:var(--bg);27 color:var(--text);28 font-family:'Space Mono',monospace;29 overflow-x:hidden;30 }31 ::selection{background:var(--green);color:#04150b}32 33 /* ── fondo por capas ─────────────────────────── */34 .bg{35 position:fixed;inset:0;z-index:-2;36 background:37 radial-gradient(55% 45% at 18% 12%, rgba(47,214,107,.10), transparent 60%),38 radial-gradient(45% 40% at 85% 88%, rgba(255,93,93,.09), transparent 60%),39 linear-gradient(180deg,#0d1411,#0a0f0d);40 }41 .bg::before{42 content:"";position:absolute;inset:0;43 background:44 linear-gradient(rgba(200,255,225,.035) 1px, transparent 1px),45 linear-gradient(90deg, rgba(200,255,225,.035) 1px, transparent 1px);46 background-size:44px 44px;47 -webkit-mask-image:radial-gradient(80% 70% at 50% 40%, #000 30%, transparent 100%);48 mask-image:radial-gradient(80% 70% at 50% 40%, #000 30%, transparent 100%);49 }50 .bg::after{51 content:"";position:absolute;inset:0;opacity:.05;52 background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.6'/></svg>");53 }54 .orb{position:fixed;z-index:-1;border-radius:50%;filter:blur(90px);pointer-events:none}55 .orb.g{width:420px;height:420px;background:rgba(47,214,107,.13);top:-140px;left:-120px;animation:drift 16s ease-in-out infinite alternate}56 .orb.r{width:380px;height:380px;background:rgba(255,93,93,.11);bottom:-140px;right:-100px;animation:drift 21s ease-in-out infinite alternate-reverse}57 @keyframes drift{to{transform:translate(60px,40px) scale(1.08)}}58 59 /* ── estructura ──────────────────────────────── */60 .wrap{max-width:1120px;margin:0 auto;padding:28px 22px 40px;min-height:100vh;display:flex;flex-direction:column;gap:26px}61 62 header{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap}63 .brand{display:flex;align-items:center;gap:14px}64 .mark{65 width:44px;height:44px;border-radius:12px;display:grid;place-items:center;66 font:800 1.5rem 'Unbounded';color:#0b1210;67 background:linear-gradient(135deg,var(--green) 0%,var(--green) 50%,var(--red) 50%,var(--red) 100%);68 box-shadow:0 5px 0 rgba(0,0,0,.35),0 10px 24px rgba(0,0,0,.3);69 text-shadow:0 1px 0 rgba(255,255,255,.25);70 }71 .brand h1{font:800 1.05rem 'Unbounded';letter-spacing:.08em}72 .brand small{display:block;margin-top:6px;color:var(--muted);font-size:.62rem;letter-spacing:.22em}73 .hud{display:flex;align-items:center;gap:14px}74 .led{width:11px;height:11px;border-radius:50%;background:#33453c;transition:background .12s,box-shadow .12s}75 .led.add{background:var(--green);box-shadow:0 0 14px var(--green)}76 .led.sub{background:var(--red);box-shadow:0 0 14px var(--red)}77 78 .ghost{79 background:transparent;border:1px solid var(--line);color:var(--muted);80 font:400 .68rem 'Space Mono',monospace;letter-spacing:.14em;81 padding:9px 14px;border-radius:9px;cursor:pointer;transition:all .18s;82 display:inline-flex;align-items:center;gap:8px;83 }84 .ghost:hover{color:var(--text);border-color:rgba(190,255,220,.3);background:rgba(190,255,220,.05)}85 kbd{86 font:700 .62rem 'Space Mono',monospace;color:var(--text);87 background:rgba(190,255,220,.07);border:1px solid var(--line);border-bottom-width:2px;88 border-radius:5px;padding:2px 6px;89 }90 91 main{display:grid;grid-template-columns:1fr 330px;gap:24px;align-items:stretch;flex:1}92 93 /* ── panel principal ─────────────────────────── */94 .stage{95 background:linear-gradient(180deg,var(--panel),var(--panel-2));96 border:1px solid var(--line);border-radius:20px;97 padding:34px 26px 38px;position:relative;overflow:hidden;98 display:flex;flex-direction:column;align-items:center;justify-content:center;gap:26px;99 min-height:440px;100 box-shadow:inset 0 1px 0 rgba(255,255,255,.04),0 24px 60px rgba(0,0,0,.35);101 }102 .topline{position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--green),var(--red));opacity:.5}103 .readout-label{display:flex;align-items:center;gap:12px;color:var(--muted);font-size:.68rem;letter-spacing:.3em}104 .readout-label::before,.readout-label::after{content:"";width:34px;height:1px;background:var(--line)}105 106 .counter-box{position:relative;display:grid;place-items:center;padding:12px 0 6px}107 .counter{108 position:relative;z-index:1;109 font:700 clamp(4.2rem,15vw,9.5rem)/1 'Space Mono',monospace;110 letter-spacing:-.03em;font-variant-numeric:tabular-nums;user-select:none;111 }112 .counter.add{animation:roll-up .3s cubic-bezier(.18,.85,.3,1.12),glow-add .8s ease-out}113 .counter.sub{animation:roll-down .3s cubic-bezier(.18,.85,.3,1.12),glow-sub .8s ease-out}114 .counter.zero{animation:pop .35s ease-out}115 @keyframes roll-up{from{transform:translateY(.32em);opacity:0;filter:blur(5px)}to{transform:none;opacity:1;filter:none}}116 @keyframes roll-down{from{transform:translateY(-.32em);opacity:0;filter:blur(5px)}to{transform:none;opacity:1;filter:none}}117 @keyframes glow-add{30%{text-shadow:0 0 14px rgba(47,214,107,.6),0 0 55px rgba(47,214,107,.4)}100%{text-shadow:0 0 0 rgba(47,214,107,0)}}118 @keyframes glow-sub{30%{text-shadow:0 0 14px rgba(255,93,93,.6),0 0 55px rgba(255,93,93,.4)}100%{text-shadow:0 0 0 rgba(255,93,93,0)}}119 @keyframes pop{from{transform:scale(1.12)}to{transform:scale(1)}}120 121 .pulse{122 position:absolute;left:50%;top:50%;width:min(52vw,320px);aspect-ratio:1/1;123 margin:calc(min(52vw,320px)/-2) 0 0 calc(min(52vw,320px)/-2);124 border-radius:50%;border:2px solid transparent;opacity:0;pointer-events:none;125 }126 .pulse.add{border-color:var(--green)}127 .pulse.sub{border-color:var(--red)}128 .pulse.go{animation:pulse .7s ease-out}129 @keyframes pulse{0%{opacity:.85;transform:scale(.5)}100%{opacity:0;transform:scale(1.6)}}130 131 .chip{132 position:absolute;left:50%;top:-6px;z-index:2;transform:translateX(-50%);133 font:700 1.15rem 'Space Mono',monospace;padding:.28em .7em;border-radius:999px;134 pointer-events:none;animation:chip .95s ease-out forwards;135 }136 .chip.add{color:var(--green);background:var(--green-soft);border:1px solid rgba(47,214,107,.45)}137 .chip.sub{color:var(--red);background:var(--red-soft);border:1px solid rgba(255,93,93,.45)}138 @keyframes chip{139 0%{opacity:0;transform:translate(-50%,14px) scale(.8)}140 25%{opacity:1}141 100%{opacity:0;transform:translate(-50%,-64px) scale(1.05)}142 }143 144 .stats{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}145 .stat{146 border:1px solid var(--line);border-radius:10px;padding:8px 14px;147 font-size:.68rem;letter-spacing:.12em;color:var(--muted);148 display:flex;gap:8px;align-items:center;149 }150 .stat b{color:var(--text);font-size:.95rem}151 .stat.g b{color:var(--green)}152 .stat.r b{color:var(--red)}153 154 /* ── botones ─────────────────────────────────── */155 .controls{display:flex;gap:18px;width:100%;max-width:540px;justify-content:center;flex-wrap:wrap}156 .btn{157 flex:1 1 190px;max-width:250px;158 display:flex;flex-direction:column;align-items:center;gap:3px;159 padding:16px 18px 14px;border-radius:16px;border:1px solid rgba(0,0,0,.4);160 cursor:pointer;font-family:'Space Mono',monospace;161 transition:transform .09s ease,box-shadow .09s ease,filter .18s ease;162 }163 .btn .op{font-size:2rem;font-weight:700;line-height:1}164 .btn .lbl{font-size:.62rem;letter-spacing:.3em;opacity:.7}165 .btn:focus-visible{outline:2px solid var(--text);outline-offset:3px}166 .btn.green{167 background:linear-gradient(180deg,#43e986,#1fb463);color:#04150b;168 box-shadow:0 7px 0 #0d5a32,0 16px 34px rgba(47,214,107,.22);169 }170 .btn.green:hover{filter:brightness(1.08);transform:translateY(-2px);box-shadow:0 9px 0 #0d5a32,0 20px 40px rgba(47,214,107,.3)}171 .btn.green:active{transform:translateY(5px);box-shadow:0 2px 0 #0d5a32,0 8px 18px rgba(47,214,107,.2)}172 .btn.red{173 background:linear-gradient(180deg,#ff8080,#ee4a4a);color:#210404;174 box-shadow:0 7px 0 #6d1a1a,0 16px 34px rgba(255,93,93,.2);175 }176 .btn.red:hover{filter:brightness(1.08);transform:translateY(-2px);box-shadow:0 9px 0 #6d1a1a,0 20px 40px rgba(255,93,93,.3)}177 .btn.red:active{transform:translateY(5px);box-shadow:0 2px 0 #6d1a1a,0 8px 18px rgba(255,93,93,.2)}178 179 .hint{display:flex;gap:16px;flex-wrap:wrap;justify-content:center;color:var(--muted);font-size:.68rem;letter-spacing:.08em}180 .hint span{display:flex;align-items:center;gap:6px}181 182 /* ── registro ────────────────────────────────── */183 .log{184 background:linear-gradient(180deg,var(--panel),var(--panel-2));185 border:1px solid var(--line);border-radius:20px;padding:20px;186 display:flex;flex-direction:column;gap:14px;187 box-shadow:inset 0 1px 0 rgba(255,255,255,.04),0 24px 60px rgba(0,0,0,.35);188 }189 .log-head{display:flex;justify-content:space-between;align-items:baseline}190 .log-head h2{font:700 .78rem 'Unbounded';letter-spacing:.16em}191 .log-head span{font-size:.62rem;color:var(--muted);letter-spacing:.12em}192 #logList{list-style:none;display:flex;flex-direction:column;gap:8px;overflow-y:auto;max-height:360px;flex:1}193 #logList::-webkit-scrollbar{width:6px}194 #logList::-webkit-scrollbar-thumb{background:rgba(190,255,220,.15);border-radius:3px}195 #logList li{196 display:grid;grid-template-columns:auto auto 1fr;align-items:center;gap:10px;197 font-size:.8rem;padding:9px 12px;border:1px solid var(--line);border-radius:10px;198 background:rgba(190,255,220,.02);animation:slidein .25s ease-out;199 }200 #logList .t{color:var(--muted);font-size:.68rem}201 #logList .d{font-weight:700}202 #logList .d.add{color:var(--green)}203 #logList .d.sub{color:var(--red)}204 #logList .v{justify-self:end;font-weight:700}205 #logList li.empty{206 display:block;text-align:center;border:1px dashed var(--line);background:transparent;207 color:var(--muted);font-size:.72rem;line-height:1.7;letter-spacing:.06em;padding:26px 10px;208 }209 @keyframes slidein{from{opacity:0;transform:translateX(10px)}to{opacity:1;transform:none}}210 211 footer{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;color:var(--muted);font-size:.66rem;letter-spacing:.12em}212 213 @media(max-width:900px){214 main{grid-template-columns:1fr}215 .stage{min-height:400px}216 #logList{max-height:220px}217 }218 @media(max-width:480px){219 .controls{flex-direction:column}220 .btn{max-width:none}221 }222 </style>223 </head>224 <body>225 226 <div class="bg" aria-hidden="true"></div>227 <div class="orb g" aria-hidden="true"></div>228 <div class="orb r" aria-hidden="true"></div>229 230 <div class="wrap">231 232 <header>233 <div class="brand">234 <div class="mark" aria-hidden="true">±</div>235 <div>236 <h1>CONTADOR</h1>237 <small>PANEL M+2 / R−3</small>238 </div>239 </div>240 <div class="hud">241 <span class="led" id="led" aria-hidden="true"></span>242 <button class="ghost" id="soundBtn" aria-pressed="true">SONIDO: ON</button>243 </div>244 </header>245 246 <main>247 <section class="stage">248 <div class="topline" aria-hidden="true"></div>249 <div class="readout-label">VALOR ACTUAL</div>250 251 <div class="counter-box" id="counterBox">252 <div class="pulse" id="pulse" aria-hidden="true"></div>253 <div class="counter" id="counter" role="status" aria-live="polite">0</div>254 </div>255 256 <div class="stats">257 <span class="stat g">SUMAS <b id="adds">0</b></span>258 <span class="stat r">RESTAS <b id="subs">0</b></span>259 <span class="stat">ÚLTIMA <b id="last">—</b></span>260 </div>261 262 <div class="controls">263 <button class="btn green" id="btnAdd">264 <span class="op">+2</span><span class="lbl">SUMAR</span>265 </button>266 <button class="btn red" id="btnSub">267 <span class="op">−3</span><span class="lbl">RESTAR</span>268 </button>269 </div>270 271 <div class="hint">272 <span><kbd>↑</kbd> o <kbd>W</kbd> suma</span>273 <span><kbd>↓</kbd> o <kbd>S</kbd> resta</span>274 </div>275 </section>276 277 <aside class="log">278 <div class="log-head">279 <h2>REGISTRO</h2>280 <span>ÚLTIMAS 12</span>281 </div>282 <ul id="logList">283 <li class="empty">Aún no hay operaciones.<br>Pulsa un botón para empezar.</li>284 </ul>285 </aside>286 </main>287 288 <footer>289 <span>HTML + JS · SIN DEPENDENCIAS</span>290 <button class="ghost" id="resetBtn">↺ REINICIAR <kbd>R</kbd></button>291 </footer>292 293 </div>294 295 <script>296 const $ = id => document.getElementById(id);297 const counterEl = $('counter'), boxEl = $('counterBox'), pulseEl = $('pulse'),298 led = $('led'), addsEl = $('adds'), subsEl = $('subs'), lastEl = $('last'),299 list = $('logList'), soundBtn = $('soundBtn');300 301 let count = 0, adds = 0, subs = 0, soundOn = true, ledTimer = null, actx = null;302 303 const fmt = n => n < 0 ? '−' + Math.abs(n) : String(n);304 const stamp = () => new Date().toLocaleTimeString('es-ES', {hour12:false});305 306 function beep(freq){307 if(!soundOn) return;308 try{309 actx = actx || new (window.AudioContext || window.webkitAudioContext)();310 if(actx.state === 'suspended') actx.resume();311 const o = actx.createOscillator(), g = actx.createGain(), t = actx.currentTime;312 o.type = 'square'; o.frequency.value = freq;313 g.gain.setValueAtTime(.05, t);314 g.gain.exponentialRampToValueAtTime(.0001, t + .15);315 o.connect(g); g.connect(actx.destination);316 o.start(t); o.stop(t + .16);317 }catch(e){}318 }319 320 function retrigger(el, ...cls){321 el.classList.remove('add','sub','zero','go');322 void el.offsetWidth; /* fuerza reflujo para reiniciar la animación */323 cls.forEach(c => el.classList.add(c));324 }325 326 function flashLed(dir){327 led.className = 'led ' + dir;328 clearTimeout(ledTimer);329 ledTimer = setTimeout(() => led.className = 'led', 380);330 }331 332 function spawnChip(text, dir){333 const c = document.createElement('span');334 c.className = 'chip ' + dir;335 c.textContent = text;336 c.style.top = (-4 + Math.random() * 14) + 'px';337 boxEl.appendChild(c);338 c.addEventListener('animationend', () => c.remove());339 }340 341 function addLog(dir, value){342 const empty = list.querySelector('.empty');343 if(empty) empty.remove();344 const label = dir === 'add' ? '+2' : dir === 'sub' ? '−3' : '↺';345 const li = document.createElement('li');346 li.innerHTML = `<span class="t">${stamp()}</span><span class="d ${dir}">${label}</span><span class="v">${fmt(value)}</span>`;347 list.prepend(li);348 while(list.children.length > 12) list.lastElementChild.remove();349 }350 351 function apply(dir, delta){352 count += delta;353 dir === 'add' ? adds++ : subs++;354 counterEl.textContent = fmt(count);355 retrigger(counterEl, dir);356 retrigger(pulseEl, dir, 'go');357 spawnChip(dir === 'add' ? '+2' : '−3', dir);358 flashLed(dir);359 beep(dir === 'add' ? 760 : 230);360 addsEl.textContent = adds;361 subsEl.textContent = subs;362 lastEl.textContent = dir === 'add' ? '+2' : '−3';363 addLog(dir, count);364 }365 366 function reset(){367 count = 0; adds = 0; subs = 0;368 counterEl.textContent = '0';369 retrigger(counterEl, 'zero');370 addsEl.textContent = '0';371 subsEl.textContent = '0';372 lastEl.textContent = '—';373 list.innerHTML = '<li class="empty">Aún no hay operaciones.<br>Pulsa un botón para empezar.</li>';374 beep(150);375 }376 377 $('btnAdd').addEventListener('click', () => apply('add', 2));378 $('btnSub').addEventListener('click', () => apply('sub', -3));379 $('resetBtn').addEventListener('click', reset);380 381 soundBtn.addEventListener('click', () => {382 soundOn = !soundOn;383 soundBtn.textContent = soundOn ? 'SONIDO: ON' : 'SONIDO: OFF';384 soundBtn.setAttribute('aria-pressed', soundOn);385 if(soundOn) beep(620);386 });387 388 addEventListener('keydown', e => {389 const k = e.key.toLowerCase();390 if(k === 'arrowup' || k === 'w'){ e.preventDefault(); apply('add', 2); }391 else if(k === 'arrowdown' || k === 's'){ e.preventDefault(); apply('sub', -3); }392 else if(k === 'r'){ reset(); }393 });394 </script>395 </body>396 </html>397 Enlace
El enlace para compartir es:

