# Hold to record voice note

> Hold the mic to record with a live waveform and timer; slide left to discard, or release to send a playable note.

- Category: [Microinteractions](https://library.aimran.es/components/microinteractions)
- Built with: HTML + CSS
- Live editor: https://library.aimran.es/blocks/uv-mi27-voice-note
- Library: AIMRAN Library (https://library.aimran.es), free, no sign-up, by Aimran Oukham

## Where to use it

Voice notes rely on a single held gesture, and this block recreates the whole flow. While the mic is held it grows red with a pulse, a timer counts and a waveform scrolls with live levels; sliding left past the hint cancels the take. Releasing sends a green bubble with the recorded waveform, a play button and its duration.

The main color is the mic and your bubbles, the secondary one the recording state. The levels are random here; replace them with values from the Web Audio API when you record for real. Space or Enter held on the keyboard records, and Escape discards.

## Code

### HTML

```html
<div class="uvq15-voc"><ol class="ls" aria-live="polite"><li class="in"><span>Can you send me a voice note with the address?</span></li></ol><div class="bar"><div class="ip"><span class="ph">Hold the mic to record</span><span class="rec" aria-hidden="true"><i class="dot"></i><span class="tm">0:00</span><span class="wv"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="cx"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 6l-6 6 6 6"/></svg><span>Slide to cancel</span></span></span></div><button type="button" class="mc" aria-label="Hold to record"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg></button></div><p class="sr" aria-live="assertive"></p></div>
```

### CSS

```css
.uvq15-voc {
  --c1: #16a34a;
  --c2: #ef4444;
  --s: 16px;
  /* tamaño */
  --k: 1;
  /* velocidad */
  --fg: #1d1d1f;
  /* texto */
  --bg: #f5f5f7;
  /* fondo */
}

.uvq15-voc {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  position: relative;
  border-radius: 1.3em;
  background: color-mix(in srgb,var(--fg) 3%,var(--bg));
  box-shadow: 0 0 0 1px color-mix(in srgb,var(--fg) 10%,transparent),0 1.4em 2.6em -1.8em rgba(0,0,0,.35);
  width: 19em;
  padding: .8em;
  --dx: 0px;
}

.uvq15-voc .ls {
  display: flex;
  flex-direction: column;
  gap: .4em;
  height: 9.5em;
  margin: 0 0 .7em;
  padding: 0;
  overflow: hidden;
  list-style: none;
  justify-content: flex-end;
}

.uvq15-voc li {
  max-width: 82%;
}

.uvq15-voc .in span {
  display: block;
  padding: .5em .8em;
  border-radius: 1.1em 1.1em 1.1em .3em;
  font-size: .85em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq15-voc .out {
  align-self: flex-end;
  display: flex;
  align-items: center;
  gap: .5em;
  padding: .4em .7em .4em .4em;
  border-radius: 1.1em 1.1em .3em 1.1em;
  color: #fff;
  background: var(--c1);
  animation: uvq15-voc-n calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1);
}

@keyframes uvq15-voc-n {
  from {
    opacity: 0;
    transform: translateY(.6em) scale(.9);
  }
}

.uvq15-voc .pb {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  place-items: center;
  width: 2em;
  height: 2em;
  border-radius: 50%;
  color: var(--c1);
  background: #fff;
}

.uvq15-voc .pb:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.uvq15-voc .pb svg {
  width: 1em;
  height: 1em;
  fill: currentColor;
}

.uvq15-voc .pb .p2,.uvq15-voc .pl .pb .p1 {
  display: none;
}

.uvq15-voc .pl .pb .p2 {
  display: inline;
}

.uvq15-voc .wb {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 1.6em;
}

.uvq15-voc .wb i {
  width: 2px;
  height: calc(var(--h) * 100%);
  border-radius: 2px;
  background: rgba(255,255,255,.45);
}

.uvq15-voc .wb .f {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: 2px;
  clip-path: inset(0 100% 0 0);
}

.uvq15-voc .wb .f i {
  background: #fff;
}

.uvq15-voc .pl .wb .f {
  animation: uvq15-voc-pg var(--d) linear forwards;
}

@keyframes uvq15-voc-pg {
  to {
    clip-path: inset(0 0 0 0);
  }
}

.uvq15-voc .out time {
  font-size: .7em;
  font-variant-numeric: tabular-nums;
  opacity: .85;
}

.uvq15-voc .bar {
  display: flex;
  align-items: center;
  gap: .5em;
}

.uvq15-voc .ip {
  position: relative;
  flex: 1;
  display: grid;
  align-items: center;
  height: 2.6em;
  padding: 0 .9em;
  border-radius: 2em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
  overflow: hidden;
}

.uvq15-voc .ip>* {
  grid-area: 1/1;
}

.uvq15-voc .ph {
  font-size: .8em;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq15-voc .rec {
  display: flex;
  align-items: center;
  gap: .45em;
  opacity: 0;
  transform: translateX(1em);
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),transform calc(0.3s / var(--k)) cubic-bezier(.22,1,.36,1);
}

.uvq15-voc .dot {
  flex: none;
  width: .6em;
  height: .6em;
  border-radius: 50%;
  background: var(--c2);
  animation: uvq15-voc-b calc(1s / var(--k)) ease-in-out infinite;
}

@keyframes uvq15-voc-b {
  50% {
    opacity: .25;
  }
}

.uvq15-voc .tm {
  font-size: .8em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.uvq15-voc .wv {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 1.4em;
}

.uvq15-voc .wv i {
  width: 2px;
  height: calc(var(--h,.15) * 100%);
  border-radius: 2px;
  background: var(--fg);
  opacity: .6;
  transition: height .09s linear;
}

.uvq15-voc .cx {
  display: flex;
  align-items: center;
  gap: .1em;
  margin-left: auto;
  font-size: .68em;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  white-space: nowrap;
  transform: translateX(calc(var(--dx) * .4));
  opacity: calc(1 + var(--dx) / 90px);
}

.uvq15-voc .cx svg {
  width: 1.1em;
  height: 1.1em;
}

.uvq15-voc.on .ph {
  opacity: 0;
}

.uvq15-voc.on .rec {
  opacity: 1;
  transform: none;
}

.uvq15-voc.cx .dot {
  background: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq15-voc .mc {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 2.6em;
  height: 2.6em;
  border-radius: 50%;
  color: #fff;
  background: var(--c1);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  transition: transform calc(0.3s / var(--k)) cubic-bezier(.34,1.56,.64,1),background calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq15-voc .mc svg {
  width: 1.25em;
  height: 1.25em;
}

.uvq15-voc .mc:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

.uvq15-voc .mc::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--c2);
  opacity: 0;
  transform: scale(1);
}

.uvq15-voc.on .mc {
  background: var(--c2);
  transform: translateX(var(--dx)) scale(1.35);
}

.uvq15-voc.on .mc::before {
  animation: uvq15-voc-r calc(1.2s / var(--k)) ease-out infinite;
}

@keyframes uvq15-voc-r {
  0% {
    opacity: .45;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1.9);
  }
}

.uvq15-voc .mc.hint {
  animation: uvq15-voc-h calc(0.4s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

@keyframes uvq15-voc-h {
  25% {
    transform: translateX(-.25em);
  }
  75% {
    transform: translateX(.25em);
  }
}

.uvq15-voc.del .ip {
  animation: uvq15-voc-x calc(0.5s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

@keyframes uvq15-voc-x {
  20% {
    background: color-mix(in srgb,var(--c2) 22%,transparent);
    transform: translateX(-.3em);
  }
  50% {
    transform: translateX(.25em);
  }
}

.uvq15-voc .sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (prefers-reduced-motion:reduce) {
  .uvq15-voc,.uvq15-voc *,.uvq15-voc *::before,.uvq15-voc *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
```

### JavaScript

```js
document.querySelectorAll(".uvq15-voc").forEach(el => {
  const k=parseFloat(getComputedStyle(el).getPropertyValue("--k"))||1,rm=matchMedia("(prefers-reduced-motion:reduce)").matches;const mc=el.querySelector(".mc"),ls=el.querySelector(".ls"),bars=[...el.querySelectorAll(".wv i")],tm=el.querySelector(".tm"),sr=el.querySelector(".sr");let on=false,t0=0,iv=0,sx=0,hs=[],dt=0;
const fmt=s=>Math.floor(s/60)+":"+String(Math.floor(s%60)).padStart(2,"0");
const start=x=>{if(on)return;on=true;t0=performance.now();sx=x;hs=[];el.classList.add("on");el.classList.remove("cx");el.style.setProperty("--dx","0px");tm.textContent="0:00";
iv=setInterval(()=>{hs.push(.2+Math.random()*.8);bars.forEach((b,i)=>{const v=hs[hs.length-bars.length+i];b.style.setProperty("--h",String(v===undefined?.15:v))});tm.textContent=fmt((performance.now()-t0)/1000)},90)};
const add=d=>{const li=document.createElement("li");li.className="out";const N=20;let w="";for(let i=0;i<N;i++){const v=hs[Math.floor(i*hs.length/N)]||.3;w+='<i style="--h:'+v.toFixed(2)+'"></i>'}
li.innerHTML='<button type="button" class="pb" aria-label="Reproducir nota de voz"><svg viewBox="0 0 24 24" aria-hidden="true"><path class="p1" d="M8 5v14l11-7z"/><path class="p2" d="M7 5h4v14H7zM13 5h4v14h-4z"/></svg></button><span class="wb">'+w+'<span class="f">'+w+'</span></span><time>'+fmt(Math.max(1,d))+'</time>';li.style.setProperty("--d",(Math.max(1,d)/k).toFixed(2)+"s");ls.appendChild(li);while(ls.children.length>3)ls.firstElementChild.remove();sr.textContent="Voice note sent"};
const stop=send=>{if(!on)return;on=false;clearInterval(iv);el.classList.remove("on","cx");el.style.setProperty("--dx","0px");const d=(performance.now()-t0)/1000;
if(!send){el.classList.remove("del");void el.offsetWidth;el.classList.add("del");sr.textContent="Recording discarded";return}if(d<.6){mc.classList.remove("hint");void mc.offsetWidth;mc.classList.add("hint");sr.textContent="Hold to record";return}add(d)};
mc.addEventListener("pointerdown",e=>{if(e.button)return;mc.setPointerCapture(e.pointerId);start(e.clientX)});
mc.addEventListener("pointermove",e=>{if(!on)return;const sc=el.getBoundingClientRect().width/el.offsetWidth||1,d=Math.max(-110,Math.min(0,(e.clientX-sx)/sc));el.style.setProperty("--dx",d+"px");el.classList.toggle("cx",d<-60);if(d<=-100)stop(false)});
mc.addEventListener("pointerup",()=>stop(true));mc.addEventListener("pointercancel",()=>stop(false));mc.addEventListener("contextmenu",e=>e.preventDefault());
mc.addEventListener("keydown",e=>{if(e.key===" "||e.key==="Enter"){e.preventDefault();if(!e.repeat)start(0)}else if(e.key==="Escape")stop(false)});
mc.addEventListener("keyup",e=>{if(e.key===" "||e.key==="Enter")stop(true)});mc.addEventListener("blur",()=>stop(false));
ls.addEventListener("click",e=>{const b=e.target.closest(".pb");if(!b)return;const li=b.closest("li"),p=!li.classList.contains("pl");ls.querySelectorAll(".pl").forEach(x=>x.classList.remove("pl"));if(p){void li.offsetWidth;li.classList.add("pl")}b.setAttribute("aria-label",p?"Pause voice note":"Play voice note")});
ls.addEventListener("animationend",e=>{if(e.target.classList.contains("f")){const li=e.target.closest("li");li.classList.remove("pl");li.querySelector(".pb").setAttribute("aria-label","Play voice note")}});
return()=>clearInterval(iv)
});
```

### React

```tsx
// @ts-nocheck: setup() es JavaScript del DOM sin tipos, el mismo de la pestaña JS.
import { useEffect, useRef, type CSSProperties } from "react";

const css = `
.uvq15-voc {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  position: relative;
  border-radius: 1.3em;
  background: color-mix(in srgb,var(--fg) 3%,var(--bg));
  box-shadow: 0 0 0 1px color-mix(in srgb,var(--fg) 10%,transparent),0 1.4em 2.6em -1.8em rgba(0,0,0,.35);
  width: 19em;
  padding: .8em;
  --dx: 0px;
}

.uvq15-voc .ls {
  display: flex;
  flex-direction: column;
  gap: .4em;
  height: 9.5em;
  margin: 0 0 .7em;
  padding: 0;
  overflow: hidden;
  list-style: none;
  justify-content: flex-end;
}

.uvq15-voc li {
  max-width: 82%;
}

.uvq15-voc .in span {
  display: block;
  padding: .5em .8em;
  border-radius: 1.1em 1.1em 1.1em .3em;
  font-size: .85em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq15-voc .out {
  align-self: flex-end;
  display: flex;
  align-items: center;
  gap: .5em;
  padding: .4em .7em .4em .4em;
  border-radius: 1.1em 1.1em .3em 1.1em;
  color: #fff;
  background: var(--c1);
  animation: uvq15-voc-n calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1);
}

@keyframes uvq15-voc-n {
  from {
    opacity: 0;
    transform: translateY(.6em) scale(.9);
  }
}

.uvq15-voc .pb {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  place-items: center;
  width: 2em;
  height: 2em;
  border-radius: 50%;
  color: var(--c1);
  background: #fff;
}

.uvq15-voc .pb:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.uvq15-voc .pb svg {
  width: 1em;
  height: 1em;
  fill: currentColor;
}

.uvq15-voc .pb .p2,.uvq15-voc .pl .pb .p1 {
  display: none;
}

.uvq15-voc .pl .pb .p2 {
  display: inline;
}

.uvq15-voc .wb {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 1.6em;
}

.uvq15-voc .wb i {
  width: 2px;
  height: calc(var(--h) * 100%);
  border-radius: 2px;
  background: rgba(255,255,255,.45);
}

.uvq15-voc .wb .f {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: 2px;
  clip-path: inset(0 100% 0 0);
}

.uvq15-voc .wb .f i {
  background: #fff;
}

.uvq15-voc .pl .wb .f {
  animation: uvq15-voc-pg var(--d) linear forwards;
}

@keyframes uvq15-voc-pg {
  to {
    clip-path: inset(0 0 0 0);
  }
}

.uvq15-voc .out time {
  font-size: .7em;
  font-variant-numeric: tabular-nums;
  opacity: .85;
}

.uvq15-voc .bar {
  display: flex;
  align-items: center;
  gap: .5em;
}

.uvq15-voc .ip {
  position: relative;
  flex: 1;
  display: grid;
  align-items: center;
  height: 2.6em;
  padding: 0 .9em;
  border-radius: 2em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
  overflow: hidden;
}

.uvq15-voc .ip>* {
  grid-area: 1/1;
}

.uvq15-voc .ph {
  font-size: .8em;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq15-voc .rec {
  display: flex;
  align-items: center;
  gap: .45em;
  opacity: 0;
  transform: translateX(1em);
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),transform calc(0.3s / var(--k)) cubic-bezier(.22,1,.36,1);
}

.uvq15-voc .dot {
  flex: none;
  width: .6em;
  height: .6em;
  border-radius: 50%;
  background: var(--c2);
  animation: uvq15-voc-b calc(1s / var(--k)) ease-in-out infinite;
}

@keyframes uvq15-voc-b {
  50% {
    opacity: .25;
  }
}

.uvq15-voc .tm {
  font-size: .8em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.uvq15-voc .wv {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 1.4em;
}

.uvq15-voc .wv i {
  width: 2px;
  height: calc(var(--h,.15) * 100%);
  border-radius: 2px;
  background: var(--fg);
  opacity: .6;
  transition: height .09s linear;
}

.uvq15-voc .cx {
  display: flex;
  align-items: center;
  gap: .1em;
  margin-left: auto;
  font-size: .68em;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  white-space: nowrap;
  transform: translateX(calc(var(--dx) * .4));
  opacity: calc(1 + var(--dx) / 90px);
}

.uvq15-voc .cx svg {
  width: 1.1em;
  height: 1.1em;
}

.uvq15-voc.on .ph {
  opacity: 0;
}

.uvq15-voc.on .rec {
  opacity: 1;
  transform: none;
}

.uvq15-voc.cx .dot {
  background: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq15-voc .mc {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 2.6em;
  height: 2.6em;
  border-radius: 50%;
  color: #fff;
  background: var(--c1);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  transition: transform calc(0.3s / var(--k)) cubic-bezier(.34,1.56,.64,1),background calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq15-voc .mc svg {
  width: 1.25em;
  height: 1.25em;
}

.uvq15-voc .mc:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

.uvq15-voc .mc::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--c2);
  opacity: 0;
  transform: scale(1);
}

.uvq15-voc.on .mc {
  background: var(--c2);
  transform: translateX(var(--dx)) scale(1.35);
}

.uvq15-voc.on .mc::before {
  animation: uvq15-voc-r calc(1.2s / var(--k)) ease-out infinite;
}

@keyframes uvq15-voc-r {
  0% {
    opacity: .45;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1.9);
  }
}

.uvq15-voc .mc.hint {
  animation: uvq15-voc-h calc(0.4s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

@keyframes uvq15-voc-h {
  25% {
    transform: translateX(-.25em);
  }
  75% {
    transform: translateX(.25em);
  }
}

.uvq15-voc.del .ip {
  animation: uvq15-voc-x calc(0.5s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

@keyframes uvq15-voc-x {
  20% {
    background: color-mix(in srgb,var(--c2) 22%,transparent);
    transform: translateX(-.3em);
  }
  50% {
    transform: translateX(.25em);
  }
}

.uvq15-voc .sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (prefers-reduced-motion:reduce) {
  .uvq15-voc,.uvq15-voc *,.uvq15-voc *::before,.uvq15-voc *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
`;

// Interacción en JavaScript del DOM: la misma que en la pestaña JS.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setup(el: any) {
  const k=parseFloat(getComputedStyle(el).getPropertyValue("--k"))||1,rm=matchMedia("(prefers-reduced-motion:reduce)").matches;const mc=el.querySelector(".mc"),ls=el.querySelector(".ls"),bars=[...el.querySelectorAll(".wv i")],tm=el.querySelector(".tm"),sr=el.querySelector(".sr");let on=false,t0=0,iv=0,sx=0,hs=[],dt=0;
const fmt=s=>Math.floor(s/60)+":"+String(Math.floor(s%60)).padStart(2,"0");
const start=x=>{if(on)return;on=true;t0=performance.now();sx=x;hs=[];el.classList.add("on");el.classList.remove("cx");el.style.setProperty("--dx","0px");tm.textContent="0:00";
iv=setInterval(()=>{hs.push(.2+Math.random()*.8);bars.forEach((b,i)=>{const v=hs[hs.length-bars.length+i];b.style.setProperty("--h",String(v===undefined?.15:v))});tm.textContent=fmt((performance.now()-t0)/1000)},90)};
const add=d=>{const li=document.createElement("li");li.className="out";const N=20;let w="";for(let i=0;i<N;i++){const v=hs[Math.floor(i*hs.length/N)]||.3;w+='<i style="--h:'+v.toFixed(2)+'"></i>'}
li.innerHTML='<button type="button" class="pb" aria-label="Reproducir nota de voz"><svg viewBox="0 0 24 24" aria-hidden="true"><path class="p1" d="M8 5v14l11-7z"/><path class="p2" d="M7 5h4v14H7zM13 5h4v14h-4z"/></svg></button><span class="wb">'+w+'<span class="f">'+w+'</span></span><time>'+fmt(Math.max(1,d))+'</time>';li.style.setProperty("--d",(Math.max(1,d)/k).toFixed(2)+"s");ls.appendChild(li);while(ls.children.length>3)ls.firstElementChild.remove();sr.textContent="Voice note sent"};
const stop=send=>{if(!on)return;on=false;clearInterval(iv);el.classList.remove("on","cx");el.style.setProperty("--dx","0px");const d=(performance.now()-t0)/1000;
if(!send){el.classList.remove("del");void el.offsetWidth;el.classList.add("del");sr.textContent="Recording discarded";return}if(d<.6){mc.classList.remove("hint");void mc.offsetWidth;mc.classList.add("hint");sr.textContent="Hold to record";return}add(d)};
mc.addEventListener("pointerdown",e=>{if(e.button)return;mc.setPointerCapture(e.pointerId);start(e.clientX)});
mc.addEventListener("pointermove",e=>{if(!on)return;const sc=el.getBoundingClientRect().width/el.offsetWidth||1,d=Math.max(-110,Math.min(0,(e.clientX-sx)/sc));el.style.setProperty("--dx",d+"px");el.classList.toggle("cx",d<-60);if(d<=-100)stop(false)});
mc.addEventListener("pointerup",()=>stop(true));mc.addEventListener("pointercancel",()=>stop(false));mc.addEventListener("contextmenu",e=>e.preventDefault());
mc.addEventListener("keydown",e=>{if(e.key===" "||e.key==="Enter"){e.preventDefault();if(!e.repeat)start(0)}else if(e.key==="Escape")stop(false)});
mc.addEventListener("keyup",e=>{if(e.key===" "||e.key==="Enter")stop(true)});mc.addEventListener("blur",()=>stop(false));
ls.addEventListener("click",e=>{const b=e.target.closest(".pb");if(!b)return;const li=b.closest("li"),p=!li.classList.contains("pl");ls.querySelectorAll(".pl").forEach(x=>x.classList.remove("pl"));if(p){void li.offsetWidth;li.classList.add("pl")}b.setAttribute("aria-label",p?"Pause voice note":"Play voice note")});
ls.addEventListener("animationend",e=>{if(e.target.classList.contains("f")){const li=e.target.closest("li");li.classList.remove("pl");li.querySelector(".pb").setAttribute("aria-label","Play voice note")}});
return()=>clearInterval(iv)
}

export default function Mi27VoiceNote() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current?.firstElementChild;
    return el ? setup(el) : undefined;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div
        ref={ref} style={{
          "--c1": "#16a34a",
          "--c2": "#ef4444",
          "--s": "16px",
          "--k": "1",
          "--fg": "#1d1d1f",
          "--bg": "#f5f5f7",
        } as CSSProperties}
      >
        <div className="uvq15-voc"><ol className="ls" aria-live="polite"><li className="in"><span>Can you send me a voice note with the address?</span></li></ol><div className="bar"><div className="ip"><span className="ph">Hold the mic to record</span><span className="rec" aria-hidden="true"><i className="dot"></i><span className="tm">0:00</span><span className="wv"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span className="cx"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M15 6l-6 6 6 6" /></svg><span>Slide to cancel</span></span></span></div><button type="button" className="mc" aria-label="Hold to record"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="9" y="3" width="6" height="11" rx="3" /><path d="M5 11a7 7 0 0 0 14 0M12 18v3" /></svg></button></div><p className="sr" aria-live="assertive"></p></div>
      </div>
    </>
  );
}
```

## Prompt for AI assistants

Paste this into ChatGPT, Claude, Cursor, v0 or Lovable to adapt the block to your stack (Tailwind, Vue, Svelte…):

````text
I want to add the “Hold to record voice note” block (Microinteractions) from AIMRAN Library to my project.

## What it is
Hold the mic to record with a live waveform and timer; slide left to discard, or release to send a playable note.

## Chosen settings
- Main color: #16a34a
- Secondary color: #ef4444
- Size: 16px (from 12 to 24px)
- Speed: 1x (from 0.5 to 2x)
- Text: "Mantén el micro para grabar"

## What I need
1. Add it to my project with the same look and animation as the reference code: same sizes, colours, radii, shadows, timings and easing.
2. It is built with plain HTML and CSS (the reference wraps it in a React component with its CSS). If my project uses something else (Vue, Svelte, Astro, plain HTML…), translate it without losing any behaviour. If I use Tailwind, turn the styles into classes; if not, keep the CSS in its own file.
3. Expose the settings above as props (or CSS variables) with those values as defaults.
4. Make it work well on phones: nothing that depends only on hover, touch targets of at least 44 px and no overflow on 320 px screens.
5. Make it look right in light and dark mode.
6. Keep it accessible: semantic HTML, roles and aria-* where needed, visible focus and keyboard support.
7. Respect prefers-reduced-motion: with reduced motion, no big animations but the same end result.

## Reference code
```tsx
// @ts-nocheck: setup() es JavaScript del DOM sin tipos, el mismo de la pestaña JS.
import { useEffect, useRef, type CSSProperties } from "react";

const css = `
.uvq15-voc {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  position: relative;
  border-radius: 1.3em;
  background: color-mix(in srgb,var(--fg) 3%,var(--bg));
  box-shadow: 0 0 0 1px color-mix(in srgb,var(--fg) 10%,transparent),0 1.4em 2.6em -1.8em rgba(0,0,0,.35);
  width: 19em;
  padding: .8em;
  --dx: 0px;
}

.uvq15-voc .ls {
  display: flex;
  flex-direction: column;
  gap: .4em;
  height: 9.5em;
  margin: 0 0 .7em;
  padding: 0;
  overflow: hidden;
  list-style: none;
  justify-content: flex-end;
}

.uvq15-voc li {
  max-width: 82%;
}

.uvq15-voc .in span {
  display: block;
  padding: .5em .8em;
  border-radius: 1.1em 1.1em 1.1em .3em;
  font-size: .85em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq15-voc .out {
  align-self: flex-end;
  display: flex;
  align-items: center;
  gap: .5em;
  padding: .4em .7em .4em .4em;
  border-radius: 1.1em 1.1em .3em 1.1em;
  color: #fff;
  background: var(--c1);
  animation: uvq15-voc-n calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1);
}

@keyframes uvq15-voc-n {
  from {
    opacity: 0;
    transform: translateY(.6em) scale(.9);
  }
}

.uvq15-voc .pb {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  place-items: center;
  width: 2em;
  height: 2em;
  border-radius: 50%;
  color: var(--c1);
  background: #fff;
}

.uvq15-voc .pb:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.uvq15-voc .pb svg {
  width: 1em;
  height: 1em;
  fill: currentColor;
}

.uvq15-voc .pb .p2,.uvq15-voc .pl .pb .p1 {
  display: none;
}

.uvq15-voc .pl .pb .p2 {
  display: inline;
}

.uvq15-voc .wb {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 1.6em;
}

.uvq15-voc .wb i {
  width: 2px;
  height: calc(var(--h) * 100%);
  border-radius: 2px;
  background: rgba(255,255,255,.45);
}

.uvq15-voc .wb .f {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: 2px;
  clip-path: inset(0 100% 0 0);
}

.uvq15-voc .wb .f i {
  background: #fff;
}

.uvq15-voc .pl .wb .f {
  animation: uvq15-voc-pg var(--d) linear forwards;
}

@keyframes uvq15-voc-pg {
  to {
    clip-path: inset(0 0 0 0);
  }
}

.uvq15-voc .out time {
  font-size: .7em;
  font-variant-numeric: tabular-nums;
  opacity: .85;
}

.uvq15-voc .bar {
  display: flex;
  align-items: center;
  gap: .5em;
}

.uvq15-voc .ip {
  position: relative;
  flex: 1;
  display: grid;
  align-items: center;
  height: 2.6em;
  padding: 0 .9em;
  border-radius: 2em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
  overflow: hidden;
}

.uvq15-voc .ip>* {
  grid-area: 1/1;
}

.uvq15-voc .ph {
  font-size: .8em;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq15-voc .rec {
  display: flex;
  align-items: center;
  gap: .45em;
  opacity: 0;
  transform: translateX(1em);
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),transform calc(0.3s / var(--k)) cubic-bezier(.22,1,.36,1);
}

.uvq15-voc .dot {
  flex: none;
  width: .6em;
  height: .6em;
  border-radius: 50%;
  background: var(--c2);
  animation: uvq15-voc-b calc(1s / var(--k)) ease-in-out infinite;
}

@keyframes uvq15-voc-b {
  50% {
    opacity: .25;
  }
}

.uvq15-voc .tm {
  font-size: .8em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.uvq15-voc .wv {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 1.4em;
}

.uvq15-voc .wv i {
  width: 2px;
  height: calc(var(--h,.15) * 100%);
  border-radius: 2px;
  background: var(--fg);
  opacity: .6;
  transition: height .09s linear;
}

.uvq15-voc .cx {
  display: flex;
  align-items: center;
  gap: .1em;
  margin-left: auto;
  font-size: .68em;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  white-space: nowrap;
  transform: translateX(calc(var(--dx) * .4));
  opacity: calc(1 + var(--dx) / 90px);
}

.uvq15-voc .cx svg {
  width: 1.1em;
  height: 1.1em;
}

.uvq15-voc.on .ph {
  opacity: 0;
}

.uvq15-voc.on .rec {
  opacity: 1;
  transform: none;
}

.uvq15-voc.cx .dot {
  background: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq15-voc .mc {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 2.6em;
  height: 2.6em;
  border-radius: 50%;
  color: #fff;
  background: var(--c1);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  transition: transform calc(0.3s / var(--k)) cubic-bezier(.34,1.56,.64,1),background calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq15-voc .mc svg {
  width: 1.25em;
  height: 1.25em;
}

.uvq15-voc .mc:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

.uvq15-voc .mc::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--c2);
  opacity: 0;
  transform: scale(1);
}

.uvq15-voc.on .mc {
  background: var(--c2);
  transform: translateX(var(--dx)) scale(1.35);
}

.uvq15-voc.on .mc::before {
  animation: uvq15-voc-r calc(1.2s / var(--k)) ease-out infinite;
}

@keyframes uvq15-voc-r {
  0% {
    opacity: .45;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1.9);
  }
}

.uvq15-voc .mc.hint {
  animation: uvq15-voc-h calc(0.4s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

@keyframes uvq15-voc-h {
  25% {
    transform: translateX(-.25em);
  }
  75% {
    transform: translateX(.25em);
  }
}

.uvq15-voc.del .ip {
  animation: uvq15-voc-x calc(0.5s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

@keyframes uvq15-voc-x {
  20% {
    background: color-mix(in srgb,var(--c2) 22%,transparent);
    transform: translateX(-.3em);
  }
  50% {
    transform: translateX(.25em);
  }
}

.uvq15-voc .sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (prefers-reduced-motion:reduce) {
  .uvq15-voc,.uvq15-voc *,.uvq15-voc *::before,.uvq15-voc *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
`;

// Interacción en JavaScript del DOM: la misma que en la pestaña JS.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setup(el: any) {
  const k=parseFloat(getComputedStyle(el).getPropertyValue("--k"))||1,rm=matchMedia("(prefers-reduced-motion:reduce)").matches;const mc=el.querySelector(".mc"),ls=el.querySelector(".ls"),bars=[...el.querySelectorAll(".wv i")],tm=el.querySelector(".tm"),sr=el.querySelector(".sr");let on=false,t0=0,iv=0,sx=0,hs=[],dt=0;
const fmt=s=>Math.floor(s/60)+":"+String(Math.floor(s%60)).padStart(2,"0");
const start=x=>{if(on)return;on=true;t0=performance.now();sx=x;hs=[];el.classList.add("on");el.classList.remove("cx");el.style.setProperty("--dx","0px");tm.textContent="0:00";
iv=setInterval(()=>{hs.push(.2+Math.random()*.8);bars.forEach((b,i)=>{const v=hs[hs.length-bars.length+i];b.style.setProperty("--h",String(v===undefined?.15:v))});tm.textContent=fmt((performance.now()-t0)/1000)},90)};
const add=d=>{const li=document.createElement("li");li.className="out";const N=20;let w="";for(let i=0;i<N;i++){const v=hs[Math.floor(i*hs.length/N)]||.3;w+='<i style="--h:'+v.toFixed(2)+'"></i>'}
li.innerHTML='<button type="button" class="pb" aria-label="Reproducir nota de voz"><svg viewBox="0 0 24 24" aria-hidden="true"><path class="p1" d="M8 5v14l11-7z"/><path class="p2" d="M7 5h4v14H7zM13 5h4v14h-4z"/></svg></button><span class="wb">'+w+'<span class="f">'+w+'</span></span><time>'+fmt(Math.max(1,d))+'</time>';li.style.setProperty("--d",(Math.max(1,d)/k).toFixed(2)+"s");ls.appendChild(li);while(ls.children.length>3)ls.firstElementChild.remove();sr.textContent="Voice note sent"};
const stop=send=>{if(!on)return;on=false;clearInterval(iv);el.classList.remove("on","cx");el.style.setProperty("--dx","0px");const d=(performance.now()-t0)/1000;
if(!send){el.classList.remove("del");void el.offsetWidth;el.classList.add("del");sr.textContent="Recording discarded";return}if(d<.6){mc.classList.remove("hint");void mc.offsetWidth;mc.classList.add("hint");sr.textContent="Hold to record";return}add(d)};
mc.addEventListener("pointerdown",e=>{if(e.button)return;mc.setPointerCapture(e.pointerId);start(e.clientX)});
mc.addEventListener("pointermove",e=>{if(!on)return;const sc=el.getBoundingClientRect().width/el.offsetWidth||1,d=Math.max(-110,Math.min(0,(e.clientX-sx)/sc));el.style.setProperty("--dx",d+"px");el.classList.toggle("cx",d<-60);if(d<=-100)stop(false)});
mc.addEventListener("pointerup",()=>stop(true));mc.addEventListener("pointercancel",()=>stop(false));mc.addEventListener("contextmenu",e=>e.preventDefault());
mc.addEventListener("keydown",e=>{if(e.key===" "||e.key==="Enter"){e.preventDefault();if(!e.repeat)start(0)}else if(e.key==="Escape")stop(false)});
mc.addEventListener("keyup",e=>{if(e.key===" "||e.key==="Enter")stop(true)});mc.addEventListener("blur",()=>stop(false));
ls.addEventListener("click",e=>{const b=e.target.closest(".pb");if(!b)return;const li=b.closest("li"),p=!li.classList.contains("pl");ls.querySelectorAll(".pl").forEach(x=>x.classList.remove("pl"));if(p){void li.offsetWidth;li.classList.add("pl")}b.setAttribute("aria-label",p?"Pause voice note":"Play voice note")});
ls.addEventListener("animationend",e=>{if(e.target.classList.contains("f")){const li=e.target.closest("li");li.classList.remove("pl");li.querySelector(".pb").setAttribute("aria-label","Play voice note")}});
return()=>clearInterval(iv)
}

export default function Mi27VoiceNote() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current?.firstElementChild;
    return el ? setup(el) : undefined;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div
        ref={ref} style={{
          "--c1": "#16a34a",
          "--c2": "#ef4444",
          "--s": "16px",
          "--k": "1",
          "--fg": "#1d1d1f",
          "--bg": "#f5f5f7",
        } as CSSProperties}
      >
        <div className="uvq15-voc"><ol className="ls" aria-live="polite"><li className="in"><span>Can you send me a voice note with the address?</span></li></ol><div className="bar"><div className="ip"><span className="ph">Hold the mic to record</span><span className="rec" aria-hidden="true"><i className="dot"></i><span className="tm">0:00</span><span className="wv"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span className="cx"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M15 6l-6 6 6 6" /></svg><span>Slide to cancel</span></span></span></div><button type="button" className="mc" aria-label="Hold to record"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="9" y="3" width="6" height="11" rx="3" /><path d="M5 11a7 7 0 0 0 14 0M12 18v3" /></svg></button></div><p className="sr" aria-live="assertive"></p></div>
      </div>
    </>
  );
}
```

Give me the complete final code, ready to paste, and tell me in one line where to put it in my project.
````
