CSS چیست؟ آموزش کامل CSS از صفر تا Flexbox و Grid با پروژه هوش مصنوعی
در این آموزش CSS را از صفر تا سطح کاربردی یاد میگیرید؛ از Selector، Cascade و Box Model تا Flexbox، Grid، Responsive Design و CSS Variables. در پایان رابط حرفهای یک دستیار هوش مصنوعی درواره را طراحی میکنیم.
CSS یکی از سه فناوری اصلی توسعه وب است. HTML ساختار صفحه را مشخص میکند، CSS ظاهر و چیدمان آن را میسازد و JavaScript رفتار تعاملی صفحه را کنترل میکند.
تقریباً هر چیزی که در ظاهر یک وبسایت میبینید، از رنگ و اندازه متن گرفته تا فاصلهها، ستونبندی، منو، کارت، دکمه، انیمیشن و نمایش صحیح در موبایل، با کمک CSS پیادهسازی میشود.
در این آموزش فقط چند ویژگی پراکنده CSS را معرفی نمیکنیم. هدف این است که منطق واقعی CSS را یاد بگیرید، بتوانید مشکلات ظاهری صفحات را تحلیل کنید و در پایان یک رابط فارسی، واکنشگرا و کاربردی برای دستیار هوش مصنوعی بسازید.
پروژه نهایی بهگونهای طراحی میشود که کلید API در مرورگر قرار نگیرد و درخواستها از طریق یک Backend کوچک به API هوش مصنوعی درواره ارسال شوند.
CSS چیست؟
CSS مخفف عبارت Cascading Style Sheets و به معنای «برگههای استایل آبشاری» است.
CSS یک زبان برنامهنویسی عمومی مانند Python یا JavaScript نیست؛ بلکه زبان Style Sheet است. با استفاده از آن مشخص میکنیم عناصر HTML چگونه نمایش داده شوند.
برای مثال، HTML زیر یک دکمه ایجاد میکند:
<button>ارسال پیام</button>
بدون CSS، مرورگر دکمه را با ظاهر پیشفرض نمایش میدهد. با CSS میتوان رنگ، اندازه، فونت، فاصله داخلی و گوشههای آن را تغییر داد:
button {
padding: 12px 20px;
color: #ffffff;
background-color: #6d4aff;
border: 0;
border-radius: 12px;
cursor: pointer;
}
براساس مستندات MDN درباره CSS، این زبان برای توصیف نحوه نمایش اسناد HTML یا XML روی صفحه، کاغذ و رسانههای دیگر استفاده میشود.
تفاوت HTML، CSS و JavaScript
این سه فناوری وظایف متفاوتی دارند:
| فناوری | مسئولیت اصلی | مثال |
|---|---|---|
| HTML | ساختار و معنای محتوا | عنوان، پاراگراف، فرم و دکمه |
| CSS | ظاهر و چیدمان | رنگ، فونت، فاصله، Grid و Responsive Design |
| JavaScript | رفتار و تعامل | ارسال فرم، دریافت API و بهروزرسانی صفحه |
یک تشبیه ساده:
- HTML اسکلت ساختمان است.
- CSS طراحی داخلی، رنگ و چیدمان ساختمان است.
- JavaScript تجهیزات و رفتارهای تعاملی ساختمان است.
CSS نمیتواند بهتنهایی درخواست API ارسال کند یا پاسخ هوش مصنوعی را دریافت کند. برای این کار به JavaScript و معمولاً یک Backend نیاز داریم.
ساختار یک قانون CSS
یک قانون ساده CSS از Selector و یک یا چند Declaration تشکیل میشود:
.message {
color: #1f2937;
font-size: 1rem;
line-height: 1.8;
}
در این مثال:
.messageسلکتور یا Selector است.colorیک Property یا ویژگی است.#1f2937مقدار ویژگی است.font-size: 1remیک Declaration است.- مجموعه Declarationها داخل
{}قرار میگیرد.
کامنت در CSS به شکل زیر نوشته میشود:
/* رنگ اصلی رابط */
:root {
--color-primary: #6d4aff;
}
سه روش اضافهکردن CSS به HTML
استفاده از Inline CSS
در این روش استایل مستقیماً در ویژگی style عنصر نوشته میشود:
<button style="background: purple; color: white;">
ارسال
</button>
Inline CSS برای پروژههای واقعی انتخاب مناسبی نیست؛ زیرا کد HTML را شلوغ میکند و استفاده مجدد از استایلها دشوار میشود.
استفاده از تگ style
میتوان CSS را داخل بخش head فایل HTML قرار داد:
<head>
<style>
button {
background-color: purple;
color: white;
}
</style>
</head>
این روش برای نمونههای کوچک و صفحات آزمایشی قابلقبول است.
استفاده از فایل خارجی
روش استاندارد پروژههای واقعی، قراردادن CSS در یک فایل جداگانه است:
<link rel="stylesheet" href="./styles.css">
ساختار پروژه میتواند چنین باشد:
ai-assistant/
├── index.html
├── styles.css
├── app.js
├── app.py
├── requirements.txt
└── .env
فایل CSS جداگانه مزایای مهمی دارد:
- نگهداری کد آسانتر میشود.
- مرورگر میتواند فایل را Cache کند.
- چند صفحه میتوانند از استایل مشترک استفاده کنند.
- مسئولیت HTML، CSS و JavaScript از یکدیگر جدا میشود.
- همکاری اعضای تیم سادهتر خواهد بود.
Selector در CSS چیست؟
Selector مشخص میکند استایل باید روی کدام عناصر اعمال شود.
انتخاب براساس نام تگ
p {
color: #374151;
}
این قانون روی تمام تگهای p اعمال میشود.
انتخاب براساس Class
<p class="description">توضیحات سرویس</p>
.description {
color: #6b7280;
}
Class با نقطه شروع میشود و میتواند روی چند عنصر استفاده شود.
انتخاب براساس ID
<main id="chat">
...
</main>
#chat {
min-height: 500px;
}
ID باید در هر صفحه یکتا باشد. برای استایلدهی عمومی، معمولاً Class انعطاف بیشتری دارد.
Selector ویژگی
input[type="email"] {
direction: ltr;
}
انتخاب عنصر داخل عنصر دیگر
.sidebar a {
color: #475569;
}
این قانون تمام لینکهای داخل .sidebar را انتخاب میکند.
انتخاب فرزند مستقیم
.message-list > article {
margin-block-end: 16px;
}
علامت > فقط فرزندهای مستقیم را انتخاب میکند.
انتخاب چند Selector
h1,
h2,
h3 {
line-height: 1.4;
}
Pseudo-class
Pseudo-class وضعیت خاص یک عنصر را انتخاب میکند:
button:hover {
background-color: #5837e8;
}
button:disabled {
opacity: 0.55;
cursor: not-allowed;
}
input:focus {
border-color: #6d4aff;
}
Pseudo-element
Pseudo-element بخشی مجازی از عنصر ایجاد یا انتخاب میکند:
.title::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
margin-inline-end: 8px;
background-color: #6d4aff;
border-radius: 50%;
}
Cascade یا آبشار در CSS چیست؟
حرف C در CSS به Cascading اشاره میکند. ممکن است چند قانون مختلف بخواهند مقدار یک ویژگی را برای یک عنصر تعیین کنند. مرورگر باید تصمیم بگیرد کدام قانون برنده شود.
عوامل اصلی این تصمیم عبارتاند از:
- منبع استایل
- اهمیت قانون
- لایههای Cascade
- Specificity
- ترتیب قرارگیری قانون
- ارثبری
مثال:
p {
color: blue;
}
.description {
color: purple;
}
اگر یک پاراگراف دارای Class برابر با description باشد، رنگ بنفش اعمال میشود؛ زیرا Selector مربوط به Class اختصاصیتر است.
Specificity چیست؟
Specificity یا میزان اختصاصیبودن Selector مشخص میکند در رقابت میان قوانین، کدام Selector اولویت بیشتری دارد.
بهطور ساده:
Inline Style
ID
Class، Attribute و Pseudo-class
Element و Pseudo-element
مثال:
p {
color: blue;
}
.content p {
color: green;
}
#main p {
color: purple;
}
در این مثال، قانون #main p اختصاصیتر است.
استفاده بیش از حد از ID و !important نگهداری CSS را دشوار میکند. بهتر است ساختار Classها ساده و قابلپیشبینی باشد.
/* معمولاً از این الگو اجتناب کنید */
.message {
color: red !important;
}
قبل از استفاده از !important بررسی کنید آیا مشکل با اصلاح Selector، ترتیب فایل یا معماری CSS حل میشود.
Inheritance یا ارثبری
بعضی ویژگیها از والد به فرزند منتقل میشوند. برای مثال، color و font-family معمولاً ارثبری میشوند:
body {
color: #111827;
font-family: Tahoma, Arial, sans-serif;
}
متن بیشتر عناصر داخل body این تنظیمات را دریافت میکند.
ویژگیهایی مانند margin، padding و border معمولاً ارثبری نمیشوند.
Box Model در CSS
مرورگر هر عنصر را بهصورت یک جعبه در نظر میگیرد. این جعبه از چهار بخش تشکیل میشود:
- Content
- Padding
- Border
- Margin
مثال:
.card {
width: 300px;
padding: 24px;
border: 1px solid #e5e7eb;
margin: 16px;
}
در حالت پیشفرض، مقدار width فقط عرض محتوا را مشخص میکند. Padding و Border به آن اضافه میشوند. این رفتار ممکن است محاسبه اندازهها را دشوار کند.
به همین دلیل معمولاً در ابتدای CSS از Reset زیر استفاده میشود:
*,
*::before,
*::after {
box-sizing: border-box;
}
با border-box، عرض و ارتفاع اعلامشده شامل Padding و Border نیز میشود.
برای آشنایی عمیقتر میتوانید مستندات رسمی CSS Box Model در MDN را مطالعه کنید.
تفاوت Margin و Padding
padding فاصله محتوای عنصر تا Border آن است:
.card {
padding: 24px;
}
margin فاصله عنصر با عناصر اطراف است:
.card {
margin-block-end: 24px;
}
اگر میخواهید پسزمینه عنصر فضای بیشتری را پوشش دهد، معمولاً باید Padding را افزایش دهید. اگر فاصله میان دو عنصر مدنظر است، Margin یا gap انتخاب مناسبتری است.
واحدهای اندازهگیری در CSS
واحد px
.icon {
width: 24px;
height: 24px;
}
px برای Border، آیکون و اندازههای کوچک و دقیق مفید است.
واحد rem
h1 {
font-size: 2rem;
}
rem براساس اندازه فونت عنصر ریشه محاسبه میشود و برای تایپوگرافی و فاصلهگذاری مقیاسپذیر مناسب است.
واحد em
button {
padding: 0.75em 1.25em;
}
em معمولاً نسبت به اندازه فونت عنصر محاسبه میشود. استفاده تودرتوی آن میتواند محاسبه اندازه را پیچیده کند.
درصد
.container {
width: 90%;
}
درصد معمولاً نسبت به اندازه عنصر والد محاسبه میشود.
واحدهای Viewport
.app {
min-height: 100dvh;
}
واحدهای vw و vh نسبت به عرض و ارتفاع Viewport هستند. واحد dvh ارتفاع پویای Viewport را در مرورگرهای موبایل بهتر مدیریت میکند.
تابع clamp
تابع clamp() برای ایجاد اندازههای سیال مفید است:
.hero-title {
font-size: clamp(1.75rem, 4vw, 3.25rem);
}
این مقدار:
- کمتر از
1.75remنمیشود. - متناسب با عرض صفحه رشد میکند.
- بیشتر از
3.25remنمیشود.
رنگها در CSS
CSS چند روش برای تعریف رنگ دارد:
.example {
color: purple;
border-color: #e5e7eb;
background-color: rgb(109 74 255);
box-shadow: 0 10px 30px rgb(15 23 42 / 10%);
}
برای پروژههای واقعی بهتر است رنگهای اصلی را بهصورت CSS Variable تعریف کنید تا تغییر Theme آسان شود.
CSS Variables چیست؟
CSS Variables یا Custom Properties مقادیر قابلاستفاده مجدد هستند.
:root {
--color-primary: #6d4aff;
--color-primary-hover: #5837e8;
--color-text: #172033;
--color-muted: #667085;
--color-surface: #ffffff;
--radius-md: 14px;
--shadow-card: 0 16px 40px rgb(15 23 42 / 8%);
}
سپس میتوان آنها را با تابع var() استفاده کرد:
.button {
color: #ffffff;
background-color: var(--color-primary);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
}
برای مقدار جایگزین نیز میتوان نوشت:
.element {
color: var(--color-text, #111827);
}
CSS Variables برای موارد زیر بسیار مفید هستند:
- مدیریت رنگهای برند
- ساخت Dark Mode
- یکسانسازی فاصلهها
- تعریف اندازه Border Radius
- ساخت Design Token
- تغییر Theme بدون بازنویسی تمام فایل
ویژگیهای منطقی CSS برای زبان فارسی
در رابط فارسی، بهتر است در بسیاری از موارد بهجای left و right از Logical Properties استفاده کنیم.
مثال قدیمی:
.icon {
margin-left: 8px;
}
نسخه منعطفتر:
.icon {
margin-inline-start: 8px;
}
ویژگیهای مهم منطقی عبارتاند از:
| ویژگی منطقی | مفهوم |
|---|---|
margin-inline-start | حاشیه ابتدای جهت نوشتار |
margin-inline-end | حاشیه انتهای جهت نوشتار |
padding-inline | Padding افقی متناسب با جهت |
padding-block | Padding عمودی |
inset-inline-start | موقعیت ابتدای محور افقی |
border-inline-start | Border در ابتدای جهت نوشتار |
inline-size | اندازه در محور Inline |
block-size | اندازه در محور Block |
این ویژگیها باعث میشوند رابط در حالت RTL و LTR انعطاف بیشتری داشته باشد. مرجع کامل آنها در مستندات CSS Logical Properties موجود است.
Display در CSS
ویژگی display مشخص میکند عنصر چگونه در جریان Layout قرار گیرد.
block
section {
display: block;
}
عنصر Block معمولاً تمام عرض در دسترس را اشغال میکند و از خط جدید شروع میشود.
inline
strong {
display: inline;
}
عنصر Inline در جریان متن قرار میگیرد و معمولاً Width و Height مستقیم روی آن مانند Block عمل نمیکند.
inline-block
.badge {
display: inline-block;
}
این حالت رفتار Inline را با امکان تعیین اندازه ترکیب میکند.
none
.mobile-only {
display: none;
}
عنصر از Layout حذف میشود. توجه کنید که display: none معمولاً آن را از دسترس فناوریهای کمکی نیز خارج میکند.
flex و grid
این دو مقدار سیستمهای مدرن چیدمان را فعال میکنند و در ادامه بررسی میشوند.
Flexbox چیست؟
Flexbox یک سیستم چیدمان یکبعدی است. یعنی برای مدیریت عناصر در یک ردیف یا یک ستون طراحی شده است.
نمونه ساده:
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
ویژگیهای مهم Flexbox:
| ویژگی | کاربرد |
|---|---|
flex-direction | تعیین جهت ردیف یا ستون |
justify-content | تراز روی محور اصلی |
align-items | تراز روی محور فرعی |
gap | فاصله میان آیتمها |
flex-wrap | انتقال آیتمها به خط بعد |
flex-grow | میزان رشد آیتم |
flex-shrink | میزان کوچکشدن آیتم |
flex-basis | اندازه اولیه آیتم |
align-self | تراز مستقل یک آیتم |
مثال یک فرم ارسال پیام:
.composer {
display: flex;
align-items: flex-end;
gap: 12px;
}
.composer textarea {
flex: 1;
min-width: 0;
}
.composer button {
flex: 0 0 auto;
}
عبارت flex: 1 باعث میشود Textarea فضای باقیمانده را بگیرد.
مستندات Flexbox در MDN محور اصلی، محور فرعی، Wrapping و تنظیم اندازه آیتمها را با جزئیات توضیح میدهد.
CSS Grid چیست؟
CSS Grid یک سیستم چیدمان دوبعدی است و میتواند همزمان ردیفها و ستونها را مدیریت کند.
مثال:
.feature-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
این کد سه ستون هماندازه ایجاد میکند.
نسخه واکنشگرای بدون Media Query:
.feature-grid {
display: grid;
grid-template-columns: repeat(
auto-fit,
minmax(min(100%, 240px), 1fr)
);
gap: 20px;
}
در این حالت:
- کارتها حداقل عرض مناسبی دارند.
- تعداد ستونها براساس فضای موجود تغییر میکند.
- در صفحه کوچک، کارتها به یک ستون تبدیل میشوند.
برای صفحه اصلی اپلیکیشن نیز میتوان نوشت:
.app-shell {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
min-height: 100dvh;
}
این Layout شامل یک Sidebar با عرض ۲۸۰ پیکسل و یک بخش اصلی منعطف است.
تفاوت کلی Flexbox و Grid:
| ویژگی | Flexbox | Grid |
|---|---|---|
| نوع چیدمان | یکبعدی | دوبعدی |
| کاربرد اصلی | ردیف، ستون، Toolbar و فرم | صفحه، داشبورد و مجموعه کارتها |
| مدیریت ردیف و ستون | یکی در هر لحظه | همزمان |
| محل قرارگیری آیتمها | بیشتر براساس محتوا | بیشتر براساس Layout |
Flexbox و Grid رقیب یکدیگر نیستند. معمولاً Grid برای ساختار کلی و Flexbox برای اجزای داخلی استفاده میشود.
مرجع تکمیلی: CSS Grid در MDN
طراحی Responsive یا واکنشگرا
Responsive Design یعنی رابط در نمایشگرهای مختلف قابلاستفاده باقی بماند.
تنها کوچککردن اندازه عناصر کافی نیست. در موبایل ممکن است لازم باشد:
- Sidebar مخفی یا به Drawer تبدیل شود.
- چند ستون به یک ستون تبدیل شوند.
- فاصلهها کاهش یابند.
- دکمهها فضای لمس کافی داشته باشند.
- متنها بدون خروج از صفحه شکسته شوند.
- فرم ارسال پیام ساختار متفاوتی پیدا کند.
ابتدا Meta Viewport را در HTML قرار دهید:
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
سپس از Media Query استفاده کنید:
@media (max-width: 768px) {
.app-shell {
grid-template-columns: 1fr;
}
.sidebar {
display: none;
}
.chat-panel {
border-radius: 0;
}
}
Media Query میتواند ویژگیهایی مانند عرض، ارتفاع، Orientation و ترجیحات دسترسی کاربر را بررسی کند. جزئیات بیشتر در راهنمای Media Query در MDN آمده است.
رویکرد Mobile First
در Mobile First ابتدا استایل صفحه کوچک نوشته میشود و سپس برای صفحههای بزرگتر توسعه مییابد:
.cards {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width: 768px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1100px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
این رویکرد در بسیاری از پروژهها باعث میشود استایل پایه سادهتر باشد، اما الزام مطلق نیست. مهم این است که Breakpoint براساس نیاز واقعی Layout انتخاب شود، نه صرفاً مدل یک دستگاه خاص.
Position در CSS
ویژگی position نحوه موقعیتدهی عنصر را کنترل میکند.
static
حالت پیشفرض عناصر است.
relative
عنصر در جریان عادی باقی میماند و میتواند مرجع عناصر Absolute باشد:
.input-wrapper {
position: relative;
}
absolute
عنصر نسبت به نزدیکترین والد دارای Position مناسب قرار میگیرد:
.input-action {
position: absolute;
inset-inline-end: 12px;
inset-block-end: 12px;
}
fixed
عنصر نسبت به Viewport ثابت میماند:
.support-button {
position: fixed;
inset-inline-end: 24px;
inset-block-end: 24px;
}
sticky
عنصر تا رسیدن به موقعیت مشخص مانند عنصر عادی حرکت میکند و سپس ثابت میشود:
.chat-header {
position: sticky;
inset-block-start: 0;
z-index: 10;
}
position: sticky ممکن است بهدلیل تنظیمات overflow والد عمل نکند. هنگام Debug، والدهای عنصر را بررسی کنید.
z-index و Stacking Context
z-index مشخص میکند عناصر رویهمافتاده با چه ترتیبی نمایش داده شوند:
.modal {
position: fixed;
z-index: 100;
}
اما z-index فقط یک عدد جهانی ساده نیست. بعضی ویژگیها مانند transform، opacity و Position میتوانند Stacking Context جدید ایجاد کنند.
استفاده از اعداد تصادفی بسیار بزرگ معمولاً مشکل معماری را پنهان میکند:
/* راهحل قابل نگهداری نیست */
.modal {
z-index: 999999999;
}
بهتر است مقیاس مشخصی تعریف شود:
:root {
--z-header: 10;
--z-dropdown: 20;
--z-modal: 30;
--z-toast: 40;
}
Overflow و شکستن متن
پاسخ یک مدل هوش مصنوعی ممکن است شامل URL، کد یا متن طولانی باشد. برای جلوگیری از خروج محتوا از صفحه:
.message-content {
overflow-wrap: anywhere;
word-break: normal;
}
.message-content pre {
max-width: 100%;
overflow-x: auto;
}
برای ناحیه پیامها:
.message-list {
overflow-y: auto;
overscroll-behavior: contain;
}
فونت و خوانایی متن فارسی
برای متن فارسی فقط انتخاب فونت کافی نیست. line-height، طول خط و فاصله پاراگرافها نیز اهمیت دارند.
body {
font-family:
Vazirmatn,
Tahoma,
Arial,
sans-serif;
line-height: 1.8;
}
.article-text {
max-width: 70ch;
}
برای رابط چت، خطهای بسیار بلند خوانایی را کاهش میدهند:
.message {
max-width: min(78%, 760px);
}
برای کد و Model ID بهتر است جهت متن مستقل تنظیم شود:
code,
pre,
.model-id {
direction: ltr;
text-align: left;
}
Focus و دسترسپذیری
حذف کامل Outline بدون جایگزین، استفاده صفحه با Keyboard را دشوار میکند:
/* مناسب نیست */
button:focus {
outline: none;
}
نسخه بهتر:
button:focus-visible,
textarea:focus-visible,
a:focus-visible {
outline: 3px solid rgb(109 74 255 / 28%);
outline-offset: 3px;
}
همچنین:
- متن باید Contrast کافی با پسزمینه داشته باشد.
- اطلاعات فقط با رنگ منتقل نشوند.
- دکمه باید متن یا Accessible Name مناسب داشته باشد.
- حالت Disabled باید علاوه بر رنگ، از نظر رفتاری نیز غیرفعال باشد.
- اندازه ناحیه قابللمس در موبایل نباید بسیار کوچک باشد.
Transition و Animation
Transition تغییر حالت را نرم میکند:
.primary-button {
transition:
background-color 160ms ease,
transform 160ms ease,
box-shadow 160ms ease;
}
.primary-button:hover {
transform: translateY(-1px);
}
انیمیشن تایپ ساده:
@keyframes pulse {
0%,
100% {
opacity: 0.35;
}
50% {
opacity: 1;
}
}
.typing-dot {
animation: pulse 1s infinite;
}
باید ترجیح کاربر برای کاهش حرکت را نیز رعایت کنیم:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
پروژه عملی: ساخت رابط دستیار هوش مصنوعی فارسی
در این پروژه رابطی میسازیم که ویژگیهای زیر را دارد:
- طراحی فارسی و RTL
- Sidebar دسکتاپ
- Header ثابت
- فهرست پیامهای قابل اسکرول
- پیام متفاوت برای کاربر و دستیار
- فرم ارسال پیام
- وضعیت در حال دریافت پاسخ
- طراحی Responsive
- CSS Variables
- Flexbox و Grid
- Focus قابلمشاهده
- رعایت
prefers-reduced-motion - اتصال امن به Backend
- استفاده از API درواره بدون افشای API Key
معماری پروژه:
مرورگر کاربر
↓
HTML + CSS + JavaScript
↓
Backend محلی با FastAPI
↓
API هوش مصنوعی درواره
↓
مدل انتخابشده
کلید API فقط در Backend نگهداری میشود و هرگز وارد فایل JavaScript مرورگر نمیشود.
مرحله اول: ساخت پوشه پروژه
mkdir darvareh-css-assistant
cd darvareh-css-assistant
ساختار فایلها:
darvareh-css-assistant/
├── static/
│ ├── index.html
│ ├── styles.css
│ └── app.js
├── app.py
├── requirements.txt
└── .env
مرحله دوم: ساخت HTML
فایل static/index.html:
<!doctype html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<meta
name="description"
content="رابط دستیار هوش مصنوعی فارسی با API درواره"
>
<title>دستیار هوش مصنوعی درواره</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="app-shell">
<aside class="sidebar" aria-label="منوی اصلی">
<a
class="brand"
href="https://darvareh.ir"
target="_blank"
rel="noopener noreferrer"
>
<span class="brand__mark" aria-hidden="true">د</span>
<span>
<strong>درواره</strong>
<small>دسترسی یکپارچه به مدلهای هوش مصنوعی</small>
</span>
</a>
<button class="new-chat-button" id="new-chat" type="button">
<span aria-hidden="true">+</span>
گفتوگوی جدید
</button>
<nav class="sidebar__nav">
<p class="sidebar__label">نمونه پرسشها</p>
<button
class="suggestion"
type="button"
data-prompt="یک برنامه هفتگی برای یادگیری CSS طراحی کن."
>
برنامه یادگیری CSS
</button>
<button
class="suggestion"
type="button"
data-prompt="تفاوت Flexbox و CSS Grid را با مثال توضیح بده."
>
مقایسه Flexbox و Grid
</button>
<button
class="suggestion"
type="button"
data-prompt="این رابط کاربری را از نظر دسترسپذیری بررسی کن."
>
بررسی دسترسپذیری
</button>
</nav>
<div class="sidebar__footer">
<a
href="https://darvareh.ir/models"
target="_blank"
rel="noopener noreferrer"
>
مشاهده مدلها و قیمتها
</a>
</div>
</aside>
<main class="chat-panel">
<header class="chat-header">
<div>
<p class="eyebrow">دستیار آنلاین</p>
<h1>دستیار هوش مصنوعی</h1>
</div>
<div class="status" aria-label="وضعیت سرویس">
<span class="status__dot" aria-hidden="true"></span>
آماده
</div>
</header>
<section
class="message-list"
id="message-list"
aria-label="پیامهای گفتوگو"
aria-live="polite"
>
<article class="message message--assistant">
<div class="message__avatar" aria-hidden="true">د</div>
<div class="message__body">
<p class="message__name">دستیار درواره</p>
<div class="message__content">
سلام! سؤال خود را بنویسید تا پاسخ را از مدل هوش مصنوعی
دریافت کنم.
</div>
</div>
</article>
</section>
<div class="composer-wrapper">
<form class="composer" id="chat-form">
<label class="sr-only" for="prompt">
متن پیام
</label>
<textarea
id="prompt"
name="prompt"
rows="1"
maxlength="4000"
placeholder="پیام خود را بنویسید..."
required
></textarea>
<button
class="send-button"
id="send-button"
type="submit"
aria-label="ارسال پیام"
>
<span class="send-button__text">ارسال</span>
<span aria-hidden="true">←</span>
</button>
</form>
<p class="composer-note">
پاسخهای هوش مصنوعی ممکن است نیازمند بررسی باشند.
</p>
</div>
</main>
</div>
<script src="/static/app.js" defer></script>
</body>
</html>
مرحله سوم: نوشتن CSS کامل پروژه
فایل static/styles.css:
:root {
color-scheme: light;
--color-primary: #6d4aff;
--color-primary-dark: #5735e8;
--color-primary-soft: #f0edff;
--color-text: #172033;
--color-muted: #667085;
--color-border: #e6e8ef;
--color-page: #f5f6fb;
--color-surface: #ffffff;
--color-sidebar: #17142a;
--color-sidebar-text: #f7f5ff;
--color-success: #12b76a;
--color-user-message: #6d4aff;
--color-assistant-message: #ffffff;
--radius-sm: 10px;
--radius-md: 16px;
--radius-lg: 24px;
--shadow-panel:
0 24px 70px rgb(20 24 40 / 10%);
--shadow-message:
0 10px 30px rgb(20 24 40 / 6%);
--sidebar-width: 280px;
--content-max-width: 900px;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
min-height: 100%;
background-color: var(--color-page);
}
body {
min-width: 320px;
min-height: 100dvh;
margin: 0;
color: var(--color-text);
background:
radial-gradient(
circle at 85% 10%,
rgb(109 74 255 / 10%),
transparent 30%
),
var(--color-page);
font-family:
Vazirmatn,
Tahoma,
Arial,
sans-serif;
line-height: 1.7;
}
button,
textarea,
input {
font: inherit;
}
button,
a {
-webkit-tap-highlight-color: transparent;
}
button {
border: 0;
}
a {
color: inherit;
text-decoration: none;
}
.app-shell {
display: grid;
grid-template-columns:
var(--sidebar-width)
minmax(0, 1fr);
min-height: 100dvh;
}
.sidebar {
position: sticky;
inset-block-start: 0;
display: flex;
flex-direction: column;
gap: 24px;
height: 100dvh;
padding: 24px 18px;
color: var(--color-sidebar-text);
background:
linear-gradient(
180deg,
rgb(109 74 255 / 20%),
transparent 35%
),
var(--color-sidebar);
overflow-y: auto;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
padding-inline: 8px;
}
.brand__mark {
display: grid;
place-items: center;
flex: 0 0 auto;
inline-size: 44px;
block-size: 44px;
color: #ffffff;
background:
linear-gradient(
135deg,
#8d72ff,
var(--color-primary)
);
border-radius: 14px;
font-size: 1.3rem;
font-weight: 800;
box-shadow:
0 12px 28px rgb(109 74 255 / 32%);
}
.brand strong,
.brand small {
display: block;
}
.brand strong {
font-size: 1.05rem;
}
.brand small {
margin-block-start: 2px;
color: rgb(247 245 255 / 65%);
font-size: 0.72rem;
line-height: 1.5;
}
.new-chat-button {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
min-height: 48px;
padding: 10px 16px;
color: #ffffff;
background-color: var(--color-primary);
border-radius: var(--radius-md);
cursor: pointer;
transition:
background-color 160ms ease,
transform 160ms ease,
box-shadow 160ms ease;
}
.new-chat-button:hover {
background-color: var(--color-primary-dark);
box-shadow:
0 12px 24px rgb(109 74 255 / 24%);
transform: translateY(-1px);
}
.sidebar__nav {
display: grid;
gap: 8px;
}
.sidebar__label {
margin: 0 8px 4px;
color: rgb(247 245 255 / 48%);
font-size: 0.75rem;
}
.suggestion {
width: 100%;
padding: 12px;
color: rgb(247 245 255 / 78%);
background-color: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
text-align: start;
cursor: pointer;
transition:
color 160ms ease,
background-color 160ms ease,
border-color 160ms ease;
}
.suggestion:hover {
color: #ffffff;
background-color: rgb(255 255 255 / 7%);
border-color: rgb(255 255 255 / 9%);
}
.sidebar__footer {
margin-block-start: auto;
padding-block-start: 16px;
border-block-start:
1px solid rgb(255 255 255 / 10%);
}
.sidebar__footer a {
display: block;
padding: 10px 8px;
color: rgb(247 245 255 / 70%);
font-size: 0.82rem;
}
.sidebar__footer a:hover {
color: #ffffff;
}
.chat-panel {
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
min-width: 0;
min-height: 100dvh;
}
.chat-header {
position: sticky;
inset-block-start: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 18px clamp(20px, 4vw, 48px);
background-color: rgb(245 246 251 / 82%);
border-block-end: 1px solid rgb(230 232 239 / 75%);
backdrop-filter: blur(14px);
}
.chat-header h1 {
margin: 0;
font-size: clamp(1.1rem, 2vw, 1.35rem);
line-height: 1.4;
}
.eyebrow {
margin: 0 0 2px;
color: var(--color-primary);
font-size: 0.72rem;
font-weight: 700;
}
.status {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 7px 11px;
color: #087443;
background-color: #eafbf3;
border: 1px solid #c8f1dd;
border-radius: 999px;
font-size: 0.75rem;
}
.status__dot {
inline-size: 7px;
block-size: 7px;
background-color: var(--color-success);
border-radius: 50%;
box-shadow:
0 0 0 4px rgb(18 183 106 / 13%);
}
.message-list {
width: 100%;
max-width: var(--content-max-width);
margin-inline: auto;
padding:
clamp(24px, 5vw, 52px)
clamp(16px, 4vw, 36px);
overflow-y: auto;
overscroll-behavior: contain;
scroll-behavior: smooth;
}
.message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-block-end: 24px;
}
.message--user {
flex-direction: row-reverse;
}
.message__avatar {
display: grid;
place-items: center;
flex: 0 0 auto;
inline-size: 38px;
block-size: 38px;
color: #ffffff;
background-color: var(--color-primary);
border-radius: 12px;
font-size: 0.9rem;
font-weight: 800;
}
.message--user .message__avatar {
color: var(--color-primary);
background-color: var(--color-primary-soft);
}
.message__body {
max-width: min(78%, 720px);
}
.message--user .message__body {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.message__name {
margin: 0 4px 5px;
color: var(--color-muted);
font-size: 0.72rem;
}
.message__content {
padding: 14px 17px;
color: var(--color-text);
background-color: var(--color-assistant-message);
border: 1px solid var(--color-border);
border-radius:
6px
var(--radius-md)
var(--radius-md)
var(--radius-md);
box-shadow: var(--shadow-message);
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.message--user .message__content {
color: #ffffff;
background-color: var(--color-user-message);
border-color: var(--color-user-message);
border-radius:
var(--radius-md)
6px
var(--radius-md)
var(--radius-md);
}
.message--loading .message__content {
display: flex;
align-items: center;
gap: 5px;
min-height: 50px;
}
.typing-dot {
inline-size: 7px;
block-size: 7px;
background-color: var(--color-primary);
border-radius: 50%;
animation: typing-pulse 1s ease-in-out infinite;
}
.typing-dot:nth-child(2) {
animation-delay: 120ms;
}
.typing-dot:nth-child(3) {
animation-delay: 240ms;
}
@keyframes typing-pulse {
0%,
100% {
opacity: 0.3;
transform: translateY(0);
}
50% {
opacity: 1;
transform: translateY(-3px);
}
}
.composer-wrapper {
position: sticky;
inset-block-end: 0;
padding:
14px
clamp(16px, 4vw, 36px)
18px;
background:
linear-gradient(
to top,
var(--color-page) 72%,
rgb(245 246 251 / 0%)
);
}
.composer {
display: flex;
align-items: flex-end;
gap: 10px;
width: 100%;
max-width: var(--content-max-width);
margin-inline: auto;
padding: 8px;
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 20px;
box-shadow: var(--shadow-panel);
}
.composer:focus-within {
border-color: rgb(109 74 255 / 55%);
box-shadow:
0 0 0 4px rgb(109 74 255 / 8%),
var(--shadow-panel);
}
.composer textarea {
flex: 1;
min-width: 0;
max-height: 180px;
padding: 11px 12px;
color: var(--color-text);
background-color: transparent;
border: 0;
outline: 0;
resize: none;
line-height: 1.7;
}
.composer textarea::placeholder {
color: #98a2b3;
}
.send-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
flex: 0 0 auto;
min-height: 44px;
padding: 9px 16px;
color: #ffffff;
background-color: var(--color-primary);
border-radius: 14px;
cursor: pointer;
transition:
background-color 160ms ease,
transform 160ms ease,
opacity 160ms ease;
}
.send-button:hover:not(:disabled) {
background-color: var(--color-primary-dark);
transform: translateY(-1px);
}
.send-button:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.composer-note {
max-width: var(--content-max-width);
margin: 8px auto 0;
color: var(--color-muted);
font-size: 0.7rem;
text-align: center;
}
button:focus-visible,
a:focus-visible,
textarea:focus-visible {
outline: 3px solid rgb(109 74 255 / 28%);
outline-offset: 3px;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
@media (max-width: 820px) {
.app-shell {
grid-template-columns: 1fr;
}
.sidebar {
display: none;
}
.chat-header {
padding-inline: 18px;
}
.message-list {
padding-block-start: 28px;
}
.message__body {
max-width: 86%;
}
}
@media (max-width: 520px) {
.chat-header {
padding-block: 14px;
}
.status {
padding: 6px 9px;
}
.message {
gap: 8px;
}
.message__avatar {
inline-size: 32px;
block-size: 32px;
border-radius: 10px;
}
.message__body {
max-width: calc(100% - 40px);
}
.message__content {
padding: 12px 14px;
}
.send-button {
inline-size: 44px;
padding-inline: 0;
}
.send-button__text {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
تحلیل CSS پروژه
در این پروژه از Grid برای ساخت Layout اصلی استفاده شده است:
.app-shell {
display: grid;
grid-template-columns:
var(--sidebar-width)
minmax(0, 1fr);
}
تابع minmax(0, 1fr) اهمیت زیادی دارد. مقدار صفر اجازه میدهد ستون اصلی در صورت وجود محتوای طولانی کوچک شود و از صفحه بیرون نزند.
داخل Sidebar از Flexbox استفاده شده است:
.sidebar {
display: flex;
flex-direction: column;
}
سپس Footer با این قانون به پایین Sidebar منتقل میشود:
.sidebar__footer {
margin-block-start: auto;
}
برای فرم ارسال نیز Flexbox مناسب است؛ زیرا Textarea و دکمه در یک ردیف قرار دارند:
.composer {
display: flex;
align-items: flex-end;
}
CSS Variables امکان تغییر Theme را در یک نقطه فراهم میکنند. همچنین Logical Properties باعث میشوند Layout با جهت RTL سازگارتر باشد.
مرحله چهارم: افزودن JavaScript
فایل static/app.js:
const form = document.querySelector("#chat-form");
const input = document.querySelector("#prompt");
const sendButton = document.querySelector("#send-button");
const messageList = document.querySelector("#message-list");
const newChatButton = document.querySelector("#new-chat");
const suggestions = document.querySelectorAll(".suggestion");
const messages = [];
function scrollToLatestMessage() {
messageList.scrollTop = messageList.scrollHeight;
}
function createMessageElement(role, text) {
const article = document.createElement("article");
article.className = `message message--${role}`;
const avatar = document.createElement("div");
avatar.className = "message__avatar";
avatar.setAttribute("aria-hidden", "true");
avatar.textContent = role === "user" ? "ش" : "د";
const body = document.createElement("div");
body.className = "message__body";
const name = document.createElement("p");
name.className = "message__name";
name.textContent =
role === "user" ? "شما" : "دستیار درواره";
const content = document.createElement("div");
content.className = "message__content";
content.textContent = text;
body.append(name, content);
article.append(avatar, body);
return article;
}
function createLoadingElement() {
const article = document.createElement("article");
article.className =
"message message--assistant message--loading";
article.id = "loading-message";
const avatar = document.createElement("div");
avatar.className = "message__avatar";
avatar.setAttribute("aria-hidden", "true");
avatar.textContent = "د";
const body = document.createElement("div");
body.className = "message__body";
const name = document.createElement("p");
name.className = "message__name";
name.textContent = "در حال آمادهسازی پاسخ";
const content = document.createElement("div");
content.className = "message__content";
content.setAttribute("aria-label", "در حال دریافت پاسخ");
for (let index = 0; index < 3; index += 1) {
const dot = document.createElement("span");
dot.className = "typing-dot";
content.append(dot);
}
body.append(name, content);
article.append(avatar, body);
return article;
}
function setLoading(isLoading) {
sendButton.disabled = isLoading;
input.disabled = isLoading;
if (isLoading) {
messageList.append(createLoadingElement());
scrollToLatestMessage();
return;
}
document.querySelector("#loading-message")?.remove();
input.disabled = false;
input.focus();
}
function resizeTextarea() {
input.style.height = "auto";
input.style.height =
`${Math.min(input.scrollHeight, 180)}px`;
}
async function sendMessage(prompt) {
messageList.append(
createMessageElement("user", prompt)
);
messages.push({
role: "user",
content: prompt
});
setLoading(true);
scrollToLatestMessage();
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
messages
})
});
const payload = await response.json();
if (!response.ok) {
throw new Error(
payload.detail || "دریافت پاسخ ناموفق بود."
);
}
const answer =
payload.answer || "پاسخی دریافت نشد.";
messages.push({
role: "assistant",
content: answer
});
messageList.append(
createMessageElement("assistant", answer)
);
} catch (error) {
messageList.append(
createMessageElement(
"assistant",
`خطا: ${error.message}`
)
);
} finally {
setLoading(false);
scrollToLatestMessage();
}
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
const prompt = input.value.trim();
if (!prompt || sendButton.disabled) {
return;
}
input.value = "";
resizeTextarea();
await sendMessage(prompt);
});
input.addEventListener("input", resizeTextarea);
input.addEventListener("keydown", (event) => {
if (
event.key === "Enter" &&
!event.shiftKey &&
!event.isComposing
) {
event.preventDefault();
form.requestSubmit();
}
});
suggestions.forEach((button) => {
button.addEventListener("click", () => {
input.value = button.dataset.prompt || "";
resizeTextarea();
input.focus();
});
});
newChatButton.addEventListener("click", () => {
messages.length = 0;
messageList.replaceChildren(
createMessageElement(
"assistant",
"گفتوگوی جدید شروع شد. سؤال خود را بنویسید."
)
);
input.value = "";
resizeTextarea();
input.focus();
});
در این کد پاسخ مدل با textContent در صفحه قرار میگیرد، نه innerHTML. این انتخاب باعث میشود متن پاسخ بهصورت HTML تفسیر نشود.
اگر در آینده بخواهید Markdown نمایش دهید، بهتر است از یک Parser معتبر استفاده کنید و خروجی HTML را قبل از نمایش Sanitization کنید.
مرحله پنجم: ساخت Backend امن با FastAPI
API Key نباید داخل app.js یا کد قابلمشاهده مرورگر قرار گیرد. Backend درخواست مرورگر را دریافت میکند و سپس با استفاده از کلید موجود در متغیر محیطی به درواره متصل میشود.
فایل app.py:
import os
from typing import Literal
import httpx
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
load_dotenv()
DARVAREH_API_KEY = os.getenv("DARVAREH_API_KEY")
DARVAREH_MODEL_ID = os.getenv(
"DARVAREH_MODEL_ID",
"YOUR_MODEL_ID",
)
DARVAREH_CHAT_URL = (
"https://api.darvareh.ir/v1/chat/completions"
)
app = FastAPI(title="Darvareh CSS Assistant")
class Message(BaseModel):
role: Literal["user", "assistant", "system"]
content: str = Field(min_length=1, max_length=10000)
class ChatRequest(BaseModel):
messages: list[Message] = Field(
min_length=1,
max_length=30,
)
@app.post("/api/chat")
async def chat(request: ChatRequest):
if not DARVAREH_API_KEY:
raise HTTPException(
status_code=500,
detail="کلید API در سرور تنظیم نشده است.",
)
payload = {
"model": DARVAREH_MODEL_ID,
"messages": [
{
"role": "system",
"content": (
"شما یک دستیار فارسی، دقیق و مفید هستید. "
"پاسخ را روشن و ساختاریافته ارائه کنید."
),
},
*[
{
"role": message.role,
"content": message.content,
}
for message in request.messages
],
],
"temperature": 0.4,
"max_tokens": 1200,
}
headers = {
"Authorization": f"Bearer {DARVAREH_API_KEY}",
"Content-Type": "application/json",
}
try:
async with httpx.AsyncClient(
timeout=httpx.Timeout(60.0)
) as client:
response = await client.post(
DARVAREH_CHAT_URL,
headers=headers,
json=payload,
)
if response.status_code >= 400:
raise HTTPException(
status_code=502,
detail=(
"سرویس مدل پاسخ موفقی ارسال نکرد. "
f"کد وضعیت: {response.status_code}"
),
)
data = response.json()
answer = data["choices"][0]["message"]["content"]
if not isinstance(answer, str):
raise ValueError("ساختار پاسخ معتبر نیست.")
return {"answer": answer}
except httpx.TimeoutException as exc:
raise HTTPException(
status_code=504,
detail="زمان انتظار برای پاسخ به پایان رسید.",
) from exc
except httpx.RequestError as exc:
raise HTTPException(
status_code=502,
detail="ارتباط با سرویس مدل برقرار نشد.",
) from exc
except (KeyError, IndexError, TypeError, ValueError) as exc:
raise HTTPException(
status_code=502,
detail="ساختار پاسخ سرویس قابل پردازش نبود.",
) from exc
app.mount(
"/",
StaticFiles(directory="static", html=True),
name="static",
)
مرحله ششم: نصب وابستگیها
فایل requirements.txt:
fastapi
uvicorn[standard]
httpx
python-dotenv
محیط مجازی بسازید:
python -m venv .venv
فعالسازی در Linux و macOS:
source .venv/bin/activate
فعالسازی در PowerShell ویندوز:
.venv\Scripts\Activate.ps1
نصب وابستگیها:
pip install -r requirements.txt
مرحله هفتم: تنظیم متغیرهای محیطی
فایل .env:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
برای دریافت دسترسی و استفاده از API میتوانید در درواره ثبتنام کنید.
مدل مناسب و اطلاعات قیمت را از صفحه مدلهای درواره بررسی کنید و Model ID نمایشدادهشده در همان صفحه را در متغیر DARVAREH_MODEL_ID قرار دهید.
فایل .env را وارد Git نکنید:
.env
.venv/
__pycache__/
مرحله هشتم: اجرای پروژه
uvicorn app:app --reload
سپس آدرس زیر را باز کنید:
http://127.0.0.1:8000
اکنون میتوانید پیام خود را در رابط فارسی وارد کنید و پاسخ را از مدل انتخابشده دریافت کنید.
چرا API Key نباید در Frontend باشد؟
هر فایلی که به مرورگر ارسال میشود، از جمله JavaScript، برای کاربر قابلمشاهده است.
قرار دادن کلید به شکل زیر امن نیست:
const API_KEY = "YOUR_DARVAREH_API_KEY";
حتی اگر کد Minify یا Obfuscate شود، کلید همچنان قابل استخراج است. متغیرهای محیطی ابزارهای Frontend نیز اگر در Bundle نهایی قرار گیرند، Secret محسوب نمیشوند.
الگوی مناسب:
Frontend
↓
Backend شما
↓
API درواره
Backend امکان اضافهکردن کنترلهای زیر را نیز فراهم میکند:
- محدودیت اندازه پیام
- محدودیت تعداد پیامها
- احراز هویت کاربران
- Rate Limiting
- ثبت مصرف
- انتخاب مدل
- Timeout
- مدیریت خطا
- جلوگیری از ارسال مستقیم کلید به مرورگر
ساخت Dark Mode با CSS Variables
ابتدا متغیرهای حالت تیره را تعریف کنید:
[data-theme="dark"] {
color-scheme: dark;
--color-text: #f5f7ff;
--color-muted: #a7afc0;
--color-border: #30364a;
--color-page: #11131a;
--color-surface: #1b1f2a;
--color-assistant-message: #1b1f2a;
}
سپس با JavaScript ویژگی Theme را روی عنصر ریشه قرار دهید:
document.documentElement.dataset.theme = "dark";
برای استفاده از تنظیم سیستمعامل نیز میتوان از Media Query استفاده کرد:
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--color-text: #f5f7ff;
--color-muted: #a7afc0;
--color-border: #30364a;
--color-page: #11131a;
--color-surface: #1b1f2a;
--color-assistant-message: #1b1f2a;
}
}
اگر کاربر دکمه انتخاب Theme دارد، انتخاب صریح کاربر باید بر تنظیم خودکار سیستم اولویت داشته باشد.
روش صحیح سازماندهی CSS
در پروژههای متوسط میتوان فایل را به بخشهای مشخص تقسیم کرد:
styles/
├── tokens.css
├── reset.css
├── base.css
├── layout.css
├── components.css
├── utilities.css
└── responsive.css
وظیفه هر فایل:
| فایل | محتوا |
|---|---|
tokens.css | رنگ، فاصله، فونت، Radius و Shadow |
reset.css | اصلاح پیشفرض مرورگر |
base.css | استایل تگهای عمومی |
layout.css | ساختار صفحه |
components.css | دکمه، کارت، فرم و پیام |
utilities.css | Classهای کمکی محدود |
responsive.css | قواعد Media Query در صورت نیاز |
برای پروژه کوچک، یک فایل منظم معمولاً بهتر از چندین فایل پراکنده است. با رشد پروژه میتوان ساختار را تفکیک کرد.
نامگذاری Classها
نام Class باید هدف عنصر را نشان دهد:
.chat-header {}
.message-list {}
.message {}
.message--user {}
.message__content {}
این نمونه از ایدههای روش BEM استفاده میکند:
block
block__element
block--modifier
مثال:
.message {}
.message__avatar {}
.message__content {}
.message--user {}
.message--assistant {}
لازم نیست در تمام پروژهها BEM را کاملاً سختگیرانه اجرا کنید. هدف اصلی باید نامگذاری قابلفهم و جلوگیری از وابستگی شدید CSS به ساختار تودرتوی HTML باشد.
CSS Nesting
مرورگرهای مدرن از CSS Nesting پشتیبانی میکنند، اما استفاده بیش از حد از Nesting میتواند Specificity و خوانایی را پیچیده کند.
نمونه:
.card {
padding: 20px;
border: 1px solid var(--color-border);
& h2 {
margin-block-start: 0;
}
&:hover {
border-color: var(--color-primary);
}
}
حتی با پشتیبانی مرورگر، بهتر است عمق Nesting محدود بماند.
Container Query چیست؟
Media Query معمولاً اندازه Viewport را بررسی میکند. Container Query اندازه Container والد را در نظر میگیرد.
این قابلیت برای Componentهایی مناسب است که ممکن است در Sidebar، صفحه اصلی یا Modal قرار گیرند.
.card-wrapper {
container-type: inline-size;
}
.card {
display: grid;
gap: 12px;
}
@container (min-width: 500px) {
.card {
grid-template-columns: 140px 1fr;
}
}
در این مثال، چیدمان کارت براساس عرض Container خودش تغییر میکند، نه عرض کل صفحه.
خطاهای رایج CSS
استایل اعمال نمیشود
موارد زیر را بررسی کنید:
- مسیر فایل CSS صحیح است؟
- فایل در Network مرورگر با وضعیت موفق بارگذاری شده؟
- Selector با HTML مطابقت دارد؟
- قانون دیگری آن را Override کرده؟
- مقدار Property معتبر است؟
- فایل Cache نشده؟
- اشتباه نگارشی در Class وجود ندارد؟
از بخش Styles در DevTools استفاده کنید. قانون خطخورده معمولاً نشان میدهد قانون دیگری برنده شده است.
Width از Container بیرون میزند
Reset زیر را اضافه کنید:
*,
*::before,
*::after {
box-sizing: border-box;
}
برای فرزندهای Grid یا Flex نیز گاهی لازم است:
.content {
min-width: 0;
}
برای تصویر:
img {
display: block;
max-width: 100%;
height: auto;
}
Ellipsis کار نمیکند
برای متن تکخطی:
.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
در Flexbox ممکن است والد یا فرزند به min-width: 0 نیاز داشته باشد.
z-index اثر ندارد
بررسی کنید:
- عنصر Position مناسب دارد؟
- والد Stacking Context ساخته است؟
- عنصر داخل Stacking Context محدودشده قرار دارد؟
transformیاopacityروی والد وجود دارد؟
position: sticky کار نمیکند
موارد مهم:
- مقدار
topیاinset-block-startتعیین شده؟ - یکی از والدها
overflow: hiddenیاoverflow: autoدارد؟ - ارتفاع کافی برای Scroll وجود دارد؟
- ساختار Grid یا Flex مانع کشیدهشدن صحیح عنصر شده است؟
ارتفاع 100vh در موبایل مشکل دارد
در مرورگر موبایل نوار آدرس میتواند ارتفاع قابلمشاهده را تغییر دهد. در مرورگرهای جدید میتوان از 100dvh استفاده کرد:
.app {
min-height: 100dvh;
}
متن فارسی و انگلیسی بههم میریزد
برای صفحه:
<html lang="fa" dir="rtl">
برای کد و شناسه انگلیسی:
code,
pre,
.model-id {
direction: ltr;
text-align: left;
}
در موارد ترکیبی میتوانید از unicode-bidi و عنصر HTML مناسب مانند bdi نیز استفاده کنید.
استفاده از DevTools برای Debug کردن CSS
مرورگرها ابزارهای قدرتمندی برای بررسی CSS دارند.
در Chrome، Edge یا Firefox:
- روی عنصر کلیک راست کنید.
- گزینه Inspect را بزنید.
- در بخش Elements، عنصر را انتخاب کنید.
- قوانین قسمت Styles را بررسی کنید.
- در بخش Computed مقدار نهایی Property را ببینید.
- Box Model را بررسی کنید.
- قوانین را موقتاً فعال یا غیرفعال کنید.
- حالت موبایل را با Device Toolbar آزمایش کنید.
- وضعیتهای
:hoverو:focusرا شبیهسازی کنید. - Grid و Flex Overlay را فعال کنید.
ویرایش در DevTools موقتی است. پس از پیدا کردن راهحل، تغییر را در فایل اصلی ثبت کنید.
چگونه CSS تولیدشده با هوش مصنوعی را بررسی کنیم؟
هوش مصنوعی میتواند در تولید CSS، تبدیل طرح به کد و رفع مشکلات Layout کمک کند؛ اما خروجی باید بررسی شود.
یک پرامپت مناسب:
این Component را با CSS استاندارد و بدون Framework طراحی کن.
نیازمندیها:
- زبان صفحه فارسی و جهت RTL است.
- از Flexbox یا Grid براساس کاربرد درست استفاده کن.
- از CSS Variables برای رنگها و فاصلهها استفاده کن.
- طراحی باید از عرض 320px تا Desktop واکنشگرا باشد.
- Focus قابلمشاهده برای Keyboard ایجاد کن.
- prefers-reduced-motion را رعایت کن.
- از !important استفاده نکن.
- Selectorها را کوتاه و قابلنگهداری نگه دار.
- برای محتوای طولانی overflow مناسب در نظر بگیر.
- HTML و CSS را جداگانه ارائه کن.
بعد از دریافت خروجی بررسی کنید:
- آیا HTML معنایی است؟
- آیا در موبایل Overflow ایجاد میشود؟
- آیا Contrast مناسب است؟
- آیا Keyboard Navigation قابلاستفاده است؟
- آیا CSS تکراری وجود دارد؟
- آیا Selectorها بیش از حد اختصاصی هستند؟
- آیا
!importantبدون ضرورت استفاده شده؟ - آیا پاسخ مدل شامل ویژگیهای آزمایشی بدون Fallback است؟
- آیا کد در مرورگرهای هدف پروژه پشتیبانی میشود؟
بهینهسازی عملکرد CSS
CSS معمولاً سبک است، اما در پروژههای بزرگ میتواند روی عملکرد اثر بگذارد.
راهکارهای عملی:
- CSS بلااستفاده را حذف کنید.
- از Framework کامل فقط برای چند Component استفاده نکنید.
- فایلهای حیاتی را بیدلیل به چند درخواست کوچک تقسیم نکنید.
- فونتها و وزنهای غیرضروری را کاهش دهید.
- Animation سنگین روی
width،heightو موقعیت Layout را محدود کنید. - برای حرکتهای ساده معمولاً
transformوopacityمناسبترند. - Selectorهای بسیار پیچیده و تودرتو نسازید.
- تصاویر پسزمینه بزرگ را بهینه کنید.
- CSS تولیدشده را قبل از انتشار Minify کنید.
- تغییرات را با DevTools و ابزارهای سنجش واقعی بررسی کنید.
آیا باید از Bootstrap یا Tailwind استفاده کنیم؟
Frameworkها میتوانند سرعت توسعه را افزایش دهند، اما جای یادگیری CSS را نمیگیرند.
| گزینه | مزیت | محدودیت |
|---|---|---|
| CSS خالص | کنترل کامل و وابستگی کم | نیازمند طراحی ساختار |
| Bootstrap | Componentهای آماده | ظاهر پیشفرض و Override بیشتر |
| Tailwind CSS | توسعه سریع با Utility Class | HTML شلوغتر و نیازمند Build |
| CSS Modules | Scope محلی در Component | وابسته به ابزار Build |
| CSS-in-JS | ترکیب Style با Component | هزینه Runtime یا پیچیدگی ابزار |
| Sass | Variable، Mixin و ساختار بیشتر | نیازمند Compile |
اگر CSS پایه را درک نکنید، رفع مشکلات هر Framework دشوار میشود. ابتدا Box Model، Cascade، Flexbox، Grid و Responsive Design را یاد بگیرید؛ سپس ابزار مناسب پروژه را انتخاب کنید.
چکلیست CSS برای پروژه واقعی
پیش از انتشار رابط، این موارد را بررسی کنید:
box-sizing: border-boxتنظیم شده است.- رابط در عرض ۳۲۰ پیکسل آزمایش شده است.
- در Desktop فضای خالی نامناسب وجود ندارد.
- متن طولانی از Container خارج نمیشود.
- تصاویر
max-width: 100%دارند. - Focus در Keyboard قابلمشاهده است.
- Contrast متن و پسزمینه مناسب است.
- حالت Hover تنها روش نمایش اطلاعات نیست.
- حالت Loading مشخص است.
- دکمه Disabled واقعاً غیرفعال میشود.
prefers-reduced-motionرعایت شده است.- Logical Properties برای RTL بررسی شدهاند.
!importantغیرضروری وجود ندارد.- Selectorهای بسیار پیچیده حذف شدهاند.
- CSS Variables برای مقادیر پرتکرار استفاده شدهاند.
- صفحه با بزرگنمایی مرورگر بررسی شده است.
- رابط در محتوای فارسی و انگلیسی آزمایش شده است.
- کلید API در Frontend وجود ندارد.
- خطای Backend به شکل قابلفهم نمایش داده میشود.
- قیمت و Model ID بهصورت Hardcode نامطمئن درج نشدهاند.
نقشه راه یادگیری CSS
یک مسیر عملی برای یادگیری:
مرحله اول: مبانی
- Syntax
- Selector
- Color
- Font
- Background
- Border
- Margin
- Padding
مرحله دوم: منطق CSS
- Cascade
- Specificity
- Inheritance
- Box Model
- واحدهای اندازهگیری
مرحله سوم: Layout
- Normal Flow
- Display
- Position
- Flexbox
- Grid
- Overflow
مرحله چهارم: Responsive Design
- Meta Viewport
- Media Query
- Mobile First
- تصاویر واکنشگرا
clamp()- Container Query
مرحله پنجم: کیفیت رابط
- CSS Variables
- دسترسپذیری
- Focus
- Dark Mode
- Animation
prefers-reduced-motion- Logical Properties
مرحله ششم: معماری پروژه
- نامگذاری Class
- Component Styling
- Design Token
- حذف CSS تکراری
- تست مرورگر
- بررسی عملکرد
سؤالهای متداول
CSS چیست؟
CSS زبان Style Sheet برای تعیین ظاهر و چیدمان اسناد HTML است. رنگ، فونت، فاصله، Grid، Flexbox، Responsive Design و انیمیشن صفحات وب با CSS کنترل میشوند.
آیا CSS زبان برنامهنویسی است؟
CSS معمولاً زبان برنامهنویسی عمومی محسوب نمیشود. این زبان برای تعریف Presentation و Style اسناد طراحی شده است و منطق برنامه را مانند JavaScript اجرا نمیکند.
CSS3 چیست؟
اصطلاح CSS3 هنوز در جستوجوها و آموزشها دیده میشود، اما CSS مدرن به مجموعهای از Moduleهای مستقل تقسیم شده است. طبق توضیح MDN، CSS امروزی یک نسخه واحد با عنوان CSS4 ندارد و Moduleها جداگانه تکامل پیدا میکنند.
Flexbox بهتر است یا Grid؟
هیچکدام بهطور مطلق بهتر نیستند. Flexbox برای چیدمان یکبعدی و Grid برای چیدمان دوبعدی مناسب است. در یک پروژه واقعی معمولاً هر دو استفاده میشوند.
چرا CSS من اعمال نمیشود؟
مسیر اشتباه فایل، Selector نادرست، Specificity، ترتیب قوانین، Cache مرورگر یا مقدار نامعتبر از علتهای رایج هستند. DevTools مرورگر سریعترین راه تشخیص مشکل است.
چرا از !important استفاده نکنیم؟
استفاده زیاد از !important منطق Cascade را دشوار و Override کردن استایلها را پیچیده میکند. ابتدا Selector، ترتیب فایل و معماری CSS را اصلاح کنید.
rem بهتر است یا px؟
برای فونت و فاصلههای مقیاسپذیر معمولاً rem مناسب است. برای Border، آیکون و مقادیر دقیق کوچک میتوان از px استفاده کرد. انتخاب باید براساس کاربرد باشد.
چگونه رابط فارسی RTL بسازیم؟
در HTML از dir="rtl" و lang="fa" استفاده کنید. در CSS نیز Logical Properties مانند margin-inline-start و padding-inline را جایگزین وابستگی مستقیم به چپ و راست کنید.
آیا میتوان با CSS به API هوش مصنوعی متصل شد؟
خیر. CSS فقط ظاهر صفحه را کنترل میکند. برای ارسال درخواست به API به JavaScript و برای محافظت از API Key به Backend نیاز دارید.
آیا API Key را میتوان در CSS مخفی کرد؟
خیر. CSS، HTML و JavaScript ارسالشده به مرورگر عمومی هستند. API Key باید در Backend یا Secret Manager نگهداری شود.
چگونه Dark Mode بسازیم؟
میتوانید رنگها را با CSS Variables تعریف کنید و با prefers-color-scheme یا یک ویژگی مانند data-theme="dark" مقادیر متغیرها را تغییر دهید.
CSS Variable چه تفاوتی با متغیر Sass دارد؟
CSS Variable در مرورگر و Runtime فعال است و میتواند براساس DOM، Media Query و Theme تغییر کند. متغیر Sass هنگام Build پردازش میشود و در Runtime وجود ندارد.
آیا CSS روی سئوی سایت اثر دارد؟
CSS بهطور مستقیم جای محتوای مفید و HTML معنایی را نمیگیرد، اما میتواند بر تجربه کاربری، نمایش موبایل، خوانایی و عملکرد صفحه اثر بگذارد. مخفیکردن محتوای غیرطبیعی یا ایجاد تجربه ضعیف نیز میتواند به کیفیت صفحه آسیب بزند.
جمعبندی
CSS فقط ابزاری برای تغییر رنگ و فونت نیست. این زبان سیستم اصلی طراحی و چیدمان رابطهای وب است.
برای تسلط واقعی باید این مفاهیم را درک کنید:
- Selector و Syntax
- Cascade و Specificity
- Inheritance
- Box Model
- واحدهای اندازهگیری
- Flexbox
- CSS Grid
- Responsive Design
- CSS Variables
- Logical Properties
- دسترسپذیری
- Debug با DevTools
در پروژه عملی این مقاله یک رابط کامل فارسی ساختیم که از Grid برای Layout اصلی، Flexbox برای اجزای داخلی، Media Query برای موبایل و CSS Variables برای مدیریت Design Tokenها استفاده میکند.
همچنین رابط را از طریق یک Backend کوچک به API درواره متصل کردیم تا API Key در مرورگر افشا نشود.
برای شروع استفاده از API هوش مصنوعی، ساخت کلید و اتصال پروژه خود میتوانید وارد وبسایت درواره شوید. برای مشاهده مدلهای قابلاستفاده، Model ID و اطلاعات بهروز قیمت نیز صفحه مدلهای درواره را بررسی کنید.
منابع تکمیلی
- مرجع کامل CSS در MDN
- آموزش Box Model در MDN
- آموزش Flexbox در MDN
- آموزش CSS Grid در MDN
- راهنمای Media Query در MDN
- راهنمای CSS Logical Properties در MDN
مقالات مرتبط
- طراحی UI/UX با هوش مصنوعی؛ آموزش رابط کاربری از ایده تا کد
- ساخت سایت با هوش مصنوعی؛ آموزش کامل طراحی وبسایت
- آموزش اضافهکردن ChatGPT به سایت
- ساخت چتبات هوش مصنوعی با Next.js، React و API درواره
- آموزش ساخت چتبات با API هوش مصنوعی درواره
- طراحی رابط با Figma AI و Figma Make
- ساخت افزونه Chrome هوش مصنوعی با JavaScript و API درواره
- هوش مصنوعی در وردپرس؛ معرفی ابزارها و اتصال API درواره
- ساخت اپلیکیشن دسکتاپ هوش مصنوعی با Electron و JavaScript
- ساخت اپلیکیشن هوش مصنوعی با Streamlit و پایتون
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.