Million-Dollar Details · EP02Einblick1 Min. Lesezeit

Die 900-Millionen-Checkbox

Citibank wollte 7,8 Millionen Dollar Zinsen zahlen. Ein Haken gesetzt, zwei Einstellungen vergessen, und der ganze Kredit ging raus. Versuch, es besser zu machen.

01

Du bist dran

Ein vereinfachtes Formular nach dem Vorbild des Falls, nicht Citis echte Software. Aufgabe: nur die Zinsen zahlen.

Aufgabe: 7.800.000 $ Zinsen zahlen. Sonst nichts.

LOAN PAYMENT · REF 4417-RV

02

Dieselbe Aufgabe, besser gestaltet

Eine klare Zeile für die Zinsen, die gefährliche Option muss man gedrückt halten, und die Summe rechnet sofort mit.

Du überweist

7.800.000 $

03

Die Checkbox zum Kopieren

Reines HTML, CSS und JavaScript. Gedrückt halten: Der Rand füllt sich, dann fällt der rote Punkt hinein und wird zum Haken.

hold-check.html
<!-- Hold-to-confirm checkbox · metic.studio · Million-Dollar Details EP 02 -->
<button class="hold-check" type="button" role="checkbox" aria-checked="false">
  <span class="hold-check__box">
    <svg viewBox="0 0 30 30" aria-hidden="true">
      <path class="hold-check__edge" pathLength="1"
        d="M15 .75H21A8.25 8.25 0 0 1 29.25 9V21A8.25 8.25 0 0 1 21 29.25H9A8.25 8.25 0 0 1 .75 21V9A8.25 8.25 0 0 1 9 .75Z"/>
      <circle class="hold-check__dot" cx="9" cy="15.5" r="3.4"/>
      <path class="hold-check__tick" pathLength="1" d="M9 15.5L13.3 19.8 21.5 10.5"/>
    </svg>
  </span>
  <span>Pay off the whole loan<small>hold to confirm</small></span>
</button>

<style>
  .hold-check {
    --ink: #121214; --line: rgb(18 18 20 / .32); --accent: #ff3e3e; --lens: cubic-bezier(.23, 1, .32, 1);
    all: unset; display: inline-flex; align-items: center; gap: 14px; min-height: 48px; cursor: pointer;
    font: 500 17px/1.25 system-ui, sans-serif; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  }
  .hold-check small { display: block; font: 12.5px ui-monospace, monospace; color: var(--accent); }
  .hold-check__box {
    position: relative; width: 30px; height: 30px; box-sizing: border-box; border: 1.5px solid var(--line); border-radius: 9px;
    transition: border-color .3s var(--lens) .1s, transform .16s var(--lens);
  }
  .hold-check__box svg { position: absolute; inset: -1.5px; width: 30px; height: 30px; overflow: visible; }
  /* the border fills while you hold */
  .hold-check__edge { fill: none; stroke: var(--accent); stroke-width: 2.6; stroke-linecap: round;
    stroke-dasharray: 1; stroke-dashoffset: 1; transition: opacity .35s var(--lens) .2s; }
  .hold-check__tick { fill: none; stroke: var(--accent); stroke-width: 2.8; stroke-linecap: round; stroke-linejoin: round;
    stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .22s ease-in; }
  .hold-check__dot { fill: var(--accent); transform-box: fill-box; transform-origin: center; transform: translateY(-22px) scale(0); }
  .hold-check.is-holding .hold-check__box { transform: scale(.96); }
  /* confirmed: the red dot drops in and becomes the check */
  .hold-check[aria-checked="true"] .hold-check__box { border-color: var(--ink); }
  .hold-check[aria-checked="true"] .hold-check__edge { opacity: 0; }
  .hold-check[aria-checked="true"] .hold-check__dot { animation: hold-check-drop .5s var(--lens) .08s forwards; }
  .hold-check[aria-checked="true"] .hold-check__tick { stroke-dashoffset: 0; transition: stroke-dashoffset .32s var(--lens) .28s; }
  .hold-check.is-shaking .hold-check__box { animation: hold-check-shake .32s var(--lens); }
  .hold-check:focus-visible .hold-check__box { outline: 2px solid var(--accent); outline-offset: 3px; }
  @keyframes hold-check-drop {
    0% { transform: translateY(-22px) scale(1); }
    32% { transform: translateY(0) scale(1.15, .8); }
    45% { transform: translateY(0) scale(1); }
    100% { transform: translateY(0) scale(0); }
  }
  @keyframes hold-check-shake { 25% { transform: translateX(-3px); } 60% { transform: translateX(2px); } }
  @media (prefers-reduced-motion: reduce) { .hold-check * { transition-duration: .01ms !important; animation-duration: .01ms !important; } }
</style>

<script>
  // Hold 900 ms to confirm. Let go early and the border runs back. One click unchecks.
  document.querySelectorAll('.hold-check').forEach((btn) => {
    const edge = btn.querySelector('.hold-check__edge'), HOLD = 900
    let t0 = 0, p = 0, raf = 0, holding = false
    const set = (v) => (edge.style.strokeDashoffset = 1 - v)
    const check = (on) => btn.setAttribute('aria-checked', String(on))
    const frame = (now) => {
      if (!holding) return
      p = Math.min(1, (now - t0) / HOLD); set(p)
      if (p === 1) { holding = false; btn.classList.remove('is-holding'); check(true); setTimeout(() => set(0), 700); return }
      raf = requestAnimationFrame(frame)
    }
    const start = () => {
      if (btn.getAttribute('aria-checked') === 'true') return check(false)
      holding = true; t0 = performance.now(); btn.classList.add('is-holding'); raf = requestAnimationFrame(frame)
    }
    const stop = () => {
      if (!holding) return
      holding = false; cancelAnimationFrame(raf); btn.classList.remove('is-holding')
      btn.classList.remove('is-shaking'); void btn.offsetWidth; btn.classList.add('is-shaking')
      const from = p, s = performance.now()
      const back = (now) => { const q = Math.min(1, (now - s) / 320); set(from * Math.pow(1 - q, 3)); if (q < 1) requestAnimationFrame(back) }
      requestAnimationFrame(back)
    }
    btn.addEventListener('pointerdown', (e) => { e.preventDefault(); start() })
    ;['pointerup', 'pointerleave', 'pointercancel'].forEach((ev) => btn.addEventListener(ev, stop))
    btn.addEventListener('keydown', (e) => { if ((e.key === ' ' || e.key === 'Enter') && !e.repeat) { e.preventDefault(); start() } })
    btn.addEventListener('keyup', (e) => { if (e.key === ' ' || e.key === 'Enter') stop() })
  })
</script>

Was passiert ist

Am 11. August 2020 sollte Citibank 7,8 Millionen Dollar Zinsen an die Geldgeber eines Kredits an Revlon überweisen. In der Kreditsoftware der Bank ging das nur über einen Umweg: das Kapital als bezahlt buchen und auf ein internes Konto leiten.

Der Bearbeiter setzte den Haken bei „principal“ und trug das interne Konto ein. Er dachte, damit bleibt das Geld in der Bank. Falsch: Zwei weitere Einstellungen, „front“ und „fund“, hätten dasselbe Konto gebraucht. Zwei Kollegen prüften und gaben frei.

Raus ging der ganze Kredit samt Zinsen, rund 900 Millionen Dollar. Im Februar 2021 entschied ein Richter, dass die Geldgeber, die nicht zurückzahlten, rund 500 Millionen behalten dürfen. Im September 2022 hob das Berufungsgericht das auf, und Citi bekam das Geld zurück.

Was sie gerettet hätte

  • Eine klare Auswahl statt drei kryptischer Kästchen.
  • Beschriftungen in normaler Sprache, nicht in Systemkürzeln.
  • Der Betrag, groß. Die eine Zahl, auf die es ankommt.
  • Eine Summe, die sich beim Auswählen sofort mitrechnet.
  • Gedrückt halten für die gefährliche Option. Wer zu früh loslässt, löst nichts aus.

Quellen

Solche Details auf deiner Website?

Die Lupe zeigt, was darunter liegt. Finger auf die Seite halten, um selbst zu schauen.