📄 src/routes/+page.svelte
<script lang="ts">
  let listening = $state(false);
</script>

<section>
  <button
    class:listening
    onclick={() => (listening = !listening)}
    aria-label={listening ? "Stop listening" : "Start listening"}
    aria-pressed={listening}
  >
    <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path
        d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z"
      />
    </svg>
  </button>
</section>

<style>
  section {
    height: 100dvh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    padding-bottom: calc(
      var(--nav-height) + env(safe-area-inset-bottom) + 2rem
    );

    button {
      position: relative;
      width: 5rem;
      height: 5rem;
      border-radius: 50%;
      border: none;
      background: color-mix(in srgb, var(--accent) 20%, var(--bg));
      color: var(--text);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: background 0.2s, transform 0.1s;

      svg {
        width: 2rem;
        height: 2rem;
      }

      &:hover {
        background: color-mix(in srgb, var(--accent) 30%, var(--bg));
      }

      &:active {
        transform: scale(0.95);
      }

      &.listening {
        background: var(--accent);

        &::after {
          content: "";
          position: absolute;
          inset: -0.5rem;
          border-radius: 50%;
          border: 2px solid var(--accent);
          pointer-events: none;
          animation: pulse 1.5s ease-out infinite;
        }
      }
    }
  }

  @keyframes pulse {
    from {
      transform: scale(1);
      opacity: 0.7;
    }
    to {
      transform: scale(1.6);
      opacity: 0;
    }
  }
</style>