PageSourceSearch

https://morvarid.pub/login

html morvarid.pub collected 2026-09-25 02:02:07 UTC 90,399 bytes, 3,077 lines download raw bytes

1<!DOCTYPE html>
2<html class="web-pages d-flex justify-content-center" lang="fa">
3
4<head>
5	<meta charset="utf-8">
6	<title itemprop="name">Login</title>
7	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
8	<meta name="generator" content="Rodin365">
9
10	
11	<meta name="title" content="Login"><meta name="language" content="fa"><meta property="og:type" content="article"><meta property="og:title" content="Login"><meta name="twitter:title" content="Login"><meta name="twitter:card" content="summary">
12	
13
14
15	
16	<link rel="shortcut icon" href="/files/WhatsApp-Image-2026-09-12-at-12.09.15-(2)01cbcb.jpeg"
17		type="image/x-icon">
18	
19
20
21	<link rel="apple-touch-icon" href="/files/WhatsApp-Image-2026-09-12-at-12.09.15-(2)01cbcb.jpeg">
22	<link rel="stylesheet" href="/assets/frappe/bootstrap-icons/bootstrap-icons.css">
23	
24	<link rel="canonical" href="https://morvarid.pub/login">
25	
26	<link rel="stylesheet" href="/assets/frappe/css/swiper_css.css">
27	<link href="/assets/frappe/css/aos.css" rel="stylesheet">
28	<link href="/assets/frappe/css/glightbox.css" rel="stylesheet">
29
30	<!-- 
30<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js" async=""></script>
30 -->
31	
31<script src="/assets/erpnext/js/swiper_js.js"></script>
31
32	
32<script src="/assets/erpnext/js/isotope.js"></script>
32
33	
33<script src="/assets/erpnext/js/glightbox.js"></script>
33
34	
34<script src="/assets/erpnext/js/imagesloaded.js"></script>
34
35
36<link type="text/css" rel="stylesheet" href="/files/website_theme/theme_rodin_bfc79be1.css">
37<link type="text/css" rel="stylesheet" href="/assets/erpnext/css/persianDatepicker.bundle.css">
38<link type="text/css" rel="stylesheet" href="/assets/erpnext/css/farsiDatepicker.bundle.css">
39<link type="text/css" rel="stylesheet" href="/assets/erpnext/dist/css-rtl/erpnext-web.bundle.RMP35DDA.css">
40
41
42
43
44<link type="text/css" rel="stylesheet" href="/assets/frappe/dist/css-rtl/login.bundle.JUM5YRMZ.css">
45
46<style>
47		.rodin-seo-hidden-heading {
48			display: flex !important;
49			height: 0 !important;
50			overflow: hidden !important;
51			margin: 0 !important;
52			padding: 0 !important;
53		}
54	</style>
55<style>
56	@media screen and (max-width:676px) {
57		.header-title{
58			font-size: 12px;
59		}
60		.page-header-actions-block .dropdown-toggle span{
61		font-size: 12px !important;
62	}
63	}
64</style>
65<script>
66		window.frappe = {};
67		frappe.ready_events = [];
68		frappe.ready = function (fn) {
69			frappe.ready_events.push(fn);
70		}
71		window.dev_server = 0;
72		window.socketio_port = 9000;
73		window.show_language_picker = false;
74	</script>
74
75</head>
76
77<body frappe-session-status="logged-out"
78	data-path="login"
79	
80	
81	class=" w-100">
82	
83	
84	
85
86
87
88
89
90
91
92
93<div id="page-login" data-path="login">
94	
95<div class="page-content-wrapper">
96	<!-- breadcrumbs -->
97	<div class="page-breadcrumbs">
98		
99			
100		
101	</div>
102
103	
104	<main class="page-container container my-0">
105		<div class="page-header-wrapper d-none">
106			<div class="page-header">
107				
108			</div>
109
110			
111		</div>
112
113		<div class="page_content">
114
115
116
117<div class="rodin365-auth" dir="rtl">
118	<noscript>
119		<div class="text-center my-5">
120			<h4>جاوا اسکریپت بر روی مرورگر شما غیر فعال شده است</h4>
121			<p class="text-muted">
122				باید جاوا اسکریپت را فعال کنید تا برنامه شما کار کند.
123			</p>
124		</div>
125	</noscript>
126
127	<div class="rodin365-auth__shell">
128		<aside class="rodin365-auth__visual" aria-hidden="false">
129			<img class="rodin365-auth__visual-img" src="/files/morvarid.png" alt="Auth visual">
130			<div class="rodin365-auth__visual-top">
131				<a class="rodin365-auth__back" href="/" aria-label="بازگشت">
132					<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
133						<path d="M5 12h14M13 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
134					</svg>
135				</a>
136				<div class="rodin365-auth__logo-pill">
137					
138<img class="app-logo" src="/files/WhatsApp-Image-2026-09-12-at-12.09.15-_2_-removebg-preview.png" alt="لوگو">
139
140				</div>
141			</div>
142		</aside>
143
144		<div class="rodin365-auth__panel">
145			<div class="rodin365-auth__panel-inner">
146				<section class="for-login">
147					<div class="login-form-container">
148						<div class="login-content page-card">
149							<div class="loader-overlay" id="loginLoader">
150								<div class="loader-container">
151									<div class="loader-spinner"></div>
152									<div class="loader-text" dir="rtl">در حال ارسال کد...</div>
153								</div>
154							</div>
155							<div class="page-card-head">
156								<h1 id="login-form-title">ورود / ثبت نام</h1>
157								<p id="login-form-subtitle">شماره موبایل خود را وارد کنید</p>
158							</div>
159							<form class="form-signin form-login" role="form">
160								
161
162<style>
163	/* —— Rodin365 Auth (/auth design) —— */
164	:root {
165		--rodin365-primary: var(--primary-color);
166		--rodin365-primary-deep: var(--dark-primary);
167		--rodin365-primary-foreground: #FFFFFF;
168		--rodin365-accent: var(--primary-color);
169		--rodin365-accent-soft: var(--primary-light);
170		--rodin365-gold: var(--secondary-color);
171		--rodin365-gold-soft: color-mix(in srgb, var(--secondary-color) 10%, #ffffff);
172		--rodin365-foreground: #1F2A37;
173		--rodin365-muted: #6B7280;
174		--rodin365-muted-soft: #9CA3AF;
175		--rodin365-border: #E8EDF4;
176		--rodin365-border-strong: #D1DBE8;
177		--rodin365-bg: #FFFFFF;
178		--rodin365-secondary: #F8FAFC;
179		--rodin365-radius: 0.75rem;
180		--rodin365-radius-xl: 1rem;
181		--rodin365-radius-2xl: 1.25rem;
182		--rodin365-shadow-card: 0 1px 2px color-mix(in srgb, var(--primary-color) 4%, transparent), 0 4px 16px color-mix(in srgb, var(--primary-color) 6%, transparent);
183		--rodin365-shadow-elevated: 0 12px 40px color-mix(in srgb, var(--primary-color) 12%, transparent);
184		--rodin365-success: #16a34a;
185		--rodin365-destructive: #dc2626;
186	}
187
188	.rodin365-auth {
189		min-height: 100vh;
190		background: var(--rodin365-bg);
191		color: var(--rodin365-foreground);
192		direction: rtl;
193	}
194
195	.rodin365-auth__shell {
196		min-height: 100vh;
197		display: flex;
198		flex-direction: column;
199	}
200
201	@media (min-width: 1024px) {
202		.rodin365-auth__shell {
203			display: grid;
204			grid-template-columns: 1fr 1fr;
205			direction: ltr;
206		}
207
208		.rodin365-auth__visual,
209		.rodin365-auth__panel {
210			direction: rtl;
211		}
212	}
213
214	.rodin365-auth__visual {
215		position: relative;
216		height: 9rem;
217		overflow: hidden;
218		flex-shrink: 0;
219	}
220
221	@media (min-width: 640px) {
222		.rodin365-auth__visual {
223			height: 12rem;
224		}
225	}
226
227	@media (min-width: 1024px) {
228		.rodin365-auth__visual {
229			height: auto;
230			min-height: 100vh;
231		}
232	}
233
234	.rodin365-auth__visual-img {
235		width: 100%;
236		height: 100%;
237		object-fit: cover;
238		display: block;
239	}
240
241	.rodin365-auth__visual-top {
242		position: absolute;
243		inset-inline: 0;
244		top: 0;
245		display: flex;
246		align-items: flex-start;
247		justify-content: space-between;
248		padding: 1rem;
249		z-index: 2;
250	}
251
252	@media (min-width: 1024px) {
253		.rodin365-auth__visual-top {
254			padding: 2rem;
255		}
256	}
257
258	.rodin365-auth__back {
259		display: grid;
260		place-items: center;
261		width: 2.5rem;
262		height: 2.5rem;
263		border: none;
264		border-radius: 999px;
265		background: color-mix(in srgb, var(--rodin365-bg) 85%, transparent);
266		color: var(--rodin365-foreground);
267		backdrop-filter: blur(8px);
268		cursor: pointer;
269		transition: background 0.15s ease;
270		text-decoration: none;
271	}
272
273	.rodin365-auth__back:hover {
274		background: var(--rodin365-bg);
275		color: var(--rodin365-foreground);
276	}
277
278	.rodin365-auth__logo-pill {
279		border-radius: 0.75rem;
280		background: color-mix(in srgb, var(--rodin365-bg) 85%, transparent);
281		backdrop-filter: blur(8px);
282		padding: 0.5rem 0.75rem;
283	}
284
285	.rodin365-auth__logo-pill .app-logo {
286		display: block;
287		height: 1.75rem;
288		width: auto;
289		max-width: 140px;
290		object-fit: contain;
291		margin: 0;
292	}
293
294	@media (min-width: 1024px) {
295		.rodin365-auth__logo-pill .app-logo {
296			height: 2.25rem;
297			max-width: 180px;
298		}
299	}
300
301	.rodin365-auth__panel {
302		flex: 1;
303		display: flex;
304		align-items: flex-start;
305		justify-content: center;
306		padding: 2rem 1rem;
307		background: var(--rodin365-bg);
308	}
309
310	@media (min-width: 640px) {
311		.rodin365-auth__panel {
312			padding-inline: 1.5rem;
313		}
314	}
315
316	@media (min-width: 1024px) {
317		.rodin365-auth__panel {
318			align-items: center;
319			padding-block: 3rem;
320			min-height: 100vh;
321		}
322	}
323
324	.rodin365-auth__panel-inner {
325		width: 100%;
326		max-width: 28rem;
327		position: relative;
328	}
329
330	/* Hide site chrome */
331	.navbar,
332	.page-header-wrapper,
333	.page-footer,
334	.page-breadcrumbs,
335	header,
336	footer,
337	.web-footer,
338	.footer-padding,
339	.footer-spacer,
340	.navbar-header,
341	.navbar-nav,
342	.bottom-menu-space {
343		display: none !important;
344		visibility: hidden !important;
345		height: 0 !important;
346		overflow: hidden !important;
347		margin: 0 !important;
348		padding: 0 !important;
349	}
350
351	.page-content-wrapper,
352	.page-container {
353		padding: 0 !important;
354		margin: 0 !important;
355		max-width: unset !important;
356		min-height: 100vh !important;
357		background: var(--rodin365-bg) !important;
358	}
359
360	.for-login,
361	.for-forgot,
362	.for-login-with-email-link,
363	.for-signup,
364	.for-email-login {
365		width: 100%;
366		margin: 0;
367		padding: 0;
368		min-height: 0;
369		position: relative;
370	}
371
372	.login-form-container,
373	.otp-container {
374		width: 100%;
375		display: flex;
376		flex-direction: column;
377		align-items: stretch;
378	}
379
380	.otp-container {
381		position: absolute;
382		inset: 0;
383		z-index: 10;
384		display: none !important;
385		visibility: hidden;
386	}
387
388	.otp-container.show-otp {
389		display: flex !important;
390		visibility: visible !important;
391	}
392
393	.otp-container.show-otp ~ .login-form-container {
394		display: none !important;
395	}
396
397	.logo-wrapper,
398	.mobile-logo-wrapper,
399	.for-email-login .app-logo,
400	.for-email-login .logo-wrapper,
401	.for-signup .app-logo,
402	.for-signup .logo-wrapper,
403	.for-forgot .app-logo,
404	.for-forgot .logo-wrapper,
405	.for-login-with-email-link .app-logo,
406	.for-login-with-email-link .logo-wrapper,
407	section:not(.for-login) .page-card-head .app-logo {
408		display: none !important;
409	}
410
411	.for-login .page-card,
412	.for-forgot .page-card,
413	.for-login-with-email-link .page-card,
414	.for-signup .page-card,
415	.for-email-login .page-card,
416	.for-otp-verification .page-card {
417		margin: 0;
418		max-width: 100%;
419		width: 100%;
420		padding: 0;
421		border-radius: 0;
422		background: transparent;
423		box-shadow: none;
424		border: none;
425		position: relative;
426	}
427
428	.for-login .page-card form,
429	.for-forgot .page-card form,
430	.for-login-with-email-link .page-card form,
431	.for-signup .page-card form,
432	.for-email-login .page-card form,
433	.for-otp-verification .page-card form {
434		max-width: 100%;
435	}
436
437	.page-card-head {
438		margin-bottom: 1.25rem;
439		text-align: right;
440	}
441
442	.page-card-head h1,
443	.page-card-head h4 {
444		font-size: 1.5rem;
445		font-weight: 900;
446		line-height: 1.25;
447		margin: 0 0 0.5rem;
448		color: var(--rodin365-foreground);
449		text-align: right;
450	}
451
452	@media (min-width: 640px) {
453		.page-card-head h1,
454		.page-card-head h4 {
455			font-size: 1.875rem;
456		}
457	}
458
459	.page-card-head p,
460	.otp-instructions {
461		font-size: 0.875rem;
462		color: var(--rodin365-muted);
463		margin: 0;
464		text-align: right;
465		line-height: 1.6;
466	}
467
468	.page-card-body {
469		display: flex;
470		flex-direction: column;
471		gap: 0.75rem;
472	}
473
474	.page-card-body > .form-group,
475	.page-card-body > .d-flex,
476	.page-card-actions {
477		margin: 0;
478	}
479
480	.form-group {
481		margin-bottom: 0 !important;
482	}
483
484	/* Hidden wrappers must not reserve flex gap */
485	.page-card-body > .d-flex:has(#edit-mobile-number),
486	.page-card-body > #counter {
487		display: none !important;
488	}
489
490	#login-label-name,
491	#login-label-email,
492	#login-label-password,
493	.login-label,
494	.rodin365-auth label,
495	.page-card-body label {
496		display: block;
497		font-size: 0.75rem;
498		font-weight: 700;
499		color: var(--rodin365-foreground);
500		margin-bottom: 0.375rem;
501		text-align: right;
502		padding: 0 !important;
503	}
504
505	#edit-mobile-number,
506	#countdown-container,
507	#send-code-again,
508	#staff,
509	#staff-label {
510		display: none !important;
511	}
512
513	#mobile-field-group #login-label-username {
514		display: block !important;
515	}
516
517	.rodin365-auth .form-control,
518	.rodin365-auth #login_mobile,
519	.rodin365-auth #login_name,
520	.rodin365-auth #login_email,
521	.rodin365-auth #login_password,
522	.rodin365-auth #forgot_email,
523	.rodin365-auth #forgot_mobile,
524	.rodin365-auth #login_with_email_link_email {
525		width: 100%;
526		height: 3rem;
527		border-radius: var(--rodin365-radius);
528		border: 1px solid var(--rodin365-border);
529		background: var(--rodin365-bg);
530		padding: 0 1rem;
531		font-size: 0.875rem;
532		color: var(--rodin365-foreground);
533		outline: none;
534		box-shadow: none !important;
535		transition: border-color 0.15s ease, box-shadow 0.15s ease;
536	}
537
538	.rodin365-auth #login_mobile,
539	.rodin365-auth #login_email,
540	.rodin365-auth #login_password,
541	.rodin365-auth #forgot_email,
542	.rodin365-auth #forgot_mobile,
543	.rodin365-auth #login_with_email_link_email {
544		direction: ltr;
545		text-align: left;
546	}
547
548	.rodin365-auth #login_name {
549		direction: rtl;
550		text-align: right;
551	}
552
553	.rodin365-auth .form-control:focus,
554	.rodin365-auth #login_mobile:focus,
555	.rodin365-auth #login_name:focus,
556	.rodin365-auth #login_email:focus,
557	.rodin365-auth #login_password:focus,
558	.rodin365-auth #forgot_email:focus,
559	.rodin365-auth #forgot_mobile:focus,
560	.rodin365-auth #login_with_email_link_email:focus {
561		border-color: var(--rodin365-primary);
562		box-shadow: 0 0 0 2px color-mix(in srgb, var(--rodin365-primary) 25%, transparent) !important;
563	}
564
565	.rodin365-auth .form-control::placeholder,
566	.rodin365-auth #login_mobile::placeholder {
567		color: color-mix(in srgb, var(--rodin365-muted) 70%, transparent);
568	}
569
570	.email-field,
571	.password-field,
572	.mobile-field {
573		position: relative;
574	}
575
576	.email-field .field-icon,
577	.password-field .field-icon {
578		display: none !important;
579	}
580
581	/* Beat login.bundle.scss (.for-login .page-card .page-card-body .toggle-password) */
582	.for-login .page-card .page-card-body .password-field .toggle-password,
583	.rodin365-auth .password-field .toggle-password,
584	.password-field .toggle-password {
585		position: absolute !important;
586		right: 0.5rem !important;
587		left: auto !important;
588		top: 50% !important;
589		transform: translateY(-50%) !important;
590		z-index: 5 !important;
591		display: grid !important;
592		place-items: center !important;
593		width: 2.25rem !important;
594		height: 2.25rem !important;
595		border-radius: 0.5rem !important;
596		border: none !important;
597		background: transparent !important;
598		color: var(--rodin365-muted) !important;
599		font-size: 0 !important;
600		line-height: 0 !important;
601		cursor: pointer !important;
602		user-select: none !important;
603		margin: 0 !important;
604		padding: 0 !important;
605		pointer-events: auto !important;
606		-webkit-tap-highlight-color: transparent;
607	}
608
609	.for-login .page-card .page-card-body .password-field .toggle-password svg,
610	.rodin365-auth .password-field .toggle-password svg,
611	.password-field .toggle-password svg {
612		width: 1.125rem !important;
613		height: 1.125rem !important;
614		display: block !important;
615		pointer-events: none !important;
616		flex-shrink: 0;
617	}
618
619	.for-login .page-card .page-card-body .password-field .toggle-password .icon-eye-off,
620	.rodin365-auth .password-field .toggle-password .icon-eye-off,
621	.password-field .toggle-password .icon-eye-off {
622		display: none !important;
623	}
624
625	.for-login .page-card .page-card-body .password-field .toggle-password.is-visible .icon-eye,
626	.rodin365-auth .password-field .toggle-password.is-visible .icon-eye,
627	.password-field .toggle-password.is-visible .icon-eye {
628		display: none !important;
629	}
630
631	.for-login .page-card .page-card-body .password-field .toggle-password.is-visible .icon-eye-off,
632	.rodin365-auth .password-field .toggle-password.is-visible .icon-eye-off,
633	.password-field .toggle-password.is-visible .icon-eye-off {
634		display: block !important;
635	}
636
637	.for-login .page-card .page-card-body .password-field .toggle-password:hover,
638	.rodin365-auth .password-field .toggle-password:hover,
639	.password-field .toggle-password:hover {
640		background: var(--rodin365-secondary) !important;
641		color: var(--rodin365-foreground) !important;
642	}
643
644	.for-login .page-card .page-card-body .password-field input,
645	.rodin365-auth #login_password,
646	.rodin365-auth .password-field #login_password {
647		padding-right: 3rem !important;
648		padding-left: 1rem !important;
649	}
650
651	.forgot-password-message {
652		display: none !important;
653	}
654
655	.rodin365-auth__text-action,
656	.toggle-to-otp-login a,
657	.otp-resend-link a,
658	.btn-edit-mobile,
659	.sign-up-message a,
660	.verification-mail a {
661		color: var(--rodin365-primary) !important;
662		font-size: 0.75rem;
663		font-weight: 700;
664		text-decoration: none;
665		background: none;
666		border: none;
667		padding: 0;
668		cursor: pointer;
669		underline-offset: 4px;
670	}
671
672	.rodin365-auth__text-action:hover,
673	.toggle-to-otp-login a:hover,
674	.otp-resend-link a:hover,
675	.btn-edit-mobile:hover,
676	.sign-up-message a:hover {
677		text-decoration: underline;
678	}
679
680	.page-card-actions {
681		display: flex;
682		flex-direction: column;
683		gap: 1.25rem;
684		margin-top: 1.25rem;
685	}
686
687	.btn-register,
688	.btn-password-login,
689	.btn-login-submit,
690	.btn-forgot,
691	.btn-login-with-email-link,
692	.rodin365-auth .btn-primary {
693		display: flex;
694		align-items: center;
695		justify-content: center;
696		gap: 0.5rem;
697		width: 100%;
698		height: 3rem;
699		border: none;
700		border-radius: var(--rodin365-radius);
701		background: var(--rodin365-primary) !important;
702		color: var(--rodin365-primary-foreground) !important;
703		font-size: 0.875rem;
704		font-weight: 700;
705		cursor: pointer;
706		transition: background 0.15s ease, opacity 0.15s ease;
707		padding: 0 1rem;
708		box-shadow: none !important;
709	}
710
711	.btn-register:hover,
712	.btn-password-login:hover,
713	.btn-login-submit:hover:not(:disabled),
714	.btn-forgot:hover,
715	.btn-login-with-email-link:hover,
716	.rodin365-auth .btn-primary:hover {
717		background: var(--rodin365-primary-deep) !important;
718		color: var(--rodin365-primary-foreground) !important;
719	}
720
721	.btn-register:disabled,
722	.btn-password-login:disabled,
723	.btn-login-submit:disabled {
724		opacity: 0.6;
725		cursor: not-allowed;
726		background: var(--rodin365-primary) !important;
727		color: var(--rodin365-primary-foreground) !important;
728	}
729
730	.btn-toggle-password-login {
731		width: auto;
732		height: auto;
733		margin: 0 auto;
734		padding: 0;
735		background: transparent;
736		color: var(--rodin365-primary);
737		border: none;
738		border-radius: 0;
739		font-size: 0.75rem;
740		font-weight: 700;
741		cursor: pointer;
742	}
743
744	.btn-toggle-password-login:hover {
745		background: transparent;
746		color: var(--rodin365-primary);
747		text-decoration: underline;
748	}
749
750	.rodin365-auth__divider {
751		height: 1px;
752		width: 100%;
753		background: var(--rodin365-border);
754		border: none;
755		margin: 0;
756	}
757
758	.toggle-to-otp-login {
759		text-align: center;
760		margin: 0;
761	}
762
763	.toggle-to-otp-login .rodin365-auth__signup-hint {
764		margin-top: 0.5rem;
765		font-size: 0.75rem;
766		color: var(--rodin365-muted);
767	}
768
769	/* OTP */
770	.otp-instructions {
771		margin-bottom: 0 !important;
772	}
773
774	.mobile-display-container {
775		display: flex;
776		align-items: center;
777		justify-content: center;
778		gap: 0.75rem;
779		width: 100%;
780		background: transparent;
781		border: none;
782		padding: 0;
783		margin: 0 0 0.25rem;
784	}
785
786	.mobile-display-number {
787		direction: ltr;
788		font-size: 0.875rem;
789		font-weight: 700;
790		font-variant-numeric: tabular-nums;
791		color: var(--rodin365-muted);
792	}
793
794	.edit-mobile-btn {
795		display: none;
796	}
797
798	.otp-input-container {
799		display: flex;
800		justify-content: center;
801		gap: 0.5rem;
802		direction: ltr;
803		margin: 0.25rem 0;
804	}
805
806	@media (min-width: 640px) {
807		.otp-input-container {
808			gap: 0.75rem;
809		}
810	}
811
812	.otp-input-wrapper {
813		position: relative;
814		display: inline-block;
815	}
816
817	.otp-input-wrapper::after {
818		display: none !important;
819	}
820
821	.otp-input {
822		width: 2.75rem;
823		height: 3rem;
824		border: 1px solid var(--rodin365-border) !important;
825		border-radius: var(--rodin365-radius);
826		text-align: center;
827		font-size: 1.125rem;
828		font-weight: 700;
829		font-variant-numeric: tabular-nums;
830		outline: none;
831		color: var(--rodin365-foreground);
832		background: var(--rodin365-bg);
833		transition: border-color 0.15s ease, box-shadow 0.15s ease;
834		box-shadow: none !important;
835	}
836
837	@media (min-width: 640px) {
838		.otp-input {
839			width: 3rem;
840			height: 3.5rem;
841		}
842	}
843
844	.otp-input:focus {
845		border-color: var(--rodin365-primary) !important;
846		box-shadow: 0 0 0 2px color-mix(in srgb, var(--rodin365-primary) 25%, transparent) !important;
847	}
848
849	.otp-input.filled {
850		border-color: var(--rodin365-border) !important;
851		color: var(--rodin365-foreground);
852		background: var(--rodin365-bg);
853	}
854
855	.otp-input.otp-invalid,
856	.otp-input.otp-invalid:focus,
857	.otp-input.otp-invalid.filled,
858	.otp-input.otp-invalid.empty {
859		border-color: var(--rodin365-destructive) !important;
860		color: var(--rodin365-destructive) !important;
861	}
862
863	.otp-input.empty:not(:focus) {
864		border-color: var(--rodin365-border) !important;
865		color: transparent;
866	}
867
868	.otp-countdown,
869	.otp-resend-link {
870		text-align: center;
871		margin: 0;
872		font-size: 0.75rem;
873		color: var(--rodin365-muted);
874	}
875
876	.otp-actions {
877		display: flex;
878		flex-direction: column;
879		gap: 1rem;
880		width: 100%;
881	}
882
883	.btn-edit-mobile {
884		width: 100%;
885		text-align: center;
886		padding: 0.25rem;
887		border: none !important;
888		background: transparent !important;
889		color: var(--rodin365-primary) !important;
890		font-size: 0.75rem;
891		font-weight: 700;
892	}
893
894	.terms-text {
895		font-size: 0.75rem;
896		color: var(--rodin365-muted);
897		text-align: center;
898		margin: 0;
899		line-height: 1.5;
900	}
901
902	.sign-up-message {
903		text-align: center;
904		font-size: 0.75rem;
905		color: var(--rodin365-muted);
906		margin-top: 1rem;
907	}
908
909	/* Loader */
910	.loader-overlay {
911		position: absolute;
912		inset: 0;
913		background: color-mix(in srgb, var(--rodin365-bg) 90%, transparent);
914		display: none;
915		align-items: center;
916		justify-content: center;
917		z-index: 1000;
918		border-radius: var(--rodin365-radius);
919		backdrop-filter: blur(2px);
920	}
921
922	.loader-overlay.active {
923		display: flex;
924	}
925
926	.loader-spinner {
927		width: 2rem;
928		height: 2rem;
929		border: 3px solid var(--rodin365-border);
930		border-top-color: var(--rodin365-primary);
931		border-radius: 50%;
932		animation: rodin365-auth-spin 0.8s linear infinite;
933	}
934
935	@keyframes rodin365-auth-spin {
936		to { transform: rotate(360deg); }
937	}
938
939	.loader-text {
940		margin-top: 0.75rem;
941		color: var(--rodin365-primary);
942		font-size: 0.875rem;
943		font-weight: 700;
944		text-align: center;
945	}
946
947	.loader-container {
948		display: flex;
949		flex-direction: column;
950		align-items: center;
951		justify-content: center;
952	}
953
954	.page-card.loading input,
955	.page-card.loading button,
956	.page-card.loading select,
957	.page-card.loading textarea {
958		pointer-events: none;
959		opacity: 0.6;
960		cursor: not-allowed;
961	}
962
963	.social-logins {
964		margin-top: 1rem;
965	}
966
967	.login-divider {
968		font-size: 0.75rem;
969		color: var(--rodin365-muted);
970	}
971</style>
972
973<div class="page-card-body">
974	<div class="form-group" id="login-name-field-group" style="display: none;" dir="rtl">
975		<label id="login-label-name" for="login_name" class="login-label">نام و نام خانوادگی</label>
976		<input type="text" id="login_name" class="form-control" placeholder="نام و نام خانوادگی" autocomplete="name" dir="rtl">
977	</div>
978	<div class="d-flex justify-content-end">
979		<p id="edit-mobile-number" class="justify-content-end" disabled="false" style="display: none;"
980			onclick="active_number()">ویرایش شماره موبایل/ ایمیل</p>
981	</div>
982	<div class="form-group" id="email-field-group" style="display: none;">
983		<label id="login-label-email" for="login_email" class="login-label">شماره موبایل / ایمیل</label>
984		<div class="email-field" id="email-field">
985			<input type="text" id="login_email" class="form-control m-0"
986				placeholder="[email protected] یا 09*********"
987				autocomplete="username">
988			<svg class="field-icon email-icon" width="20" height="20" viewBox="0 0 20 20" fill="none"
989				xmlns="http://www.w3.org/2000/svg">
990				<path
991					d="M2.5 7.65149V15.0757C2.5 15.4374 2.64367 15.7842 2.8994 16.04C3.15513 16.2957 3.50198 16.4394 3.86364 16.4394H16.1364C16.498 16.4394 16.8449 16.2957 17.1006 16.04C17.3563 15.7842 17.5 15.4374 17.5 15.0757V7.65149"
992					stroke="#74808B" stroke-miterlimit="10" stroke-linecap="square" />
993				<path
994					d="M17.5 7.57572V5.53026C17.5 5.1686 17.3563 4.82176 17.1006 4.56603C16.8449 4.31029 16.498 4.16663 16.1364 4.16663H3.86364C3.50198 4.16663 3.15513 4.31029 2.8994 4.56603C2.64367 4.82176 2.5 5.1686 2.5 5.53026V7.57572L10 10.8333L17.5 7.57572Z"
995					stroke="#74808B" stroke-miterlimit="10" stroke-linecap="square" />
996			</svg>
997		</div>
998	</div>
999
1000	<div class="form-group" id="password-field-group" style="display: none;">
1001		<label id="login-label-password" for="login_password" class="login-label">رمز عبور</label>
1002		<div class="password-field" id="password-field">
1003			<input type="password" id="login_password" class="form-control m-0"
1004				placeholder="رمز عبور" autocomplete="current-password">
1005			<svg class="field-icon password-icon" width="20" height="20" viewBox="0 0 20 20" fill="none"
1006				xmlns="http://www.w3.org/2000/svg">
1007				<path fill-rule="evenodd" clip-rule="evenodd"
1008					d="M10.0961 1.93768H10.0264C8.94806 1.92763 7.90976 2.34591 7.13951 3.10075C6.36866 3.85619 5.9294 4.88687 5.91833 5.96612L5.91831 5.96612V5.97124V6.52695H4.3125C3.20793 6.52695 2.3125 7.42238 2.3125 8.52695V16.0165C2.3125 17.121 3.20793 18.0165 4.3125 18.0165H15.7356C16.8401 18.0165 17.7356 17.121 17.7356 16.0165V8.52695C17.7356 7.42238 16.8401 6.52695 15.7356 6.52695H14.1297V6.04576C14.1397 4.96742 13.7214 3.92913 12.9666 3.15888C12.2112 2.38803 11.1805 1.94877 10.1012 1.9377V1.93768H10.0961ZM13.1297 6.52695V6.04336V6.03838H13.1297C13.1378 5.22428 12.8222 4.44029 12.2524 3.85881C11.6831 3.27793 10.9067 2.94667 10.0934 2.93768H10.024H10.019V2.93765C9.20491 2.92955 8.42092 3.24512 7.83944 3.81497C7.25856 4.38423 6.9273 5.1607 6.91831 5.9739V6.52695H13.1297ZM4.3125 7.52695C3.76022 7.52695 3.3125 7.97467 3.3125 8.52695V16.0165C3.3125 16.5687 3.76022 17.0165 4.3125 17.0165H15.7356C16.2879 17.0165 16.7356 16.5687 16.7356 16.0165V8.52695C16.7356 7.97467 16.2879 7.52695 15.7356 7.52695H4.3125ZM10.0242 13.2384C10.5581 13.2384 10.9909 12.8056 10.9909 12.2717C10.9909 11.7377 10.5581 11.3049 10.0242 11.3049C9.49023 11.3049 9.05738 11.7377 9.05738 12.2717C9.05738 12.8056 9.49023 13.2384 10.0242 13.2384ZM11.9909 12.2717C11.9909 13.3579 11.1104 14.2384 10.0242 14.2384C8.93794 14.2384 8.05738 13.3579 8.05738 12.2717C8.05738 11.1854 8.93794 10.3049 10.0242 10.3049C11.1104 10.3049 11.9909 11.1854 11.9909 12.2717Z"
1009					fill="#74808B" />
1010			</svg>
1011			<button
1012				type="button"
1013				class="toggle-password"
1014				data-toggle-target="#login_password"
1015				aria-label="نمایش رمز عبور"
1016				aria-pressed="false"
1017			>
1018				<svg class="icon-eye" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
1019					<path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7Z" stroke="currentColor" stroke-width="1.8"/>
1020					<path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" stroke="currentColor" stroke-width="1.8"/>
1021				</svg>
1022				<svg class="icon-eye-off" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
1023					<path d="M3 3l18 18M10.585 10.587A2 2 0 0 0 12 14a2 2 0 0 0 1.414-.586M9.88 4.24A9.05 9.05 0 0 1 12 4c4.478 0 8.268 2.943 9.542 7a10.3 10.3 0 0 1-1.39 2.68M6.228 6.228A10.45 10.45 0 0 0 2.458 12c1.274 4.057 5.065 7 9.542 7a9.74 9.74 0 0 0 4.29-.97" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
1024				</svg>
1025			</button>
1026		</div>
1027	</div>
1028
1029	<p class="forgot-password-message d-flex justify-content-end" style="display: none !important;">
1030		<a href="#forgot">رمز عبور را فراموش کرده اید؟</a>
1031	</p>
1032
1033	<div class="form-group" id="mobile-field-group">
1034		
1035		
1036		<label id="login-label-username" for="login_mobile" class="login-label">شماره موبایل</label>
1037		<input type="text" id="login_mobile" class="form-control" placeholder="09*********" autofocus inputmode="tel" dir="ltr">
1038		
1039	</div>
1040
1041	<div class="d-flex justify-content-between" id="counter" dir="rtl">
1042		<h4 id="countdown-container" class="m-0" style="display: none;">
1043			<p id="countdown">120 ثانیه تا ارسال مجدد کد</p>
1044		</h4>
1045		<h4 id="send-code-again" class="m-0 gap-1 align-items-center" style="display: none;">ارسال مجدد<i
1046				class="bi bi-arrow-clockwise"></i></h4>
1047	</div>
1048</div>
1049
1050
1051<div class="page-card-actions">
1052	
1053	<button id="loginButton" class="btn-register" type="submit">
1054		ادامه</button>
1055	
1056
1057	<button id="passwordLoginButton" class="btn-password-login" type="submit" style="display: none;">
1058		ورود</button>
1059
1060	<hr class="rodin365-auth__divider" id="authModeDivider">
1061
1062	<button type="button" id="togglePasswordLoginBtn" class="btn-toggle-password-login" onclick="togglePasswordLogin()">
1063		ورود با رمز ثابت</button>
1064
1065	<div class="toggle-to-otp-login" id="toggleToOtpLogin" style="display: none;">
1066		<a href="#" onclick="togglePasswordLogin(); return false;">ورود با رمز یکبار مصرف</a>
1067		<div class="rodin365-auth__signup-hint">
1068			حساب کاربری ندارید؟
1069			<a href="#" onclick="togglePasswordLogin(); return false;">ثبت نام</a>
1070		</div>
1071	</div>
1072
1073	
1074	<input type="checkbox" class="m-0" id="staff" name="staff" onclick="staff_login()" hidden>
1075	
1076	<label class="m-0" id="staff-label" for="staff" style="display: none;">ورود با رمز ثابت</label>
1077	
1078</div>
1079</form>
1080						</div>
1081					</div>
1082
1083					<div class="otp-container">
1084						<div class="login-content page-card">
1085							<div class="loader-overlay" id="otpLoader">
1086								<div class="loader-container">
1087									<div class="loader-spinner"></div>
1088									<div class="loader-text" dir="rtl">در حال بررسی...</div>
1089								</div>
1090							</div>
1091							<div class="page-card-head">
1092								<h1>تأیید شماره موبایل</h1>
1093								<p class="otp-instructions">
1094									کد پنج رقمی ارسال‌شده به شماره زیر را وارد کنید.
1095								</p>
1096							</div>
1097							<form class="form-signin form-otp-verification" role="form">
1098								<div class="page-card-body d-flex flex-column align-items-stretch">
1099									<div class="mobile-display-container">
1100										<span class="mobile-display-number" id="display_mobile_number" dir="ltr"></span>
1101									</div>
1102
1103									<div class="otp-input-container" dir="ltr">
1104										<div class="otp-input-wrapper">
1105											<input type="text" class="otp-input" id="otp_1" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" aria-label="رقم 1 کد تأیید">
1106										</div>
1107										<div class="otp-input-wrapper">
1108											<input type="text" class="otp-input" id="otp_2" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رقم 2 کد تأیید">
1109										</div>
1110										<div class="otp-input-wrapper">
1111											<input type="text" class="otp-input" id="otp_3" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رقم 3 کد تأیید">
1112										</div>
1113										<div class="otp-input-wrapper">
1114											<input type="text" class="otp-input" id="otp_4" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رقم 4 کد تأیید">
1115										</div>
1116										<div class="otp-input-wrapper">
1117											<input type="text" class="otp-input" id="otp_5" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رقم 5 کد تأیید">
1118										</div>
1119									</div>
1120
1121									<div class="otp-countdown" id="otp_countdown" style="display: none;">
1122										<span id="countdown_text" class="fa-num">ارسال مجدد کد تا 2:00</span>
1123									</div>
1124									<div class="otp-resend-link" id="otp_resend_link" style="display: none;">
1125										<a href="#" onclick="resendOTP(); return false;">ارسال مجدد کد</a>
1126									</div>
1127
1128									
1129								</div>
1130								<div class="page-card-actions otp-actions">
1131									<button type="submit" class="btn-login-submit" id="otp_submit_btn" disabled>
1132										تأیید و ادامه
1133									</button>
1134									<button type="button" class="btn-edit-mobile" id="edit_mobile_link" onclick="editMobileNumber()">
1135										ویرایش شماره همراه
1136									</button>
1137								</div>
1138							</form>
1139						</div>
1140					</div><div class="text-center sign-up-message" style="display: none;">
1141						حساب کاربری ندارید؟
1142						<a href="#signup">ثبت نام</a>
1143					</div></section><section class="for-signup ">
1144					<div class="login-content page-card"><style>
1145    .sign-up-message{
1146        margin-top: 20px !important;
1147    }
1148
1149    .sign-up-message a{
1150        color: black !important;
1151        text-decoration: underline !important;
1152    }
1153</style>
1154
1155<form class="form-signin form-signup hide" role="form">
1156    <div class="page-card-body">
1157        <div class="form-group">
1158            <label class="form-label sr-only" for="signup_fullname" >نام کامل</label>
1159            <input type="text" id="signup_fullname" class="form-control" dir="rtl" placeholder="نام کامل"
1160                required autofocus autocomplete="name">
1161        </div>
1162        
1163        <div class="form-group">
1164            <label class="form-label sr-only" for="mobile_no">شماره موبایل</label>
1165            <input type="text" id="mobile_no" class="form-control text-right"
1166                placeholder="09*********" required autocomplete="username">
1167        </div>
1168        
1169    </div>
1170    <div class="page-card-actions">
1171        <button class="btn btn-sm btn-primary btn-block btn-signup"
1172            type="submit">ثبت نام</button>
1173
1174        <p class="text-center sign-up-message">
1175            <a href="#login">حساب کاربری دارید؟ وارد شدن</a>
1176        </p>
1177    </div>
1178</form></div>
1179				</section>
1180
1181				<section class="for-forgot" style="display: none;">
1182					
1183					<div class="login-content page-card">
1184						<form class="form-signin form-forgot hide" role="form">
1185							<div class="page-card-head">
1186								<h4>رمز عبور را فراموش کرده اید</h4>
1187							</div>
1188							<div class="page-card-body">
1189								
1190								
1191								<div class="mobile-field">
1192									<input type="text" id="forgot_mobile" class="form-control" placeholder="09*********"
1193										required autofocus autocomplete="username">
1194								</div>
1195								
1196								
1197							</div>
1198							<div class="page-card-actions">
1199								<button class="btn btn-sm btn-primary btn-block btn-forgot" type="submit">بازنشانی رمز عبور</button>
1200								<p class="text-center verification-mail">
1201
1202								</p>
1203								<p class="text-center sign-up-message">
1204									<a href="#login">بازگشت به صفحه ورود</a>
1205								</p>
1206							</div>
1207						</form>
1208					</div>
1209				</section>
1210
1211				<section class="for-login-with-email-link">
1212					<div class="login-content page-card">
1213						<form class="form-signin form-login-with-email-link hide" role="form">
1214							<div class="page-card-head">
1215								<h1>با لینک ایمیل وارد شوید</h1>
1216							</div>
1217							<div class="page-card-body">
1218								<div class="email-field">
1219									<label class="login-label" for="login_with_email_link_email">ایمیل</label>
1220									<input type="email" id="login_with_email_link_email" class="form-control"
1221										placeholder="آدرس ایمیل" required autofocus autocomplete="username">
1222								</div>
1223							</div>
1224							<div class="page-card-actions">
1225								<button class="btn-login-with-email-link" type="submit">ارسال لینک ورود</button>
1226								<p class="text-center sign-up-message">
1227									<a href="#login">بازگشت به صفحه ورود</a>
1228								</p>
1229							</div>
1230						</form>
1231					</div>
1232				</section>
1233			</div>
1234		</div>
1235	</div>
1236</div>
1237
1238
1239<script>
1240	window.system_settings = {
1241		allow_easy_login_with_email: 0
1242    };
1243	window.get_name_at_sign_up = false;
1244</script>
1244
1245</div>
1246
1247		<div class="page-footer"></div>
1248	</main>
1249	
1250</div>
1251
1252</div>
1253
1254
1255
1256	<!-- js should be loaded in body! -->
1257	
1257<script>
1258		frappe.boot = {'sysdefaults': {'float_precision': 3, 'date_format': 'yyyy-mm-dd', 'time_format': 'HH:mm:ss'}, 'time_zone': {'system': 'Asia/Tehran', 'user': 'Asia/Kolkata'}}
1259		// for backward compatibility of some libs
1260		frappe.sys_defaults = frappe.boot.sysdefaults;
1261		frappe._messages = frappe.boot["__messages"] || {};
1262		window.__ = frappe._;
1263	</script>
1263
1264	
1264<script type="text/javascript" src="/assets/frappe/dist/js/frappe-web.bundle.SI6CYMX2.js"></script>
1264
1265	
1266	
1266<script type="text/javascript" src="/website_script.js"></script>
1266
1267	
1267<script type="text/javascript" src="/assets/frappe/js/portal_field_validation.js?v=20260715b"></script>
1267
1268	
1268<script type="text/javascript" src="/assets/frappe/js/person_name_validation.js"></script>
1268
1269	
1269<script type="text/javascript" src="/assets/erpnext/dist/js/erpnext-web.bundle.IO24HLWL.js"></script>
1269
1270	
1270<script type="text/javascript" src="/assets/frappe/dist/js/components.bundle.UJ2R2EF7.js"></script>
1270
1271	
1271<script type="text/javascript" src="/assets/erpnext/js/persian-date.js"></script>
1271
1272	
1272<script type="text/javascript" src="/assets/erpnext/js/persian-datepicker.js"></script>
1272
1273	
1273<script type="text/javascript" src="/assets/erpnext/js/jalaali.js"></script>
1273
1274	
1274<script type="text/javascript" src="/assets/erpnext/js/persian_read_only.js"></script>
1274
1275	
1275<script type="text/javascript" src="/assets/erpnext/js/persian_js_class.js"></script>
1275
1276	
1276<script type="text/javascript" src="/assets/erpnext/js/beauty_banner.js?v=1.0.1"></script>
1276
1277	
1277<script type="text/javascript" src="/assets/erpnext/js/blog_post_swiper.js?v=1.0.0"></script>
1277
1278	
1278<script type="text/javascript" src="/assets/erpnext/js/customer_reviews_rodin.js?v=1.0.0"></script>
1278
1279	
1279<script type="text/javascript" src="/assets/erpnext/js/engraving_gallery_swiper_section.js?v=1.0.0"></script>
1279
1280	
1280<script type="text/javascript" src="/assets/erpnext/js/item_brand_card_group.js?v=1.0.2"></script>
1280
1281	
1281<script type="text/javascript" src="/assets/erpnext/js/mega_nav_4.js?v=1.0.6"></script>
1281
1282	
1282<script type="text/javascript" src="/assets/erpnext/js/publishers_section_swiper.js?v=1.0.0"></script>
1282
1283	
1283<script type="text/javascript" src="/assets/erpnext/js/subscribe.js?v=1.1.0"></script>
1283
1284	
1284<script type="text/javascript" src="/assets/erpnext/js/sub_bs.js?v=1.1.0"></script>
1284
1285	
1285<script type="text/javascript" src="/assets/erpnext/js/swipper_group.js?v=1.0.1"></script>
1285
1286	
1286<script type="text/javascript" src="/assets/erpnext/js/glightbox.js?v=1.0.0"></script>
1286
1287	
1287<script type="text/javascript" src="/assets/erpnext/js/isotope.js?v=1.0.0"></script>
1287
1288	
1288<script type="text/javascript" src="/assets/erpnext/js/imagesloaded.js?v=1.0.0"></script>
1288
1289	
1289<script type="text/javascript" src="/assets/erpnext/js/rodin_slider.js?v=1.0.52"></script>
1289
1290	
1290<script type="text/javascript" src="/assets/erpnext/js/pdf/pdf.js"></script>
1290
1291	
1291<script type="text/javascript" src="/assets/erpnext/js/pdf/pdf.worker.js"></script>
1291
1292	
1292<script type="text/javascript" src="/assets/rodin_services/js/rodin_navbar_cart_sync.js?v=1.1.0"></script>
1292
1293	
1293<script type="text/javascript" src="/assets/rodin_services/js/r365_website_session.js?v=1.3"></script>
vendor: 1 bytes, line 1293
1293
1294<script>// Helper functions for loader
1295function showLoader(loaderId, text) {
1296	const loader = document.getElementById(loaderId);
1297	if (loader) {
1298		if (text) {
1299			const textElement = loader.querySelector('.loader-text');
1300			if (textElement) {
1301				textElement.textContent = text;
1302			}
1303		}
1304		loader.classList.add('active');
1305		// Add loading class to page-card to disable inputs
1306		const pageCard = loader.closest('.page-card');
1307		if (pageCard) {
1308			pageCard.classList.add('loading');
1309		}
1310	}
1311}
1312
1313function hideLoader(loaderId) {
1314	const loader = document.getElementById(loaderId);
1315	if (loader) {
1316		loader.classList.remove('active');
1317		// Remove loading class from page-card
1318		const pageCard = loader.closest('.page-card');
1319		if (pageCard) {
1320			pageCard.classList.remove('loading');
1321		}
1322	}
1323}
1324
1325// login.js
1326
1327window.disable_signup = false;
1328
1329window.login = {};
1330
1331window.verify = {};
1332
1333function person_name_validation_message(result) {
1334	if (!result || result.ok) {
1335		return null;
1336	}
1337	if (result.message) {
1338		return result.message;
1339	}
1340	switch (result.reason) {
1341		case "empty":
1342			return 'لطفا نام خود را وارد کنید';
1343		case "first":
1344			return 'نام نمی تواند شامل عدد یا ستاره (*) باشد';
1345		case "last":
1346			return 'نام خانوادگی نمی تواند شامل عدد یا ستاره (*) باشد';
1347		default:
1348			return 'Please enter a valid name.';
1349	}
1350}
1351
1352const LOGIN_PENDING_NAME_KEY = "login_pending_full_name";
1353
1354function setLoginNameFieldVisible(visible) {
1355	const group = document.getElementById("login-name-field-group");
1356	const input = document.getElementById("login_name");
1357	if (!group || !input) {
1358		return;
1359	}
1360	group.style.display = visible ? "block" : "none";
1361	if (visible) {
1362		input.setAttribute("required", "required");
1363		bindLoginNameInputFilter();
1364	} else {
1365		input.removeAttribute("required");
1366		input.value = "";
1367	}
1368}
1369
1370function isLoginNameRequired() {
1371	return window.get_name_at_sign_up;
1372}
1373
1374function isLoginNameFieldVisible() {
1375	return $("#login-name-field-group").is(":visible");
1376}
1377
1378function normalizeLoginFullName(rawName) {
1379	let full_name = frappe.utils.xss_sanitise((rawName || "").trim());
1380	if (!full_name) {
1381		return { ok: false, full_name: "" };
1382	}
1383	const nameCheck =
1384		window.frappe_validate_person_name &&
1385		window.frappe_validate_person_name.checkSignupFullName(full_name);
1386	if (nameCheck && !nameCheck.ok) {
1387		return { ok: false, full_name: "", message: person_name_validation_message(nameCheck) };
1388	}
1389	if (nameCheck && nameCheck.ok && nameCheck.normalized) {
1390		full_name = frappe.utils.xss_sanitise(nameCheck.normalized);
1391	}
1392	return { ok: true, full_name };
1393}
1394
1395function storePendingLoginName(fullName) {
1396	if (fullName) {
1397		sessionStorage.setItem(LOGIN_PENDING_NAME_KEY, fullName);
1398	} else {
1399		sessionStorage.removeItem(LOGIN_PENDING_NAME_KEY);
1400	}
1401}
1402
1403function getPendingLoginName() {
1404	return sessionStorage.getItem(LOGIN_PENDING_NAME_KEY) || "";
1405}
1406
1407function resolveLoginFullName(mobileNo) {
1408	if (!window.get_name_at_sign_up) {
1409		return { ok: true, full_name: mobileNo };
1410	}
1411	if (!isLoginNameFieldVisible()) {
1412		return { ok: true, full_name: "" };
1413	}
1414	return normalizeLoginFullName($("#login_name").val());
1415}
1416
1417function updateLoginNameForMobile() {
1418	if (!window.get_name_at_sign_up) {
1419		return;
1420	}
1421	const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim());
1422	const mobileNo = check_digits_ascii(mobile);
1423	const regex = /^09\d{9}$/;
1424	if (!regex.test(mobileNo)) {
1425		setLoginNameFieldVisible(true);
1426		return;
1427	}
1428	frappe.call({
1429		method: "frappe.core.doctype.user.user.check_got_name",
1430		args: { user: mobileNo },
1431		callback: function (response) {
1432			setLoginNameFieldVisible(!!response.message);
1433		},
1434	});
1435}
1436
1437function loginNameCharIsDigit(ch) {
1438	try {
1439		return /\p{Nd}/u.test(ch);
1440	} catch (e) {
1441		var code = ch.charCodeAt(0);
1442		return (
1443			(code >= 48 && code <= 57) ||
1444			(code >= 0x06f0 && code <= 0x06f9) ||
1445			(code >= 0x0660 && code <= 0x0669)
1446		);
1447	}
1448}
1449
1450function loginNameCharIsLetter(ch) {
1451	try {
1452		return /\p{L}/u.test(ch);
1453	} catch (e) {
1454		return /[A-Za-z\u0600-\u06FF]/.test(ch);
1455	}
1456}
1457
1458function filterLoginNameInput(raw) {
1459	var s = raw == null ? "" : String(raw);
1460	var out = "";
1461	for (var i = 0; i < s.length; i++) {
1462		var ch = s[i];
1463		if (/\s/u.test(ch) || ch === "\u200c") {
1464			out += ch;
1465			continue;
1466		}
1467		if (loginNameCharIsDigit(ch) || ch === "*") {
1468			continue;
1469		}
1470		if (loginNameCharIsLetter(ch)) {
1471			out += ch;
1472		}
1473	}
1474	return out;
1475}
1476
1477function bindLoginNameInputFilter() {
1478	var el = document.getElementById("login_name");
1479	if (!el || el.__loginNameInputBound) {
1480		return;
1481	}
1482	el.__loginNameInputBound = true;
1483
1484	function applyFilter() {
1485		var filtered = filterLoginNameInput(el.value);
1486		if (filtered !== el.value) {
1487			var pos = el.selectionStart;
1488			var newPos =
1489				typeof pos === "number"
1490					? Math.min(filterLoginNameInput(el.value.slice(0, pos)).length, filtered.length)
1491					: filtered.length;
1492			el.value = filtered;
1493			try {
1494				el.setSelectionRange(newPos, newPos);
1495			} catch (e) {
1496				/* ignore */
1497			}
1498		}
1499	}
1500
1501	el.addEventListener("input", applyFilter);
1502	el.addEventListener("keydown", function (e) {
1503		if (e.ctrlKey || e.metaKey || e.altKey) {
1504			return;
1505		}
1506		if (
1507			e.key === "Backspace" ||
1508			e.key === "Delete" ||
1509			e.key === "Tab" ||
1510			e.key === "Enter" ||
1511			e.key.indexOf("Arrow") === 0
1512		) {
1513			return;
1514		}
1515		if (e.key && e.key.length === 1 && !filterLoginNameInput(e.key)) {
1516			e.preventDefault();
1517		}
1518	});
1519	el.addEventListener("paste", function (e) {
1520		e.preventDefault();
1521		var pasted = (e.clipboardData || window.clipboardData).getData("text");
1522		var start = el.selectionStart == null ? el.value.length : el.selectionStart;
1523		var end = el.selectionEnd == null ? start : el.selectionEnd;
1524		var merged = el.value.slice(0, start) + pasted + el.value.slice(end);
1525		el.value = filterLoginNameInput(merged);
1526		var newPos = filterLoginNameInput(el.value.slice(0, start) + pasted).length;
1527		try {
1528			el.setSelectionRange(newPos, newPos);
1529		} catch (err) {
1530			/* ignore */
1531		}
1532	});
1533}
1534
1535function initLoginNameField() {
1536	if (!window.get_name_at_sign_up) {
1537		setLoginNameFieldVisible(false);
1538		return;
1539	}
1540	setLoginNameFieldVisible(true);
1541	$("#login_mobile").off("blur.loginName").on("blur.loginName", updateLoginNameForMobile);
1542}
1543
1544login.bind_events = function () {
1545	$(window).on("hashchange", function () {
1546		login.route();
1547	});
1548
1549
1550	$(".form-login").on("submit", function (event) {
1551
1552		event.preventDefault();
1553		const password = $("#login_password").val();
1554		const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim());
1555		const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim());
1556
1557		// Check if we're in password login mode
1558		const isPasswordMode = $('#email-field-group').is(':visible') && $('#password-field-group').is(':visible');
1559		
1560		// If in password mode, validate email and password
1561		if (isPasswordMode) {
1562			if (!user) {
1563				frappe.msgprint('لطفا شماره موبایل یا ایمیل را وارد کنید');
1564				return false;
1565			}
1566			if (!password) {
1567				frappe.msgprint('لطفا رمز عبور را وارد کنید');
1568				return false;
1569			}
1570			// Show loader for password login
1571			showLoader('loginLoader', 'در حال ورود...');
1572			
1573			// Continue with normal login flow for password mode
1574			var args = {};
1575			args.cmd = "login";
1576			args.usr = check_digits_ascii(user);
1577			args.pwd = check_digits_ascii(password);
1578			args.staff = $("#staff").is(":checked");
1579			args.device = "desktop";
1580
1581			login.call(args).then(() => {
1582				hideLoader('loginLoader');
1583			}).catch(() => {
1584				hideLoader('loginLoader');
1585			});
1586			return false;
1587		}
1588
1589		// OTP login mode - validate mobile number BEFORE making API call
1590		if (!mobile || mobile.trim() === '') {
1591			frappe.msgprint('لطفا یک شماره موبایل معتبر وارد نمایید');
1592			return false;
1593		}
1594		
1595		var args = {};
1596		args.mobile_no = check_digits_ascii(mobile);
1597		const regex = /^09\d{9}$/;
1598		const email_regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
1599		if (!regex.test(args.mobile_no) && !email_regex.test(args.mobile_no)) {
1600			frappe.msgprint('شماره موبایل معتبر نیست');
1601			return false;
1602		}
1603
1604		let allow_email_login = window.system_settings && window.system_settings.allow_easy_login_with_email;
1605		if (email_regex.test(args.mobile_no) && !allow_email_login) {
1606			frappe.msgprint('شماره موبایل معتبر نیست');
1607			return false;
1608		}
1609
1610		// Show loader
1611		showLoader('loginLoader', 'در حال ارسال کد...');
1612
1613		let button = document.getElementById("loginButton");
1614		if (button) {
1615			button.disabled = true;
1616		}
1617
1618		args.cmd = "login";
1619		args.staff = $("#staff").is(":checked");
1620		args.device = "desktop";
1621		
1622		// Only set usr and pwd if we're in password mode (they should be empty for OTP mode)
1623		// We already checked isPasswordMode above, so if we reach here, we're in OTP mode
1624		args.usr = "";
1625		args.pwd = "";
1626
1627		const nameResult = resolveLoginFullName(args.mobile_no);
1628		if (!nameResult.ok) {
1629			hideLoader("loginLoader");
1630			if (button) {
1631				button.disabled = false;
1632			}
1633			frappe.msgprint(nameResult.message || 'لطفا نام خود را وارد کنید');
1634			return false;
1635		}
1636
1637		const full_name = nameResult.full_name;
1638		args.name = full_name;
1639		args.get_name = window.get_name_at_sign_up;
1640		args.full_name = full_name;
1641		storePendingLoginName(full_name);
1642
1643		// Send OTP and show OTP page
1644		args.email = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim());
1645		args.cmd = "frappe.core.doctype.user.user.login_sign_up_mobile";
1646		args.redirect_to = "";
1647
1648		frappe.call({
1649			type: "POST",
1650			method: "frappe.core.doctype.user.user.login_sign_up_mobile",
1651			args: args,
1652			callback: function(r) {
1653				hideLoader('loginLoader');
1654				if (button) {
1655					button.disabled = false;
1656				}
1657				if (r && r.exc) {
1658					frappe.msgprint(r.exc);
1659					return;
1660				}
1661				showOTPPage(args.mobile_no, full_name);
1662				if (r && r.message) {
1663					if (typeof r.message === 'object' && r.message.tmp_id) {
1664						document.cookie = "tmp_id=" + r.message.tmp_id;
1665					} else if (r.tmp_id) {
1666						document.cookie = "tmp_id=" + r.tmp_id;
1667					}
1668				}
1669			},
1670			error: function(r) {
1671				hideLoader('loginLoader');
1672				if (button) {
1673					button.disabled = false;
1674				}
1675				let message = 'خطا در ارسال کد. لطفا دوباره تلاش کنید.';
1676				if (r && r.responseJSON && r.responseJSON._server_messages) {
1677					try {
1678						message = JSON.parse(r.responseJSON._server_messages || '[]').map(function(v) {
1679							try {
1680								return JSON.parse(v).message;
1681							} catch (e) {
1682								return v;
1683							}
1684						}).filter(Boolean).join('<br>') || message;
1685					} catch (e) {
1686						// use default message
1687					}
1688				}
1689				frappe.msgprint(message);
1690			}
1691		});
1692		return false;
1693	});
1694
1695	$(".form-signup").on("submit", function (event) {
1696		event.preventDefault();
1697		var args = {};
1698		args.cmd = "frappe.core.doctype.user.user.sign_up";
1699		args.email = check_digits_ascii(($("#signup_email").val() || "").trim());
1700		args.mobile_no = check_digits_ascii(($("#mobile_no").val() || "").trim());
1701		args.redirect_to = frappe.utils.sanitise_redirect(frappe.utils.get_url_arg("redirect-to"));
1702		args.full_name = frappe.utils.xss_sanitise(($("#signup_fullname").val() || "").trim());
1703
1704		var signupNameCheck =
1705			window.frappe_validate_person_name &&
1706			window.frappe_validate_person_name.checkSignupFullName(args.full_name);
1707		if (signupNameCheck && !signupNameCheck.ok) {
1708			login.set_status(person_name_validation_message(signupNameCheck), "red");
1709			return false;
1710		}
1711		if (signupNameCheck && signupNameCheck.ok && signupNameCheck.normalized) {
1712			args.full_name = frappe.utils.xss_sanitise(signupNameCheck.normalized);
1713		}
1714
1715		if (!args.mobile_no) {
1716
1717			if (!args.email || !validate_email(args.email) || !args.full_name) {
1718				login.set_status('ایمیل و نام معتبر مورد نیاز است', 'red');
1719				return false;
1720			}
1721			login.call(args);
1722		}
1723		else {
1724			if (!args.mobile_no || !args.full_name) {
1725				login.set_status('Valid mobile and name required', 'red');
1726				return false;
1727			}
1728			const regex = /^09\d{9}$/;
1729			if (!regex.test(args.mobile_no)) {
1730				login.set_status('شماره موبایل معتبر نیست', 'red');
1731				return false;
1732			}
1733			login.call(args);
1734			window.location.href = "/login#login";
1735
1736		}
1737
1738
1739		return false;
1740	});
1741
1742	$(".form-forgot").on("submit", function (event) {
1743		event.preventDefault();
1744		var args = {};
1745		args.cmd = "frappe.core.doctype.user.user.reset_password";
1746		args.user = ($("#forgot_email").val() || "").trim();
1747		args.mobile_no = ($("#forgot_mobile").val() || "").trim();
1748		if (!args.mobile_no) {
1749			if (!args.user) {
1750				login.set_status('شناسه ورود معتبر مورد نیاز است.', 'red');
1751				return false;
1752			}
1753			login.call(args);
1754			return false;
1755		}
1756		else {
1757			if (!args.mobile_no) {
1758
1759
1760
1761				login.set_status('Valid mobile and name required', 'red');
1762				return false;
1763
1764
1765			}
1766			const regex = /^09\d{9}$/;
1767			if (!regex.test(args.mobile_no)) {
1768				login.set_status('شماره موبایل معتبر نیست', 'red');
1769				return false;
1770			}
1771			login.call(args);
1772			window.location.href = "/login#login";
1773
1774		}
1775
1776	});
1777
1778	$(".form-login-with-email-link").on("submit", function (event) {
1779		event.preventDefault();
1780		var args = {};
1781		args.cmd = "frappe.www.login.send_login_link";
1782		args.email = ($("#login_with_email_link_email").val() || "").trim();
1783		if (!args.email) {
1784			login.set_status('شناسه ورود معتبر مورد نیاز است.', 'red');
1785			return false;
1786		}
1787		login.call(args).then(() => {
1788			login.set_status('لینک ورود به ایمیل شما ارسال شد', 'blue');
1789			$("#login_with_email_link_email").val("");
1790		}).catch(() => {
1791			login.set_status('ارسال لینک ورود', 'blue');
1792		});
1793
1794		return false;
1795	});
1796
1797	$(document)
1798		.off("click.rodinTogglePassword", ".toggle-password")
1799		.on("click.rodinTogglePassword", ".toggle-password", function (e) {
1800			e.preventDefault();
1801			e.stopPropagation();
1802			var $btn = $(this);
1803			var target =
1804				$btn.attr("data-toggle-target") ||
1805				$btn.attr("toggle") ||
1806				"#login_password";
1807			var $input = $(target);
1808			if (!$input.length) {
1809				$input = $btn.closest(".password-field").find('input[type="password"], input[type="text"]').first();
1810			}
1811			if (!$input.length) return false;
1812
1813			var show = $input.attr("type") === "password";
1814			$input.attr("type", show ? "text" : "password");
1815			$btn
1816				.toggleClass("is-visible", show)
1817				.attr("aria-pressed", show ? "true" : "false")
1818				.attr(
1819					"aria-label",
1820					show ? 'پنهان کردن رمز عبور' : 'نمایش رمز عبور'
1821				);
1822			return false;
1823		});
1824
1825	
1826}
1827
1828
1829login.route = function () {
1830	var route = window.location.hash.slice(1);
1831	if (!route) route = "login";
1832	route = route.replaceAll("-", "_");
1833	login[route]();
1834}
1835
1836login.reset_sections = function (hide) {
1837	if (hide || hide === undefined) {
1838		$("section.for-login").toggle(false);
1839		$("section.for-email-login").toggle(false);
1840		$("section.for-forgot").toggle(false);
1841		$("section.for-login-with-email-link").toggle(false);
1842		$("section.for-signup").toggle(false);
1843	}
1844	$('section:not(.signup-disabled) .indicator').each(function () {
1845		$(this).removeClass().addClass('indicator').addClass('blue')
1846			.text($(this).attr('data-text'));
1847	});
1848}
1849
1850login.login = function () {
1851	login.reset_sections();
1852	$(".for-login").toggle(true);
1853}
1854
1855login.email = function () {
1856	login.reset_sections();
1857	$(".for-email-login").toggle(true);
1858	$("#login_email").focus();
1859}
1860
1861login.steptwo = function () {
1862	login.reset_sections();
1863	$(".for-login").toggle(true);
1864	$("#login_email").focus();
1865}
1866
1867login.forgot = function () {
1868	login.reset_sections();
1869	if ($("#login_email").val()) {
1870		$("#forgot_email").val($("#login_email").val());
1871	}
1872	$(".for-forgot").toggle(true);
1873	$("#forgot_email").focus();
1874}
1875
1876login.login_with_email_link = function () {
1877	login.reset_sections();
1878	if ($("#login_email").val()) {
1879		$("#login_with_email_link_email").val($("#login_email").val());
1880	}
1881	$(".for-login-with-email-link").toggle(true);
1882	$("#login_with_email_link_email").focus();
1883}
1884
1885login.signup = function () {
1886	login.reset_sections();
1887	$(".for-signup").toggle(true);
1888	$("#signup_fullname").focus();
1889}
1890
1891
1892// Login
1893login.call = function (args, callback) {
1894	// setTimeout(()=>{
1895
1896	// 	login.set_status('در حال تأیید...', 'blue');
1897	// }, "1000")
1898	if (!args.staff) {
1899		login.set_status('ورود', 'green');
1900
1901	} else {
1902
1903		login.set_status('در حال تأیید...', 'blue');
1904		$('.verification-mail').text("Check your email please.");
1905	}
1906
1907
1908
1909
1910	return frappe.call({
1911		type: "POST",
1912		args: args,
1913		callback: callback,
1914		freeze: true,
1915		statusCode: login.login_handlers
1916	});
1917}
1918
1919login.set_status = function (message, color) {
1920	$('section:visible .btn-primary').text(message)
1921	if (color == "red") {
1922		$('section:visible .page-card-body').addClass("invalid");
1923	}
1924}
1925
1926login.set_invalid = function (message) {
1927	$(".login-content.page-card").addClass('invalid-login');
1928	setTimeout(() => {
1929		$(".login-content.page-card").removeClass('invalid-login');
1930	}, 500)
1931	login.set_status(message, 'red');
1932	$("#login_password").focus();
1933}
1934
1935login.login_handlers = (function () {
1936	var get_error_handler = function (default_message) {
1937		return function (xhr, data) {
1938			if (xhr.responseJSON) {
1939				data = xhr.responseJSON;
1940			}
1941
1942			var message = default_message;
1943			if (data._server_messages) {
1944				message = ($.map(JSON.parse(data._server_messages || '[]'), function (v) {
1945					// temp fix for messages sent as dict
1946					try {
1947						return JSON.parse(v).message;
1948					} catch (e) {
1949						return v;
1950					}
1951				}) || []).join('<br>') || default_message;
1952			}
1953
1954			if (message === default_message) {
1955				login.set_invalid(message);
1956			} else {
1957				login.reset_sections(false);
1958			}
1959
1960		};
1961	}
1962
1963	var login_handlers = {
1964		200: function (data) {
1965			if (data.message == 'Logged In') {
1966				storePendingLoginName("");
1967				login.set_status('ورود با موفقیت انجام شد', 'green');
1968				document.body.innerHTML = `<div class="centered splash d-flex w-100 h-100 justify-content-center align-items-center" style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;">
1969	<img src="/assets/erpnext/images/erpnext-logo.svg"
1970		style="max-width: 100px; max-height: 100px;display: flex;">
1971</div>`;
1972				window.location.href = frappe.utils.sanitise_redirect(frappe.utils.get_url_arg("redirect-to")) || data.home_page;
1973			} else if (data.message == 'Password Reset') {
1974				window.location.href = frappe.utils.sanitise_redirect(data.redirect_to);
1975			} else if (data.message == "No App") {
1976				storePendingLoginName("");
1977				login.set_status("ورود با موفقیت انجام شد", 'green');
1978				if (localStorage) {
1979					localStorage.removeItem("last_visited");
1980				}
1981				window.location.href = "/";
1982			} else if (window.location.hash === '#forgot') {
1983				if (data.message === 'not found') {
1984					login.set_status('کاربر معتبری نیست', 'red');
1985				} else if (data.message == 'not allowed') {
1986					login.set_status('مجاز نیست', 'red');
1987				} else if (data.message == 'disabled') {
1988					login.set_status('مجاز نیست: کاربر غیرفعال', 'red');
1989				} else {
1990					login.set_status('دستورالعمل ها ایمیل شد', 'green');
1991				}
1992
1993
1994			} else if (window.location.hash === '#signup') {
1995				if (cint(data.message[0]) == 0) {
1996					login.set_status(data.message[1], 'red');
1997				} else {
1998					login.set_status('موفقیت', 'green');
1999					frappe.msgprint(data.message[1])
2000				}
2001				//login.set_status(__(data.message), 'green');
2002			}
2003
2004			//OTP verification
2005			if (data.verification && data.message != 'Logged In') {
2006				login.set_status('موفقیت', 'green');
2007
2008				document.cookie = "tmp_id=" + data.tmp_id;
2009
2010				if (data.verification.method == 'OTP App') {
2011					continue_otp_app(data.verification.setup, data.verification.qrcode);
2012				} else if (data.verification.method == 'SMS') {
2013					// Show OTP page for SMS
2014					const mobileNo = args.mobile_no || $('#login_mobile').val();
2015					if (mobileNo) {
2016						showOTPPage(mobileNo);
2017					} else {
2018						continue_sms(data.verification.setup, data.verification.prompt);
2019					}
2020				} else if (data.verification.method == 'Email') {
2021					continue_email(data.verification.setup, data.verification.prompt);
2022				}
2023			}
2024		},
2025		401: get_error_handler('ورود نامعتبر دوباره امتحان کنید.'),
2026		417: get_error_handler('اوه! مشکلی پیش آمد'),
2027		404: get_error_handler('کاربر وجود ندارد.')
2028	};
2029
2030	return login_handlers;
2031})();
2032
2033frappe.ready(function () {
2034
2035	login.bind_events();
2036	initLoginNameField();
2037
2038	if (!window.location.hash) {
2039		window.location.hash = "#login";
2040	} else {
2041		$(window).trigger("hashchange");
2042	}
2043
2044	$(".form-signup, .form-forgot, .form-login-with-email-link").removeClass("hide");
2045	$(document).trigger('login_rendered');
2046
2047	// Auto-enable staff mode if signup is disabled
2048	if (window.disable_signup) {
2049		$("#staff").prop("checked", true);
2050		staff_login();
2051		// Hide the staff label and its container when signup is disabled
2052		$("#staff-label").closest(".form-group").hide();
2053	}
2054});
2055
2056var verify_token = function (event) {
2057	$(".form-verify").on("submit", function (eventx) {
2058		eventx.preventDefault();
2059		var args = {};
2060		args.cmd = "login";
2061		args.otp = $("#login_token").val();
2062		args.tmp_id = frappe.get_cookie('tmp_id');
2063		if (!args.otp) {
2064			frappe.msgprint('رمز ورود لازم است');
2065			return false;
2066		}
2067		login.call(args);
2068		return false;
2069	});
2070}
2071
2072var request_otp = function (r) {
2073	$('.login-content').empty();
2074	$('.login-content:visible').append(
2075		`<div id="twofactor_div">
2076			<form class="form-verify">
2077				<div class="page-card-head">
2078					<span class="indicator blue" data-text="Verification">تایید</span>
2079				</div>
2080				<div id="otp_div"></div>
2081				<input type="text" id="login_token" autocomplete="off" class="form-control" placeholder=کد تایید required="" autofocus="">
2082				<button class="btn btn-sm btn-primary btn-block mt-3" id="verify_token">بررسی مجدد</button>
2083			</form>
2084		</div>`
2085	);
2086	// add event handler for submit button
2087	verify_token();
2088}
2089
2090var continue_otp_app = function (setup, qrcode) {
2091	request_otp();
2092	var qrcode_div = $('<div class="text-muted" style="padding-bottom: 15px;"></div>');
2093
2094	if (setup) {
2095		direction = $('<div>').attr('id', 'qr_info').html('کد نمایش داده شده در OTP App را وارد کنید.');
2096		qrcode_div.append(direction);
2097		$('#otp_div').prepend(qrcode_div);
2098	} else {
2099		direction = $('<div>').attr('id', 'qr_info').html('راه‌اندازی OTP با استفاده از برنامه OTP تکمیل نشد. لطفا با مدیر تماس بگیرید');
2100		qrcode_div.append(direction);
2101		$('#otp_div').prepend(qrcode_div);
2102	}
2103}
2104
2105var continue_sms = function (setup, prompt) {
2106	// Use new OTP page instead of old request_otp
2107	const mobileNo = $('#login_mobile').val() || $('#display_mobile_number').text();
2108	if (mobileNo && setup) {
2109		showOTPPage(mobileNo);
2110	} else {
2111		// Fallback to old method if needed
2112		request_otp();
2113		var sms_div = $('<div class="text-muted" style="padding-bottom: 15px;"></div>');
2114		if (setup) {
2115			sms_div.append(prompt)
2116			$('#otp_div').prepend(sms_div);
2117		} else {
2118			direction = $('<div>').attr('id', 'qr_info').html(prompt || 'اس ام اس ارسال نشد لطفا با مدیر تماس بگیرید');
2119			sms_div.append(direction);
2120			$('#otp_div').prepend(sms_div)
2121		}
2122	}
2123}
2124
2125var continue_email = function (setup, prompt) {
2126	request_otp();
2127	var email_div = $('<div class="text-muted" style="padding-bottom: 15px;"></div>');
2128
2129	if (setup) {
2130		email_div.append(prompt)
2131		$('#otp_div').prepend(email_div);
2132	} else {
2133		var direction = $('<div>').attr('id', 'qr_info').html(prompt || 'ایمیل کد تأیید ارسال نشد. لطفا با مدیر تماس بگیرید');
2134		email_div.append(direction);
2135		$('#otp_div').prepend(email_div);
2136	}
2137}
2138let interval; // Store the interval ID globally
2139let countdown; // Store the countdown value globally
2140
2141function startCountdown(seconds) {
2142	// Clear any existing interval to avoid multiple intervals running simultaneously
2143	clearInterval(interval);
2144
2145	if (seconds !== 0) {
2146		countdown = seconds;
2147		const countdownElement = document.getElementById('countdown-container');
2148		countdownElement.style.display = 'flex';
2149
2150		interval = setInterval(() => {
2151			document.getElementById('countdown').textContent = `${countdown} ثانیه تا ارسال مجدد کد`;
2152			if (countdown === 0) {
2153				clearInterval(interval);
2154				var is_checked = $("#staff").is(":checked");
2155				if (!is_checked) {
2156					const send_again = document.getElementById('send-code-again');
2157					send_again.style.display = 'flex';
2158				}
2159			}
2160			countdown--;
2161		}, 1000);
2162	}
2163}
2164
2165function stopCountdown() {
2166	clearInterval(interval);
2167}
2168
2169function resetCountdown(seconds) {
2170	stopCountdown();
2171	startCountdown(seconds);
2172}
2173
2174document.addEventListener('DOMContentLoaded', function () {
2175
2176	// document.getElementById("loginButton").addEventListener("click", function() {
2177	// 	let button = document.getElementById("loginButton");
2178	// 	button.disabled = true;  // Disable button
2179	// 	setTimeout(() => {
2180	// 		button.disabled = false;  // Re-enable after 100ms
2181	// 	}, 500);
2182	// });
2183
2184	const login_password = document.getElementById('password-field');
2185
2186	const login_email = document.getElementById('email-field');
2187
2188	const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim());
2189	if (login_password.style.display == 'none' && !mobile) {
2190		$("#login_email").val("");
2191		$("#login_password").val("");
2192		login_email.style.display = 'none';
2193		const username_label = document.getElementById('login-label-username');
2194		username_label.style.display = 'flex';
2195	}
2196
2197	const element = document.getElementById("send-code-again");
2198	// element.addEventListener("click", myFunction);
2199	if (!element.hasEventListener) {
2200		element.addEventListener("click", myFunction);
2201		element.hasEventListener = true; // Mark that the event listener has been added
2202	}
2203	// u = $("#login_email").val();
2204
2205
2206	function myFunction() {
2207
2208		const login_email = document.getElementById('login_email');
2209		login_email.disabled = true;
2210		$('#login_email').css('color', 'gray')
2211		$('#login_email').css('background-color', 'rgba(255, 255, 255, 0.43')
2212
2213		// var get_name = false;
2214		// var full_name = "";
2215
2216
2217		// frappe.call({
2218		// 	method: "frappe.website.doctype.website_settings.website_settings.get_name_at_sign_up",
2219		// 	args:{},
2220		// 	callback: function(response){
2221		// 		if(response){
2222
2223		// 			get_name = response.message;
2224
2225		// 		}
2226		// 	}
2227		// })
2228
2229		// if(get_name){
2230		// 	full_name = frappe.utils.xss_sanitise(($("#name").val() || "").trim())
2231
2232		// }else{
2233		// 	full_name = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim())
2234		// }
2235		let inputVal = ($("#login_email").val() || "").trim();
2236		let isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inputVal);
2237
2238		if (isEmail && allow_email_login) {
2239			frappe.call({
2240				type: "POST",
2241				method: "frappe.core.doctype.user.user.login_sign_up_mobile",
2242				args: {
2243					email: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()),
2244					full_name: "",
2245					mobile_no: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()),
2246					redirect_to: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim())
2247				},
2248
2249				callback: function (r) {
2250					const send_again = document.getElementById('send-code-again');
2251					send_again.style.display = 'none';
2252
2253					startCountdown(120);
2254
2255				}
2256			})
2257
2258		} else {
2259			frappe.call({
2260				type: "POST",
2261				method: "frappe.core.doctype.user.user.login_sign_up_mobile",
2262				args: {
2263					email: "",
2264					full_name: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()),
2265					mobile_no: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()),
2266					redirect_to: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim())
2267				},
2268
2269				callback: function (r) {
2270					const send_again = document.getElementById('send-code-again');
2271					send_again.style.display = 'none';
2272
2273					startCountdown(120);
2274
2275				}
2276			})
2277		}
2278
2279		// frappe.call({
2280		// 	type: "POST",
2281		// 	method: "frappe.core.doctype.user.user.login_sign_up_mobile",
2282		// 	args: (function () {
2283		// 		let inputVal = ($("#login_email").val() || "").trim();
2284		// 		let isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inputVal);
2285
2286		// 		return {
2287		// 			email: isEmail ? frappe.utils.xss_sanitise(inputVal) : "",
2288		// 			full_name: frappe.utils.xss_sanitise(inputVal),
2289		// 			mobile_no: isEmail ? "" : frappe.utils.xss_sanitise(inputVal),
2290		// 			redirect_to: frappe.utils.xss_sanitise(inputVal)
2291		// 		};
2292		// 	})(),
2293
2294		// 	callback: function (r) {
2295		// 		const send_again = document.getElementById('send-code-again');
2296		// 		send_again.style.display = 'none';
2297
2298		// 		startCountdown(120);
2299		// 	}
2300		// });
2301
2302	}
2303
2304});
2305
2306function active_number() {
2307	const login_email = document.getElementById('login_email');
2308	login_email.disabled = false;
2309
2310	const edit_number = document.getElementById('edit-mobile-number');
2311	edit_number.disabled = true;
2312	stopCountdown();
2313
2314
2315	$('#login_email').css('color', 'black')
2316	$('#login_email').css('background-color', 'white')
2317	const send_again = document.getElementById('send-code-again');
2318	send_again.style.display = 'flex';
2319	// startCountdown(0);
2320
2321}
2322
2323function staff_login() {
2324	var is_staff = $("#staff").is(":checked");
2325
2326	if (is_staff) {
2327		setLoginNameFieldVisible(false);
2328		login.set_status('ورود', 'blue');
2329		const email_label = document.getElementById('login-label-email');
2330		email_label.style.display = 'flex';
2331		const password_label = document.getElementById('login-label-password');
2332		password_label.style.display = 'flex';
2333		const username_label = document.getElementById('login-label-username');
2334		username_label.style.display = 'none';
2335		$("#login_mobile").css("border", "none");
2336		$("#login_email").css("border", "none");
2337		$("#login_password").css("border", "none");
2338		const email_field = document.getElementById('email-field');
2339		email_field.style.display = 'flex';
2340		const password_field = document.getElementById('password-field');
2341		password_field.style.display = 'flex';
2342		const login_mobile = document.getElementById('login_mobile');
2343		login_mobile.style.display = 'none';
2344		const login_email = document.getElementById('login_email');
2345		login_email.disabled = false;
2346		$('#login_email').css('color', 'black')
2347		$('#login_email').css('background-color', 'white')
2348
2349		const countdownElement = document.getElementById('countdown-container');
2350		countdownElement.style.display = 'none';
2351		const send_again = document.getElementById('send-code-again');
2352		send_again.style.display = 'none ';
2353		const edit_mobile = document.getElementById('edit-mobile-number');
2354		edit_mobile.style.display = 'none';
2355
2356		const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim());
2357
2358		// Hide staff label if signup is disabled, otherwise set the text
2359		if (window.disable_signup === false || window.disable_signup === "false") {
2360			$("#staff-label").text("ورود با رمز یکبار مصرف");
2361		} 
2362
2363
2364
2365
2366
2367
2368
2369	} else {
2370		initLoginNameField();
2371		login.set_status('ورود', 'blue');
2372		$("#login_email").val("");
2373		$("#login_password").val("");
2374
2375		const email_label = document.getElementById('login-label-email');
2376		email_label.style.display = 'none';
2377		const password_label = document.getElementById('login-label-password');
2378		password_label.style.display = 'none';
2379		const username_label = document.getElementById('login-label-username');
2380		username_label.style.display = 'flex';
2381		$("#login_mobile").css("border", "none");
2382		$("#login_email").css("border", "none");
2383		$("#login_password").css("border", "none");
2384		const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim());
2385		const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim());
2386		const pass = $("#login_password").val()
2387		const email_field = document.getElementById('email-field');
2388		email_field.style.display = 'none';
2389		const password_field = document.getElementById('password-field');
2390		password_field.style.display = 'none';
2391		const login_mobile = document.getElementById('login_mobile');
2392		login_mobile.style.display = 'flex';
2393		const login_email = document.getElementById('login_email');
2394		login_email.disabled = true;
2395		$('#login_email').css('color', 'gray')
2396		$('#login_email').css('background-color', 'rgba(255, 255, 255, 0.43)');
2397		if (user && mobile) {
2398
2399			const edit_mobile = document.getElementById('edit-mobile-number');
2400			edit_mobile.style.display = 'flex';
2401
2402
2403		}
2404		if (user && !mobile) {
2405
2406			$("#login_email").val("");
2407			$("#login_password").val("");
2408
2409		}
2410		if (pass && !mobile) {
2411			$("#login_email").val("");
2412			$("#login_password").val("");
2413
2414		}
2415		if (password_field.style.display == 'none') {
2416			var edit_mobile = document.getElementById('edit-mobile-number');
2417			edit_mobile.style.display = 'none';
2418		}
2419
2420		// Hide staff label if signup is disabled, otherwise set the text
2421		if (window.disable_signup) {
2422			$("#staff-label").closest(".form-group").hide();
2423		} else {
2424			$("#staff-label").text("ورود با رمز ثابت");
2425		}
2426
2427
2428
2429
2430
2431
2432	}
2433
2434}
2435
2436
2437
2438
2439
2440function check_staff() {
2441	var is_checked = $("#staff").is(":checked");
2442
2443	if ($("#staff-label").text() == "Website User") {
2444		const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim());
2445		const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim());
2446
2447	}
2448	if (is_checked) {
2449		$("#staff").is(":checked") = false;
2450
2451
2452	} else {
2453
2454		$("#staff").is(":checked") = true;
2455
2456
2457	}
2458
2459}
2460
2461
2462function check_digits_ascii(digits) {
2463
2464	var inputValue = "";
2465
2466	for (var i = 0; i < digits.length; i++) {
2467		var currentChar = digits.charAt(i);
2468		var ascii = currentChar.charCodeAt(0);
2469
2470
2471
2472
2473		switch (ascii) {
2474
2475			// 0
2476			case 176219:
2477				currentChar = String.fromCharCode(48);
2478				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2479				break;
2480
2481			case 1776:
2482				currentChar = String.fromCharCode(48);
2483				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2484				break;
2485
2486			// 1
2487			case 177219:
2488				currentChar = String.fromCharCode(49);
2489				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2490				break;
2491
2492			case 1777:
2493				currentChar = String.fromCharCode(49);
2494				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2495				break;
2496
2497			// 2
2498			case 178219:
2499				currentChar = String.fromCharCode(50);
2500				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2501				break;
2502
2503			case 1778:
2504				currentChar = String.fromCharCode(50);
2505				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2506				break;
2507
2508			// 3
2509			case 179219:
2510				currentChar = String.fromCharCode(51);
2511				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2512				break;
2513
2514			case 1779:
2515				currentChar = String.fromCharCode(51);
2516				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2517				break;
2518
2519			// 4
2520			case 180219:
2521				currentChar = String.fromCharCode(52);
2522				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2523				break;
2524
2525			case 1780:
2526				currentChar = String.fromCharCode(52);
2527				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2528				break;
2529
2530			// 5
2531			case 181219:
2532				currentChar = String.fromCharCode(53);
2533				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2534				break;
2535
2536			case 1781:
2537				currentChar = String.fromCharCode(53);
2538				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2539				break;
2540
2541			// 6
2542			case 182219:
2543				currentChar = String.fromCharCode(54);
2544				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2545				break;
2546
2547			case 1782:
2548				currentChar = String.fromCharCode(54);
2549				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2550				break;
2551
2552			// 7
2553			case 183219:
2554				currentChar = String.fromCharCode(55);
2555				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2556				break;
2557
2558			case 1783:
2559				currentChar = String.fromCharCode(55);
2560				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2561				break;
2562
2563			// 8
2564			case 184219:
2565				currentChar = String.fromCharCode(56);
2566				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2567				break;
2568
2569			case 1784:
2570				currentChar = String.fromCharCode(56);
2571				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2572				break;
2573
2574			// 9
2575			case 185219:
2576				currentChar = String.fromCharCode(57);
2577				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2578				break;
2579
2580			case 1785:
2581				currentChar = String.fromCharCode(57);
2582				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2583				break;
2584
2585			default:
2586				inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1);
2587		}
2588
2589
2590	}
2591
2592
2593	return inputValue
2594
2595}
2596
2597// OTP Management Functions
2598function showOTPPage(mobileNumber, fullName) {
2599	if (fullName) {
2600		storePendingLoginName(fullName);
2601	}
2602	// Hide login loader if active
2603	hideLoader('loginLoader');
2604	
2605	// Convert to Persian digits if needed
2606	const persianDigits = ['Û°', 'Û±', 'Û²', 'Û³', 'Û´', 'Ûµ', 'Û¶', 'Û·', 'Û¸', 'Û¹'];
2607	let displayNumber = mobileNumber;
2608	if (/^\d+$/.test(mobileNumber)) {
2609		displayNumber = mobileNumber.split('').map(d => persianDigits[parseInt(d)]).join('');
2610	}
2611	
2612	// Hide login form container completely
2613	$('.login-form-container').css('display', 'none').attr('style', function(i, s) {
2614		return (s || '') + ' display: none !important;';
2615	});
2616	// Show OTP page container - add show-otp class
2617	$('.otp-container').addClass('show-otp');
2618	
2619	// Set mobile number
2620	$('#display_mobile_number').text(displayNumber);
2621	// Focus first OTP input
2622	setTimeout(() => {
2623		$('#otp_1').focus();
2624	}, 100);
2625	// Clear all OTP inputs
2626	$('.otp-input').val('').removeClass('filled').addClass('empty').css('color', 'transparent');
2627	clearOTPErrorState();
2628	otpAutoSubmitEnabled = true;
2629	otpSubmitInProgress = false;
2630	// Disable submit button
2631	$('#otp_submit_btn').prop('disabled', true).text('تأیید و ادامه');
2632	
2633	// Start countdown timer
2634	startOTPCountdown(120);
2635}
2636
2637function editMobileNumber() {
2638	// Hide OTP loader if active
2639	hideLoader('otpLoader');
2640	
2641	// Get mobile number from display (convert Persian to English if needed)
2642	const displayNumber = $('#display_mobile_number').text();
2643	const englishDigits = displayNumber.replace(/[Û°-Û¹]/g, function(d) {
2644		return 'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(d);
2645	}).replace(/\D/g, '');
2646	
2647	// Hide OTP page container - remove show-otp class
2648	$('.otp-container').removeClass('show-otp');
2649	// Show login form container
2650	$('.login-form-container').attr('style', function(i, s) {
2651		return (s ? s.replace(/display:\s*none\s*!important;?/gi, '') : '') + ' display: flex !important;';
2652	});
2653	// Set mobile number in input field
2654	$('#login_mobile').val(englishDigits);
2655	// Focus mobile input
2656	setTimeout(() => {
2657		$('#login_mobile').focus().select();
2658		if (window.get_name_at_sign_up) {
2659			updateLoginNameForMobile();
2660		}
2661	}, 100);
2662	
2663	// Stop countdown if running
2664	if (otpCountdownInterval) {
2665		clearInterval(otpCountdownInterval);
2666		otpCountdownInterval = null;
2667	}
2668	$('#otp_countdown').hide();
2669	$('#otp_resend_link').hide();
2670}
2671
2672// Initialize OTP input handlers
2673function initOTPInputs() {
2674	// Function to update empty state (show/hide dash)
2675	function updateEmptyState(input) {
2676		const $input = $(input);
2677		const $wrapper = $input.closest('.otp-input-wrapper');
2678		if (!$input.val() || $input.val().trim() === '') {
2679			$input.addClass('empty');
2680			// Only show dash if not focused
2681			if (!$input.is(':focus')) {
2682				$wrapper.addClass('show-dash');
2683			} else {
2684				$wrapper.removeClass('show-dash');
2685			}
2686		} else {
2687			$input.removeClass('empty');
2688			$wrapper.removeClass('show-dash');
2689		}
2690	}
2691	
2692	// Initialize all inputs with empty state
2693	$('.otp-input').each(function() {
2694		updateEmptyState(this);
2695	});
2696	
2697	// Handle focus - border changes to dark blue, hide dash
2698	$('.otp-input').on('focus', function() {
2699		const $input = $(this);
2700		const $wrapper = $input.closest('.otp-input-wrapper');
2701		// Hide dash when focused
2702		$wrapper.removeClass('show-dash');
2703		// Border will change to dark blue via CSS :focus
2704	});
2705	
2706	// Handle blur - show dash if empty, reset border
2707	$('.otp-input').on('blur', function() {
2708		const $input = $(this);
2709		updateEmptyState(this);
2710	});
2711	
2712	$('.otp-input').on('input', function() {
2713		const currentInput = $(this);
2714		const value = currentInput.val();
2715		
2716		// Only allow numbers
2717		if (value && !/^\d$/.test(value)) {
2718			currentInput.val('');
2719			updateEmptyState(currentInput[0]);
2720			return;
2721		}
2722		
2723		// Remove empty class and add filled class
2724		if (value) {
2725			clearOTPErrorState();
2726			currentInput.removeClass('empty');
2727			currentInput.addClass('filled');
2728			// Change text color to dark primary color
2729			currentInput.css('color', 'var(--dark-primary)');
2730		} else {
2731			currentInput.removeClass('filled');
2732			currentInput.addClass('empty');
2733			currentInput.css('color', 'transparent');
2734		}
2735		
2736		// Update empty state to manage dash display
2737		updateEmptyState(currentInput[0]);
2738		
2739		// Move to next input if value entered
2740		if (value && currentInput.closest('.otp-input-wrapper').next('.otp-input-wrapper').length) {
2741			currentInput.closest('.otp-input-wrapper').next('.otp-input-wrapper').find('.otp-input').focus();
2742		}
2743		
2744		// Check if all fields are filled
2745		checkOTPComplete();
2746	});
2747	
2748	// Handle backspace
2749	$('.otp-input').on('keydown', function(e) {
2750		const $input = $(this);
2751		if (e.key === 'Backspace' && !$input.val() && $input.closest('.otp-input-wrapper').prev('.otp-input-wrapper').length) {
2752			$input.closest('.otp-input-wrapper').prev('.otp-input-wrapper').find('.otp-input').focus();
2753		} else if (e.key === 'Backspace' && $input.val()) {
2754			// Clear current input on backspace
2755			setTimeout(() => {
2756				if (!$input.val()) {
2757					$input.removeClass('filled');
2758					$input.addClass('empty');
2759					$input.css('color', 'transparent');
2760					updateEmptyState($input[0]);
2761				}
2762			}, 0);
2763		}
2764	});
2765	
2766	// Handle paste
2767	$('.otp-input').on('paste', function(e) {
2768		e.preventDefault();
2769		const pastedData = (e.originalEvent.clipboardData || window.clipboardData).getData('text');
2770		const digits = pastedData.replace(/\D/g, '').substring(0, 5);
2771		
2772		$('.otp-input').each(function(index) {
2773			const $input = $(this);
2774			if (index < digits.length) {
2775				$input.val(digits[index]).addClass('filled').removeClass('empty');
2776				$input.css('color', 'var(--dark-primary)');
2777			} else {
2778				$input.val('').removeClass('filled').addClass('empty');
2779				$input.css('color', 'transparent');
2780			}
2781			updateEmptyState($input[0]);
2782		});
2783		
2784		if (digits.length === 5) {
2785			$('#otp_5').focus();
2786		} else if (digits.length > 0) {
2787			$('#otp_' + digits.length).focus();
2788		}
2789		
2790		checkOTPComplete();
2791	});
2792}
2793
2794function checkOTPComplete() {
2795	const otp1 = $('#otp_1').val();
2796	const otp2 = $('#otp_2').val();
2797	const otp3 = $('#otp_3').val();
2798	const otp4 = $('#otp_4').val();
2799	const otp5 = $('#otp_5').val();
2800	
2801	if (otp1 && otp2 && otp3 && otp4 && otp5) {
2802		$('#otp_submit_btn').prop('disabled', false);
2803		if (otpAutoSubmitEnabled && !otpSubmitInProgress) {
2804			handleOTPSubmit(true);
2805		}
2806	} else {
2807		$('#otp_submit_btn').prop('disabled', true);
2808	}
2809}
2810
2811// Handle OTP form submission
2812function handleOTPSubmit(isAutoSubmit = false) {
2813	if (otpSubmitInProgress) {
2814		return;
2815	}
2816
2817	const otp = $('#otp_1').val() + $('#otp_2').val() + $('#otp_3').val() + $('#otp_4').val() + $('#otp_5').val();
2818	
2819	if (otp.length !== 5) {
2820		frappe.msgprint('لطفا کد 5 رقمی را کامل وارد کنید');
2821		return;
2822	}
2823	
2824	// Get mobile number and convert Persian to English
2825	const displayNumber = $('#display_mobile_number').text();
2826	const mobileNo = displayNumber.replace(/[Û°-Û¹]/g, function(d) {
2827		return 'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(d);
2828	}).replace(/\D/g, '');
2829	
2830	if (!mobileNo) {
2831		frappe.msgprint('شماره موبایل یافت نشد');
2832		return;
2833	}
2834	
2835	otpSubmitInProgress = true;
2836
2837	// Show loader
2838	showLoader('otpLoader', 'در حال بررسی...');
2839	
2840	var args = {};
2841	args.cmd = "login";
2842	args.usr = mobileNo; // Mobile number as username
2843	args.pwd = otp; // OTP as password for SMS login
2844	args.otp = otp; // Also send as otp for 2FA compatibility
2845	args.mobile_no = mobileNo;
2846	args.tmp_id = frappe.get_cookie('tmp_id');
2847	const pendingName = getPendingLoginName();
2848	if (pendingName) {
2849		args.name = pendingName;
2850	}
2851	
2852	// Disable submit button during request
2853	$('#otp_submit_btn').prop('disabled', true).text('در حال بررسی...');
2854	
2855	login.call(args, function(data) {
2856		// Hide loader
2857		hideLoader('otpLoader');
2858		otpSubmitInProgress = false;
2859		// Re-enable button if login fails
2860		if (data && data.message !== 'Logged In' && data.message !== 'No App') {
2861			if (isAutoSubmit) {
2862				otpAutoSubmitEnabled = false;
2863			}
2864			setOTPErrorState();
2865			$('#otp_submit_btn').prop('disabled', false).text('تأیید و ادامه');
2866			// Show error message if available
2867			if (data.exc) {
2868				frappe.msgprint(data.exc);
2869			} else if (data.message) {
2870				frappe.msgprint(data.message);
2871			}
2872		}
2873	}).fail(function(error) {
2874		// Hide loader
2875		hideLoader('otpLoader');
2876		otpSubmitInProgress = false;
2877		otpAutoSubmitEnabled = false;
2878		setOTPErrorState();
2879		$('#otp_submit_btn').prop('disabled', false).text('تأیید و ادامه');
2880		if (error && error.responseJSON && error.responseJSON._server_messages) {
2881			try {
2882				const messages = JSON.parse(error.responseJSON._server_messages || '[]').map(function(v) {
2883					try {
2884						return JSON.parse(v).message;
2885					} catch (e) {
2886						return v;
2887					}
2888				}).filter(Boolean);
2889				if (messages.length) {
2890					frappe.msgprint(messages.join('<br>'));
2891					return;
2892				}
2893			} catch (e) {
2894				// fall through to generic message
2895			}
2896		}
2897		if (error && error.message) {
2898			frappe.msgprint(error.message);
2899		} else {
2900			frappe.msgprint('خطا در ورود. لطفا دوباره تلاش کنید.');
2901		}
2902	});
2903}
2904
2905// OTP Countdown Timer
2906let otpCountdownInterval = null;
2907let otpCountdownSeconds = 0;
2908let otpAutoSubmitEnabled = true;
2909let otpSubmitInProgress = false;
2910
2911function clearOTPErrorState() {
2912	$('.otp-input').removeClass('otp-invalid');
2913}
2914
2915function setOTPErrorState() {
2916	$('.otp-input').addClass('otp-invalid');
2917}
2918
2919function startOTPCountdown(seconds) {
2920	otpCountdownSeconds = seconds;
2921	
2922	// Clear existing interval
2923	if (otpCountdownInterval) {
2924		clearInterval(otpCountdownInterval);
2925	}
2926	
2927	// Show countdown, hide resend link
2928	$('#otp_countdown').show();
2929	$('#otp_resend_link').hide();
2930	
2931	// Update countdown display
2932	updateOTPCountdown();
2933	
2934	// Start interval
2935	otpCountdownInterval = setInterval(function() {
2936		otpCountdownSeconds--;
2937		updateOTPCountdown();
2938		
2939		if (otpCountdownSeconds <= 0) {
2940			clearInterval(otpCountdownInterval);
2941			otpCountdownInterval = null;
2942			// Show resend link, hide countdown
2943			$('#otp_countdown').hide();
2944			$('#otp_resend_link').show();
2945		}
2946	}, 1000);
2947}
2948
2949function updateOTPCountdown() {
2950	const minutes = Math.floor(otpCountdownSeconds / 60);
2951	const seconds = otpCountdownSeconds % 60;
2952	const persianDigits = ['Û°', 'Û±', 'Û²', 'Û³', 'Û´', 'Ûµ', 'Û¶', 'Û·', 'Û¸', 'Û¹'];
2953	const toFa = (n) => String(n).split('').map((d) => (/^\d$/.test(d) ? persianDigits[parseInt(d, 10)] : d)).join('');
2954	const mm = toFa(minutes);
2955	const ss = toFa(String(seconds).padStart(2, '0'));
2956	$('#countdown_text').text('ارسال مجدد کد تا ' + mm + ':' + ss);
2957}
2958
2959function resendOTP() {
2960	const mobileNumber = $('#display_mobile_number').text().replace(/[Û°-Û¹]/g, function(d) {
2961		return 'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(d);
2962	}).replace(/\D/g, '');
2963	
2964	if (!mobileNumber) {
2965		frappe.msgprint('شماره موبایل یافت نشد');
2966		return;
2967	}
2968	
2969	// Call API to resend OTP
2970	var args = {
2971		cmd: "frappe.core.doctype.user.user.login_sign_up_mobile",
2972		mobile_no: mobileNumber,
2973		email: "",
2974		full_name: getPendingLoginName(),
2975		redirect_to: ""
2976	};
2977	
2978	frappe.call({
2979		type: "POST",
2980		method: "frappe.core.doctype.user.user.login_sign_up_mobile",
2981		args: args,
2982		callback: function(r) {
2983			if (r && !r.exc) {
2984				frappe.show_alert('کد مجدداً ارسال شد', 3);
2985				// Restart countdown
2986				startOTPCountdown(120);
2987			} else {
2988				frappe.msgprint('خطا در ارسال مجدد کد');
2989			}
2990		}
2991	});
2992}
2993
2994// Toggle between mobile OTP login and password login
2995function togglePasswordLogin() {
2996	// Hide any active loaders
2997	hideLoader('loginLoader');
2998	hideLoader('otpLoader');
2999	
3000	const isPasswordMode = $('#email-field-group').is(':visible');
3001	
3002	if (isPasswordMode) {
3003		// Switch to mobile OTP mode
3004		$('#email-field-group').hide();
3005		$('#password-field-group').hide();
3006		$('#login_mobile').closest('.form-group').show();
3007		initLoginNameField();
3008		$('#loginButton').show();
3009		$('#passwordLoginButton').hide();
3010		$('#togglePasswordLoginBtn').show();
3011		$('#authModeDivider').show();
3012		$('#toggleToOtpLogin').hide();
3013		$('#login-form-title').text('ورود / ثبت نام');
3014		$('#login-form-subtitle').text(
3015			window.get_name_at_sign_up
3016				? 'شماره موبایل و نام خود را وارد کنید'
3017				: 'شماره موبایل خود را وارد کنید'
3018		);
3019		// Clear email and password fields when switching back to OTP mode
3020		$('#login_email').val('');
3021		$('#login_password').val('');
3022		$('#login_mobile').focus();
3023	} else {
3024		// Switch to password login mode — name is only collected in OTP/signup flow
3025		setLoginNameFieldVisible(false);
3026		$('#login_mobile').closest('.form-group').hide();
3027		$('#email-field-group').show();
3028		$('#password-field-group').show();
3029		$('#loginButton').hide();
3030		$('#passwordLoginButton').show();
3031		$('#togglePasswordLoginBtn').hide();
3032		$('#authModeDivider').show();
3033		$('#toggleToOtpLogin').show();
3034		$('#login-form-title').text('ورود با رمز ثابت');
3035		$('#login-form-subtitle').text('با شماره موبایل یا ایمیل خود وارد شوید.');
3036		// Clear mobile field when switching to password mode
3037		$('#login_mobile').val('');
3038		$('#login_email').focus();
3039	}
3040}
3041
3042// Initialize on page load
3043$(document).ready(function() {
3044	// Ensure OTP container is hidden on page load
3045	$('.otp-container').removeClass('show-otp');
3046	$('.login-form-container').attr('style', function(i, s) {
3047		return (s ? s.replace(/display:\s*none\s*!important;?/gi, '') : '') + ' display: flex !important;';
3048	});
3049	
3050	// Ensure password login fields are hidden by default
3051	$('#email-field-group').hide();
3052	$('#password-field-group').hide();
3053	$('#passwordLoginButton').hide();
3054	$('#toggleToOtpLogin').hide();
3055	
3056	initOTPInputs();
3057	
3058	// Handle OTP form submission
3059	$('.form-otp-verification').on('submit', function(e) {
3060		e.preventDefault();
3061		handleOTPSubmit(false);
3062	});
3063});</script>
3063
3064
3065	
3065<script>frappe.csrf_token = "None";</script>
3065<!-- 
3065<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aos.js"></script>
3065
3066	
3066<script>
3067		AOS.init({
3068			duration: 300,  // Faster animation (default: 1000ms)
3069			once: true,     // Animation happens only once
3070			offset: 200,    // Trigger animation 100px before element comes into view
3071			easing: 'ease-in-out'
3072		});
3073	</script>
3073 -->
3074
3075</body>
3076
3077</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.