/* ResetCard.jsx — PW-010 Password Reset (3-step + success) */ const RT = { primary: '#0D7A6E', primarySoft: '#E6F4F2', primaryPressed: '#085C53', n900: '#111827', n700: '#374151', n500: '#6B7280', n300: '#D1D5DB', n100: '#F3F4F6', n000: '#FFFFFF', error: '#DC2626', errorBg: '#FEF2F2', warning: '#D97706', warningBg: '#FFFBEB', success: '#059669', successBg: '#ECFDF5', }; const FONT_R = "'Inter', 'Pretendard', 'Noto Sans KR', system-ui, -apple-system, sans-serif"; const MONO_R = "'JetBrains Mono', 'SF Mono', Menlo, monospace"; const rs = { frame: { width: 1280, height: 900, background: RT.n100, position: 'relative', fontFamily: FONT_R, color: RT.n900, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', }, backLink: { position: 'absolute', top: 32, left: 32, display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13, fontWeight: 500, color: RT.n700, cursor: 'pointer', }, stateTag: { position: 'absolute', top: 20, right: 20, display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', background: RT.n000, border: `1px solid ${RT.n300}`, borderRadius: 999, fontSize: 11, fontWeight: 600, color: RT.n700, letterSpacing: 0.3, textTransform: 'uppercase', }, stateDot: { width: 6, height: 6, borderRadius: 999 }, card: { width: 480, background: RT.n000, borderRadius: 12, boxShadow: '0 4px 12px rgba(0,0,0,0.12)', padding: '40px 48px 40px', boxSizing: 'border-box', }, logoRow: { display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 28, }, logoInner: { display: 'flex', alignItems: 'center', gap: 10 }, logoMark: { width: 32, height: 32, borderRadius: 8, background: RT.primary, color: RT.n000, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: 15, }, logoText: { fontSize: 24, fontWeight: 700, color: RT.n900, letterSpacing: -0.3 }, subtitle: { marginTop: 10, fontSize: 13, fontWeight: 500, color: RT.n500, letterSpacing: 0.4, textTransform: 'uppercase', }, // Stepper stepper: { display: 'flex', alignItems: 'center', gap: 0, marginBottom: 28, }, stepCircle: (state) => ({ width: 28, height: 28, borderRadius: 999, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700, flexShrink: 0, background: state === 'done' ? RT.primary : state === 'current' ? RT.n000 : RT.n000, color: state === 'done' ? RT.n000 : state === 'current' ? RT.primary : RT.n300, border: state === 'current' ? `2px solid ${RT.primary}` : state === 'future' ? `1.5px solid ${RT.n300}` : 'none', }), stepLabel: (state) => ({ fontSize: 11, fontWeight: 600, marginLeft: 8, color: state === 'future' ? RT.n500 : RT.n900, whiteSpace: 'nowrap', letterSpacing: 0.2, }), stepConnector: (done) => ({ flex: 1, height: 2, background: done ? RT.primary : RT.n300, margin: '0 10px', borderRadius: 2, }), h1: { fontSize: 20, fontWeight: 700, color: RT.n900, margin: '0 0 8px 0', letterSpacing: -0.2, }, body: { fontSize: 13, fontWeight: 400, color: RT.n500, margin: '0 0 24px 0', lineHeight: 1.6, }, bodyStrong: { color: RT.n900, fontWeight: 600 }, label: { fontSize: 12, fontWeight: 600, color: RT.n700, marginBottom: 6, display: 'block' }, inputWrap: (opts = {}) => ({ height: 44, borderRadius: 8, border: `1px solid ${opts.error ? RT.error : opts.focus ? RT.primary : RT.n300}`, background: RT.n000, boxShadow: opts.focus ? `0 0 0 3px ${opts.error ? 'rgba(220,38,38,0.15)' : 'rgba(13,122,110,0.15)'}` : 'none', display: 'flex', alignItems: 'center', }), input: { flex: 1, border: 'none', outline: 'none', background: 'transparent', fontSize: 14, color: RT.n900, padding: '0 14px', height: '100%', fontFamily: 'inherit', display: 'flex', alignItems: 'center', }, btn: (enabled) => ({ marginTop: 24, width: '100%', height: 48, borderRadius: 8, border: 'none', background: enabled ? RT.primary : RT.n300, color: enabled ? RT.n000 : RT.n500, fontSize: 15, fontWeight: 600, fontFamily: FONT_R, cursor: enabled ? 'pointer' : 'not-allowed', letterSpacing: 0.2, }), banner: { display: 'flex', gap: 10, padding: '12px 14px', borderRadius: 8, background: RT.errorBg, border: '1px solid rgba(220,38,38,0.2)', fontSize: 13, fontWeight: 500, color: RT.error, lineHeight: 1.5, marginBottom: 16, alignItems: 'flex-start', }, bannerCode: { fontSize: 11, fontWeight: 500, opacity: 0.75, marginLeft: 4, fontFamily: MONO_R, }, inlineErr: { marginTop: 6, fontSize: 12, color: RT.error, display: 'flex', alignItems: 'center', gap: 6, }, // OTP otpRow: { display: 'flex', gap: 10, justifyContent: 'space-between', marginBottom: 4, }, otpCell: (state) => ({ width: 48, height: 56, borderRadius: 8, border: `1.5px solid ${state === 'error' ? RT.error : state === 'focus' ? RT.primary : state === 'filled' ? RT.n700 : RT.n300}`, background: RT.n000, boxShadow: state === 'focus' ? '0 0 0 3px rgba(13,122,110,0.15)' : 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: MONO_R, fontSize: 22, fontWeight: 600, color: RT.n900, }), otpMeta: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 12, marginBottom: 8, fontSize: 12, color: RT.n500, }, resendActive: { color: RT.primary, fontWeight: 600, cursor: 'pointer' }, resendDisabled: { color: RT.n300, fontWeight: 600 }, // Strength strengthWrap: { marginTop: 12 }, strengthRow: { display: 'flex', gap: 6, marginBottom: 8 }, strengthBar: (active, color) => ({ flex: 1, height: 4, borderRadius: 2, background: active ? color : RT.n100, }), strengthLabel: (color) => ({ fontSize: 12, fontWeight: 600, color, marginBottom: 10, }), policy: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px 12px', fontSize: 12, }, policyRow: { display: 'flex', alignItems: 'center', gap: 6 }, // Success successWrap: { textAlign: 'center', padding: '8px 0 0' }, successCircle: { width: 64, height: 64, borderRadius: 999, background: RT.successBg, color: RT.success, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 20, }, successTitle: { fontSize: 20, fontWeight: 700, color: RT.n900, margin: '0 0 10px 0', letterSpacing: -0.2, }, successBody: { fontSize: 13, color: RT.n500, lineHeight: 1.6, margin: 0, }, footer: { position: 'absolute', bottom: 24, left: 0, right: 0, textAlign: 'center', fontSize: 12, color: RT.n500, letterSpacing: 0.2, }, }; /* icons */ const RIc = { arrowLeft: (), alert: (color) => (), eye: (), check: (color) => (), dot: (color) => (), bigCheck: (), }; /* Logo + back link + footer */ function Logo() { return (
K
KARE
Partner
); } function BackLink() { return (
{RIc.arrowLeft} 로그인으로 돌아가기
); } function Footer() { return
© KARE 2026 · Medical Tourism Platform
; } function StateTag({ label, color }) { return (
{label}
); } /* Stepper */ function Stepper({ active }) { const steps = [ { n: 1, label: '이메일' }, { n: 2, label: '인증코드' }, { n: 3, label: '비밀번호' }, ]; return (
{steps.map((s, i) => { const state = s.n < active ? 'done' : s.n === active ? 'current' : 'future'; return (
{state === 'done' ? RIc.check(RT.n000) : s.n}
{s.label}
{i < steps.length - 1 &&
} ); })}
); } /* Step 1 — email */ function Step1({ errored }) { return (

비밀번호를 재설정합니다

가입 시 사용한 이메일로 인증코드를 발송합니다.

{errored && (
{RIc.alert(RT.error)} 가입되지 않은 이메일입니다. 입력한 주소를 다시 확인해주세요. ERR-001
)}
Email
admin@seoulclinic.co.kr
BTN-012
); } /* Step 2 — OTP */ function Step2({ expired }) { const otp = expired ? ['4','9','2','1','—','—'] : ['4','9','2','1','5','8']; const stateFor = (i) => { if (expired) return 'error'; if (i === 5) return 'focus'; return 'filled'; }; return (

인증코드를 입력하세요

admin@seoulclinic.co.kr {' '}로 발송된 6자리 코드를 입력하세요.

{expired && (
{RIc.alert(RT.error)} 인증코드가 만료되었습니다. 재전송 버튼을 눌러 새 코드를 받아주세요. ERR-006
)}
{otp.map((d, i) => (
{d === '—' ? : d}
))}
{expired ? '00:00' : '00:42'} 남음 재전송{!expired && ' (60초 후)'}
BTN-016
); } /* Step 3 — new password */ function Strength({ level }) { // level: 'weak' | 'normal' | 'strong' const map = { weak: { label: 'WEAK', color: RT.error, count: 1 }, normal: { label: 'NORMAL', color: RT.warning, count: 2 }, strong: { label: 'STRONG', color: RT.success, count: 3 }, }; const s = map[level]; return (
{[1,2,3].map(i => (
))}
비밀번호 강도: {s.label}
); } function Policy({ pass }) { const items = [ { k: 'len', label: '8자 이상' }, { k: 'case', label: '대·소문자 포함' }, { k: 'digit', label: '숫자 포함' }, { k: 'symbol', label: '특수문자 포함' }, ]; return (
{items.map(it => { const ok = pass[it.k]; return (
{ok ? {RIc.check(RT.success)} : } {it.label}
); })}
); } function Step3({ variant = 'strong' }) { // variants: 'strong' (ok) | 'weak' (policy error ERR-004) const isErr = variant === 'weak'; const pass = isErr ? { len: false, case: true, digit: false, symbol: false } : { len: true, case: true, digit: true, symbol: true }; const pwValue = isErr ? '•••••' : '•••••••••••'; const confirmValue = isErr ? '•••••' : '•••••••••••'; return (

새 비밀번호를 설정하세요

안전을 위해 이전에 사용한 적 없는 비밀번호를 사용해주세요.

{isErr && (
{RIc.alert(RT.error)} 비밀번호 정책을 충족하지 않습니다. 아래 조건을 모두 만족해야 합니다. ERR-004
)}
새 비밀번호
{pwValue}
{RIc.eye}
비밀번호 확인
{confirmValue}
{RIc.eye}
BTN-017
); } /* Success — 4th variant */ function StepSuccess() { return (
{RIc.bigCheck}

비밀번호가 재설정되었습니다

이제 새 비밀번호로 로그인할 수 있습니다.
보안을 위해 다른 기기에서는 자동 로그아웃됩니다.

→ PW-001
); } function Step1Default() { return ; } function Step1Error() { return ; } function Step2Default() { return ; } function Step2Expired() { return ; } function Step3Default() { return ; } function Step3Error() { return ; } Object.assign(window, { Step1Default, Step1Error, Step2Default, Step2Expired, Step3Default, Step3Error, StepSuccess, });