[Help] Add help/learning page + interactive guided app tour
New /Help page: quick-start, separate guides for کادر درمان (search/near-me/preferences/اعلام تمایل/saved) and مراکز درمانی (register/post/verify-with-docs/applicants), notifications+install, report/complaint, and an FAQ accordion. Self-hosted tour.js (no CDN, RTL): spotlights elements via data-tour hooks in the nav, auto-runs once for new visitors on the home page (localStorage flag), re-runnable from the Help page button or ?tour=1; skips steps whose target is hidden so it works on mobile/other pages. Help linked from nav + footer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
@page
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "راهنما و آموزش";
|
||||||
|
ViewData["Description"] = "راهنمای کامل استفاده از همکادر برای کادر درمان و مراکز درمانی: جستجوی شیفت و استخدام، اعلام تمایل، انتشار آگهی، تأیید مرکز و اعلانها.";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-head">
|
||||||
|
<div class="container">
|
||||||
|
<h1>راهنما و آموزش</h1>
|
||||||
|
<p class="muted">یاد بگیر چطور از همکادر بیشترین استفاده را ببری — برای کادر درمان و مراکز درمانی.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container section" style="max-width:820px;">
|
||||||
|
|
||||||
|
<div class="rec-banner" style="margin-bottom:18px;">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin:0 0 4px;">تور تعاملی برنامه</h2>
|
||||||
|
<span style="opacity:.9; font-size:14px;">در چند ثانیه بخشهای اصلی را روی همین صفحه نشانت میدهیم.</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-outline" onclick="window.hamkadrTour && window.hamkadrTour.start()">▶ شروع تور راهنما</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-pad legal" style="margin-bottom:14px;">
|
||||||
|
<h2>شروع سریع (۳ گام)</h2>
|
||||||
|
<ol style="padding-inline-start:20px; line-height:2;">
|
||||||
|
<li><strong>وارد شو</strong> — با شماره موبایل و کد پیامکی. هنگام ورود نوع حساب را انتخاب کن: «کادر درمان» یا «کارفرما / مرکز درمانی».</li>
|
||||||
|
<li><strong>تنظیم کن</strong> — اگر کادر درمانی، در «علاقهمندیها» نقش و شهر را مشخص کن؛ اگر کارفرمایی، مرکزت را ثبت کن.</li>
|
||||||
|
<li><strong>شروع کن</strong> — فرصتها را ببین و «اعلام تمایل» بده، یا آگهی منتشر کن.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-pad legal" style="margin-bottom:14px;">
|
||||||
|
<h2>👩⚕️ برای کادر درمان (کارجو)</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>یافتن شیفت/استخدام:</strong> از منو وارد «شیفتها» یا «استخدام» شو و با فیلترِ شهر، محله، نقش و نوع شیفت نتایج را محدود کن.</li>
|
||||||
|
<li><strong>نزدیک من:</strong> با اجازهی دسترسی به موقعیت، نزدیکترین فرصتها بر اساس فاصله مرتب میشوند.</li>
|
||||||
|
<li><strong>علاقهمندیها:</strong> نقش/شهر/نوع شیفت موردنظرت را ذخیره کن تا پیشنهادهای شخصیسازیشده و اعلانِ فرصتهای تازه بگیری.</li>
|
||||||
|
<li><strong>اعلام تمایل:</strong> روی آگهی، دکمهی «اعلام تمایل و مشاهده راه ارتباطی» را بزن تا اطلاعات تماس مرکز نمایش داده شود.</li>
|
||||||
|
<li><strong>ذخیره و حذف:</strong> فرصتهای جالب را ذخیره کن یا «علاقهمند نیستم» را بزن تا پیشنهادها دقیقتر شوند.</li>
|
||||||
|
<li><strong>پنل کارجو:</strong> فرصتهای ذخیرهشده و فعالیتهایت را اینجا دنبال کن.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-pad legal" style="margin-bottom:14px;">
|
||||||
|
<h2>🏥 برای مراکز درمانی (کارفرما)</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>ثبت مرکز:</strong> از «پنل کارفرما» مرکزت را ثبت کن و موقعیت را روی نقشه یا با «موقعیت فعلی من» مشخص کن.</li>
|
||||||
|
<li><strong>انتشار آگهی:</strong> شیفت یا موقعیت استخدامی منتشر کن؛ نوع پرداخت (مبلغ ثابت یا درصد سهم) و شرط جنسیت را تعیین کن.</li>
|
||||||
|
<li><strong>تأیید مرکز:</strong> در پنل، «درخواست تأیید و بارگذاری مدارک» را بزن و مجوز/پروانه را آپلود کن. پس از بررسی، نشان «✓ تأیید شده» روی آگهیهایت نمایش داده میشود.</li>
|
||||||
|
<li><strong>مدیریت متقاضیان:</strong> فهرست افرادی که «اعلام تمایل» کردهاند را با نام و شماره ببین و هماهنگ کن.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-pad legal" style="margin-bottom:14px;">
|
||||||
|
<h2>🔔 اعلانها و نصب اپ</h2>
|
||||||
|
<ul>
|
||||||
|
<li>اعلانهای درونبرنامهای (زنگوله) بهصورت زنده کار میکند و در ایران بدون نیاز به سرویسهای خارجی در دسترس است.</li>
|
||||||
|
<li>برای دریافت بهتر اعلانها، همکادر را بهصورت اپ نصب کن: <a asp-page="/Download">صفحهی دریافت اپلیکیشن</a> (اندروید، iOS، ویندوز، وب).</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-pad legal" style="margin-bottom:14px;">
|
||||||
|
<h2>🛡️ گزارش و شکایت</h2>
|
||||||
|
<p>
|
||||||
|
اگر آگهی نادرست یا مرکز متخلفی دیدی، از دکمهی «گزارش تخلف» یا «شکایت از این مرکز» در صفحهی همان آگهی استفاده کن.
|
||||||
|
تیم همکادر بررسی میکند. قوانین کامل را در <a asp-page="/Rules">قوانین و مقررات</a> ببین.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-pad legal">
|
||||||
|
<h2>❓ سؤالات متداول</h2>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>ورود به همکادر هزینه دارد؟</summary>
|
||||||
|
<p>خیر؛ استفاده برای کادر درمان و انتشار آگهی برای مراکز در حال حاضر رایگان است.</p>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>کد ورود برایم نیامد، چه کنم؟</summary>
|
||||||
|
<p>چند ثانیه صبر کن و «ارسال مجدد کد» را بزن. از درستبودن شماره موبایل مطمئن شو. اگر مشکل ادامه داشت، بعداً دوباره تلاش کن.</p>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>«اعلام تمایل» یعنی چه؟</summary>
|
||||||
|
<p>یعنی به آن فرصت علاقهمندی؛ با زدن آن، اطلاعات تماس مرکز برایت نمایش داده میشود و فرصت در سوابق تو ثبت میشود تا پیشنهادها دقیقتر شوند.</p>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>نشان «✓ تأیید شده» چه معنایی دارد؟</summary>
|
||||||
|
<p>یعنی مدارک آن مرکز توسط همکادر بررسی شده است. این نشان بررسی اولیه است و جای راستیآزمایی مستقیم پیش از توافق را نمیگیرد.</p>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>چطور مرکزم را تأیید کنم؟</summary>
|
||||||
|
<p>وارد «پنل کارفرما» شو، روی مرکز موردنظر «درخواست تأیید و بارگذاری مدارک» را بزن و مجوز/پروانه را آپلود کن تا بررسی شود.</p>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>چطور این تور را دوباره ببینم؟</summary>
|
||||||
|
<p>دکمهی «▶ شروع تور راهنما» در بالای همین صفحه را بزن.</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<p class="muted" style="font-size:13px; margin-top:14px; margin-bottom:0;">
|
||||||
|
پاسخت را نیافتی؟ با ما در تماس باش: <span dir="ltr">support@@hamkadr.ir</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<body data-unread="@unreadCount" data-authed="@(User.Identity?.IsAuthenticated == true ? "1" : "0")">
|
<body data-unread="@unreadCount" data-authed="@(User.Identity?.IsAuthenticated == true ? "1" : "0")">
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
<div class="container header-inner">
|
<div class="container header-inner">
|
||||||
<a class="brand" asp-page="/Index">
|
<a class="brand" asp-page="/Index" data-tour="home">
|
||||||
<span class="brand-mark">ه</span>
|
<span class="brand-mark">ه</span>
|
||||||
<span class="brand-text">همکادر</span>
|
<span class="brand-text">همکادر</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -44,19 +44,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<input type="checkbox" id="nav-toggle" class="nav-toggle" hidden />
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" hidden />
|
||||||
<label for="nav-toggle" class="nav-burger" aria-label="باز/بستن منو">
|
<label for="nav-toggle" class="nav-burger" aria-label="باز/بستن منو" data-tour="menu">
|
||||||
<span></span><span></span><span></span>
|
<span></span><span></span><span></span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="nav-collapse">
|
<div class="nav-collapse">
|
||||||
<nav class="main-nav">
|
<nav class="main-nav">
|
||||||
<a asp-page="/Index">خانه</a>
|
<a asp-page="/Index">خانه</a>
|
||||||
<a asp-page="/Shifts/Index">شیفتها</a>
|
<a asp-page="/Shifts/Index" data-tour="shifts">شیفتها</a>
|
||||||
<a asp-page="/Jobs/Index">استخدام</a>
|
<a asp-page="/Jobs/Index" data-tour="jobs">استخدام</a>
|
||||||
<a asp-page="/Calendar/Index">تقویم هفتگی</a>
|
<a asp-page="/Calendar/Index">تقویم هفتگی</a>
|
||||||
<a asp-page="/Download">دریافت اپ</a>
|
<a asp-page="/Download">دریافت اپ</a>
|
||||||
<a asp-page="/Facilities/Index">مراکز درمانی</a>
|
<a asp-page="/Facilities/Index">مراکز درمانی</a>
|
||||||
<a asp-page="/Preferences/Index">علاقهمندیها</a>
|
<a asp-page="/Preferences/Index" data-tour="prefs">علاقهمندیها</a>
|
||||||
|
<a asp-page="/Help" data-tour="help">راهنما</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
@if (User.Identity?.IsAuthenticated == true)
|
@if (User.Identity?.IsAuthenticated == true)
|
||||||
@@ -70,15 +71,15 @@
|
|||||||
{
|
{
|
||||||
<a class="nav-action" asp-page="/Employer/Index">پنل کارفرما</a>
|
<a class="nav-action" asp-page="/Employer/Index">پنل کارفرما</a>
|
||||||
}
|
}
|
||||||
<a class="nav-action bell-inline js-bell" asp-page="/Me/Notifications" title="اعلانها"><span class="bell-ico">🔔</span><span class="bell-label">اعلانها</span>@if (unreadCount > 0) {<span class="bell-badge">@JalaliDate.ToPersianDigits(unreadCount > 99 ? "99+" : unreadCount.ToString())</span>}</a>
|
<a class="nav-action bell-inline js-bell" asp-page="/Me/Notifications" title="اعلانها" data-tour="bell"><span class="bell-ico">🔔</span><span class="bell-label">اعلانها</span>@if (unreadCount > 0) {<span class="bell-badge">@JalaliDate.ToPersianDigits(unreadCount > 99 ? "99+" : unreadCount.ToString())</span>}</a>
|
||||||
<a class="nav-action" asp-page="/Me/Index">پنل کارجو</a>
|
<a class="nav-action" asp-page="/Me/Index" data-tour="panel">پنل کارجو</a>
|
||||||
<form method="post" asp-page="/Account/Logout" style="display:contents;">
|
<form method="post" asp-page="/Account/Logout" style="display:contents;">
|
||||||
<button type="submit" class="btn btn-outline btn-sm">خروج</button>
|
<button type="submit" class="btn btn-outline btn-sm">خروج</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<a class="btn btn-outline btn-sm" asp-page="/Account/Login">ورود</a>
|
<a class="btn btn-outline btn-sm" asp-page="/Account/Login" data-tour="login">ورود</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,6 +100,7 @@
|
|||||||
<div class="muted">
|
<div class="muted">
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a asp-page="/Download" style="font-weight:700;">📲 دریافت اپلیکیشن</a>
|
<a asp-page="/Download" style="font-weight:700;">📲 دریافت اپلیکیشن</a>
|
||||||
|
<a asp-page="/Help">راهنما</a>
|
||||||
<a asp-page="/Privacy">حریم خصوصی</a>
|
<a asp-page="/Privacy">حریم خصوصی</a>
|
||||||
<a asp-page="/Rules">قوانین و مقررات</a>
|
<a asp-page="/Rules">قوانین و مقررات</a>
|
||||||
<a asp-page="/Terms">شرایط استفاده</a>
|
<a asp-page="/Terms">شرایط استفاده</a>
|
||||||
@@ -117,6 +119,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@* Self-hosted guided app tour (no CDN). Auto-runs once for new visitors; re-runnable from /Help. *@
|
||||||
|
<script src="~/js/tour.js" asp-append-version="true" defer></script>
|
||||||
|
|
||||||
@* Live in-app notifications over SSE (our own origin — works in Iran, no Google push).
|
@* Live in-app notifications over SSE (our own origin — works in Iran, no Google push).
|
||||||
Updates the bell badge, shows a toast, and fires a local OS notification when allowed. *@
|
Updates the bell badge, shows a toast, and fires a local OS notification when allowed. *@
|
||||||
@if (User.Identity?.IsAuthenticated == true)
|
@if (User.Identity?.IsAuthenticated == true)
|
||||||
|
|||||||
@@ -235,6 +235,24 @@ label { font-size: 13px; }
|
|||||||
.footer-links a { color: var(--muted); }
|
.footer-links a { color: var(--muted); }
|
||||||
.footer-links a:hover { color: var(--primary); }
|
.footer-links a:hover { color: var(--primary); }
|
||||||
|
|
||||||
|
/* ---------- Guided tour ---------- */
|
||||||
|
.tour-overlay { position: fixed; inset: 0; z-index: 1000; }
|
||||||
|
.tour-hole {
|
||||||
|
position: fixed; border-radius: 12px; pointer-events: none;
|
||||||
|
box-shadow: 0 0 0 9999px rgba(13,30,40,.62);
|
||||||
|
outline: 2px solid var(--accent); transition: all .2s ease;
|
||||||
|
}
|
||||||
|
.tour-bubble {
|
||||||
|
position: fixed; z-index: 1001; background: var(--surface); color: var(--text);
|
||||||
|
border-radius: 14px; box-shadow: 0 14px 40px rgba(0,0,0,.28);
|
||||||
|
padding: 16px; max-width: 320px;
|
||||||
|
}
|
||||||
|
.tour-title { font-weight: 800; font-size: 16px; margin-bottom: 6px; }
|
||||||
|
.tour-text { font-size: 14px; color: var(--muted); line-height: 1.9; }
|
||||||
|
.tour-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 14px; }
|
||||||
|
.tour-count { font-size: 12px; color: var(--muted); }
|
||||||
|
.tour-btns { display: flex; gap: 6px; }
|
||||||
|
|
||||||
/* Legal/policy pages (privacy, rules, terms) — comfortable long-form reading. */
|
/* Legal/policy pages (privacy, rules, terms) — comfortable long-form reading. */
|
||||||
.legal { line-height: 2; }
|
.legal { line-height: 2; }
|
||||||
.legal h2 { font-size: 17px; margin: 22px 0 8px; color: var(--primary-dark); }
|
.legal h2 { font-size: 17px; margin: 22px 0 8px; color: var(--primary-dark); }
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/* همکادر — lightweight guided tour (no external libs, RTL/Persian).
|
||||||
|
Spotlights elements marked with [data-tour]. Auto-runs once for new visitors on the
|
||||||
|
home page; re-runnable any time via window.hamkadrTour.start() or ?tour=1.
|
||||||
|
Steps whose target is missing/hidden are skipped, so it works across pages & mobile. */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
var DONE_KEY = 'hamkadr_tour_done_v1';
|
||||||
|
|
||||||
|
// Ordered steps. `sel` is a [data-tour] value; first matching visible element is used.
|
||||||
|
var STEPS = [
|
||||||
|
{ sel: 'home', title: 'به همکادر خوش آمدید 👋', text: 'سامانهی یافتن شیفت و استخدام برای کادر درمان. این تور کوتاه بخشهای اصلی را نشان میدهد.' },
|
||||||
|
{ sel: 'menu', title: 'منو', text: 'از این دکمه به همهی بخشها دسترسی داری.' },
|
||||||
|
{ sel: 'shifts', title: 'شیفتها', text: 'فرصتهای شیفت کاری را اینجا ببین و با فیلتر شهر/محله/نقش پیدا کن.' },
|
||||||
|
{ sel: 'jobs', title: 'استخدام', text: 'موقعیتهای استخدامی تماموقت، پارهوقت و طرح را اینجا مرور کن.' },
|
||||||
|
{ sel: 'prefs', title: 'علاقهمندیها', text: 'نقش، شهر و نوع شیفتِ موردنظرت را تعیین کن تا پیشنهادهای متناسب بگیری.' },
|
||||||
|
{ sel: 'bell', title: 'اعلانها', text: 'وقتی فرصت تازهای مطابق علاقهات منتشر شود، همینجا باخبر میشوی.' },
|
||||||
|
{ sel: 'login', title: 'ورود', text: 'با شماره موبایل وارد شو تا فرصتها را ذخیره کنی و «اعلام تمایل» بدهی.' },
|
||||||
|
{ sel: 'panel', title: 'پنل تو', text: 'فرصتهای ذخیرهشده و درخواستهایت را در پنل کاربری دنبال کن.' },
|
||||||
|
{ sel: 'help', title: 'راهنما', text: 'هر زمان خواستی، راهنمای کامل و همین تور را از این بخش باز کن. موفق باشی! 🌟' }
|
||||||
|
];
|
||||||
|
|
||||||
|
var overlay, hole, bubble, idx, steps;
|
||||||
|
|
||||||
|
function visible(el) {
|
||||||
|
if (!el) return false;
|
||||||
|
var r = el.getBoundingClientRect();
|
||||||
|
return el.offsetParent !== null && r.width > 0 && r.height > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
overlay = document.createElement('div'); overlay.className = 'tour-overlay';
|
||||||
|
hole = document.createElement('div'); hole.className = 'tour-hole';
|
||||||
|
bubble = document.createElement('div'); bubble.className = 'tour-bubble';
|
||||||
|
bubble.innerHTML =
|
||||||
|
'<div class="tour-title"></div>' +
|
||||||
|
'<div class="tour-text"></div>' +
|
||||||
|
'<div class="tour-foot">' +
|
||||||
|
'<span class="tour-count"></span>' +
|
||||||
|
'<span class="tour-btns">' +
|
||||||
|
'<button type="button" class="btn btn-outline btn-sm tour-skip">رد کردن</button>' +
|
||||||
|
'<button type="button" class="btn btn-outline btn-sm tour-prev">قبلی</button>' +
|
||||||
|
'<button type="button" class="btn btn-accent btn-sm tour-next">بعدی</button>' +
|
||||||
|
'</span>' +
|
||||||
|
'</div>';
|
||||||
|
overlay.appendChild(hole);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
document.body.appendChild(bubble);
|
||||||
|
|
||||||
|
overlay.addEventListener('click', finish);
|
||||||
|
bubble.querySelector('.tour-skip').addEventListener('click', finish);
|
||||||
|
bubble.querySelector('.tour-prev').addEventListener('click', function () { go(idx - 1); });
|
||||||
|
bubble.querySelector('.tour-next').addEventListener('click', function () { go(idx + 1); });
|
||||||
|
window.addEventListener('resize', reposition);
|
||||||
|
window.addEventListener('scroll', reposition, true);
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKey(e) {
|
||||||
|
if (!overlay) return;
|
||||||
|
if (e.key === 'Escape') finish();
|
||||||
|
else if (e.key === 'ArrowLeft') go(idx + 1); // RTL: left = next
|
||||||
|
else if (e.key === 'ArrowRight') go(idx - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function target() { return document.querySelector('[data-tour="' + steps[idx].sel + '"]'); }
|
||||||
|
|
||||||
|
function reposition() {
|
||||||
|
if (!overlay || idx == null) return;
|
||||||
|
var el = target();
|
||||||
|
if (!el) return;
|
||||||
|
var r = el.getBoundingClientRect(), pad = 6;
|
||||||
|
hole.style.top = (r.top - pad) + 'px';
|
||||||
|
hole.style.left = (r.left - pad) + 'px';
|
||||||
|
hole.style.width = (r.width + pad * 2) + 'px';
|
||||||
|
hole.style.height = (r.height + pad * 2) + 'px';
|
||||||
|
|
||||||
|
// place the bubble below the target if room, else above
|
||||||
|
var below = r.bottom + 12, bw = Math.min(320, window.innerWidth - 24);
|
||||||
|
bubble.style.width = bw + 'px';
|
||||||
|
var left = Math.min(Math.max(12, r.left + r.width / 2 - bw / 2), window.innerWidth - bw - 12);
|
||||||
|
bubble.style.left = left + 'px';
|
||||||
|
var bh = bubble.offsetHeight || 150;
|
||||||
|
if (below + bh > window.innerHeight - 8 && r.top - 12 - bh > 8) bubble.style.top = (r.top - 12 - bh) + 'px';
|
||||||
|
else bubble.style.top = below + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
var s = steps[idx];
|
||||||
|
bubble.querySelector('.tour-title').textContent = s.title;
|
||||||
|
bubble.querySelector('.tour-text').textContent = s.text;
|
||||||
|
bubble.querySelector('.tour-count').textContent = toFa((idx + 1) + ' / ' + steps.length);
|
||||||
|
bubble.querySelector('.tour-prev').style.visibility = idx === 0 ? 'hidden' : 'visible';
|
||||||
|
bubble.querySelector('.tour-next').textContent = idx === steps.length - 1 ? 'پایان' : 'بعدی';
|
||||||
|
}
|
||||||
|
|
||||||
|
function go(n) {
|
||||||
|
if (n < 0) return;
|
||||||
|
if (n >= steps.length) return finish();
|
||||||
|
idx = n;
|
||||||
|
var el = target();
|
||||||
|
if (el && el.scrollIntoView) el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
||||||
|
render();
|
||||||
|
setTimeout(reposition, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
try { localStorage.setItem(DONE_KEY, '1'); } catch (e) {}
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
window.removeEventListener('resize', reposition);
|
||||||
|
window.removeEventListener('scroll', reposition, true);
|
||||||
|
if (overlay) overlay.remove();
|
||||||
|
if (bubble) bubble.remove();
|
||||||
|
overlay = bubble = null; idx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
// recompute visible steps each run (depends on auth state / page / viewport)
|
||||||
|
steps = STEPS.filter(function (s) { return visible(document.querySelector('[data-tour="' + s.sel + '"]')); });
|
||||||
|
if (steps.length === 0) return;
|
||||||
|
if (overlay) finish();
|
||||||
|
build();
|
||||||
|
go(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFa(s) {
|
||||||
|
var d = ['۰','۱','۲','۳','۴','۵','۶','۷','۸','۹'];
|
||||||
|
return String(s).replace(/[0-9]/g, function (c) { return d[+c]; });
|
||||||
|
}
|
||||||
|
|
||||||
|
window.hamkadrTour = { start: start, reset: function () { try { localStorage.removeItem(DONE_KEY); } catch (e) {} } };
|
||||||
|
|
||||||
|
function maybeAutoStart() {
|
||||||
|
var params = new URLSearchParams(location.search);
|
||||||
|
if (params.get('tour') === '1') { start(); return; }
|
||||||
|
var onHome = location.pathname === '/' || location.pathname === '';
|
||||||
|
var done = false; try { done = localStorage.getItem(DONE_KEY) === '1'; } catch (e) {}
|
||||||
|
if (onHome && !done) setTimeout(start, 900);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', maybeAutoStart);
|
||||||
|
else maybeAutoStart();
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user