GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

카드 번호 입력 UI 9종 — 자릿수 값마다 반응이 달라지는 아홉 가지 방식

카드 번호 입력 ui는 자릿수 값이 채워지는 상태에 따라 화면 여러 곳의 값이 함께 바뀌는 인터랙션인데, 아홉 개 중 하나는 입력된 숫자 값이 네 자리에 이를 때마다 그 사이 간격 값이 자동으로 생기고, 하나는 자릿수 값이 채워질 때마다 번호 전체의 유효성 값이 다시 계산되어 표시

아홉 항목 모두 로드 즉시 도는 자동 시연 루프는 값을 미리 보여주는 보조 장치일 뿐이고, 실제 계산은 클릭·타이핑·포커스·포인터 이동 같은 실사용자 입력이 일어날 때 다시 돕니다 — 아홉 개 전부 자동 전용 리스너가 하나도 없고 Math.random도 0이라, 같은 순서로 조작하면 항상 같은 값이 나오는 결정적 계산입니다. 예시 카드 번호는 룬 계산이 통과하는 합성값 9999 9999 9999 9995와 통과하지 않는 9999999999999990 뿐이고, 실제 카드사 접두·브랜드명은 물론 결제·PG 연동·서버 fetch도 하나 없이 화면 안에서만 계산이 끝납니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개의 해부 — 어떤 값이 언제 바뀌는가

카드 정보 입력 ui 화면 아홉 개는 각각 다른 실사용자 입력(01·02·05·07·08은 입력 칸 타이핑, 03은 포커스·블러, 04는 버튼 클릭, 06은 타이핑과 클릭을 함께, 09는 포인터 이동·이탈)으로 값을 다시 계산하고, 계산 방식이 같은 것끼리 아래 다섯 무리로 나뉩니다.

번호 이름 리스너 누적면적% 강도 움직인 프레임/23
01 자릿수 값 그룹 전환 input(카드 번호 칸) 0.91 163.0 15
02 만료 값 구분자 전환 input(유효기간 칸) 1.64 105.5 21
03 보안 코드 초점 값 전환 focus/blur(CVC 칸) 17.10 149.4 6
04 가림 값 전환 click(가림 값 전환 버튼) 0.73 107.3 7
05 유효성 값 실시간 전환 input(카드 번호 칸) 0.77 144.7 14
06 오류 값 흔들림 전환 input+click(입력+확인 버튼) 2.29 127.1 11
07 완료 값 빛남 전환 input(카드 번호 칸) 3.31 118.4 20
08 패턴 인식 값 전환 input(카드 번호 칸) 1.34 120.4 6
09 초점 값과 기울기 값 연동 pointermove/leave(카드 패드) 3.65 55.0 23

09는 스크롤 인터랙션처럼 23프레임 내내 살아 움직이지만 누적면적 자체는 다른 여덟 항목과 비슷한 수준입니다 — 화면 전체를 채우는 장면이 아니라 입력 칸 하나·카드 한 장 크기의 컴포넌트라, 09를 살아 있게 만드는 건 면적이 아니라 관성 보간이 만드는 정교함입니다.

자릿수 값을 세어 표시 요소를 다시 조립하는 값 — 01·02·04

01은 정규식 /.{1,4}/g로 잘라낸 자릿수 값을 4개씩 재조립해 간격 값을 넣고, 02는 앞 2자리 값이 채워지는 순간 슬래시 구분자 값을 켜며, 04는 KEEP(4)보다 뒤에 있는 자릿수 값마다 STAGGER_MS(45ms)만큼 늦게 가림 값을 전환합니다. 셋 다 값을 세는 대상은 자릿수지만 01은 간격을, 02는 구분자를, 04는 가림 여부를 각각 다르게 재구성합니다.

룬 체크섬을 자릿수 값이 바뀔 때마다 다시 계산하는 값 — 05·07

05의 luhnSum()과 07의 luhnSum()은 오른쪽부터 한 자리씩 건너 2배로 만드는 같은 가중 합 로직이지만, 05는 digits.length >= 13이면 유효성 판정에 들어가고 07은 digits.length === 16이어야만 잠금 절차로 넘어갑니다. 같은 계산을 쓰면서도 코드에 박힌 통과 자릿수 기준 값이 서로 다릅니다.

초점 값 하나로 화면 전체를 뒤집는 값 — 03

03은 CVC 입력 칸의 초점 값이 켜지고 꺼지는 것만으로 카드 그림 전체가 180도 넘게 회전했다가 정착하고, input 리스너는 뒷면 숫자 표시만 갱신할 뿐 회전 자체는 건드리지 않습니다.

정해진 지연 뒤 스스로 되돌리는 값 — 06

06은 확인 버튼을 눌러야 verify()가 돌고, 16자리에 못 미치면 흔들림 값을 켠 뒤 AUTO_CLEAR_MS(700ms) 후 입력 값 자체를 비웁니다 — 값을 켜는 것과 되돌리는 것이 한 함수 안에서 타이머로 묶여 있습니다.

목표값까지 매 프레임 비율만큼 다가가는 rAF 루프 값 — 08·09

08은 requestAnimationFrame으로 도형의 점 좌표 값을 MORPH_MS(420ms) 동안 이징 곡선을 타고 목표 도형까지 보간하고, 09는 매 프레임 curX·curY 값을 목표 기울기 값까지 LERP(0.14) 비율만큼만 좁혀 관성 있게 따라갑니다. 두 항목만 CSS transition이 아니라 자바스크립트 루프 자체가 값을 그리고, 나머지 일곱 항목은 클래스만 토글하고 실제 전환은 CSS transition·animation이 맡습니다.

패턴별 코드

아홉 항목의 계산 겹만 추려 코드로 봅니다 — 카드 번호 입력 ui 아홉 가지를 실제로 붙여넣어 돌려 보면 각 함수가 어떤 값을 언제 다시 계산하는지 눈으로 확인할 수 있습니다.

01자릿수 값 그룹 전환 — 입력된 숫자 값이 네 자리마다 그 사이 간격 값이 자동으로 생기는 방식

groupsOf()가 자릿수 값을 4개씩 잘라 배열로 만들고, paintTrack()이 각 그룹의 채움 비율만큼 세그먼트 바의 scaleX 값을 키우면서 4자리 경계를 넘을 때마다 간격 표시 값을 켭니다. 결제 폼의 카드 번호 입력 칸, 회원 카드 등록 화면에 맞는 계산입니다.

  function groupsOf(rawDigits) {
    var g = rawDigits.match(/.{1,4}/g) || [];
    while (g.length < 4) g.push('');
    return g;
  }

  function paintTrack(rawDigits) {
    var g = groupsOf(rawDigits);
    for (var i = 0; i < 4; i++) {
      segFills[i].style.transform = 'scaleX(' + (g[i].length / 4) + ')';
    }
    for (var j = 0; j < 3; j++) {
      var on = rawDigits.length > (j + 1) * 4;
      gaps[j].classList.toggle('ci__gap--on', on);
    }
  }
자릿수 값 그룹 전환 — 입력된 숫자 값이 네 자리마다 그 사이 간격 값이 자동으로 생기는 방식
새 창에서 열기

01 · 자릿수 값 그룹 전환 — 입력된 숫자 값이 네 자리마다 그 사이 간격 값이 자동으로 생기는 방식 — 측정 누적면적 0.912% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

digitsBeforegroupsOfpaintTrack

02만료 값 구분자 전환 — 만료 월과 연도의 자릿수 값이 채워지면 그 사이 구분자 값이 자동으로 나타나는 방식

build()가 월 값을 01~12 범위로 clamp 하고, 월 자릿수 값이 두 자리를 채우는 순간 slashOn 값을 켜 구분자를 나타냅니다. 결제 폼의 카드 유효기간 입력, 정기결제 등록 화면에 쓰는 계산입니다.

  function build(rawDigits) {
    var mm = rawDigits.slice(0, 2);
    var yy = rawDigits.slice(2, 4);
    var clamped = false;
    if (mm.length === 2) {
      var n = parseInt(mm, 10);
      if (n === 0) { mm = '01'; clamped = true; }
      else if (n > 12) { mm = '12'; clamped = true; }
    }
    var slashOn = mm.length === 2;
    return { display: mm + (slashOn ? '/' : '') + yy, mm: mm, yy: yy, slashOn: slashOn, clamped: clamped };
  }
만료 값 구분자 전환 — 만료 월과 연도의 자릿수 값이 채워지면 그 사이 구분자 값이 자동으로 나타나는 방식
새 창에서 열기

02 · 만료 값 구분자 전환 — 만료 월과 연도의 자릿수 값이 채워지면 그 사이 구분자 값이 자동으로 나타나는 방식 — 측정 누적면적 1.644% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

buildpulsedigitsOnly

03보안 코드 초점 값 전환 — 보안 코드 칸의 초점 값이 켜지면 카드 뒷면을 보여주는 값으로 화면이 바뀌는 방식

setFlipped()는 초점 값 on/off만으로 카드의 회전 클래스를 토글하고, 실제 180도 회전과 정착은 CSS 전환 이징 토큰($ease-lerp-settle)이 맡습니다. 결제 폼의 CVC 입력 칸, 카드 등록 화면의 보안 코드 안내에 쓰는 계산입니다.

  // 계산 겹: 초점 값 on/off 에 따라 카드의 3D 회전 각 값이 스프링 곡선($ease-lerp-settle)을
  // 타고 180deg 를 넘겼다가 되돌아와 정착한다 — 등속 회전이 아니라 관성 있는 회전.
  function setFlipped(on) { card.classList.toggle('cv__card--flipped', on); }

  input.addEventListener('focus', function () { root.classList.remove('is-demo'); stopAutoplay(); setFlipped(true); });
  input.addEventListener('blur', function () { setFlipped(false); });
  input.addEventListener('input', function () {
    box.textContent = (input.value.replace(/\D/g, '').slice(0, 3) || '').padEnd(3, '•');
  });
보안 코드 초점 값 전환 — 보안 코드 칸의 초점 값이 켜지면 카드 뒷면을 보여주는 값으로 화면이 바뀌는 방식
새 창에서 열기

03 · 보안 코드 초점 값 전환 — 보안 코드 칸의 초점 값이 켜지면 카드 뒷면을 보여주는 값으로 화면이 바뀌는 방식 — 측정 누적면적 17.101% · 움직인 프레임 6/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

setFlippedcv__input--focus$ease-lerp-settle

04가림 값 전환 — 가림 값이 켜지면 앞의 몇 자리만 남기고 나머지 자릿수 값이 기호 값으로 바뀌는 방식

KEEP(4)보다 뒤에 있는 자릿수마다 CSS 변수 --i에 순서 값을 심어 STAGGER_MS(45ms)씩 늦게 전환되도록 만들고, 토글 버튼이 masked 값을 뒤집으면 그 순서대로 기호 값이 번져 나갑니다. 저장된 카드 목록의 번호 표시, 결제 내역의 카드 번호 안내에 쓰는 계산입니다.

  var DIGITS = '9999999999999995';
  var KEEP = 4; // 앞의 몇 자리 값은 항상 그대로
  var STAGGER_MS = 45; // 계산 겹: 자릿수 값마다 이만큼씩 늦게 전환된다

  var cells = [];
  DIGITS.split('').forEach(function (ch, i) {
    var group = document.createElement('span');
    group.className = 'cm__group';
    if (i > 0 && i % 4 === 0) group.classList.add('cm__group--gap');
    if (i < KEEP) {
      group.innerHTML = '<span class="cm__digit cm__digit--fixed">' + ch + '</span>';
    } else {
      var idx = i - KEEP;
      group.style.setProperty('--i', idx);
      group.innerHTML =
        '<span class="cm__digit">' + ch + '</span>' +
        '<span class="cm__mask">•</span>';
      cells.push(group);
    }
    row.appendChild(group);
  });
가림 값 전환 — 가림 값이 켜지면 앞의 몇 자리만 남기고 나머지 자릿수 값이 기호 값으로 바뀌는 방식
새 창에서 열기

04 · 가림 값 전환 — 가림 값이 켜지면 앞의 몇 자리만 남기고 나머지 자릿수 값이 기호 값으로 바뀌는 방식 — 측정 누적면적 0.732% · 움직인 프레임 7/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

STAGGER_MSKEEPDIGITS

05유효성 값 실시간 전환 — 자릿수 값이 채워질 때마다 번호 전체의 유효성 값이 계산되어 표시 값이 바뀌는 방식

luhnSum()이 오른쪽부터 한 자리씩 건너 2배로 만든 가중 합 값을 매 입력마다 다시 구하고, 그 합이 10의 배수이면서 자릿수 값이 13자리 이상이면 배지의 확인 표시 값이 켜집니다. 결제 폼의 카드 번호 실시간 검증, 회원가입의 카드 등록 확인에 쓰는 계산입니다.

  function luhnSum(digits) {
    var sum = 0, dbl = false;
    for (var i = digits.length - 1; i >= 0; i--) {
      var d = digits.charCodeAt(i) - 48;
      if (dbl) { d *= 2; if (d > 9) d -= 9; }
      sum += d;
      dbl = !dbl;
    }
    return sum;
  }

  function paint(digits) {
    var sum = digits.length ? luhnSum(digits) : 0;
    var valid = digits.length >= 13 && sum % 10 === 0;
    sumLabel.textContent = '가중 합 값 ' + sum;
    badge.classList.toggle('cl__badge--on', valid);
  }
유효성 값 실시간 전환 — 자릿수 값이 채워질 때마다 번호 전체의 유효성 값이 계산되어 표시 값이 바뀌는 방식
새 창에서 열기

05 · 유효성 값 실시간 전환 — 자릿수 값이 채워질 때마다 번호 전체의 유효성 값이 계산되어 표시 값이 바뀌는 방식 — 측정 누적면적 0.766% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

luhnSumcl__badge--onstroke-dasharray

06오류 값 흔들림 전환 — 번호의 오류 값이 켜지면 칸의 흔들림 값이 커졌다 잦아들며 입력 값이 지워지는 방식

verify()는 확인 버튼을 눌렀을 때만 돌아 16자리가 아니면 shake()로 흔들림 값을 한 번 켜고, AUTO_CLEAR_MS(700ms) 뒤 입력 값 자체를 비웁니다. 결제 실패 안내가 필요한 카드 입력 폼, 형식이 틀린 번호를 되돌리는 화면에 쓰는 계산입니다.

  function shake() {
    field.classList.remove('ck__field--error');
    void field.offsetWidth;
    field.classList.add('ck__field--error');
  }

  function verify() {
    var digits = input.value.replace(/\D/g, '');
    if (digits.length === 16) {
      msg.textContent = '';
      return true;
    }
    msg.textContent = '16자리 값이 아닙니다';
    shake();
    setTimeout(function () {
      input.value = '';
      msg.textContent = '';
    }, AUTO_CLEAR_MS);
    return false;
  }
오류 값 흔들림 전환 — 번호의 오류 값이 켜지면 칸의 흔들림 값이 커졌다 잦아들며 입력 값이 지워지는 방식
새 창에서 열기

06 · 오류 값 흔들림 전환 — 번호의 오류 값이 켜지면 칸의 흔들림 값이 커졌다 잦아들며 입력 값이 지워지는 방식 — 측정 누적면적 2.292% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

AUTO_CLEAR_MSshakeverify

07완료 값 빛남 전환 — 번호의 완료 값이 켜지면 테두리의 빛남 값이 서서히 올라가며 칸이 잠금 값으로 바뀌는 방식

tryLock()은 16자리 값이 룬 체크섬까지 통과할 때만 빛남 클래스를 다시 걸어 애니메이션을 재생시키고, GLOW_MS(900ms) 뒤에야 칸을 잠금 값으로 바꿔 더 이상 눌리지 않게 합니다. 카드 등록이 끝났음을 보여주는 화면, 결제 폼의 완료 상태 안내에 쓰는 계산입니다.

  function tryLock() {
    var digits = input.value.replace(/\D/g, '');
    if (digits.length === 16 && luhnSum(digits) % 10 === 0) {
      glow.classList.remove('cg__glow--on');
      void glow.offsetWidth;
      glow.classList.add('cg__glow--on');
      setTimeout(function () {
        field.classList.add('cg__field--locked');
        input.setAttribute('readonly', 'readonly');
      }, GLOW_MS);
    }
  }

  input.addEventListener('input', function () {
    root.classList.remove('is-demo');
    stopAutoplay();
    input.value = input.value.replace(/\D/g, '').slice(0, 16);
    tryLock();
  });
완료 값 빛남 전환 — 번호의 완료 값이 켜지면 테두리의 빛남 값이 서서히 올라가며 칸이 잠금 값으로 바뀌는 방식
새 창에서 열기

07 · 완료 값 빛남 전환 — 번호의 완료 값이 켜지면 테두리의 빛남 값이 서서히 올라가며 칸이 잠금 값으로 바뀌는 방식 — 측정 누적면적 3.308% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

GLOW_MStryLockcg__glow--on

08패턴 인식 값 전환 — 앞자리 숫자 값이 정해진 패턴 값과 맞으면 옆의 도형 값이 다른 모양으로 바뀌는 방식

PREFIX_MAP이 앞자리 숫자 하나를 가상 도형 A/B/C 중 하나로 연결하고, morphTo()가 requestAnimationFrame으로 도형 점 좌표 값을 MORPH_MS(420ms) 동안 이징 곡선을 타고 목표 도형까지 보간합니다. 카드 번호로 카드 종류를 구분해 보여주는 폼, 결제 폼의 실시간 판별 표시에 쓰는 계산입니다.

  var MORPH_MS = 420; // 계산 겹: 앞자리 패턴 값이 바뀔 때 도형 점을 이만큼 동안 보간한다

  // 가상 도형 3종(A/B/C) — 실제 카드사 로고·BIN 범위와 무관한 중립 라벨.
  var SHAPES = {
    A: [[20, 4], [33.9, 12], [33.9, 28], [20, 36], [6.1, 28], [6.1, 12]],
    B: [[29, 4.4], [33, 20], [29, 35.6], [13.5, 31.3], [2, 20], [13.5, 8.7]],
    C: [[20, 1], [26.9, 16], [36.5, 29.5], [20, 28], [3.5, 29.5], [13.1, 16]],
    N: [[20, 10], [28, 15], [28, 25], [20, 30], [12, 25], [12, 15]]
  };
  var PREFIX_MAP = { '3': 'C', '4': 'A', '5': 'B' };

  var current = SHAPES.N.map(function (p) { return p.slice(); });
  var raf = null;

  function easeOut(t) { return 1 - Math.pow(1 - t, 3); }

  function morphTo(key) {
    var target = SHAPES[key];
    var start = current.map(function (p) { return p.slice(); });
    var t0 = performance.now();
    if (raf) cancelAnimationFrame(raf);
패턴 인식 값 전환 — 앞자리 숫자 값이 정해진 패턴 값과 맞으면 옆의 도형 값이 다른 모양으로 바뀌는 방식
새 창에서 열기

08 · 패턴 인식 값 전환 — 앞자리 숫자 값이 정해진 패턴 값과 맞으면 옆의 도형 값이 다른 모양으로 바뀌는 방식 — 측정 누적면적 1.34% · 움직인 프레임 6/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

MORPH_MSPREFIX_MAPSHAPES

09초점 값과 기울기 값 연동 — 칸의 초점 위치 값이 움직이면 카드 그림의 기울기 값이 함께 따라 움직이는 방식

setTargetFromPoint()가 포인터 좌표 값을 패드 크기에 대한 비율로 환산해 MAX_DEG(14)를 상한으로 하는 목표 기울기 값을 정하고, apply()가 매 프레임 그 목표까지 LERP(0.14) 비율만큼만 좁혀 관성 있게 회전시킵니다. 카드 등록 화면의 입체감 있는 카드 그림, 결제 폼의 인터랙션 장식에 쓰는 계산입니다.

  var MAX_DEG = 14;
  var LERP = 0.14; // 계산 겹: 목표 기울기 값으로 매 프레임 이 비율만큼만 다가가는 관성 보간

  var targetX = 0, targetY = 0, curX = 0, curY = 0;
  var raf = null;

  function apply() {
    curX += (targetX - curX) * LERP;
    curY += (targetY - curY) * LERP;
    card.style.transform = 'perspective(600px) rotateX(' + curX.toFixed(2) + 'deg) rotateY(' + curY.toFixed(2) + 'deg)';
    sheen.style.transform = 'translate(' + (curY * 1.4).toFixed(1) + 'px,' + (curX * -1.4).toFixed(1) + 'px)';
    raf = requestAnimationFrame(apply);
  }
  raf = requestAnimationFrame(apply);

  function setTargetFromPoint(px, py) {
    var r = pad.getBoundingClientRect();
    var nx = (px - r.left) / r.width - 0.5;
    var ny = (py - r.top) / r.height - 0.5;
    targetY = nx * MAX_DEG * 2;
    targetX = -ny * MAX_DEG * 2;
  }
초점 값과 기울기 값 연동 — 칸의 초점 위치 값이 움직이면 카드 그림의 기울기 값이 함께 따라 움직이는 방식
새 창에서 열기

09 · 초점 값과 기울기 값 연동 — 칸의 초점 위치 값이 움직이면 카드 그림의 기울기 값이 함께 따라 움직이는 방식 — 측정 누적면적 3.647% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

MAX_DEGLERPsetTargetFromPoint

카드번호 입력 방법이 아홉 갈래로 갈리는 기준 — 간격 기준·유효성 계산·기울기 감도

간격 기준은 01과 04가 서로 다르게 잡습니다. 01은 정규식 /.{1,4}/g에 4라는 숫자가 그대로 박혀 있어 간격이 항상 4자리마다 생기고, 04는 KEEP(4)로 그대로 둘 자리 수를 정하고 STAGGER_MS(45ms)로 나머지 자릿수 값이 얼마나 늦게 번지는지를 정합니다 — 같은 "4"라는 값도 01에서는 자르는 단위, 04에서는 고정으로 남기는 개수로 쓰입니다.

유효성 계산의 기준은 05와 07이 갈립니다. 둘 다 luhnSum()으로 같은 가중 합을 구하지만 05는 digits.length >= 13이면 통과로 보고, 07은 digits.length === 16이어야만 통과로 봅니다 — 같은 룬 계산 함수를 코드 안 어디에 얼마나 엄격한 자릿수 조건과 묶느냐에 따라 통과 시점이 달라집니다.

기울기 감도의 기준은 09의 MAX_DEG(14)와 LERP(0.14) 두 값이 정합니다. MAX_DEG는 포인터가 패드 가장자리까지 움직였을 때 기울기 값이 도달하는 상한이고, LERP는 그 목표까지 매 프레임 얼마나 빨리 다가가는지를 정해 값이 클수록 뻣뻣하고 작을수록 더디게 따라옵니다.

어디서 깨지는가 — 붙여넣기 순간·좁은 화면·모션 감소

붙여넣기 순간을 다루는 방식이 항목마다 다릅니다. 06의 input 리스너는 자릿수 값만 걸러 슬라이스할 뿐 verify()를 부르지 않아, 클립보드에서 잘못된 자릿수 값을 붙여넣어도 흔들림 값이나 오류 문구가 즉시 뜨지 않고 확인 버튼을 눌러야만 검증이 돌아갑니다. 붙여넣는 즉시 검증을 원한다면 06의 input 리스너 안에 verify() 호출을 더해 코드로 직접 맞춰볼 수 있습니다.

좁은 화면(340px 이하)에서는 값을 그리는 요소 크기와 그 값을 계산하는 상수 사이 관계가 항목마다 다릅니다. 09는 .ct__card가 220×132에서 190×114로 줄어도 setTargetFromPoint()가 매번 pad.getBoundingClientRect()로 실측한 크기를 쓰기 때문에 기울기 범위 자체는 어긋나지 않지만, 반짝임 효과(sheen)의 이동 값은 curY * 1.4처럼 카드 크기와 무관한 고정 배율이라 카드가 작아질수록 반짝임이 카드 폭에 비해 상대적으로 더 크게 훑고 지나갑니다. 04는 반대로 .cm__group 너비가 13px에서 11px로, 글자 크기가 16px에서 14px로 CSS 쪽에서만 함께 줄어들고 자바스크립트 쪽은 애초에 픽셀 값을 참조하지 않아 어긋날 자리가 없습니다. 아홉 항목의 html·scss·js 원본은 비번 krbccgxc 으로 열리는 압축 파일 안에 그대로 들어 있어 이런 지점을 직접 고쳐 비교해 볼 수 있습니다.

모션 감소는 아홉 항목 모두 window.matchMedia('(prefers-reduced-motion: reduce)')로 자바스크립트에서 감지하고 style.scss의 prefers-reduced-motion 규칙으로 CSS transition·animation도 끄는 이중 처리를 하지만, 실제로 멈추는 대상은 갈립니다. 01·02·03·04·05·06·07·08은 reduce가 켜지면 자동 시연 루프(setInterval·setTimeout 체인)를 아예 시작하지 않고 값을 마지막 상태로 한 번만 그리는 반면, 09는 원을 그리며 도는 자동 루프만 멈출 뿐 apply()의 requestAnimationFrame 자체는 reduce와 무관하게 계속 돌아 실제 포인터 이동에는 그대로 반응합니다.

변형 세 가지와 접근성

이름 값 느낌
간격 전파 속도 04 STAGGER_MS 45 → 90 자릿수 값이 가림에서 풀릴 때 뒤로 갈수록 두 배 느리게 번져 그룹 전환이 또렷하게 보입니다
기울기 관성 비율 09 LERP 0.14 → 0.28 카드 그림이 포인터의 위치 값을 더 빨리 따라잡아 뻣뻣하게 움직입니다
빛남 지속 시간 07 GLOW_MS 900 → 1800 완료 값의 빛남이 두 배 오래 잦아들어 잠금 전환이 더 여유롭게 느껴집니다

접근성은 입력 칸을 가진 일곱 항목(01·02·03·05·06·07·08) 모두 type="text"에 inputmode="numeric"과 autocomplete="off"를 함께 걸어 모바일에서 숫자 자판을 띄우면서도 브라우저 자동완성이 값을 대신 채우지 않게 했습니다 — 04는 아예 <input> 없이 버튼 클릭으로 span 의 글자 값을 숫자↔기호로 치환하고, 09도 입력 칸 없이 포인터 패드의 pointermove·pointerleave만 씁니다. 장식용 요소(01의 트랙, 02의 칩, 05·07의 아이콘 svg)에는 aria-hidden="true"를 달아 스크린리더가 읽지 않게 뺐고, 04의 가림 토글 버튼은 aria-pressed 값을 상태에 맞춰 갱신하며, 06의 오류 문구는 aria-live="polite"로 걸려 있어 화면을 보지 않아도 오류 문구가 나오는 순간을 읽어 줍니다. 09의 포인터 패드에는 touch-action: none이 걸려 있어 손가락으로 기울기를 조작할 때 화면 스크롤과 충돌하지 않습니다.

FAQ

05와 07은 둘 다 룬 체크섬을 쓰는데 왜 통과 기준이 다른가요?

두 항목 모두 luhnSum()으로 같은 가중 합 계산을 하지만, 05는 실시간 확인 배지를 위한 항목이라 digits.length >= 13이면 판정에 들어가고 07은 완료·잠금까지 보여주는 항목이라 digits.length === 16으로 더 좁게 잡아 놓았습니다. 계산 로직이 같아도 그 계산을 어떤 조건과 묶어 언제 통과로 볼지는 항목의 쓰임에 따라 값이 다르게 정해져 있습니다.

09의 카드 기울기 인터랙션은 실제 결제가 진행되는 화면인가요?

아닙니다. pointermove 값을 받아 curX·curY 기울기 값을 화면 안에서만 계산해 transform에 반영할 뿐, 서버로 값을 보내는 fetch도 결제·PG 연동 코드도 소스 어디에도 없습니다. 카드 등록 화면에 장식으로 얹는 입체감 있는 카드 그림이지, 그 자체로 결제를 진행하는 로직은 아닙니다.

카드 번호 입력 ui 아홉 가지를 실제 결제 폼에 그대로 옮겨 써도 되나요?

값을 그리는 화면 로직(간격·가림·흔들림·기울기 등)은 그대로 옮길 수 있지만, 룬 체크섬은 입력 형식이 맞는지만 걸러줄 뿐 카드가 실제로 유효한지는 결제 대행사 쪽 검증을 거쳐야 확정됩니다. 이 아홉 항목은 화면 반응을 보여주는 프런트엔드 계산이라, 실제 결제 흐름에 붙이려면 서버 쪽 검증·PG 연동을 따로 갖춰야 합니다.

더 읽을거리

입력창 디자인 9가지는 입력 칸 하나하나의 테두리·라벨·포커스 표현처럼 폭넓은 폼 위젯 값을 다루는 데 비해, 이 편은 카드 번호라는 한 가지 문자열 값에서 파생되는 포맷·검증·표시 계산 아홉 가지만 깊게 판다는 점에서 계산 축이 다릅니다. 폼 유효성 검사 UI 9종은 입력 값이 규칙에 맞는지 보여주는 오류 안내 표시 값을 다루는 반면, 이 편의 05·06·07은 그 판정 값이 카드 번호라는 특정 문자열의 룬 체크섬·자릿수 조건에서만 나온다는 점에서 계산 축이 다릅니다. 장바구니 UI 9종은 수량·쿠폰·합계처럼 결제 화면 전체의 구성 값을 계산하는데, 이 편은 그 결제 화면 안 카드 번호 입력 칸 하나가 값을 어떻게 스스로 재구성하는지에만 집중한다는 점에서 계산 축이 다릅니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.