GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

パスワード 目のアイコン 9パターン — 文字とドットの入れ替え計算

「パスワード 目のアイコン」の作り方は9通りある。目のアイコンの下には九つに枝分かれする路地がある。01番の路地は文字と点が入れ替わる交差点で、05番の路地は光が壁を伝って広がる通路で、06番の路地は文字が一歩ずつ歩いて入ってくる階段だ。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

パスワード 目のアイコン 9パターン — input type の値ひとつが九通りに変わる瞬間

以下の9パターンはすべて同じ問いに対してそれぞれ別々の答えを出しています — パスワード欄のマスク状態が切り替わる瞬間、何が起きるべきか、という問いです。01番は一つのフィールド自身の反応にとどまりますが、03番では同じ変化が二つのフィールドで共有され、05番と06番はマスク解除以外の目的でフィールドを使い、09番はここまでの仕組みの一部を一つの会員登録フォームにまとめています。全体としては、一つのフィールドが自分自身に反応する形から、複数のフィールドと機能がひとつの共有された値に反応する形へと移っていきます。

01文字と点が入れ替わる目のアイコンのモーフ

buildOverlay() は文字数ぶんの pem__ch ラッパーを一つずつ組み立て、マスク用の pem__mask の点と実際の文字を表す pem__char を同じグリッド位置に重ねています。別々の要素を出し入れしているわけではありません。animateEyeStep() は単一の eyeProgress 値を EYE_MORPH_MS かけて0か1へイージングし、paintEye() がその一つの値を読んでまぶたのパスの曲がりと瞳の不透明度を同時に変えています。

文字と点が入れ替わる目のアイコンのモーフ
新しいタブで開く

01 · 文字と点が入れ替わる目のアイコンのモーフ — 計測 累積面積 76.015% · 動いたフレーム 14/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

EYE_MORPH_MSbuildOverlaypem__ch
  function buildOverlay(value) {
    overlay.textContent = '';
    for (var i = 0; i < value.length; i += 1) {
      var ch = document.createElement('span');
      ch.className = 'pem__ch';
      var delay = (i * CHAR_STAGGER_MS) + 'ms';
      var mask = document.createElement('span');
      mask.className = 'pem__mask';
      mask.style.transitionDelay = delay;
      mask.textContent = String.fromCharCode(8226);
      var real = document.createElement('span');
      real.className = 'pem__char';
      real.style.transitionDelay = delay;
      real.textContent = value.charAt(i);
      ch.appendChild(mask);
      ch.appendChild(real);
      overlay.appendChild(ch);
    }
  }
  buildOverlay(FAKE_PW);

01番は23フレーム中14フレームで動き、累積面積76.0%・平均強度44.6を記録しました。

02Caps Lockのオンをスプリングで知らせる警告バッジ

onKey() は keydown と keyup のたびに本物の KeyboardEvent.getModifierState('CapsLock') を読み取り、その真偽値をそのまま setCapsLock() に渡しています。この関数が変えるのは target という数値ひとつだけです。stepSpring() は毎フレームその target を pos と vel に対して積分するので、CAPSLOCK_BADGE_OVERSHOOT_PX の値がバッジが静止位置をどれだけ超えて跳ねるかを決め、最後に paintBadge() が元の位置へ収めます。

Caps Lockのオンをスプリングで知らせる警告バッジ
新しいタブで開く

02 · Caps Lockのオンをスプリングで知らせる警告バッジ — 計測 累積面積 75.869% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CAPSLOCK_BADGE_OVERSHOOT_PXstepSpringpcs__badge--active
  function stepSpring(ts) {
    if (lastT === null) lastT = ts;
    var dt = Math.min(0.032, (ts - lastT) / 1000);
    lastT = ts;
    var acc = (target - pos) * SPRING_STIFFNESS - vel * SPRING_DAMPING;
    vel += acc * dt;
    pos += vel * dt;
    paintBadge(pos);
    var settled = Math.abs(target - pos) < 0.002 && Math.abs(vel) < 0.01;
    if (!settled) {
      springRaf = requestAnimationFrame(stepSpring);
    } else {
      pos = target;
      paintBadge(pos);
      springRaf = null;
      lastT = null;
    }
  }

  function setCapsLock(on) {
    var next = on ? 1 : 0;
    if (target === next) return;
    target = next;
    badge.classList.toggle('pcs__badge--active', on);
    root.classList.toggle('pcs--warning', on);

02番は23フレーム中15フレームで動き、累積面積75.9%・平均強度49.3を記録しました。

03複数の入力欄が一斉に開く同期トグル

一つの onToggle() ハンドラがパスワード欄と確認用パスワード欄で共有された revealed 値を切り替え、paintEyes() はその同じイージング済みの進行値を一回の関数呼び出しの中で両方の目のアイコンのまぶたパスと瞳へ同時に書き込みます。SYNC_EYE_MORPH_MS はどちらの欄も参照する唯一のタイミング値で、それぞれの欄が別々の進行値を持つことはありません。

複数の入力欄が一斉に開く同期トグル
新しいタブで開く

03 · 複数の入力欄が一斉に開く同期トグル — 計測 累積面積 76.029% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SYNC_EYE_MORPH_MSpaintEyespfs__hint
  function paintEyes(progress) {
    var eased = easeOutCubic(progress);
    var topY = 12 - 8 * eased;
    var botY = 12 + 8 * eased;
    var d1 = 'M2,12 Q12,' + topY + ' 22,12';
    var d2 = 'M2,12 Q12,' + botY + ' 22,12';
    lidTopA.setAttribute('d', d1);
    lidBottomA.setAttribute('d', d2);
    pupilA.style.opacity = String(eased);
    lidTopB.setAttribute('d', d1);
    lidBottomB.setAttribute('d', d2);
    pupilB.style.opacity = String(eased);
  }

  var eyeProgress = 0;
  var eyeRaf = null;
  var animT0 = null, animStart = 0, animTarget = 0;
  paintEyes(eyeProgress);

  function animateEyeStep(ts) {

03番は23フレーム中10フレームで動き、累積面積76.0%・平均強度65.2を記録しました。

04貼り付けを検知してせり出す案内バナー

この入力欄はキー入力の回数や文字数の急な変化ではなく、本物の paste イベントそのものを監視しています。handlePaste() は event.clipboardData を一切読み取らないので、バナーは「何かが貼り付けられた」ことだけを伝え、その中身は伝えません。PASTE_BANNER_VISIBLE_MS がバナーの表示時間を決め、その後 PASTE_BANNER_SLIDE_MS が退場にかかる時間を決めています。

貼り付けを検知してせり出す案内バナー
新しいタブで開く

04 · 貼り付けを検知してせり出す案内バナー — 計測 累積面積 10.463% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

PASTE_BANNER_SLIDE_MSPASTE_BANNER_VISIBLE_MSsimulatePaste
  function handlePaste() {
    clearTimeout(hideTimer);
    clearTimeout(clearTimer);
    banner.textContent = BANNER_MESSAGE;
    banner.classList.add('is-visible');
    hideTimer = setTimeout(function () {
      banner.classList.remove('is-visible');
      clearTimer = setTimeout(function () { banner.textContent = ''; }, PASTE_BANNER_SLIDE_MS);
    }, PASTE_BANNER_VISIBLE_MS);
  }

  input.addEventListener('paste', function () { handlePaste(); });

04番は23フレーム中12フレームで動き、累積面積10.5%・平均強度57.4を記録しました。

05ブラウザの自動入力を検知して広がるハイライトグロー

このパターンの裏側に自動入力検知APIは存在しません — ソース内のコメントにもそう明記されています。代わりに「疑似入力」ボタンが fillAndGlow() を呼び、欄を埋めると同時に glowTick() を起動します。この関数は毎フレーム上昇するサインカーブに指数的な減衰を掛け合わせているので、AUTOFILL_GLOW_PULSE_MS が制御しているのはループの周期ではなく一回分のパルスの長さだけです。

ブラウザの自動入力を検知して広がるハイライトグロー
新しいタブで開く

05 · ブラウザの自動入力を検知して広がるハイライトグロー — 計測 累積面積 23.938% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

AUTOFILL_GLOW_PULSE_MSfillAndGlowafg__input
  function glowTick(ts) {
    if (glowStartTs === null) glowStartTs = ts;
    var t = ts - glowStartTs;
    if (t > AUTOFILL_GLOW_PULSE_MS) {
      input.style.setProperty('--glow', '0');
      root.style.setProperty('--glow', '0');
      glowRaf = 0;
      glowStartTs = null;
      return;
    }
    var envelope = Math.max(0, Math.sin(Math.PI * t / AUTOFILL_GLOW_PULSE_MS)) * Math.exp(-2.2 * t / AUTOFILL_GLOW_PULSE_MS);
    input.style.setProperty('--glow', String(envelope));
    root.style.setProperty('--glow', String(envelope));
    glowRaf = requestAnimationFrame(glowTick);
  }

  function resetField() {
    input.value = '';
  }

05番は23フレーム中11フレームで動き、累積面積23.9%・平均強度34.0を記録しました。

06生成ボタンで一文字ずつ現れるカスケード

generatePassword() は読み込み時に一度だけ seedRand() で組んだ候補プールから文字列を選びます。stepCascade() は経過時間を積み上げ、その値が GENERATE_CASCADE_STEP_MS を超えるたびに文字を一つずつ表示していきます。全文字が出そろうと同じ関数が COPY_MORPH_MS で計るクロスフェードへ切り替わりチェックマークへモーフしますが、そのコピーアイコンの裏に Clipboard API の呼び出しはありません。

生成ボタンで一文字ずつ現れるカスケード
新しいタブで開く

06 · 生成ボタンで一文字ずつ現れるカスケード — 計測 累積面積 14.485% · 動いたフレーム 14/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

GENERATE_CASCADE_STEP_MSCOPY_MORPH_MSgeneratePassword
  function stepCascade(dtMs) {
    if (cascade.active) {
      cascade.acc += dtMs;
      while (cascade.acc >= GENERATE_CASCADE_STEP_MS && cascade.revealed < cascade.target.length) {
        cascade.acc -= GENERATE_CASCADE_STEP_MS;
        cascade.revealed += 1;
        display.textContent = cascade.target.slice(0, cascade.revealed);
      }
      if (cascade.revealed >= cascade.target.length) {
        cascade.active = false;
        cascade.morphing = true;
        cascade.morphAcc = 0;
      }
    } else if (cascade.morphing) {
      cascade.morphAcc += dtMs;
      var p = Math.min(1, cascade.morphAcc / COPY_MORPH_MS);
      root.style.setProperty('--copyMorph', String(p));
      if (p >= 1) {
        cascade.morphing = false;
        root.classList.add('is-copied');
      }
    }
  }

  genBtn.addEventListener('click', generatePassword);

06番は23フレーム中14フレームで動き、累積面積14.5%・平均強度126.1を記録しました。

07押している間だけ進行リングで開くホールド方式

startHold() は本物の pointerdown と Space キーの keydown の両方から呼ばれます。loop() はボタンを押し続けている時間を HOLD_DURATION_MS と比較してリングを満たし、その比率が1に達した瞬間だけ input.type を text に切り替えます。指を離す、ボタンの外へ指をずらす、キーを離す — どの終わり方でも同じ endHold() が呼ばれるので、マスクへ戻る経路は常に一つだけです。

押している間だけ進行リングで開くホールド方式
新しいタブで開く

07 · 押している間だけ進行リングで開くホールド方式 — 計測 累積面積 75.911% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

HOLD_DURATION_MSphrRingFgEYE_OPEN_MS
  function loop(ts) {
    if (!holding) return;
    var elapsed = ts - holdStart;
    var progress = Math.max(0, Math.min(1, elapsed / HOLD_DURATION_MS));
    setRing(progress);
    if (progress >= 1 && !revealed) {
      revealed = true;
      input.type = 'text';
      btn.setAttribute('aria-pressed', 'true');
    }
    var eyeProgress = revealed ? 1 : Math.max(0, Math.min(1, elapsed / EYE_OPEN_MS));
    setEye(eyeProgress);
    rafId = requestAnimationFrame(loop);
  }

  function startHold() {
    if (holding) return;
    holding = true;
    revealed = false;
    holdStart = performance.now();
    rafId = requestAnimationFrame(loop);
  }

  function endHold() {

07番は23フレーム中10フレームで動き、累積面積75.9%・平均強度92.4を記録しました。

08ショートカットで切り替えるとフォーカスリングが明るくなるキーボード経路

input.type を切り替える関数は toggleReveal() ただ一つで、目のアイコンボタンのクリックも Ctrl+Shift+H の keydown も同じ関数を呼んだ後に flashFocusRing() を実行します。その中の step() は --ring-alpha というカスタムプロパティを FOCUS_RING_FLASH_MS かけて減衰させるので、ボタンだけでなくフィールド自体がポインター操作なしのショートカットに目に見える形で応えます。

ショートカットで切り替えるとフォーカスリングが明るくなるキーボード経路
新しいタブで開く

08 · ショートカットで切り替えるとフォーカスリングが明るくなるキーボード経路 — 計測 累積面積 75.756% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

FOCUS_RING_FLASH_MSflashFocusRing--ring-alpha
  function toggleReveal() {
    var willShow = input.type === 'password';
    input.type = willShow ? 'text' : 'password';
    eyeBtn.setAttribute('aria-pressed', String(willShow));
    root.classList.toggle('is-open', willShow);
  }

  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  var flashStart = 0;
  var flashRaf = null;
  var reduceFlashTimer = null;

  function step(ts) {
    var t = ts - flashStart;
    var envelope = Math.max(0, 1 - t / FOCUS_RING_FLASH_MS);
    input.style.setProperty('--ring-alpha', String(envelope));
    if (envelope > 0) {
      flashRaf = requestAnimationFrame(step);
    } else {
      flashRaf = null;
    }
  }

  function flashFocusRing() {

08番は23フレーム中10フレームで動き、累積面積75.8%・平均強度29.0を記録しました。

09複数の方式を一つのフォームにまとめた会員登録ヒーロー場面

heroSimulateAutofill() と heroRunCascade() は、01番・05番・06番の仕組みをそれぞれ改名して、別々のデモ欄ではなく同じフォームのパスワード欄一つに向けたものです。01番の toggleEye() によるモーフも同じ pwInput 上で並行して動きます。HERO_CASCADE_STEP_MS だけがこの場面で新しく足された定数で、それ以外はすべて既存の三つの仕組みを組み合わせただけで、作り直されたものではありません。

複数の方式を一つのフォームにまとめた会員登録ヒーロー場面
新しいタブで開く

09 · 複数の方式を一つのフォームにまとめた会員登録ヒーロー場面 — 計測 累積面積 75.046% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

HERO_CASCADE_STEP_MSheroSimulateAutofillheroMorphEye
  function heroSimulateAutofill() {
    glowStart = performance.now();
    if (glowRafId) return;
    glowRafId = requestAnimationFrame(glowStep);
  }

  var cascadeTimer = null;
  function heroRunCascade() {
    var target = pickPassword(9, cascadeRand);
    var i = 0;
    pwInput.type = 'text';
    eyeBtn.setAttribute('aria-pressed', 'true');
    animateEyeTo(1);
    pwInput.value = '';
    root.classList.remove('is-generated');
    window.clearTimeout(cascadeTimer);
    function step() {
      i += 1;
      pwInput.value = target.slice(0, i);
      if (i < target.length) {
        cascadeTimer = window.setTimeout(step, HERO_CASCADE_STEP_MS);
      } else {
        root.classList.add('is-generated');
        cascadeTimer = window.setTimeout(function () {

09番は23フレーム中16フレームで動き、累積面積75.0%・平均強度75.9を記録しました。

どのパスワード欄にどの9パターンを使うべきか

どんなログインフォームにもパスワード表示/非表示 アイコンは必要ですが、この9つのうちどれが合うかは実際に何を守りたいかで変わります。01番のクロスフェードする目は普通のログイン欄に向いていて、02番のスプリングバッジは会員登録フォームのパスワードステップでCaps Lockのつけっぱなしを送信前に警告したい場面で力を発揮します。03番の共有トグルはパスワード欄と確認用パスワード欄が並んでいる場所にふさわしく、04番の貼り付けバナーはパスワードマネージャーが本物のキー入力ではなく貼り付けで自動入力する欄のために作られています。05番は保存済みの認証情報が正しい欄に入ったことを確認し、06番は会員登録フローに生成とコピーのステップを与え、07番のホールド方式は共有画面やプレゼン画面で一瞬だけパスワードを見せたい場面に合います。08番はマウスに触れずにパスワード 目のアイコン どっちを押すべきかという疑問にキーボードだけで答え、09番はこれらのいくつかを一つのランディングページの会員登録ヒーローにまとめています。このような欄の下によく置かれる強度フィードバックについては、パスワード強度チェック UI 9選 — 強度の値が計算される仕組み が姉妹編にあたり、フォーム バリデーション UI 9選 — 打ち込んだ瞬間に判定するエラー表示 は同じ入力欄まわりのエラー表示のより近い親戚です。

9つのパスワード表示パターンを面積と強度で測る

これらの9つのうち多くは状態が変わるたびにカード状の背景ごと色を変えて塗り直すため、以下の表では9つ中6つが累積面積75%を超えています — 累積面積は固定カメラの各フレームのうち実際に塗り直された割合の平均で、強度はその塗り直しが起きた場所の平均ピクセル差です。

| パターン | 累積面積 | 平均強度 | 動いたフレーム |

|---|---|---|---|

| 01 目のアイコンモーフ | 76.0% | 44.6 | 14/23 |

| 02 Caps Lock警告スプリング | 75.9% | 49.3 | 15/23 |

| 03 複数欄同期トグル | 76.0% | 65.2 | 10/23 |

| 04 貼り付け検知バナー | 10.5% | 57.4 | 12/23 |

| 05 自動入力ハイライトグロー | 23.9% | 34.0 | 11/23 |

| 06 生成コピーカスケード | 14.5% | 126.1 | 14/23 |

| 07 ホールド進行リング | 75.9% | 92.4 | 10/23 |

| 08 キーボードショートカットフォーカスリング | 75.8% | 29.0 | 10/23 |

| 09 会員登録ヒーロー場面 | 75.0% | 75.9 | 16/23 |

値が反応を変える場所 — Caps Lockバッジのオーバーシュート・自動入力グローのパルス・生成カスケードの速度

決め手になる数値は主に三つです。CAPSLOCK_BADGE_OVERSHOOT_PX は02番のバッジが静止位置をどれだけ超えて跳ねてからスプリングで戻るかを決め、AUTOFILL_GLOW_PULSE_MS は05番の欄まわりに広がって消えるサインカーブ×減衰のパルス一回分の長さを決め、GENERATE_CASCADE_STEP_MS は06番が一文字ごとに空ける間隔をミリ秒単位で決めています。三つとも形そのものは変えず、目がその変化を見つめる時間の長さだけを変えます — それこそが、考え抜かれたパスワード表示 アイコンと、ただクラスを切り替えるだけのアイコンを分ける最大の違いです。

どこで壊れるか — ブラウザの強制マスキング・スクリーンリーダー通知の欠落・ホールド操作の衝突

パスワード欄のマスクは type 属性に完全に紐づいており、type が password のままではどんなCSSも点を文字に見せることはできません。だからこそ、この9つのうち01・02・03・07・08・09の六つは実際に input.type を password と text の間で切り替えていますが、04・05・06の三つは、そもそもマスクされていない欄の上で自分の技法を見せています。見せたいのは「表示」そのものではないからです。9つのうち aria-live 領域で状態変化を読み上げているのは02・04・06の三つだけなので、それ以外の六つをトグルする際、スクリーンリーダーのユーザーはボタン自身の aria-pressed の値だけを頼りにすることになり、別途の読み上げ確認はありません。07番のホールド操作は keydown に明示的な e.repeat チェックを入れて自分自身の繰り返しイベントを防いでいます — このガードがなければOS自体のキーリピートが繰り返しイベントのたびに startHold() を再起動してしまい、一回のキー保持でリングを一度だけ満たすことができなくなります。ボタンの外へポインターがずれた場合も同じ理由で pointerleave が拾います。これらの9パターンを支える index.html・style.scss の組は、xnzbmd2q、どの番号が本物の type 属性を切り替え、どれが見た目だけそう見せているのかを確かめる一番早い方法です。

変形3つとアクセシビリティ

| 変形 | 値の変化 | 感触 |

|---|---|---|

| Caps Lockバッジのオーバーシュートを調整する | 02 CAPSLOCK_BADGE_OVERSHOOT_PX 6 → 14 | 値を下げるとバッジはほとんど跳ねずに収まり、値を上げるとバッジはより大きく跳ね上がってから元の位置に戻る。 |

| 自動入力グローのパルス周期を調整する | 05 AUTOFILL_GLOW_PULSE_MS 900 → 400 | 値を上げるとグローはゆっくり柔らかく広がって消え、値を下げるとグローは点滅のように素早く光る。 |

| 生成カスケードの速度を調整する | 06 GENERATE_CASCADE_STEP_MS 60 → 20 | 値を上げると文字は一つずつゆっくりはっきり現れ、値を下げると文字列全体がほぼ一度に流れ出るように現れる。 |

prefers-reduced-motion は9本すべてのスクリプトの冒頭でチェックされていて、個別のプロパティをただ止めるのではなく、それぞれが最終状態へ一気に飛びます — 02番のバッジはスプリングの通過なしに最終位置へ現れ、06番のカスケードは GENERATE_CASCADE_STEP_MS ごとの一文字ずつではなく文字列全体を一段階で表示します。ここでのトグルはすべて本物の <button type="button"> なので、EnterとSpaceはブラウザ自身のボタンの仕様を通じて動作し、独自実装のkeydownハンドラには頼っていません。aria-pressed はそのすべてで欄が現在マスク解除されているかどうかを映しています。08番はさらにもう一つの経路を足していて、フィールド上の Ctrl+Shift+H ショートカットがクリックと同じ toggleReveal() を呼んだ後 --ring-alpha カスタムプロパティを明るくするので、キーボードだけの訪問者もマウスクリックと同じ反応をフィールド自身から見ることができます。<input type="password">・:autofill・prefers-reduced-motion・requestAnimationFrame() はいずれもMDNに文書化されています。

パスワード表示/非表示 アイコンはボタンと何が違うのか?

パスワード 目のアイコン 名前を調べてボタンとの違いにたどり着いた方への答えは、この9つのすべてに共通しています。実物の入力要素の type を password と text の間で切り替え、隠しフィールドやCSSの小細工ではなく単一の進行値だけがアイコンとフィールド双方の動きを同時に駆動する、という一点です。アイコンの見た目だけが変わって欄自体はマスクされたまま、あるいはアイコンが気づかないまま欄だけがマスク解除される — それが二つがずれてしまう原因で、上の9パターンはどれもアイコンの状態と欄の type を一つの共有変数に結びつけているのでずれようがありません。同じ「値ひとつを共有する」考え方は隣接する認証UIにも通じていて、認証コード 入力画面が直面する9つの状況 バリデーションまで対応 では似た単一の状態値がマス目ごとのマスクとバリデーションの両方を結んでいます。

FAQ — 作る前によくある質問

type を password から text へ切り替えること自体がアクセシビリティ上の問題になりますか?

いいえ — その切り替え自体は標準的でアクセシブルな表示方法で、支援技術にもマスク済みの欄とは異なるものとしてすでに認識されます。この9つが標準の上に加えているのはトグル操作そのものを伝えるための合図で、aria-live で読み上げているのは02・04・06の三つだけです。アクセシビリティの穴は type の切り替えそのものではなく、残り六つに読み上げ通知がないことにあります。

9つのうちどれが最もフレームを塗り直していますか、またその理由は?

01番と03番が累積面積76.0%で並んでいます。どちらもキー入力のたびにマスクされた点と実際の文字からなるオーバーレイ全体を欄の横幅いっぱいでクロスフェードさせていて、小さな一部分だけをアニメーションさせているわけではないため、buildOverlay() や paintEyes() が走るたびに欄のほぼ全体が塗り直されます。

貼り付け検知バナーはパスワード欄に貼り付けられた中身を読み取っていますか?

いいえ — handlePaste() は入力欄の本物の paste イベントで発火しますが、event.clipboardData も欄自体の値も一切読み取りません。バナーが伝えるのは貼り付けが発生した事実だけで、パスワードマネージャーの貼り付けによる自動入力の挙動を説明するにはそれで十分であり、中身を露出することはありません。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。