در درس قبل سیستم چهارلایهٔ پرامپت را ساختیم. حالا وقت اجراست. در این درس یک صفحهٔ کامل میسازیم — از جملهٔ اول تا کد قابلاجرا — و همهٔ کدها را هم میگذاریم تا مستقیم برداری و استفاده کنی.
پروژهٔ نمونه: لندینگ یک شرکت حملونقل با حالوهوای تیره و صنعتی. اگر کسبوکار تو چیز دیگری است، فقط متنها را عوض کن؛ ساختار عیناً قابلاستفاده است.

لیست مطالب
گام صفر: تصمیمهای پایه
قبل از هر پرامپتی، این پنج مورد را روی کاغذ مینویسیم:
| تصمیم | انتخاب ما |
|---|---|
| پیام اصلی | بار شما را با ردیابی لحظهای و بدون تأخیر میرسانیم |
| هدف صفحه | پرکردن فرم درخواست قیمت |
| حالوهوا | تیره، صنعتی، کنتراست بالا، تأکید نارنجی |
| پشتهٔ فنی | HTML و CSS و JS خالص + GSAP و ScrollTrigger + Lenis |
| تعداد پرده | شش پرده |
گام ۱: استوریبورد
خروجی لایهٔ اول، این جدول شد:
۱ هیرو | انرژی ۵ | تصویر جادهٔ شب + «بار شما، سرِ وقت.»
۲ آمار | انرژی ۳ | سه عدد: ۱۲ سال، ۴۸۰ ناوگان، ۹۹٪ تحویل بهموقع
۳ مسئله | انرژی ۱ | متن ساده: «بار گمشده یعنی مشتری ازدسترفته»
۴ فرایند | انرژی ۵ | پینشده: سه مرحلهٔ ثبت، حمل، تحویل
۵ گواه | انرژی ۲ | سه نظر مشتری، نوار افقی
۶ اقدام | انرژی ۲ | فرم کوتاه، بدون هیچ انیمیشنی
دقت کن که قوس انرژی یکنواخت نیست: بالا، متوسط، پایین، بالا، پایین. همان چیزی که در درس استوریبورد گفتیم.
گام ۲: اسکلت
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ترابری آریا — بار شما، سرِ وقت</title>
<style>
:root{
--ink:#0B0D0F; --ink-2:#14181C; --cream:#F2F4F6;
--muted:#9AA5B1; --accent:#FF7A18;
--s3:2rem; --s4:4rem; --s5:8rem;
--wrap:min(1200px, 90vw);
}
*{box-sizing:border-box; margin:0; padding:0;}
body{background:var(--ink); color:var(--cream);
font-family:Vazirmatn, system-ui, sans-serif; line-height:1.9;}
.wrap{width:var(--wrap); margin-inline:auto;}
.scene{position:relative; padding-block:var(--s5);}
/* ۱ هیرو */
.scene--hero{min-height:100svh; display:grid; place-items:center;
overflow:hidden; padding:0;}
.hero__media{position:absolute; inset:0;}
.hero__media img{width:100%; height:100%; object-fit:cover;}
.hero__veil{position:absolute; inset:0;
background:linear-gradient(180deg,rgba(0,0,0,.35),rgba(0,0,0,.8));}
.hero__content{position:relative; z-index:2; text-align:center; padding:0 6vw;}
.hero__title{font-size:clamp(2.2rem,7vw,5.5rem); font-weight:900; line-height:1.2;}
.hero__sub{color:var(--muted); margin-top:var(--s3); font-size:clamp(1rem,2vw,1.3rem);}
/* ۲ آمار */
.stats{display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
gap:var(--s4); text-align:center;}
.stat__num{font-size:clamp(2.5rem,6vw,4.5rem); font-weight:900; color:var(--accent);}
.stat__label{color:var(--muted);}
/* ۳ مسئله */
.scene--problem{text-align:center;}
.manifesto{font-size:clamp(1.5rem,4vw,3rem); font-weight:800; line-height:1.7;}
/* ۴ فرایند — پین */
.scene--pin{height:300vh; padding:0;}
.pin__stage{height:100svh; display:grid; place-items:center; position:relative;}
.pin__panels{position:relative; width:var(--wrap); height:60svh;}
.panel{position:absolute; inset:0; display:grid; place-content:center;
text-align:center; background:var(--ink-2); border-radius:20px;
border:1px solid #222A31; padding:var(--s4);}
.panel h3{font-size:clamp(1.6rem,4vw,2.6rem); color:var(--accent);}
/* ۵ گواه */
.scene--proof{overflow:hidden; padding-block:0;}
.proof__track{display:flex; gap:var(--s4); width:max-content; padding:var(--s5) 0;}
.quote{width:min(520px,80vw); background:var(--ink-2); padding:var(--s4);
border-radius:20px; border:1px solid #222A31;}
/* ۶ اقدام */
.scene--cta{text-align:center;}
.btn{display:inline-block; background:var(--accent); color:#111;
padding:1rem 2.5rem; border-radius:999px; font-weight:800;
text-decoration:none; margin-top:var(--s3);}
@media (prefers-reduced-motion: reduce){
*,*::before,*::after{animation-duration:.01ms!important;
transition-duration:.01ms!important;}
}
</style>
</head>
<body>
<div id="smooth-wrapper"><div id="smooth-content">
<section class="scene scene--hero">
<div class="hero__media"><img src="road.jpg" width="1920" height="1080"
alt="جادهٔ شبانه و کامیون در حال حرکت"></div>
<div class="hero__veil"></div>
<div class="hero__content">
<h1 class="hero__title" data-split
aria-label="بار شما، سرِ وقت.">بار شما، سرِ وقت.</h1>
<p class="hero__sub">ردیابی لحظهای، از لحظهٔ بارگیری تا تحویل.</p>
</div>
</section>
<section class="scene scene--stats"><div class="wrap stats">
<div><div class="stat__num" data-count="12">0</div>
<div class="stat__label">سال تجربه</div></div>
<div><div class="stat__num" data-count="480">0</div>
<div class="stat__label">دستگاه ناوگان</div></div>
<div><div class="stat__num" data-count="99">0</div>
<div class="stat__label">درصد تحویل بهموقع</div></div>
</div></section>
<section class="scene scene--problem"><div class="wrap">
<p class="manifesto" data-glow
aria-label="یک بار گمشده، فقط یک محموله نیست. یک مشتری ازدسترفته است.">
یک بار گمشده، فقط یک محموله نیست. یک مشتری ازدسترفته است.</p>
</div></section>
<section class="scene scene--pin">
<div class="pin__stage">
<div class="pin__panels">
<article class="panel" data-step="1">
<h3>۰۱ — ثبت سفارش</h3><p>در کمتر از دو دقیقه، آنلاین.</p></article>
<article class="panel" data-step="2">
<h3>۰۲ — حمل و ردیابی</h3><p>موقعیت لحظهای روی نقشه.</p></article>
<article class="panel" data-step="3">
<h3>۰۳ — تحویل و تأیید</h3><p>امضای دیجیتال گیرنده.</p></article>
</div>
</div>
</section>
<section class="scene scene--proof">
<div class="proof__track">
<blockquote class="quote">«سه سال است تمام بارمان با آریاست.»</blockquote>
<blockquote class="quote">«ردیابی لحظهای، خیال ما را راحت کرد.»</blockquote>
<blockquote class="quote">«یکبار هم تأخیر نداشتیم.»</blockquote>
</div>
</section>
<section class="scene scene--cta"><div class="wrap">
<h2 data-reveal>بار بعدیتان را با ما بفرستید</h2>
<a class="btn" href="#form">درخواست قیمت</a>
</div></section>
</div></div>
</body></html>
توجه کن که در این مرحله هیچ چیزی پنهان نیست. اگر همین فایل را باز کنی، یک صفحهٔ ساده اما کاملاً خوانا میبینی. این دقیقاً همان چیزی است که در درس آناتومی رویش تأکید کردیم.

گام ۳: لایهٔ حرکت
<script src="https://unpkg.com/[email protected]/dist/lenis.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollTrigger.min.js"></script>
<script>
const DEBUG = false;
gsap.registerPlugin(ScrollTrigger);
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const isTouch = matchMedia('(hover:none) and (pointer:coarse)').matches;
/* ---------- ۱) اسکرول نرم ---------- */
let lenis = null;
if (!reduce && !isTouch){
lenis = new Lenis({ duration: 1.1, smoothWheel: true });
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add(t => lenis.raf(t * 1000));
gsap.ticker.lagSmoothing(0);
}
/* ---------- ۲) شکستن متن به کلمه (مخصوص فارسی) ---------- */
function splitWords(el){
const words = el.textContent.trim().split(/\s+/);
el.innerHTML = words
.map(w => '<span class="w"><span class="w__in">' + w + '</span></span>')
.join(' ');
return el.querySelectorAll('.w__in');
}
const css = document.createElement('style');
css.textContent = '.w{display:inline-block;overflow:hidden;vertical-align:top}' +
'.w__in{display:inline-block}';
document.head.appendChild(css);
/* ---------- ۳) انیمیشنها ---------- */
const mm = gsap.matchMedia();
mm.add('(min-width: 900px)', () => {
// هیرو: زوم تصویر + تیرهشدن پرده
gsap.to('.hero__media img', { scale:1.2, ease:'none',
scrollTrigger:{ trigger:'.scene--hero', start:'top top',
end:'bottom top', scrub:true, markers:DEBUG }});
gsap.to('.hero__veil', { opacity:.95, ease:'none',
scrollTrigger:{ trigger:'.scene--hero', start:'top top',
end:'bottom top', scrub:true }});
// فرایند: پین + اسکراب سه پنل
const tl = gsap.timeline({ scrollTrigger:{
trigger:'.scene--pin', start:'top top', end:'+=300%',
pin:'.pin__stage', scrub:1, markers:DEBUG }});
tl.from('.panel[data-step="1"]', { yPercent:60, opacity:0 })
.to ('.panel[data-step="1"]', { scale:.85, opacity:.35 })
.from('.panel[data-step="2"]', { yPercent:60, opacity:0 }, '<')
.to ('.panel[data-step="2"]', { scale:.85, opacity:.35 })
.from('.panel[data-step="3"]', { yPercent:60, opacity:0 }, '<');
// گواه: نوار افقی
const track = document.querySelector('.proof__track');
gsap.to(track, { x: () => -(track.scrollWidth - innerWidth), ease:'none',
scrollTrigger:{ trigger:'.scene--proof', start:'top top',
end: () => '+=' + (track.scrollWidth - innerWidth),
pin:true, scrub:1, invalidateOnRefresh:true }});
return () => { tl.kill(); };
});
// عنوان هیرو: کلمهبهکلمه (روی همهٔ اندازهها)
mm.add('(min-width: 1px)', () => {
document.querySelectorAll('[data-split]').forEach(el => {
const inner = splitWords(el);
gsap.from(inner, { yPercent:110, duration:.8, stagger:.05,
ease:'power3.out', delay:.15 });
});
// مسئله: روشنشدن تدریجی کلمات
document.querySelectorAll('[data-glow]').forEach(el => {
const inner = splitWords(el);
gsap.fromTo(inner, { opacity:.18 }, { opacity:1, stagger:.5, ease:'none',
scrollTrigger:{ trigger:el, start:'top 78%', end:'bottom 55%', scrub:.5 }});
});
// آمار: شمارش
gsap.utils.toArray('[data-count]').forEach(el => {
gsap.fromTo(el, { innerText:0 }, { innerText:+el.dataset.count,
duration:1.6, snap:{ innerText:1 },
scrollTrigger:{ trigger:el, start:'top 85%', once:true }});
});
// ظهور ساده
gsap.utils.toArray('[data-reveal]').forEach(el => {
gsap.from(el, { y:40, opacity:0, duration:.8, ease:'power2.out',
scrollTrigger:{ trigger:el, start:'top 85%' }});
});
});
// کاهش حرکت: همهچیز خاموش
mm.add('(prefers-reduced-motion: reduce)', () => {
ScrollTrigger.getAll().forEach(t => t.kill());
gsap.set('[data-reveal], .panel, .w__in', { clearProps:'all' });
});
addEventListener('load', () => ScrollTrigger.refresh());
</script>
گام ۴: نکات ظریفی که در کد بالا هست
- ثابت
DEBUG: یکجا تعریف شده و به همهٔ تریگرها وصل است. برای عیبیابی فقطtrueکن. - تفکیک
reduceوisTouch: اسکرول نرم روی موبایل و در حالت کاهش حرکت اصلاً ساخته نمیشود. - دو بلوک
matchMediaجدا: انیمیشنهای سنگین فقط بالای ۹۰۰ پیکسل، انیمیشنهای سبک روی همه. - تابع بازگشتی
return () => tl.kill(): هنگام تغییر اندازه، تایملاین قدیمی پاک میشود. - تزریق CSS از داخل جاوااسکریپت: کلاسهای
.wفقط وقتی لازماند که شکستن متن انجام شده باشد.

گام ۵: چکلیست تست قبل از انتشار
| تست | چطور | انتظار |
|---|---|---|
| بدون جاوااسکریپت | در ابزار توسعهدهنده JS را غیرفعال کن | همهٔ متنها خوانا |
| کاهش حرکت | تنظیمات سیستمعامل را روشن کن | هیچ انیمیشنی، محتوای کامل |
| موبایل واقعی | روی گوشی، نه فقط شبیهساز | بدون پرش و گیر |
| چرخاندن گوشی | افقی و عمودی | چیدمان سالم بماند |
| پیمایش با کیبورد | فقط با Tab | همهٔ لینکها قابلدسترس |
| اینترنت کند | حالت 3G در ابزار توسعهدهنده | محتوا قبل از تصاویر بیاید |
پنج خطای رایج در این پروژه و راهحل
- پین در جای اشتباه: تصویر هیرو هنوز لود نشده.
ScrollTrigger.refresh()بعد ازloadرا فراموش نکن. - نوار افقی تا آخر نمیرود:
invalidateOnRefreshخاموش است یا عرض بهصورت ثابت محاسبه شده. حتماً از تابع استفاده کن، نه عدد ثابت. - عنوان فارسی بههم میریزد: متن به حرف شکسته شده. فقط به کلمه بشکن.
- پرش عمودی هنگام اسکرول موبایل: از
vhاستفاده کردهای. بهsvhتغییر بده. - بعد از انیمیشن، متن جابهجا میماند: در بلوک کاهش حرکت،
clearPropsرا اضافه کن.
گام ۶: پرامپت صیقل نهایی
نقش: مهندس کارایی و دسترسپذیری وب.
زمینه: [کل فایل]
کار: فقط فهرست مشکلات را بده، فایل را بازنویسی نکن.
بررسی کن:
- آیا تصویر هیرو fetchpriority="high" دارد و بقیه loading="lazy"؟
- آیا انیمیشنی هست که width یا top را تغییر میدهد
بهجای transform و opacity؟
- آیا will-change روی عنصر غیرمتحرک گذاشته شده؟
- آیا کنتراست متن سفید روی تصویر با پردهٔ فعلی کافی است؟
- آیا ترتیب h1 تا h3 منطقی است؟
- آیا فرم انتهای صفحه با کیبورد کاملاً قابلاستفاده است؟
خروجی: جدول «مشکل | شدت | اصلاح» و بعد فقط قطعهکدهای اصلاحی.
پرسشهای پرتکرار
میتوانم این کد را در وردپرس استفاده کنم؟
بله. HTML و CSS را در یک قالب صفحهٔ سفارشی یا بلوک کد بگذار و اسکریپتها را در فوتر لود کن. فقط مواظب تداخل با انیمیشنهای خود قالب باش.
چقدر طول میکشد این صفحه ساخته شود؟
با این روش، برای کسی که مفاهیم را بلد است، بین سه تا شش ساعت — شامل تست موبایل. بدون روش لایهای، همین کار معمولاً چند روز طول میکشد و نتیجهاش هم شکنندهتر است.
اگر مدل کد اشتباه داد چه کنم؟
از قالب عیبیابی درس قبل استفاده کن: بگو چه انتظاری داشتی، چه اتفاقی افتاد، چه چیزی را امتحان کردی، و بخواه اول سه علت محتمل را فهرست کند نه اینکه مستقیم کد بنویسد.
تصاویر را از کجا بیاورم؟
از بانکهای تصویر رایگان. فقط حتماً قبل از استفاده فشردهشان کن و در قالب مدرن ذخیره کن — که موضوع درس بعدی است.
جمعبندی
یک صفحهٔ سینمایی کامل، شش پرده، حدود دویست خط CSS و صد خط جاوااسکریپت. رمز کار، ترتیب بود: اول تصمیمها، بعد استوریبورد، بعد اسکلتِ کاملاً خوانا، بعد لایهٔ حرکت، و در آخر صیقل. هر بار که این ترتیب را بههم بزنی، به همان آشفتگی همیشگی برمیگردی.
در دو درس آخر این نقشهٔ راه سراغ چیزی میرویم که پروژه را از «قشنگ» به «حرفهای» میبرد: کارایی و موبایل و بعد دسترسپذیری. مسیر کامل را در نقشهٔ راه طراحی سایت سینمایی ببین و در @MrChatGPT_IR همراه ما باش.
