Initial commit — AsadiTools v1.0
CI/CD / CI — dotnet build (push) Successful in 44s
CI/CD / Deploy — docker compose (push) Failing after 1s

Full ASP.NET Core 10 Razor Pages app for آساد ابزار tool repair shop
in Karaj, Iran (official DeWalt representative).

Features:
- Homepage, Services, DeWalt page, Shop (pagination + images)
- 10 brand SEO pages (/brands/*) with rich Persian content + FAQ schema
- Blog engine with admin management (/blog, /Admin/Blog)
- Cart, Checkout, Contact (OpenStreetMap embed)
- Admin panel: Products CRUD, Orders, Blog, Change Password
- Jalali date formatting, product images, SiteData centralised contact
- Docker + docker-compose with healthcheck
- Gitea CI/CD via .gitea/workflows/ci-cd.yml (NuGet through Nexus mirror)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Soroush Asadi
2026-06-01 22:08:43 +03:30
commit f97f891d67
146 changed files with 88128 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "asadi-dotnet",
"runtimeExecutable": "dotnet",
"runtimeArgs": ["run", "--urls", "http://localhost:5050"],
"port": 5050
}
]
}
+15
View File
@@ -0,0 +1,15 @@
**/.git
**/.gitignore
**/bin
**/obj
**/.vs
**/.vscode
**/node_modules
*.user
*.suo
.DS_Store
data/
*.db
*.db-shm
*.db-wal
run.log
+8
View File
@@ -0,0 +1,8 @@
# AsadiTools — ENV_FILE template
# Copy this content into the Gitea secret:
# https://git.soroushasadi.com/<user>/AsadiTools/settings/secrets
# Secret name: ENV_FILE
ASPNETCORE_ENVIRONMENT=Production
ConnectionStrings__Default=Data Source=/app/data/asadi.db
DataProtection__KeysPath=/app/data/keys
+105
View File
@@ -0,0 +1,105 @@
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: asaditools-cicd-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Build & verify ──────────────────────────────────────────────────────────
build:
name: "CI — dotnet build"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/dotnet/sdk:10.0
options: --add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout FETCH_HEAD
- name: Write NuGet config
run: |
cat > /tmp/nuget.ci.config << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nexus"
value="https://mirror.soroushasadi.com/repository/nuget-group/index.json"
protocolVersion="3" />
</packageSources>
</configuration>
EOF
- name: Restore
run: dotnet restore AsadiTools.csproj --configfile /tmp/nuget.ci.config
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
- name: Build
run: dotnet build AsadiTools.csproj --no-restore -c Release
# ── Deploy ──────────────────────────────────────────────────────────────────
deploy:
name: "Deploy — docker compose"
runs-on: self-hosted
env:
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
needs: [build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
timeout-minutes: 30
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout FETCH_HEAD
- name: Write .env
run: printf '%s' "$ENV_FILE" > .env
env:
ENV_FILE: ${{ secrets.ENV_FILE }}
- name: Build image
run: docker compose build asadi-tools
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
- name: Start service
run: docker compose up -d --no-deps asadi-tools
- name: Wait for healthy
run: |
for i in $(seq 1 24); do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' asadi-tools 2>/dev/null || echo "missing")
echo " [$i/24] $STATUS"
[ "$STATUS" = "healthy" ] && echo "✅ asadi-tools healthy" && break
[ "$i" = "24" ] && echo "❌ TIMEOUT — asadi-tools never became healthy" \
&& docker compose logs --tail=60 asadi-tools && exit 1
sleep 5
done
- name: Prune old images
if: success()
run: docker image prune -f
+27
View File
@@ -0,0 +1,27 @@
## .NET / ASP.NET Core
bin/
obj/
*.user
*.suo
.vs/
.vscode/
*.ncrunchproject
## Runtime artefacts
*.db
*.db-shm
*.db-wal
/app/data/
## Secrets & env
.env
!.env.example
## Logs & temp
logs/
*.log
*.tmp
## OS
.DS_Store
Thumbs.db
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />
</ItemGroup>
</Project>
+22
View File
@@ -0,0 +1,22 @@
using AsadiTools.Models;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Data;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<Product> Products => Set<Product>();
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
public DbSet<AdminUser> AdminUsers => Set<AdminUser>();
public DbSet<BlogPost> BlogPosts => Set<BlogPost>();
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Product>().Property(p => p.Price).HasColumnType("TEXT");
mb.Entity<Product>().Property(p => p.DiscountPrice).HasColumnType("TEXT");
mb.Entity<Order>().Property(o => o.Subtotal).HasColumnType("TEXT");
mb.Entity<Order>().Property(o => o.Total).HasColumnType("TEXT");
mb.Entity<OrderItem>().Property(i => i.Price).HasColumnType("TEXT");
}
}
+295
View File
@@ -0,0 +1,295 @@
using AsadiTools.Models;
using BCrypt.Net;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Data;
public static class SeedData
{
public static void Initialize(AppDbContext db)
{
db.Database.EnsureCreated();
// SQLite schema migrations
try { db.Database.ExecuteSqlRaw("ALTER TABLE Products ADD COLUMN ImageUrl TEXT"); } catch { }
try { db.Database.ExecuteSqlRaw(@"
CREATE TABLE IF NOT EXISTS BlogPosts (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Title TEXT NOT NULL,
Slug TEXT NOT NULL,
Content TEXT NOT NULL,
Excerpt TEXT,
MetaDescription TEXT,
FeaturedImage TEXT,
Tags TEXT,
IsPublished INTEGER NOT NULL DEFAULT 0,
CreatedAt TEXT NOT NULL,
UpdatedAt TEXT NOT NULL,
PublishedAt TEXT
)"); } catch { }
if (!db.AdminUsers.Any())
{
db.AdminUsers.Add(new AdminUser
{
Username = "admin",
PasswordHash = BCrypt.Net.BCrypt.HashPassword("admin1234")
});
db.SaveChanges();
}
if (!db.BlogPosts.Any())
{
var now = DateTime.Now;
db.BlogPosts.AddRange(
new BlogPost
{
Title = "راهنمای کامل تعویض کاربن (زغال) فرز برقی",
Slug = "carbon-brush-replacement-guide",
Excerpt = "کاربن موتور یکی از مهم‌ترین قطعات مصرفی فرز برقی است. در این مقاله یاد می‌گیریم چه زمانی باید کاربن را تعویض کنیم و چطور این کار را انجام دهیم.",
MetaDescription = "راهنمای کامل تشخیص فرسودگی و تعویض کاربن فرز برقی. علائم فرسودگی، مراحل تعویض و نکات مهم برای دریل و فرز دیوالت، بوش، رونیکس.",
FeaturedImage = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=800&q=80&auto=format&fit=crop",
Tags = "تعمیر فرز,کاربن,نگهداری ابزار,دیوالت,بوش",
IsPublished = true,
PublishedAt = now.AddDays(-20),
CreatedAt = now.AddDays(-21), UpdatedAt = now.AddDays(-20),
Content = """
<h2>کاربن موتور چیست؟</h2>
<p>کاربن (یا زغال موتور) قطعهای است که با چرخش آرمیچر تماس مستقیم دارد و جریان برق را به موتور منتقل میکند. این قطعه با گذشت زمان و استفاده مداوم، کوچک میشود و نهایتاً باید تعویض شود. تعویض به موقع کاربن از آسیب جدیتر به آرمیچر جلوگیری میکند.</p>
<h2>علائم فرسودگی کاربن</h2>
<ul>
<li><strong>جرقههای زیاد داخل دستگاه:</strong> کمی جرقه طبیعی است، اما جرقه شدید و دود نشانه فرسودگی است.</li>
<li><strong>کاهش قدرت دستگاه:</strong> اگر فرز یا دریل شما ضعیفتر از قبل کار میکند، کاربن را بررسی کنید.</li>
<li><strong>گرم شدن بیش از حد:</strong> کاربنهای فرسوده اصطکاک بیشتری ایجاد میکنند.</li>
<li><strong>صدای غیرطبیعی:</strong> سر و صدای تقتق از موتور نشانه مشکل است.</li>
</ul>
<h2>زمان تعویض</h2>
<p>به طور معمول کاربنها بین <strong>۵۰ تا ۱۵۰ ساعت</strong> کار باید تعویض شوند. این مقدار بستگی به نوع ابزار، شدت کار و برند دارد. ابزارهای حرفهای مانند دیوالت و بوش معمولاً کاربنهای باکیفیتتری دارند که عمر بیشتری دارند.</p>
<h2>مراحل تعویض کاربن</h2>
<ol>
<li>ابزار را خاموش و از برق بکشید.</li>
<li>درپوش کاربن (معمولاً دو تا در دو طرف موتور) را پیدا کنید.</li>
<li>با یک پیچگوشتی درپوش را باز کنید.</li>
<li>کاربن قدیمی را بیرون بیاورید و اندازه آن را با نمونه نو مقایسه کنید.</li>
<li>کاربن جدید را داخل محفظه بگذارید مطمئن شوید جهت درست است.</li>
<li>درپوش را ببندید و دستگاه را تست کنید.</li>
</ol>
<h2>نکات مهم</h2>
<p>همیشه از <strong>کاربن اصلی</strong> همان برند استفاده کنید. کاربنهای جایگزین ارزانقیمت میتوانند به آرمیچر آسیب بزنند و هزینه تعمیر را چندین برابر کنند. آساد ابزار در کرج موجودی کامل کاربن اصلی دیوالت، بوش، ماکیتا، رونیکس و توسن را دارد.</p>
<p>اگر در هنگام تعویض کاربن متوجه آسیب به آرمیچر یا کموتاتور شدید، بدون تخصص لازم اقدام نکنید با آساد ابزار کرج تماس بگیرید: <strong>۰۲۶-۳۴۵۶۷۸۹۰</strong></p>
"""
},
new BlogPost
{
Title = "تعمیر کنیم یا نو بخریم؟ راهنمای تصمیم‌گیری هوشمند",
Slug = "repair-or-buy-new",
Excerpt = "یکی از سوالات رایج در مورد ابزار برقی این است که آیا تعمیر آن به صرفه است یا خرید نو؟ در این مقاله معیارهای کاربردی برای تصمیم‌گیری را بررسی می‌کنیم.",
MetaDescription = "راهنمای تصمیم‌گیری برای تعمیر یا خرید نو ابزار برقی. محاسبه هزینه، عمر مفید و معیارهای اقتصادی برای دریل، فرز و بتن‌کن.",
FeaturedImage = "https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=800&q=80&auto=format&fit=crop",
Tags = "تعمیر ابزار,خرید ابزار,راهنما,صرفه‌جویی",
IsPublished = true,
PublishedAt = now.AddDays(-14),
CreatedAt = now.AddDays(-15), UpdatedAt = now.AddDays(-14),
Content = """
<h2>قانون کلی ۵۰٪</h2>
<p>یک قاعده سرانگشتی ساده: اگر هزینه تعمیر از <strong>۵۰٪ قیمت دستگاه نو</strong> تجاوز کند، خرید نو اقتصادیتر است. البته این فقط یک نقطه شروع است و عوامل دیگری هم باید در نظر گرفته شوند.</p>
<h2>عواملی که به سود تعمیر هستند</h2>
<ul>
<li><strong>برند حرفهای:</strong> ابزارهای برندهای معتبر مانند دیوالت، هیلتی، بوش و متابو عموماً تعمیرپذیرتر هستند. بدنه مستحکم آنها سالها دوام میآورد.</li>
<li><strong>عمر کم دستگاه:</strong> اگر ابزارتان کمتر از ۳ سال عمر دارد، معمولاً تعمیر منطقیتر است.</li>
<li><strong>خرابی ساده:</strong> تعویض کاربن، بیرینگ یا کلید معمولاً بسیار مقرونبهصرفه است.</li>
<li><strong>قطعات در دسترس:</strong> اگر قطعات اصلی راحت تهیه میشوند، عمر ابزار بعد از تعمیر طولانی خواهد بود.</li>
</ul>
<h2>عواملی که به سود خرید نو هستند</h2>
<ul>
<li><strong>بدنه آسیبدیده:</strong> اگر بدنه ابزار شکسته، خورده یا تغییر شکل داده باشد.</li>
<li><strong>خرابی چندگانه:</strong> وقتی چند قطعه کلیدی همزمان آسیب دیدهاند.</li>
<li><strong>برند ارزان:</strong> برای ابزارهای بودجهای که قیمت پایینی دارند.</li>
<li><strong>قطعه ناموجود:</strong> اگر قطعه اصلی تولید نمیشود یا ماهها تأخیر دارد.</li>
</ul>
<h2>محاسبه هوشمند هزینه</h2>
<p>فرمول ساده برای تصمیمگیری:</p>
<blockquote>
اگر (هزینه تعمیر ÷ قیمت دستگاه مشابه نو) × ۱۰۰ کمتر از ۴۰٪ باشد تعمیر کنید<br>
اگر بین ۴۰٪ تا ۶۰٪ باشد به عوامل دیگر توجه کنید<br>
اگر بیشتر از ۶۰٪ باشد احتمالاً خرید نو بهتر است
</blockquote>
<p>آساد ابزار در کرج <strong>تشخیص رایگان</strong> انجام میدهد و پس از بررسی، صادقانه توصیه میکند که تعمیر مقرونبهصرفه است یا نه. با شماره <strong>۰۲۶-۳۴۵۶۷۸۹۰</strong> تماس بگیرید.</p>
"""
},
new BlogPost
{
Title = "۵ نکته طلایی برای افزایش عمر ابزار برقی",
Slug = "power-tool-maintenance-tips",
Excerpt = "نگهداری صحیح از ابزار برقی عمر آن را چند برابر می‌کند و از خرابی‌های پرهزینه جلوگیری می‌کند. این ۵ نکته ساده را رعایت کنید.",
MetaDescription = "۵ نکته طلایی نگهداری از ابزار برقی. روغن‌کاری، تمیزکاری، نگهداری باتری و سرویس دوره‌ای برای دریل، فرز و بتن‌کن.",
FeaturedImage = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=800&q=80&auto=format&fit=crop",
Tags = "نگهداری ابزار,سرویس دوره‌ای,دریل,فرز,بتن‌کن",
IsPublished = true,
PublishedAt = now.AddDays(-7),
CreatedAt = now.AddDays(-8), UpdatedAt = now.AddDays(-7),
Content = """
<h2>۱. تمیزکاری بعد از هر بار استفاده</h2>
<p>گرد و خاک و برادههای فلزی دشمن اصلی موتور ابزار هستند. بعد از هر استفاده با یک پارچه خشک یا پمپ هوا، دریچههای تهویه و بدنه ابزار را تمیز کنید. هرگز ابزار را در محیط مرطوب نگهداری نکنید.</p>
<h2>۲. روغنکاری منظم قطعات متحرک</h2>
<p>گیربکسها و چاک دریل نیاز به روغنکاری دورهای دارند. برای اکثر ابزارهای حرفهای هر ۶ ماه یک بار کافی است. از گریس مخصوص ابزار برقی استفاده کنید گریس معمولی یا روغن موتور مناسب نیست.</p>
<h2>۳. بیرینگها را جدی بگیرید</h2>
<p>اگر صدای غیرعادی از ابزارتان میشنوید، احتمالاً بیرینگ در حال خرابی است. ادامه کار با بیرینگ خراب میتواند به آرمیچر، گیربکس یا حتی بدنه آسیب بزند. بیرینگ ارزان است آرمیچر گران. به موقع تعمیر کنید.</p>
<h2>۴. باتریها را درست شارژ کنید</h2>
<p>برای ابزارهای شارژی:</p>
<ul>
<li>باتری لیتیومیون را نباید کاملاً تخلیه کنید</li>
<li>از شارژر اصلی استفاده کنید</li>
<li>باتری را در دمای اتاق نگهداری کنید (نه داخل ماشین زیر آفتاب)</li>
<li>اگر ابزار را برای مدت طولانی استفاده نمیکنید، باتری را با ۵۰٪ شارژ نگهداری کنید</li>
</ul>
<h2>۵. سرویس دورهای حرفهای</h2>
<p>حتی بهترین ابزارهای دنیا نیاز به سرویس دورهای دارند. توصیه میکنیم ابزارهای پرکاربرد را سالانه یک بار به تعمیرگاه بیاورید. سرویس دورهای شامل تعویض کاربن، بررسی بیرینگها، روغنکاری گیربکس و بررسی کلیه اتصالات الکتریکی میشود.</p>
<p><strong>آساد ابزار کرج</strong> سرویس دورهای تمام برندها را انجام میدهد. برای تعیین وقت با شماره <strong>۰۲۶-۳۴۵۶۷۸۹۰</strong> تماس بگیرید.</p>
"""
},
new BlogPost
{
Title = "تفاوت دریل SDS-Plus و SDS-Max — کدام را بخرم؟",
Slug = "sds-plus-vs-sds-max",
Excerpt = "دو استاندارد اتصال مته در دریل‌های چکشی حرفه‌ای. فرق اصلی کجاست و برای کدام کارها مناسب است؟",
MetaDescription = "مقایسه دریل SDS-Plus و SDS-Max. تفاوت در قدرت، کاربرد، ابعاد مته و قیمت. راهنمای خرید دریل چکشی حرفه‌ای.",
FeaturedImage = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=800&q=80&auto=format&fit=crop",
Tags = "دریل چکشی,SDS,بتن‌کن,راهنمای خرید,هیلتی,بوش",
IsPublished = true,
PublishedAt = now.AddDays(-3),
CreatedAt = now.AddDays(-4), UpdatedAt = now.AddDays(-3),
Content = """
<h2>SDS چیست؟</h2>
<p>SDS مخفف عبارت آلمانی <em>SteckDrehSitzt</em> (بگذاربچرخانبنشین) است. این یک سیستم اتصال سریع مته به دریل است که جایگزین چاک سه فک معمولی شده است. مزیت اصلی آن است که مته میتواند در طول دستگاه جلو-عقب حرکت کند و این حرکت محوری را به ضربه تبدیل میکند.</p>
<h2>SDS-Plus برای کارهای متوسط</h2>
<p>SDS-Plus (که گاهی SDS+ هم نوشته میشود) استاندارد رایجتر است. متههای آن دارای قطر ۱۰ میلیمتر در محل اتصال هستند.</p>
<ul>
<li>حداکثر قطر حفاری در بتن: معمولاً تا ۳۲ میلیمتر (با مته معمولی)</li>
<li>وزن معمول دستگاه: ۲.۵ تا ۵ کیلوگرم</li>
<li>انرژی ضربه: ۱ تا ۵ ژول</li>
<li>مناسب برای: کارهای ساختمانی معمول، نصب آنکر، سوراخکاری تا ۳۰ میلیمتر</li>
<li>برندهای محبوب: بوش GBH، هیلتی TE 6، دیوالت DCH</li>
</ul>
<h2>SDS-Max برای کارهای سنگین</h2>
<p>SDS-Max استاندارد برای کارهای سنگینتر است. متهها با قطر ۱۸ میلیمتر متصل میشوند و قدرت بیشتری انتقال میدهند.</p>
<ul>
<li>حداکثر قطر حفاری: تا ۵۰+ میلیمتر</li>
<li>وزن دستگاه: ۵ تا ۱۵ کیلوگرم</li>
<li>انرژی ضربه: ۵ تا ۲۰+ ژول</li>
<li>مناسب برای: تخریب بتن، حفاری در بتن مسلح، حفاریهای عمیق</li>
<li>برندهای محبوب: هیلتی TE 60/70، بوش GSH، دیوالت D25723K</li>
</ul>
<h2>کدام را بخریم؟</h2>
<p>پاسخ به کار شما بستگی دارد:</p>
<ul>
<li><strong>نصب آنکر، درپوش گچ، سوراخکاری معمول:</strong> SDS-Plus کافی است</li>
<li><strong>نصب داربست، سوراخکاری در ستون و کف بتنی:</strong> SDS-Plus قویتر</li>
<li><strong>تخریب بتن، حفاری کانال، کار مستمر روزانه:</strong> SDS-Max</li>
</ul>
<p>برای مشاوره در انتخاب و همچنین تعمیر دریلهای چکشی تمام برندها در کرج، با <strong>آساد ابزار</strong> تماس بگیرید: <strong>۰۲۶-۳۴۵۶۷۸۹۰</strong></p>
"""
},
new BlogPost
{
Title = "آنکر شیمیایی یا مکانیکی؟ راهنمای انتخاب برای پروژه‌های ساختمانی",
Slug = "chemical-vs-mechanical-anchor",
Excerpt = "آنکرها ابزارهای حیاتی در اتصال سازه‌ها به بتن هستند. در این مقاله تفاوت آنکر شیمیایی و مکانیکی را بررسی می‌کنیم.",
MetaDescription = "مقایسه آنکر شیمیایی و مکانیکی برای ساختمان‌سازی. کاربرد، ظرفیت بار، شرایط محیطی و راهنمای انتخاب.",
FeaturedImage = "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=800&q=80&auto=format&fit=crop",
Tags = "آنکر,آنکر شیمیایی,آنکر مکانیکی,PM آنکر,ساختمان",
IsPublished = true,
PublishedAt = now.AddDays(-1),
CreatedAt = now.AddDays(-2), UpdatedAt = now.AddDays(-1),
Content = """
<h2>آنکر چیست و چرا مهم است؟</h2>
<p>آنکر (یا لنگر) یک المان اتصال است که یک سازه یا قطعه را به پایه بتنی یا بنایی متصل میکند. از نصب نرده و داربست گرفته تا اتصال تجهیزات صنعتی سنگین، آنکرها نقش حیاتی در ایمنی سازهها دارند.</p>
<h2>آنکر مکانیکی (انبساطی)</h2>
<p>آنکرهای مکانیکی از اصطکاک و انبساط مکانیکی برای نگه داشتن در بتن استفاده میکنند.</p>
<ul>
<li><strong>نصب سریع:</strong> بدون نیاز به زمان عملآوری</li>
<li><strong>قابل بازیابی:</strong> میتوان آنها را بیرون آورد</li>
<li><strong>محدودیت:</strong> نیاز به فاصله از لبه بتن دارند</li>
<li><strong>انواع:</strong> آنکر بادکنکی، آنکر خار ماهی، آنکر ابسا</li>
<li><strong>مناسب برای:</strong> نصبهای سبک تا متوسط، تأسیسات، داربست</li>
</ul>
<h2>آنکر شیمیایی (رزینی)</h2>
<p>آنکرهای شیمیایی از رزین دو جزئی یا کپسول شیشهای برای اتصال استفاده میکنند. رزین در سوراخ پخش شده، سخت میشود و با بتن اتصال مستقیم میسازد.</p>
<ul>
<li><strong>ظرفیت بار بسیار بالا:</strong> تا چند برابر آنکر مکانیکی</li>
<li><strong>مناسب برای لبه:</strong> میتوان نزدیک لبه بتن نصب کرد</li>
<li><strong>محدودیت:</strong> نیاز به زمان عملآوری (۲۰ دقیقه تا چند ساعت)</li>
<li><strong>برندها:</strong> PM آنکر، هیلتی HIT، فیشر FIS</li>
<li><strong>مناسب برای:</strong> اتصالات سازهای، تجهیزات سنگین، بتن ترکدار</li>
</ul>
<h2>راهنمای انتخاب</h2>
<table>
<tr><th>معیار</th><th>آنکر مکانیکی</th><th>آنکر شیمیایی</th></tr>
<tr><td>سرعت نصب</td><td>فوری</td><td>نیاز به انتظار</td></tr>
<tr><td>ظرفیت کشش</td><td>متوسط</td><td>بسیار بالا</td></tr>
<tr><td>بتن ترکدار</td><td>محدود</td><td>مناسب</td></tr>
<tr><td>قیمت</td><td>پایینتر</td><td>بالاتر</td></tr>
</table>
<p>آساد ابزار کرج در زمینه انتخاب، تأمین و نصب انواع آنکر مشاوره تخصصی ارائه میدهد. برای اطلاعات بیشتر با شماره <strong>۰۲۶-۳۴۵۶۷۸۹۰</strong> تماس بگیرید.</p>
"""
}
);
db.SaveChanges();
}
if (!db.Products.Any())
{
var products = new List<Product>
{
new() { NameFa = "کاربن دیوالت DCD776", NameEn = "DeWalt DCD776 Carbon Brush", Category = "carbon", Brand = "dewalt", Price = 85000, Stock = 20, Sku = "DW-CBR-776",
ImageUrl = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "کاربن ماکیتا HR2470", NameEn = "Makita HR2470 Carbon Brush", Category = "carbon", Brand = "makita", Price = 95000, Stock = 15, Sku = "MK-CBR-2470",
ImageUrl = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "بیرینگ فرز رونیکس 3220", NameEn = "Ronix 3220 Bearing", Category = "bearing", Brand = "ronix", Price = 120000, Stock = 10, Sku = "RX-BRG-3220",
ImageUrl = "https://images.unsplash.com/photo-1581092160607-ee22621dd758?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "چاک دریل ۱۳ میل دیوالت", NameEn = "DeWalt 13mm Chuck", Category = "chuck", Brand = "dewalt", Price = 450000, Stock = 5, Sku = "DW-CHK-13",
ImageUrl = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "کلید دریل توسن ۱۰۱۰", NameEn = "Tosan 1010 Switch", Category = "switch", Brand = "tosan", Price = 180000, Stock = 8, Sku = "TS-SW-1010",
ImageUrl = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "آرمیچر فرز بلک اند دکر ۱۱۵", NameEn = "Black & Decker 115mm Armature", Category = "armature", Brand = "black-decker", Price = 650000, Stock = 3, Sku = "BD-ARM-115",
ImageUrl = "https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "چرخ‌دنده بتن‌کن ماکیتا HR2470", NameEn = "Makita HR2470 Gear", Category = "gear", Brand = "makita", Price = 380000, Stock = 6, Sku = "MK-GR-2470",
ImageUrl = "https://images.unsplash.com/photo-1581092160607-ee22621dd758?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "کاربن فرز بزرگ رونیکس", NameEn = "Ronix Large Grinder Carbon", Category = "carbon", Brand = "ronix", Price = 75000, Stock = 25, Sku = "RX-CBR-LG",
ImageUrl = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "بیرینگ دریل دیوالت DCD796", NameEn = "DeWalt DCD796 Bearing Set", Category = "bearing", Brand = "dewalt", Price = 250000, Stock = 7, Sku = "DW-BRG-796",
ImageUrl = "https://images.unsplash.com/photo-1581092160607-ee22621dd758?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "کلید رئوستا مینی فرز دیوالت", NameEn = "DeWalt Mini Grinder Rheostat", Category = "switch", Brand = "dewalt", Price = 320000, Stock = 4, Sku = "DW-RS-MG",
ImageUrl = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "دیسک سنباده فرز ۱۲۵ میل", NameEn = "Sanding Disc 125mm", Category = "accessory", Brand = null, Price = 45000, Stock = 50, Sku = "ACC-SD-125",
ImageUrl = "https://images.unsplash.com/photo-1607400201515-c2c41c07d307?w=400&q=75&auto=format&fit=crop" },
new() { NameFa = "استاتور دریل توسن", NameEn = "Tosan Drill Stator", Category = "stator", Brand = "tosan", Price = 550000, Stock = 3, Sku = "TS-ST-DR",
ImageUrl = "https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=400&q=75&auto=format&fit=crop" },
};
db.Products.AddRange(products);
db.SaveChanges();
}
}
}
+35
View File
@@ -0,0 +1,35 @@
# ── Stage 1: Build ──────────────────────────────────────────────────────────
ARG DOTNET_SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:10.0
FROM ${DOTNET_SDK_IMAGE} AS build
WORKDIR /src
# NuGet through Nexus mirror
COPY nuget.docker.config /tmp/nuget.config
# Restore (layer-cached unless .csproj changes)
COPY AsadiTools.csproj .
RUN dotnet restore --configfile /tmp/nuget.config
# Copy everything else and publish
COPY . .
RUN dotnet publish -c Release -o /app/publish --no-restore
# ── Stage 2: Runtime ─────────────────────────────────────────────────────────
ARG DOTNET_RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0
FROM ${DOTNET_RUNTIME_IMAGE} AS runtime
WORKDIR /app
# Create data directory for SQLite volume mount
RUN mkdir -p /app/data && chmod 777 /app/data
# Copy published output
COPY --from=build /app/publish .
# Override connection string to use /app/data/asadi.db (volume path)
ENV ConnectionStrings__Default="Data Source=/app/data/asadi.db"
ENV ASPNETCORE_URLS="http://+:8080"
ENV ASPNETCORE_ENVIRONMENT="Production"
EXPOSE 8080
ENTRYPOINT ["dotnet", "AsadiTools.dll"]
+8
View File
@@ -0,0 +1,8 @@
namespace AsadiTools.Models;
public class AdminUser
{
public int Id { get; set; }
public string Username { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
}
+29
View File
@@ -0,0 +1,29 @@
namespace AsadiTools.Models;
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty; // raw HTML
public string? Excerpt { get; set; }
public string? MetaDescription { get; set; }
public string? FeaturedImage { get; set; }
public string? Tags { get; set; } // comma-separated
public bool IsPublished { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.Now;
public DateTime UpdatedAt { get; set; } = DateTime.Now;
public DateTime? PublishedAt { get; set; }
// Helpers
public string[] TagList =>
Tags?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? [];
public string EffectiveSlug =>
!string.IsNullOrWhiteSpace(Slug) ? Slug : $"post-{Id}";
public string DisplayDate =>
(PublishedAt ?? CreatedAt) is var dt
? AsadiTools.Services.SiteData.ToJalali(dt)
: string.Empty;
}
+11
View File
@@ -0,0 +1,11 @@
namespace AsadiTools.Models;
public class CartItem
{
public int ProductId { get; set; }
public string NameFa { get; set; } = string.Empty;
public string? Sku { get; set; }
public decimal Price { get; set; }
public int Qty { get; set; }
public decimal Subtotal => Price * Qty;
}
+37
View File
@@ -0,0 +1,37 @@
namespace AsadiTools.Models;
public enum OrderStatus
{
Pending,
Confirmed,
Shipped,
Delivered,
Cancelled
}
public class Order
{
public int Id { get; set; }
public string OrderNumber { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
public string CustomerPhone { get; set; } = string.Empty;
public string? CustomerAddress { get; set; }
public string? Notes { get; set; }
public decimal Subtotal { get; set; }
public decimal Total { get; set; }
public OrderStatus Status { get; set; } = OrderStatus.Pending;
public DateTime CreatedAt { get; set; } = DateTime.Now;
public List<OrderItem> Items { get; set; } = [];
}
public class OrderItem
{
public int Id { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; } = null!;
public int ProductId { get; set; }
public string ProductNameFa { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
public decimal Subtotal => Price * Quantity;
}
+21
View File
@@ -0,0 +1,21 @@
namespace AsadiTools.Models;
public class Product
{
public int Id { get; set; }
public string NameFa { get; set; } = string.Empty;
public string? NameEn { get; set; }
public string? Description { get; set; }
public decimal Price { get; set; }
public decimal? DiscountPrice { get; set; }
public string Category { get; set; } = string.Empty;
public string? Brand { get; set; }
public string? Sku { get; set; }
public int Stock { get; set; }
public bool IsActive { get; set; } = true;
public string? ImageUrl { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
public decimal FinalPrice => DiscountPrice.HasValue && DiscountPrice < Price ? DiscountPrice.Value : Price;
public bool HasDiscount => DiscountPrice.HasValue && DiscountPrice < Price;
}
+22
View File
@@ -0,0 +1,22 @@
@page
@model AsadiTools.Pages.Admin.Blog.AdminBlogCreateModel
@{ ViewData["Title"] = "نوشته جدید"; Layout = "_AdminLayout"; }
<div class="p-6 md:p-8 max-w-3xl">
<div class="flex items-center gap-3 mb-8">
<a href="/Admin/Blog" class="text-gray-400 hover:text-gray-600 text-sm">← بازگشت</a>
<h1 class="text-2xl font-extrabold text-gray-900">نوشته جدید</h1>
</div>
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6">
@Html.AntiForgeryToken()
@await Html.PartialAsync("_BlogFormFields", Model.Input)
<div class="flex gap-3 mt-6 pt-5 border-t">
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">
ذخیره مقاله
</button>
<a href="/Admin/Blog" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">
انصراف
</a>
</div>
</form>
</div>
+58
View File
@@ -0,0 +1,58 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
namespace AsadiTools.Pages.Admin.Blog;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class AdminBlogCreateModel(AppDbContext db) : PageModel
{
[BindProperty] public BlogPostInput Input { get; set; } = new();
public void OnGet() { ViewData["Title"] = "نوشته جدید"; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
var slug = string.IsNullOrWhiteSpace(Input.Slug)
? Input.Title.ToLower()
.Replace(" ", "-")
.Replace("،", "")
.Replace(".", "-")
: Input.Slug.Trim();
var now = DateTime.Now;
db.BlogPosts.Add(new BlogPost
{
Title = Input.Title,
Slug = slug,
Content = Input.Content,
Excerpt = Input.Excerpt,
MetaDescription = Input.MetaDescription,
FeaturedImage = string.IsNullOrWhiteSpace(Input.FeaturedImage) ? null : Input.FeaturedImage,
Tags = Input.Tags,
IsPublished = Input.IsPublished,
PublishedAt = Input.IsPublished ? now : null,
CreatedAt = now,
UpdatedAt = now,
});
await db.SaveChangesAsync();
return RedirectToPage("/Admin/Blog/Index");
}
}
public class BlogPostInput
{
[Required] public string Title { get; set; } = string.Empty;
public string? Slug { get; set; }
[Required] public string Content { get; set; } = string.Empty;
public string? Excerpt { get; set; }
public string? MetaDescription { get; set; }
public string? FeaturedImage { get; set; }
public string? Tags { get; set; }
public bool IsPublished { get; set; }
}
+23
View File
@@ -0,0 +1,23 @@
@page
@model AsadiTools.Pages.Admin.Blog.AdminBlogEditModel
@{ Layout = "_AdminLayout"; }
<div class="p-6 md:p-8 max-w-3xl">
<div class="flex items-center gap-3 mb-8">
<a href="/Admin/Blog" class="text-gray-400 hover:text-gray-600 text-sm">← بازگشت</a>
<h1 class="text-2xl font-extrabold text-gray-900">ویرایش مقاله</h1>
</div>
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6">
@Html.AntiForgeryToken()
<input type="hidden" name="id" value="@Model.PostId" />
@await Html.PartialAsync("_BlogFormFields", Model.Input)
<div class="flex gap-3 mt-6 pt-5 border-t">
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">
ذخیره تغییرات
</button>
<a href="/Admin/Blog" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">
انصراف
</a>
</div>
</form>
</div>
+57
View File
@@ -0,0 +1,57 @@
using AsadiTools.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Admin.Blog;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class AdminBlogEditModel(AppDbContext db) : PageModel
{
[BindProperty] public BlogPostInput Input { get; set; } = new();
public int PostId { get; private set; }
public async Task<IActionResult> OnGetAsync(int id)
{
var post = await db.BlogPosts.FindAsync(id);
if (post is null) return NotFound();
PostId = id;
Input = new BlogPostInput
{
Title = post.Title,
Slug = post.Slug,
Content = post.Content,
Excerpt = post.Excerpt,
MetaDescription = post.MetaDescription,
FeaturedImage = post.FeaturedImage,
Tags = post.Tags,
IsPublished = post.IsPublished,
};
ViewData["Title"] = "ویرایش: " + post.Title;
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
if (!ModelState.IsValid) { PostId = id; return Page(); }
var post = await db.BlogPosts.FindAsync(id);
if (post is null) return NotFound();
var wasUnpublished = !post.IsPublished;
post.Title = Input.Title;
post.Slug = string.IsNullOrWhiteSpace(Input.Slug) ? post.Slug : Input.Slug.Trim();
post.Content = Input.Content;
post.Excerpt = Input.Excerpt;
post.MetaDescription = Input.MetaDescription;
post.FeaturedImage = string.IsNullOrWhiteSpace(Input.FeaturedImage) ? null : Input.FeaturedImage;
post.Tags = Input.Tags;
post.IsPublished = Input.IsPublished;
post.UpdatedAt = DateTime.Now;
if (Input.IsPublished && wasUnpublished)
post.PublishedAt = DateTime.Now;
await db.SaveChangesAsync();
return RedirectToPage("/Admin/Blog/Index");
}
}
+86
View File
@@ -0,0 +1,86 @@
@page
@model AsadiTools.Pages.Admin.Blog.AdminBlogIndexModel
@{ ViewData["Title"] = "مدیریت بلاگ"; Layout = "_AdminLayout"; }
<div class="p-6 md:p-8">
<div class="flex items-center justify-between mb-8">
<h1 class="text-2xl font-extrabold text-gray-900">مدیریت بلاگ</h1>
<a href="/Admin/Blog/Create"
class="bg-blue-700 text-white px-5 py-2.5 rounded-xl font-bold hover:bg-blue-800 transition-colors flex items-center gap-2">
✏️ نوشته جدید
</a>
</div>
@if (!Model.Posts.Any())
{
<div class="text-center py-20 text-gray-400">
<div class="text-5xl mb-4">📝</div>
<p>هنوز مقاله‌ای ثبت نشده</p>
</div>
}
else
{
<div class="bg-white rounded-2xl border border-gray-100 overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b">
<tr>
<th class="p-4 text-right font-medium text-gray-500">عنوان</th>
<th class="p-4 text-right font-medium text-gray-500 hidden md:table-cell">برچسب‌ها</th>
<th class="p-4 text-right font-medium text-gray-500">وضعیت</th>
<th class="p-4 text-right font-medium text-gray-500 hidden lg:table-cell">تاریخ</th>
<th class="p-4 text-right font-medium text-gray-500">عملیات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50">
@foreach (var post in Model.Posts)
{
<tr class="hover:bg-gray-50">
<td class="p-4">
<div class="font-medium text-gray-900 line-clamp-1">@post.Title</div>
<div class="text-xs text-gray-400 font-mono mt-0.5">@post.EffectiveSlug</div>
</td>
<td class="p-4 hidden md:table-cell">
<div class="flex flex-wrap gap-1">
@foreach (var tag in post.TagList.Take(3))
{
<span class="text-xs bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded">@tag</span>
}
</div>
</td>
<td class="p-4">
<form method="post" asp-page-handler="TogglePublish" class="inline">
@Html.AntiForgeryToken()
<input type="hidden" name="id" value="@post.Id" />
<button type="submit"
class="text-xs px-2.5 py-1 rounded-full font-medium border transition-colors @(post.IsPublished ? "bg-green-100 text-green-700 border-green-200 hover:bg-green-200" : "bg-gray-100 text-gray-500 border-gray-200 hover:bg-gray-200")">
@(post.IsPublished ? "✅ منتشر" : "⏸ پیش‌نویس")
</button>
</form>
</td>
<td class="p-4 hidden lg:table-cell text-gray-400 text-xs">
@SiteData.ToJalali(post.CreatedAt)
</td>
<td class="p-4">
<div class="flex items-center gap-2">
<a href="/Admin/Blog/Edit?id=@post.Id"
class="text-blue-600 hover:underline text-xs font-medium">ویرایش</a>
@if (post.IsPublished)
{
<a href="/blog/@post.EffectiveSlug" target="_blank"
class="text-gray-400 hover:text-gray-600 text-xs">مشاهده</a>
}
<form method="post" asp-page-handler="Delete"
onsubmit="return confirm('این مقاله حذف شود؟')" class="inline">
@Html.AntiForgeryToken()
<input type="hidden" name="id" value="@post.Id" />
<button type="submit" class="text-red-400 hover:text-red-600 text-xs">حذف</button>
</form>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
+41
View File
@@ -0,0 +1,41 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Pages.Admin.Blog;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class AdminBlogIndexModel(AppDbContext db) : PageModel
{
public List<BlogPost> Posts { get; private set; } = [];
public async Task OnGetAsync()
{
Posts = await db.BlogPosts.OrderByDescending(p => p.CreatedAt).ToListAsync();
ViewData["Title"] = "مدیریت بلاگ";
}
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var post = await db.BlogPosts.FindAsync(id);
if (post is not null) { db.BlogPosts.Remove(post); await db.SaveChangesAsync(); }
return RedirectToPage();
}
public async Task<IActionResult> OnPostTogglePublishAsync(int id)
{
var post = await db.BlogPosts.FindAsync(id);
if (post is not null)
{
post.IsPublished = !post.IsPublished;
if (post.IsPublished && post.PublishedAt is null)
post.PublishedAt = DateTime.Now;
post.UpdatedAt = DateTime.Now;
await db.SaveChangesAsync();
}
return RedirectToPage();
}
}
+55
View File
@@ -0,0 +1,55 @@
@model AsadiTools.Pages.Admin.Blog.BlogPostInput
@{
var cls = "w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500";
}
<div class="space-y-5">
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">عنوان مقاله <span class="text-red-500">*</span></label>
<input asp-for="Title" class="@cls" placeholder="مثال: راهنمای تعمیر فرز برقی" />
<span asp-validation-for="Title" class="text-red-500 text-xs"></span>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">اسلاگ (URL)</label>
<input asp-for="Slug" class="@cls" dir="ltr" placeholder="carbon-brush-guide (خودکار از عنوان)" />
<p class="text-xs text-gray-400 mt-1">URL صفحه: /blog/اسلاگ — اگر خالی بماند از عنوان ساخته می‌شود</p>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">چکیده (برای لیست بلاگ)</label>
<textarea asp-for="Excerpt" rows="2" class="@cls resize-none" placeholder="خلاصه کوتاه مقاله..."></textarea>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">توضیح متا (SEO)</label>
<textarea asp-for="MetaDescription" rows="2" class="@cls resize-none" placeholder="توضیح برای گوگل (حداکثر ۱۶۰ کاراکتر)..."></textarea>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">آدرس تصویر شاخص (URL)</label>
<input asp-for="FeaturedImage" class="@cls" dir="ltr" placeholder="https://..." />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">برچسب‌ها (با کاما جدا شوند)</label>
<input asp-for="Tags" class="@cls" placeholder="تعمیر فرز,کاربن,دیوالت" />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">محتوا (HTML) <span class="text-red-500">*</span></label>
<textarea asp-for="Content" rows="20" class="@cls resize-y font-mono text-xs leading-relaxed" dir="auto"
placeholder="<h2>عنوان بخش</h2>&#10;<p>متن مقاله...</p>"></textarea>
<p class="text-xs text-gray-400 mt-1">محتوا به صورت HTML نوشته شود. از تگ‌های h2، h3، p، ul، li، strong استفاده کنید.</p>
<span asp-validation-for="Content" class="text-red-500 text-xs"></span>
</div>
<div class="flex items-center gap-3 p-4 bg-gray-50 rounded-xl border border-gray-100">
<input asp-for="IsPublished" type="checkbox" class="w-4 h-4 rounded accent-blue-600" />
<div>
<label asp-for="IsPublished" class="font-medium text-gray-800 text-sm cursor-pointer">انتشار فوری</label>
<p class="text-xs text-gray-400">اگر تیک بزنید، مقاله بلافاصله در سایت نمایش داده می‌شود.</p>
</div>
</div>
</div>
+50
View File
@@ -0,0 +1,50 @@
@page
@model AsadiTools.Pages.Admin.ChangePassword.ChangePasswordModel
@{ ViewData["Title"] = "تغییر رمز عبور"; Layout = "_AdminLayout"; }
@{
var inputCls = "w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500";
}
<div class="p-6 md:p-8 max-w-lg">
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">تغییر رمز عبور</h1>
@if (Model.Success)
{
<div class="bg-green-50 border border-green-200 text-green-700 px-5 py-4 rounded-xl mb-6 flex items-center gap-2">
✅ رمز عبور با موفقیت تغییر یافت.
</div>
}
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6 space-y-5">
@Html.AntiForgeryToken()
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3 rounded-xl">@Model.ErrorMessage</div>
}
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">رمز عبور فعلی <span class="text-red-500">*</span></label>
<input asp-for="Input.CurrentPassword" type="password" class="@inputCls" />
<span asp-validation-for="Input.CurrentPassword" class="text-red-500 text-xs"></span>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">رمز عبور جدید <span class="text-red-500">*</span></label>
<input asp-for="Input.NewPassword" type="password" class="@inputCls" />
<span asp-validation-for="Input.NewPassword" class="text-red-500 text-xs"></span>
<p class="text-xs text-gray-400 mt-1">حداقل ۶ کاراکتر</p>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">تکرار رمز عبور جدید <span class="text-red-500">*</span></label>
<input asp-for="Input.ConfirmPassword" type="password" class="@inputCls" />
<span asp-validation-for="Input.ConfirmPassword" class="text-red-500 text-xs"></span>
</div>
<button type="submit" class="w-full bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">
🔑 تغییر رمز عبور
</button>
</form>
</div>
@@ -0,0 +1,51 @@
using AsadiTools.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
namespace AsadiTools.Pages.Admin.ChangePassword;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class ChangePasswordModel(AppDbContext db) : PageModel
{
[BindProperty] public ChangePasswordInput Input { get; set; } = new();
public string? ErrorMessage { get; private set; }
public bool Success { get; private set; }
public void OnGet() { }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var user = await db.AdminUsers.FindAsync(userId);
if (user is null) return RedirectToPage("/Admin/Login");
if (!BCrypt.Net.BCrypt.Verify(Input.CurrentPassword, user.PasswordHash))
{
ErrorMessage = "رمز عبور فعلی اشتباه است";
return Page();
}
if (Input.NewPassword != Input.ConfirmPassword)
{
ErrorMessage = "رمز عبور جدید و تکرار آن یکسان نیستند";
return Page();
}
user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(Input.NewPassword);
await db.SaveChangesAsync();
Success = true;
return Page();
}
}
public class ChangePasswordInput
{
[Required] public string CurrentPassword { get; set; } = string.Empty;
[Required, MinLength(6)] public string NewPassword { get; set; } = string.Empty;
[Required] public string ConfirmPassword { get; set; } = string.Empty;
}
+75
View File
@@ -0,0 +1,75 @@
@page
@model AsadiTools.Pages.Admin.AdminIndexModel
@{ ViewData["Title"] = "داشبورد"; Layout = "_AdminLayout"; }
<div class="p-6 md:p-8">
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">داشبورد</h1>
<!-- Stats -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-10">
<div class="bg-white rounded-2xl border border-gray-100 p-5">
<div class="w-10 h-10 rounded-xl bg-blue-100 text-blue-600 flex items-center justify-center text-xl mb-3">🛍️</div>
<div class="text-2xl font-extrabold text-gray-900 mb-0.5">@Model.TotalOrders</div>
<div class="text-sm text-gray-500">کل سفارش‌ها</div>
</div>
<div class="bg-white rounded-2xl border border-gray-100 p-5">
<div class="w-10 h-10 rounded-xl bg-yellow-100 text-yellow-600 flex items-center justify-center text-xl mb-3">⏳</div>
<div class="text-2xl font-extrabold text-gray-900 mb-0.5">@Model.PendingOrders</div>
<div class="text-sm text-gray-500">در انتظار تأیید</div>
</div>
<div class="bg-white rounded-2xl border border-gray-100 p-5">
<div class="w-10 h-10 rounded-xl bg-green-100 text-green-600 flex items-center justify-center text-xl mb-3">💰</div>
<div class="text-lg font-extrabold text-gray-900 mb-0.5">@SiteData.FormatPrice(Model.TotalRevenue)</div>
<div class="text-sm text-gray-500">درآمد کل</div>
</div>
<div class="bg-white rounded-2xl border border-gray-100 p-5">
<div class="w-10 h-10 rounded-xl bg-purple-100 text-purple-600 flex items-center justify-center text-xl mb-3">📦</div>
<div class="text-2xl font-extrabold text-gray-900 mb-0.5">@Model.ActiveProducts</div>
<div class="text-sm text-gray-500">محصولات فعال</div>
</div>
</div>
<!-- Recent orders -->
<div class="bg-white rounded-2xl border border-gray-100 p-6">
<div class="flex items-center justify-between mb-5">
<h2 class="font-bold text-gray-900">آخرین سفارش‌ها</h2>
<a href="/Admin/Orders" class="text-sm text-blue-600 hover:underline">مشاهده همه</a>
</div>
@if (!Model.RecentOrders.Any())
{
<p class="text-gray-400 text-sm text-center py-8">هنوز سفارشی ثبت نشده</p>
}
else
{
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b text-gray-500">
<th class="pb-3 text-right font-medium">شماره</th>
<th class="pb-3 text-right font-medium">مشتری</th>
<th class="pb-3 text-right font-medium">مبلغ</th>
<th class="pb-3 text-right font-medium">وضعیت</th>
<th class="pb-3 text-right font-medium">تاریخ</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50">
@foreach (var o in Model.RecentOrders)
{
<tr class="hover:bg-gray-50">
<td class="py-3 font-mono text-xs text-gray-500">@o.OrderNumber</td>
<td class="py-3 font-medium">@o.CustomerName</td>
<td class="py-3 text-blue-700 font-bold">@SiteData.FormatPrice(o.Total)</td>
<td class="py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium @SiteData.OrderStatusBadge(o.Status)">
@SiteData.OrderStatusLabel(o.Status)
</span>
</td>
<td class="py-3 text-gray-400 text-xs">@SiteData.ToJalali(o.CreatedAt)</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
</div>
+28
View File
@@ -0,0 +1,28 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Pages.Admin;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class AdminIndexModel(AppDbContext db) : PageModel
{
public int TotalOrders { get; private set; }
public int PendingOrders { get; private set; }
public decimal TotalRevenue { get; private set; }
public int ActiveProducts { get; private set; }
public List<Order> RecentOrders { get; private set; } = [];
public async Task<IActionResult> OnGetAsync()
{
TotalOrders = await db.Orders.CountAsync();
PendingOrders = await db.Orders.CountAsync(o => o.Status == OrderStatus.Pending);
TotalRevenue = await db.Orders.Where(o => o.Status != OrderStatus.Cancelled).SumAsync(o => o.Total);
ActiveProducts = await db.Products.CountAsync(p => p.IsActive);
RecentOrders = await db.Orders.OrderByDescending(o => o.Id).Take(6).ToListAsync();
return Page();
}
}
+47
View File
@@ -0,0 +1,47 @@
@page
@model AsadiTools.Pages.Admin.LoginModel
@{ ViewData["Title"] = "ورود به پنل مدیریت"; Layout = null; }
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ورود | پنل مدیریت آساد ابزار</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2") format("woff2"); font-display:swap; }
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
* { font-family:"Vazirmatn",Tahoma,sans-serif !important; }
</style>
</head>
<body class="min-h-screen bg-gray-50 flex items-center justify-center px-4">
<div class="w-full max-w-md">
<div class="text-center mb-8">
<div class="bg-blue-700 text-white rounded-2xl p-4 inline-flex text-2xl mb-4">🔧</div>
<h1 class="text-2xl font-extrabold text-gray-900">پنل مدیریت</h1>
<p class="text-gray-500 text-sm mt-1">آساد ابزار کرج</p>
</div>
<form method="post" class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 space-y-5">
@Html.AntiForgeryToken()
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام کاربری</label>
<input asp-for="Input.Username" dir="ltr" placeholder="admin"
class="w-full border border-gray-200 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">رمز عبور</label>
<input asp-for="Input.Password" type="password" dir="ltr" placeholder="••••••••"
class="w-full border border-gray-200 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
</div>
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3 rounded-xl">@Model.ErrorMessage</div>
}
<button type="submit" class="w-full bg-blue-700 text-white py-3.5 rounded-xl font-bold hover:bg-blue-800 transition-colors">
🔒 ورود
</button>
<p class="text-xs text-gray-400 text-center">رمز پیش‌فرض: admin / admin1234</p>
</form>
</div>
</body>
</html>
+51
View File
@@ -0,0 +1,51 @@
using AsadiTools.Data;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
namespace AsadiTools.Pages.Admin;
public class LoginModel(AppDbContext db) : PageModel
{
[BindProperty]
public InputModel Input { get; set; } = new();
public string? ErrorMessage { get; private set; }
public class InputModel
{
[Required] public string Username { get; set; } = string.Empty;
[Required] public string Password { get; set; } = string.Empty;
}
public IActionResult OnGet()
{
if (User.Identity?.IsAuthenticated == true)
return RedirectToPage("/Admin/Index");
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
var user = db.AdminUsers.FirstOrDefault(u => u.Username == Input.Username);
if (user is null || !BCrypt.Net.BCrypt.Verify(Input.Password, user.PasswordHash))
{
ErrorMessage = "نام کاربری یا رمز اشتباه است";
return Page();
}
var claims = new List<Claim>
{
new(ClaimTypes.Name, user.Username),
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
};
var identity = new ClaimsIdentity(claims, "AdminCookie");
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync("AdminCookie", principal);
return RedirectToPage("/Admin/Index");
}
}
+2
View File
@@ -0,0 +1,2 @@
@page
@model AsadiTools.Pages.Admin.LogoutModel
+14
View File
@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Admin;
public class LogoutModel : PageModel
{
public async Task<IActionResult> OnPostAsync()
{
await HttpContext.SignOutAsync("AdminCookie");
return RedirectToPage("/Admin/Login");
}
}
+74
View File
@@ -0,0 +1,74 @@
@page
@model AsadiTools.Pages.Admin.Orders.OrdersIndexModel
@{ ViewData["Title"] = "سفارش‌ها"; Layout = "_AdminLayout"; }
<div class="p-6 md:p-8">
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">سفارش‌ها</h1>
@if (!Model.Orders.Any())
{
<div class="text-center py-20 text-gray-400">
<div class="text-5xl mb-4">📦</div>
<p>هنوز سفارشی ثبت نشده</p>
</div>
}
else
{
<div class="space-y-4">
@foreach (var o in Model.Orders)
{
<div class="bg-white rounded-2xl border border-gray-100 p-6">
<div class="flex flex-wrap items-start justify-between gap-4 mb-4">
<div>
<div class="flex items-center gap-3 mb-1">
<span class="font-mono text-sm text-gray-500">@o.OrderNumber</span>
<span class="text-xs px-2 py-0.5 rounded-full font-medium @SiteData.OrderStatusBadge(o.Status)">
@SiteData.OrderStatusLabel(o.Status)
</span>
</div>
<div class="font-bold text-gray-900">@o.CustomerName</div>
<div class="text-sm text-gray-500">@o.CustomerPhone</div>
@if (o.CustomerAddress != null) { <div class="text-xs text-gray-400 mt-1">@o.CustomerAddress</div> }
</div>
<div class="text-left">
<div class="text-xl font-extrabold text-blue-700">@SiteData.FormatPrice(o.Total)</div>
<div class="text-xs text-gray-400 mt-1">@SiteData.ToJalaliWithTime(o.CreatedAt)</div>
</div>
</div>
<!-- Items -->
<div class="bg-gray-50 rounded-xl p-4 mb-4">
<p class="text-xs text-gray-500 font-bold mb-2">اقلام سفارش:</p>
<ul class="space-y-1">
@foreach (var item in o.Items)
{
<li class="flex justify-between text-sm">
<span class="text-gray-700">@item.ProductNameFa × @item.Quantity</span>
<span class="text-gray-500">@SiteData.FormatPrice(item.Subtotal)</span>
</li>
}
</ul>
</div>
@if (o.Notes != null) { <p class="text-sm text-gray-500 mb-4">یادداشت: @o.Notes</p> }
<!-- Status update -->
<form method="post" asp-page-handler="UpdateStatus" class="flex items-center gap-3">
@Html.AntiForgeryToken()
<input type="hidden" name="id" value="@o.Id" />
<span class="text-sm text-gray-500 font-medium">وضعیت:</span>
<select name="status" class="border border-gray-200 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
@foreach (var s in Enum.GetValues<AsadiTools.Models.OrderStatus>())
{
<option value="@s" selected="@(o.Status == s)">@SiteData.OrderStatusLabel(s)</option>
}
</select>
<button type="submit" class="bg-blue-700 text-white px-4 py-1.5 rounded-lg text-sm font-medium hover:bg-blue-800 transition-colors">
ذخیره
</button>
</form>
</div>
}
</div>
}
</div>
+31
View File
@@ -0,0 +1,31 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Pages.Admin.Orders;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class OrdersIndexModel(AppDbContext db) : PageModel
{
public List<Order> Orders { get; private set; } = [];
public async Task OnGetAsync() =>
Orders = await db.Orders
.Include(o => o.Items)
.OrderByDescending(o => o.Id)
.ToListAsync();
public async Task<IActionResult> OnPostUpdateStatusAsync(int id, OrderStatus status)
{
var order = await db.Orders.FindAsync(id);
if (order is not null)
{
order.Status = status;
await db.SaveChangesAsync();
}
return RedirectToPage();
}
}
+15
View File
@@ -0,0 +1,15 @@
@page
@model AsadiTools.Pages.Admin.Products.CreateModel
@{ ViewData["Title"] = "افزودن محصول"; Layout = "_AdminLayout"; }
<div class="p-6 md:p-8 max-w-2xl">
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">افزودن محصول جدید</h1>
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6 space-y-5">
@Html.AntiForgeryToken()
@await Html.PartialAsync("_ProductFormFields", Model.Input)
<div class="flex gap-3">
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">افزودن محصول</button>
<a href="/Admin/Products" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">انصراف</a>
</div>
</form>
</div>
+52
View File
@@ -0,0 +1,52 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
namespace AsadiTools.Pages.Admin.Products;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class CreateModel(AppDbContext db) : PageModel
{
[BindProperty] public ProductInput Input { get; set; } = new();
public void OnGet() { }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
db.Products.Add(new Product
{
NameFa = Input.NameFa,
NameEn = Input.NameEn,
Description = Input.Description,
Price = Input.Price,
DiscountPrice = Input.DiscountPrice,
Category = Input.Category,
Brand = string.IsNullOrEmpty(Input.Brand) ? null : Input.Brand,
Sku = Input.Sku,
Stock = Input.Stock,
IsActive = Input.IsActive,
ImageUrl = string.IsNullOrWhiteSpace(Input.ImageUrl) ? null : Input.ImageUrl,
});
await db.SaveChangesAsync();
return RedirectToPage("/Admin/Products/Index");
}
}
public class ProductInput
{
[Required] public string NameFa { get; set; } = string.Empty;
public string? NameEn { get; set; }
public string? Description { get; set; }
[Required, Range(1, int.MaxValue)] public decimal Price { get; set; }
public decimal? DiscountPrice { get; set; }
[Required] public string Category { get; set; } = "carbon";
public string? Brand { get; set; }
public string? Sku { get; set; }
public int Stock { get; set; }
public bool IsActive { get; set; } = true;
public string? ImageUrl { get; set; }
}
+16
View File
@@ -0,0 +1,16 @@
@page
@model AsadiTools.Pages.Admin.Products.EditModel
@{ ViewData["Title"] = "ویرایش محصول"; Layout = "_AdminLayout"; }
<div class="p-6 md:p-8 max-w-2xl">
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">ویرایش محصول</h1>
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6 space-y-5">
@Html.AntiForgeryToken()
<input type="hidden" name="id" value="@Model.ProductId" />
@await Html.PartialAsync("_ProductFormFields", Model.Input)
<div class="flex gap-3">
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">ذخیره تغییرات</button>
<a href="/Admin/Products" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">انصراف</a>
</div>
</form>
</div>
+57
View File
@@ -0,0 +1,57 @@
using AsadiTools.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Admin.Products;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class EditModel(AppDbContext db) : PageModel
{
[BindProperty] public ProductInput Input { get; set; } = new();
public int ProductId { get; private set; }
public async Task<IActionResult> OnGetAsync(int id)
{
var p = await db.Products.FindAsync(id);
if (p is null) return NotFound();
ProductId = id;
Input = new ProductInput
{
NameFa = p.NameFa,
NameEn = p.NameEn,
Description = p.Description,
Price = p.Price,
DiscountPrice = p.DiscountPrice,
Category = p.Category,
Brand = p.Brand,
Sku = p.Sku,
Stock = p.Stock,
IsActive = p.IsActive,
ImageUrl = p.ImageUrl,
};
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
if (!ModelState.IsValid) { ProductId = id; return Page(); }
var p = await db.Products.FindAsync(id);
if (p is null) return NotFound();
p.NameFa = Input.NameFa;
p.NameEn = Input.NameEn;
p.Description = Input.Description;
p.Price = Input.Price;
p.DiscountPrice = Input.DiscountPrice;
p.Category = Input.Category;
p.Brand = string.IsNullOrEmpty(Input.Brand) ? null : Input.Brand;
p.Sku = Input.Sku;
p.Stock = Input.Stock;
p.IsActive = Input.IsActive;
p.ImageUrl = string.IsNullOrWhiteSpace(Input.ImageUrl) ? null : Input.ImageUrl;
await db.SaveChangesAsync();
return RedirectToPage("/Admin/Products/Index");
}
}
+70
View File
@@ -0,0 +1,70 @@
@page
@model AsadiTools.Pages.Admin.Products.ProductsIndexModel
@{ ViewData["Title"] = "محصولات"; Layout = "_AdminLayout"; }
<div class="p-6 md:p-8">
<div class="flex items-center justify-between mb-8">
<h1 class="text-2xl font-extrabold text-gray-900">محصولات</h1>
<a href="/Admin/Products/Create" class="flex items-center gap-2 bg-blue-700 text-white px-4 py-2.5 rounded-xl text-sm font-bold hover:bg-blue-800 transition-colors">
➕ افزودن محصول
</a>
</div>
<div class="bg-white rounded-2xl border border-gray-100 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b">
<tr>
<th class="px-5 py-3.5 text-right font-medium text-gray-500">نام</th>
<th class="px-5 py-3.5 text-right font-medium text-gray-500">دسته</th>
<th class="px-5 py-3.5 text-right font-medium text-gray-500">برند</th>
<th class="px-5 py-3.5 text-right font-medium text-gray-500">قیمت</th>
<th class="px-5 py-3.5 text-right font-medium text-gray-500">موجودی</th>
<th class="px-5 py-3.5 text-right font-medium text-gray-500">وضعیت</th>
<th class="px-5 py-3.5 text-right font-medium text-gray-500">عملیات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50">
@foreach (var p in Model.Products)
{
var cat = SiteData.Categories.FirstOrDefault(c => c.Id == p.Category);
var brand = SiteData.Brands.FirstOrDefault(b => b.Id == p.Brand);
<tr class="hover:bg-gray-50/50">
<td class="px-5 py-4">
<div class="font-medium text-gray-900">@p.NameFa</div>
@if (p.Sku != null) { <div class="text-xs text-gray-400 font-mono">@p.Sku</div> }
</td>
<td class="px-5 py-4 text-gray-500">@(cat != null ? cat.Icon + " " + cat.NameFa : p.Category)</td>
<td class="px-5 py-4">
@if (brand != null)
{
<span class="text-xs font-bold px-2 py-0.5 rounded-full text-white" style="background-color:@brand.Color">@brand.NameFa</span>
}
else { <span class="text-gray-400"></span> }
</td>
<td class="px-5 py-4 font-bold text-blue-700">@SiteData.FormatPrice(p.Price)</td>
<td class="px-5 py-4">
<span class="font-bold @(p.Stock == 0 ? "text-red-500" : p.Stock < 5 ? "text-yellow-500" : "text-green-600")">@p.Stock</span>
</td>
<td class="px-5 py-4">
<span class="text-xs px-2 py-1 rounded-full font-medium @(p.IsActive ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-500")">
@(p.IsActive ? "فعال" : "غیرفعال")
</span>
</td>
<td class="px-5 py-4">
<div class="flex items-center gap-3">
<a href="/Admin/Products/Edit?id=@p.Id" class="text-blue-600 hover:text-blue-800 text-xs font-medium">ویرایش</a>
<form method="post" asp-page-handler="Delete" onsubmit="return confirm('حذف شود؟')">
@Html.AntiForgeryToken()
<input type="hidden" name="id" value="@p.Id" />
<button type="submit" class="text-red-400 hover:text-red-600 text-xs font-medium">حذف</button>
</form>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
+24
View File
@@ -0,0 +1,24 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Pages.Admin.Products;
[Authorize(AuthenticationSchemes = "AdminCookie")]
public class ProductsIndexModel(AppDbContext db) : PageModel
{
public List<Product> Products { get; private set; } = [];
public async Task OnGetAsync() =>
Products = await db.Products.OrderByDescending(p => p.Id).ToListAsync();
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var p = await db.Products.FindAsync(id);
if (p is not null) { p.IsActive = false; await db.SaveChangesAsync(); }
return RedirectToPage();
}
}
@@ -0,0 +1,74 @@
@model AsadiTools.Pages.Admin.Products.ProductInput
@using AsadiTools.Services
@{
var inputCls = "w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500";
}
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2">
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام فارسی <span class="text-red-500">*</span></label>
<input asp-for="NameFa" class="@inputCls" placeholder="مثال: کاربن دیوالت DCD776" />
<span asp-validation-for="NameFa" class="text-red-500 text-xs"></span>
</div>
<div class="col-span-2">
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام انگلیسی</label>
<input asp-for="NameEn" class="@inputCls" dir="ltr" placeholder="e.g. DeWalt DCD776 Carbon Brush" />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">کد محصول (SKU)</label>
<input asp-for="Sku" class="@inputCls" dir="ltr" placeholder="DW-CBR-776" />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">دسته‌بندی <span class="text-red-500">*</span></label>
<select asp-for="Category" class="@inputCls">
@foreach (var c in SiteData.Categories)
{
<option value="@c.Id">@c.Icon @c.NameFa</option>
}
</select>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">برند</label>
<select asp-for="Brand" class="@inputCls">
<option value="">بدون برند</option>
@foreach (var b in SiteData.Brands)
{
<option value="@b.Id">@b.NameFa</option>
}
</select>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">موجودی</label>
<input asp-for="Stock" type="number" min="0" class="@inputCls" dir="ltr" />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">قیمت (تومان) <span class="text-red-500">*</span></label>
<input asp-for="Price" type="number" min="0" class="@inputCls" dir="ltr" />
<span asp-validation-for="Price" class="text-red-500 text-xs"></span>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">قیمت با تخفیف</label>
<input asp-for="DiscountPrice" type="number" min="0" class="@inputCls" dir="ltr" />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">وضعیت</label>
<select asp-for="IsActive" class="@inputCls">
<option value="true">فعال</option>
<option value="false">غیرفعال</option>
</select>
</div>
<div class="col-span-2">
<label class="text-sm font-medium text-gray-700 mb-1.5 block">توضیحات</label>
<textarea asp-for="Description" rows="3" class="@inputCls resize-none"></textarea>
</div>
<div class="col-span-2">
<label class="text-sm font-medium text-gray-700 mb-1.5 block">آدرس تصویر (URL)</label>
<input asp-for="ImageUrl" class="@inputCls" dir="ltr" placeholder="https://..." />
<p class="text-xs text-gray-400 mt-1">لینک مستقیم به تصویر محصول. در صورت خالی ماندن آیکون دسته‌بندی نمایش داده می‌شود.</p>
@if (!string.IsNullOrEmpty(Model?.ImageUrl))
{
<img src="@Model.ImageUrl" alt="پیش‌نمایش" class="mt-2 h-24 w-24 object-cover rounded-xl border border-gray-200" onerror="this.style.display='none'" />
}
</div>
</div>
+96
View File
@@ -0,0 +1,96 @@
@page
@model AsadiTools.Pages.Blog.BlogIndexModel
@{ Layout = "_Layout"; }
<div class="bg-blue-800 text-white py-12 px-4">
<div class="max-w-6xl mx-auto">
<nav class="flex items-center gap-2 text-sm text-blue-300 mb-4">
<a href="/" class="hover:text-white">خانه</a><span>/</span>
<span class="text-white">بلاگ</span>
</nav>
<h1 class="text-3xl font-extrabold mb-2">بلاگ آساد ابزار</h1>
<p class="text-blue-200">راهنما، نکات فنی و مقالات تخصصی تعمیر ابزار برقی</p>
</div>
</div>
<div class="max-w-6xl mx-auto px-4 py-10">
@if (!string.IsNullOrEmpty(Model.Tag))
{
<div class="mb-6 flex items-center gap-3">
<span class="bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm font-bold">برچسب: @Model.Tag</span>
<a href="/blog" class="text-sm text-gray-400 hover:text-gray-600">× حذف فیلتر</a>
</div>
}
@if (!Model.Posts.Any())
{
<div class="text-center py-20 text-gray-400">
<div class="text-5xl mb-4">📝</div>
<p>مقاله‌ای یافت نشد.</p>
<a href="/blog" class="text-blue-600 text-sm mt-2 block hover:underline">مشاهده همه مقالات</a>
</div>
}
else
{
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach (var post in Model.Posts)
{
<article class="bg-white rounded-2xl overflow-hidden border border-gray-100 hover:shadow-lg transition-shadow flex flex-col">
@if (!string.IsNullOrEmpty(post.FeaturedImage))
{
<a href="/blog/@post.EffectiveSlug" class="block overflow-hidden" style="height:200px">
<img src="@post.FeaturedImage" alt="@post.Title" loading="lazy"
class="w-full h-full object-cover hover:scale-105 transition-transform duration-500" />
</a>
}
<div class="p-5 flex flex-col flex-1">
@if (post.TagList.Any())
{
<div class="flex flex-wrap gap-1.5 mb-3">
@foreach (var tag in post.TagList.Take(3))
{
<a href="/blog?tag=@Uri.EscapeDataString(tag)"
class="text-xs bg-blue-50 text-blue-600 px-2 py-0.5 rounded-full hover:bg-blue-100 transition-colors">@tag</a>
}
</div>
}
<h2 class="font-bold text-lg text-gray-900 mb-2 leading-snug hover:text-blue-700 transition-colors">
<a href="/blog/@post.EffectiveSlug">@post.Title</a>
</h2>
@if (!string.IsNullOrEmpty(post.Excerpt))
{
<p class="text-sm text-gray-500 leading-7 mb-4 line-clamp-3 flex-1">@post.Excerpt</p>
}
<div class="flex items-center justify-between mt-auto pt-3 border-t border-gray-50">
<span class="text-xs text-gray-400">📅 @post.DisplayDate</span>
<a href="/blog/@post.EffectiveSlug"
class="text-sm text-blue-600 font-medium hover:underline">ادامه مطلب </a>
</div>
</div>
</article>
}
</div>
@if (Model.TotalPages > 1)
{
<div class="flex justify-center items-center gap-2 mt-10">
@if (Model.CurrentPage > 1)
{
<a href="/blog?page=@(Model.CurrentPage - 1)@(Model.Tag != null ? "&tag=" + Uri.EscapeDataString(Model.Tag) : "")"
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors"> قبلی</a>
}
@for (var i = Math.Max(1, Model.CurrentPage - 2); i <= Math.Min(Model.TotalPages, Model.CurrentPage + 2); i++)
{
<a href="/blog?page=@i@(Model.Tag != null ? "&tag=" + Uri.EscapeDataString(Model.Tag) : "")"
class="px-4 py-2 rounded-xl border text-sm transition-colors @(i == Model.CurrentPage ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">@i</a>
}
@if (Model.CurrentPage < Model.TotalPages)
{
<a href="/blog?page=@(Model.CurrentPage + 1)@(Model.Tag != null ? "&tag=" + Uri.EscapeDataString(Model.Tag) : "")"
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors">بعدی </a>
}
</div>
}
}
</div>
+36
View File
@@ -0,0 +1,36 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Pages.Blog;
public class BlogIndexModel(AppDbContext db) : PageModel
{
public const int PageSize = 6;
public List<BlogPost> Posts { get; private set; } = [];
public int CurrentPage { get; private set; } = 1;
public int TotalPages { get; private set; }
public string? Tag { get; private set; }
public async Task OnGetAsync(string? tag, int page = 1)
{
Tag = tag;
var q = db.BlogPosts.Where(p => p.IsPublished);
if (!string.IsNullOrEmpty(tag))
q = q.Where(p => p.Tags != null && p.Tags.Contains(tag));
var total = await q.CountAsync();
TotalPages = (int)Math.Ceiling(total / (double)PageSize);
CurrentPage = Math.Clamp(page, 1, Math.Max(1, TotalPages));
Posts = await q.OrderByDescending(p => p.PublishedAt)
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
ViewData["Title"] = "بلاگ آساد ابزار — راهنما و مقالات تعمیر ابزار";
ViewData["Description"] = "مقالات تخصصی تعمیر و نگهداری ابزار برقی. راهنمای خرید، نکات فنی و اخبار صنعت ابزار از آساد ابزار کرج.";
}
}
+143
View File
@@ -0,0 +1,143 @@
@page "/blog/{slug}"
@model AsadiTools.Pages.Blog.BlogPostModel
@{ Layout = "_Layout"; var p = Model.Post!; var c = SiteData.Company; }
@section Head {
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "BlogPosting",
"headline": "@p.Title.Replace("\"","'")",
"description": "@((p.MetaDescription ?? p.Excerpt ?? "").Replace("\"","'"))",
"image": "@(p.FeaturedImage ?? "")",
"datePublished": "@(p.PublishedAt?.ToString("yyyy-MM-dd") ?? p.CreatedAt.ToString("yyyy-MM-dd"))",
"dateModified": "@p.UpdatedAt.ToString("yyyy-MM-dd")",
"author": { "@@type": "Organization", "name": "آساد ابزار کرج" },
"publisher": { "@@type": "Organization", "name": "آساد ابزار کرج", "logo": { "@@type": "ImageObject", "url": "" } },
"mainEntityOfPage": { "@@type": "WebPage", "@@id": "/blog/@p.EffectiveSlug" }
}
</script>
}
<div class="max-w-6xl mx-auto px-4 py-10">
<div class="grid lg:grid-cols-3 gap-10">
<!-- ── Article ──────────────────────────────────────────────────── -->
<article class="lg:col-span-2">
<!-- Breadcrumb -->
<nav class="flex items-center gap-2 text-sm text-gray-400 mb-6">
<a href="/" class="hover:text-blue-600">خانه</a><span>/</span>
<a href="/blog" class="hover:text-blue-600">بلاگ</a><span>/</span>
<span class="text-gray-700 line-clamp-1">@p.Title</span>
</nav>
<!-- Tags -->
@if (p.TagList.Any())
{
<div class="flex flex-wrap gap-1.5 mb-4">
@foreach (var tag in p.TagList)
{
<a href="/blog?tag=@Uri.EscapeDataString(tag)"
class="text-xs bg-blue-50 text-blue-600 px-2.5 py-1 rounded-full hover:bg-blue-100 transition-colors">@tag</a>
}
</div>
}
<h1 class="text-3xl font-extrabold text-gray-900 leading-tight mb-4">@p.Title</h1>
<div class="flex items-center gap-4 text-sm text-gray-400 mb-8 pb-8 border-b">
<span>📅 @p.DisplayDate</span>
<span>✍️ آساد ابزار کرج</span>
</div>
@if (!string.IsNullOrEmpty(p.FeaturedImage))
{
<div class="rounded-2xl overflow-hidden mb-8" style="max-height:420px">
<img src="@p.FeaturedImage" alt="@p.Title" class="w-full h-full object-cover" loading="eager" />
</div>
}
<!-- Content -->
<div class="prose prose-lg max-w-none text-gray-700 leading-8
[&_h2]:text-2xl [&_h2]:font-extrabold [&_h2]:text-gray-900 [&_h2]:mt-10 [&_h2]:mb-4 [&_h2]:pb-2 [&_h2]:border-b
[&_h3]:text-xl [&_h3]:font-bold [&_h3]:text-gray-800 [&_h3]:mt-6 [&_h3]:mb-3
[&_p]:mb-4 [&_p]:leading-8
[&_ul]:mb-4 [&_ul]:space-y-2 [&_ul]:list-disc [&_ul]:pr-6
[&_ol]:mb-4 [&_ol]:space-y-2 [&_ol]:list-decimal [&_ol]:pr-6
[&_li]:leading-7
[&_blockquote]:border-r-4 [&_blockquote]:border-blue-400 [&_blockquote]:pr-4 [&_blockquote]:italic [&_blockquote]:text-gray-600 [&_blockquote]:my-6
[&_strong]:font-bold [&_strong]:text-gray-900
[&_table]:w-full [&_table]:border-collapse [&_table]:my-6
[&_th]:bg-gray-100 [&_th]:p-3 [&_th]:text-right [&_th]:font-bold [&_th]:border [&_th]:border-gray-200
[&_td]:p-3 [&_td]:border [&_td]:border-gray-200 [&_td]:text-right">
@Html.Raw(p.Content)
</div>
<!-- Related -->
@if (Model.RelatedPosts.Any())
{
<div class="mt-12 pt-8 border-t">
<h2 class="text-xl font-bold text-gray-900 mb-5">مقالات مرتبط</h2>
<div class="grid sm:grid-cols-3 gap-4">
@foreach (var rp in Model.RelatedPosts)
{
<a href="/blog/@rp.EffectiveSlug"
class="group bg-white rounded-xl border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
@if (!string.IsNullOrEmpty(rp.FeaturedImage))
{
<div style="height:120px" class="overflow-hidden">
<img src="@rp.FeaturedImage" alt="@rp.Title" loading="lazy"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" />
</div>
}
<div class="p-3">
<p class="text-sm font-medium text-gray-800 leading-snug line-clamp-2 group-hover:text-blue-700">@rp.Title</p>
<p class="text-xs text-gray-400 mt-1">@rp.DisplayDate</p>
</div>
</a>
}
</div>
</div>
}
</article>
<!-- ── Sidebar ───────────────────────────────────────────────────── -->
<aside class="space-y-5 lg:sticky lg:top-24 lg:self-start">
<!-- CTA -->
<div class="bg-blue-700 text-white rounded-2xl p-6 text-center">
<div class="text-3xl mb-2">🔧</div>
<h3 class="font-extrabold mb-2">تعمیر ابزار در کرج</h3>
<p class="text-blue-200 text-sm mb-5">تشخیص رایگان • ضمانت ۳ ماهه</p>
<a href="tel:@c.TelPhone"
class="block bg-white text-blue-700 font-bold py-3 rounded-xl hover:opacity-90 mb-3 transition-opacity">
📞 @c.Phone
</a>
<a href="https://wa.me/@c.Whatsapp" target="_blank"
class="block bg-green-500 text-white font-bold py-3 rounded-xl hover:bg-green-600 transition-colors">
💬 واتساپ
</a>
</div>
<!-- Tags cloud -->
@if (p.TagList.Any())
{
<div class="bg-white rounded-2xl border border-gray-100 p-5">
<h3 class="font-bold text-gray-900 mb-3 text-sm">برچسب‌ها</h3>
<div class="flex flex-wrap gap-2">
@foreach (var tag in p.TagList)
{
<a href="/blog?tag=@Uri.EscapeDataString(tag)"
class="text-xs bg-gray-100 text-gray-600 px-2.5 py-1 rounded-full hover:bg-blue-100 hover:text-blue-700 transition-colors">@tag</a>
}
</div>
</div>
}
<a href="/blog" class="block bg-gray-50 border border-gray-200 rounded-2xl p-5 hover:shadow-md transition-shadow text-center">
<span class="text-xl block mb-1">📖</span>
<span class="font-bold text-gray-800 text-sm">مشاهده همه مقالات</span>
</a>
</aside>
</div>
</div>
+44
View File
@@ -0,0 +1,44 @@
using AsadiTools.Data;
using AsadiTools.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Pages.Blog;
public class BlogPostModel(AppDbContext db) : PageModel
{
public BlogPost? Post { get; private set; }
public List<BlogPost> RelatedPosts { get; private set; } = [];
public async Task<IActionResult> OnGetAsync(string slug)
{
Post = await db.BlogPosts
.Where(p => p.IsPublished && p.Slug == slug)
.FirstOrDefaultAsync();
// Fallback: numeric slug = post ID
if (Post is null && int.TryParse(slug, out var id))
Post = await db.BlogPosts.Where(p => p.IsPublished && p.Id == id).FirstOrDefaultAsync();
if (Post is null) return NotFound();
// Related: same tag(s)
var tags = Post.TagList;
if (tags.Length > 0)
{
RelatedPosts = await db.BlogPosts
.Where(p => p.IsPublished && p.Id != Post.Id && p.Tags != null)
.ToListAsync();
RelatedPosts = RelatedPosts
.Where(p => p.TagList.Any(t => tags.Contains(t)))
.OrderByDescending(p => p.PublishedAt)
.Take(3)
.ToList();
}
ViewData["Title"] = Post.Title + " | آساد ابزار";
ViewData["Description"] = Post.MetaDescription ?? Post.Excerpt ?? Post.Title;
return Page();
}
}
+333
View File
@@ -0,0 +1,333 @@
@page "/brands/{brand}"
@model AsadiTools.Pages.Brands.BrandDetailModel
@{ Layout = "_Layout"; var b = Model.Brand!; var c = SiteData.Company; }
@section Head {
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@graph": [
{
"@@type": "LocalBusiness",
"name": "آساد ابزار کرج",
"telephone": "+98@c.TelPhone.Substring(1)",
"address": { "@@type": "PostalAddress", "addressLocality": "کرج", "addressRegion": "البرز", "addressCountry": "IR" },
"openingHours": "Mo-Sa 08:00-18:00",
"priceRange": "$$"
},
{
"@@type": "Service",
"name": "تعمیر ابزار @b.NameFa در کرج",
"provider": { "@@type": "LocalBusiness", "name": "آساد ابزار کرج" },
"areaServed": { "@@type": "City", "name": "کرج" },
"description": "@ViewData["Description"]",
"offers": { "@@type": "Offer", "availability": "https://schema.org/InStock" }
},
{
"@@type": "FAQPage",
"mainEntity": [
@for (int i = 0; i < b.Faqs.Length; i++) {
var faq = b.Faqs[i];
<text>{ "@@type": "Question", "name": "@faq.Q.Replace("\"","'")", "acceptedAnswer": { "@@type": "Answer", "text": "@faq.A.Replace("\"","'")" } }@(i < b.Faqs.Length - 1 ? "," : "")</text>
}
]
},
{
"@@type": "BreadcrumbList",
"itemListElement": [
{ "@@type": "ListItem", "position": 1, "name": "خانه", "item": "/" },
{ "@@type": "ListItem", "position": 2, "name": "برندها", "item": "/brands" },
{ "@@type": "ListItem", "position": 3, "name": "تعمیر ابزار @b.NameFa", "item": "/brands/@b.Id" }
]
}
]
}
</script>
}
<!-- ═══════════════════════ HERO ══════════════════════════════════════════ -->
<div class="relative text-white py-16 px-4 overflow-hidden" style="min-height:320px">
<div class="absolute inset-0">
<img src="@b.HeroImage" alt="تعمیر ابزار @b.NameFa در کرج" class="w-full h-full object-cover" />
<div class="absolute inset-0" style="background:linear-gradient(135deg, @b.Color+ee 0%, @b.Color+bb 50%, rgba(0,0,0,0.7) 100%)"></div>
</div>
<div class="relative max-w-6xl mx-auto">
<!-- Breadcrumb -->
<nav class="flex items-center gap-2 text-sm mb-6 opacity-80" style="color:@(b.TextColor == "#fff" ? "white" : "#374151")">
<a href="/" class="hover:opacity-100">خانه</a>
<span>/</span>
<a href="/brands" class="hover:opacity-100">برندها</a>
<span>/</span>
<span class="opacity-100 font-medium">@b.NameFa</span>
</nav>
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-6">
<div class="w-24 h-24 rounded-2xl flex items-center justify-center text-2xl font-extrabold border-2 border-white/40 bg-white/20 backdrop-blur shrink-0 shadow-xl"
style="color:@b.TextColor">
@b.Name.Substring(0, Math.Min(3, b.Name.Length))
</div>
<div>
<div class="flex flex-wrap items-center gap-3 mb-3">
<h1 class="text-4xl font-extrabold" style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
تعمیر ابزار @b.NameFa در کرج
</h1>
@if (b.IsOfficial)
{
<span class="bg-yellow-400 text-gray-900 text-sm font-bold px-3 py-1 rounded-full shadow-lg">🛡️ نمایندگی رسمی</span>
}
</div>
<p class="text-lg max-w-2xl leading-7 opacity-90" style="color:@(b.TextColor == "#fff" ? "#e5e7eb" : "#374151")">
@b.Tagline
</p>
<div class="flex flex-wrap gap-2 mt-4 text-sm opacity-80" style="color:@(b.TextColor == "#fff" ? "#d1d5db" : "#6b7280")">
<span>📍 تأسیس @b.Founded</span>
<span>·</span>
<span>🌍 @b.Country</span>
@if (b.IsOfficial) { <span>·</span> <span class="text-yellow-300 font-bold">✅ نمایندگی رسمی در کرج</span> }
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════ MAIN GRID ═════════════════════════════════════ -->
<div class="max-w-6xl mx-auto px-4 py-12 grid lg:grid-cols-3 gap-10">
<!-- ══ LEFT / MAIN ════════════════════════════════════════════════════ -->
<div class="lg:col-span-2 space-y-12">
@if (b.IsOfficial) {
<!-- Official badge -->
<div class="bg-yellow-50 border-2 border-yellow-300 rounded-2xl p-6">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-yellow-400 rounded-xl flex items-center justify-center text-2xl shrink-0">🛡️</div>
<div>
<h2 class="text-xl font-extrabold text-gray-900 mb-2">نمایندگی رسمی مجاز @b.NameFa در کرج</h2>
<p class="text-gray-600 leading-7">آساد ابزار تنها نمایندگی رسمی و مجاز برند @b.NameFa در شهر کرج و استان البرز است. تمام تعمیرات توسط تکنیسین‌های آموزش‌دیده رسمی و با قطعات کاملاً اورجینال @b.NameFa انجام می‌شود. هر تعمیر دارای ضمانت‌نامه کتبی ۳ ماهه است.</p>
</div>
</div>
</div>
}
<!-- About brand -->
<section>
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2 flex items-center gap-3" style="border-color:@b.Color">
<span class="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-bold text-white" style="background:@b.Color">@b.Name[0]</span>
درباره برند @b.NameFa
</h2>
<div class="prose prose-lg max-w-none space-y-4 text-gray-600 leading-8">
<p>@b.About1</p>
<p>@b.About2</p>
<p>@b.About3</p>
</div>
</section>
<!-- Popular models -->
<section>
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
مدل‌های محبوب @b.NameFa در ایران
</h2>
<div class="grid sm:grid-cols-2 gap-4">
@foreach (var m in b.Models)
{
<div class="bg-white rounded-2xl border border-gray-100 p-5 hover:shadow-md transition-shadow hover:border-gray-200">
<div class="flex items-center justify-between mb-2">
<span class="font-mono text-sm font-bold text-gray-800">@m.Model</span>
<span class="text-xs font-bold px-2 py-0.5 rounded-full text-white" style="background:@b.Color">@m.Watts</span>
</div>
<div class="font-semibold text-gray-900 mb-1">@m.NameFa</div>
<p class="text-sm text-gray-500 leading-6">@m.Desc</p>
</div>
}
</div>
</section>
<!-- Repair services -->
<section>
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
خدمات تعمیر @b.NameFa در آساد ابزار
</h2>
<div class="grid sm:grid-cols-2 gap-3">
@foreach (var svc in b.RepairServices)
{
<div class="flex items-start gap-3 bg-white rounded-xl border border-gray-100 p-4 hover:border-gray-200 transition-colors">
<span class="w-6 h-6 rounded-full flex items-center justify-center text-white text-xs font-bold shrink-0 mt-0.5" style="background:@b.Color">✓</span>
<span class="text-sm text-gray-700 leading-6">@svc</span>
</div>
}
</div>
</section>
<!-- Common problems -->
<section>
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
مشکلات رایج ابزار @b.NameFa و راه‌حل
</h2>
<div class="space-y-4">
@foreach (var prob in b.CommonProblems)
{
<div class="bg-white rounded-2xl border border-gray-100 p-6">
<h3 class="font-bold text-gray-900 mb-3 flex items-center gap-2">
<span class="w-6 h-6 rounded-full bg-red-100 text-red-600 flex items-center justify-center text-xs shrink-0">!</span>
@prob.Problem
</h3>
<div class="grid sm:grid-cols-2 gap-4 text-sm">
<div class="bg-red-50 rounded-xl p-3">
<div class="text-xs font-bold text-red-700 mb-1">علت احتمالی:</div>
<div class="text-gray-600 leading-6">@prob.Cause</div>
</div>
<div class="bg-green-50 rounded-xl p-3">
<div class="text-xs font-bold text-green-700 mb-1">راه‌حل:</div>
<div class="text-gray-600 leading-6">@prob.Solution</div>
</div>
</div>
</div>
}
</div>
</section>
<!-- Repair process -->
<section>
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
فرآیند تعمیر ابزار @b.NameFa
</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
@{ var steps = new[] { ("۱","تحویل ابزار","حضوری یا از طریق پست"), ("۲","بررسی رایگان","تشخیص دقیق عیب"), ("۳","تعمیر تخصصی","قطعه اصل + ضمانت"), ("۴","تحویل با گارانتی","ضمانت‌نامه کتبی ۳ ماهه") }; }
@foreach (var step in steps)
{
<div class="text-center p-5 bg-gray-50 rounded-2xl border border-gray-100 hover:bg-white hover:shadow-sm transition-all">
<div class="w-11 h-11 rounded-full flex items-center justify-center text-lg font-extrabold mx-auto mb-3 shadow"
style="background:@b.Color;color:@b.TextColor">@step.Item1</div>
<div class="font-bold text-gray-800 text-sm mb-1">@step.Item2</div>
<div class="text-xs text-gray-400">@step.Item3</div>
</div>
}
</div>
</section>
<!-- FAQ -->
<section>
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
سوالات متداول درباره تعمیر ابزار @b.NameFa
</h2>
<div class="space-y-3" id="faq-list">
@for (int i = 0; i < b.Faqs.Length; i++)
{
var faq = b.Faqs[i];
<details class="bg-white rounded-2xl border border-gray-100 overflow-hidden group" @(i == 0 ? "open" : "")>
<summary class="flex items-center justify-between p-5 cursor-pointer font-bold text-gray-900 hover:bg-gray-50 select-none list-none">
<span>@faq.Q</span>
<span class="text-gray-400 group-open:rotate-180 transition-transform shrink-0 mr-3">▾</span>
</summary>
<div class="px-5 pb-5 text-gray-600 leading-8 text-sm border-t border-gray-50 pt-4">@faq.A</div>
</details>
}
</div>
</section>
</div>
<!-- ══ SIDEBAR ══════════════════════════════════════════════════════ -->
<div class="space-y-5 lg:sticky lg:top-24 lg:self-start">
<!-- CTA card -->
<div class="rounded-2xl p-6 text-center shadow-lg" style="background:@b.Color">
<div class="text-2xl mb-2">🔧</div>
<h3 class="font-extrabold text-lg mb-1" style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
درخواست تعمیر @b.NameFa
</h3>
<p class="text-sm mb-5 opacity-80" style="color:@(b.TextColor == "#fff" ? "#e5e7eb" : "#374151")">
مشاوره رایگان – تشخیص رایگان
</p>
<a href="tel:@c.TelPhone"
class="block bg-white font-bold text-center py-3 rounded-xl hover:opacity-90 mb-3 transition-opacity shadow"
style="color:@b.Color">
📞 @c.Phone
</a>
<a href="https://wa.me/@c.Whatsapp" target="_blank"
class="block bg-green-500 text-white font-bold text-center py-3 rounded-xl hover:bg-green-600 transition-colors">
💬 پیام واتساپ
</a>
</div>
<!-- Guarantees -->
<div class="bg-white rounded-2xl border border-gray-100 p-6">
<h3 class="font-bold text-gray-900 mb-4 flex items-center gap-2">
<span class="w-6 h-6 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-xs">⭐</span>
مزایای تعمیر در آساد ابزار
</h3>
<ul class="space-y-3 text-sm text-gray-600">
@foreach (var item in new[] {
"تشخیص عیب رایگان بدون پیش‌پرداخت",
"قطعات ۱۰۰٪ اورجینال تأییدشده",
"ضمانت‌نامه کتبی ۳ ماهه",
"تکنیسین مجاز و متخصص",
"قیمت شفاف پیش از شروع",
"تحویل سریع زیر ۴۸ ساعت",
"ارسال از سراسر کشور"
})
{
<li class="flex items-center gap-2">
<span class="text-green-500 shrink-0">✓</span> @item
</li>
}
</ul>
</div>
<!-- Parts shop link -->
<a href="/Shop?brand=@b.Id"
class="block bg-gray-50 border border-gray-200 rounded-2xl p-5 hover:shadow-md hover:bg-white transition-all">
<div class="flex items-center gap-3">
<span class="text-2xl">🛒</span>
<div>
<h3 class="font-bold text-gray-900 text-sm">قطعات یدکی @b.NameFa</h3>
<p class="text-xs text-gray-400 mt-0.5">خرید آنلاین از فروشگاه ما</p>
</div>
</div>
</a>
<!-- Other brands -->
<div class="bg-white rounded-2xl border border-gray-100 p-5">
<h3 class="font-bold text-gray-900 mb-3 text-sm">سایر برندها</h3>
<div class="flex flex-wrap gap-2">
@foreach (var ob in BrandSeoData.AllBrands.Where(x => x.Id != b.Id))
{
<a href="/brands/@ob.Id"
class="text-xs px-3 py-1.5 rounded-full border border-gray-200 text-gray-600 hover:text-white transition-all hover:border-transparent"
style="--hover-bg:@ob.Color"
onmouseover="this.style.background='@ob.Color';this.style.borderColor='@ob.Color'"
onmouseout="this.style.background='';this.style.borderColor=''">
@ob.NameFa
</a>
}
</div>
</div>
</div>
</div>
<!-- ══════════════════════ BOTTOM CTA ════════════════════════════════════ -->
<section class="py-14 px-4" style="background:@b.Color">
<div class="max-w-3xl mx-auto text-center">
<h2 class="text-2xl font-extrabold mb-3" style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
ابزار @b.NameFa شما خراب شده؟
</h2>
<p class="mb-8 text-lg opacity-85" style="color:@(b.TextColor == "#fff" ? "#e5e7eb" : "#374151")">
همین الان تماس بگیرید. تشخیص رایگان، تعمیر با ضمانت کتبی ۳ ماهه.
</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="tel:@c.TelPhone"
class="bg-white font-bold px-8 py-3.5 rounded-xl hover:opacity-90 transition-opacity text-lg shadow-lg"
style="color:@b.Color">
📞 @c.Phone
</a>
<a href="https://wa.me/@c.Whatsapp" target="_blank"
class="bg-green-600 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-green-700 transition-colors text-lg shadow-lg">
💬 واتساپ
</a>
<a href="/Shop?brand=@b.Id"
class="bg-white/20 border border-white/50 font-bold px-8 py-3.5 rounded-xl hover:bg-white/30 transition-colors text-lg"
style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
🛒 قطعات یدکی
</a>
</div>
</div>
</section>
+22
View File
@@ -0,0 +1,22 @@
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Brands;
public class BrandDetailModel : PageModel
{
public BrandSeoPage? Brand { get; private set; }
public IActionResult OnGet(string brand)
{
Brand = BrandSeoData.AllBrands.FirstOrDefault(b =>
b.Id.Equals(brand, StringComparison.OrdinalIgnoreCase));
if (Brand is null) return NotFound();
ViewData["Title"] = $"تعمیر ابزار {Brand.NameFa} در کرج | آساد ابزار{(Brand.IsOfficial ? " نمایندگی رسمی" : "")}";
ViewData["Description"] = $"تعمیر تخصصی ابزار {Brand.NameFa} در کرج با ضمانت ۳ ماهه. قطعات اصل، تکنیسین مجاز. {string.Join("، ", Brand.RepairServices.Take(3))}. تماس: ۰۲۶-۳۴۵۶۷۸۹۰";
return Page();
}
}
+63
View File
@@ -0,0 +1,63 @@
@page
@model AsadiTools.Pages.Brands.BrandsIndexModel
@{ Layout = "_Layout"; }
<div class="bg-blue-800 text-white py-12 px-4">
<div class="max-w-6xl mx-auto">
<nav class="flex items-center gap-2 text-sm text-blue-300 mb-4">
<a href="/" class="hover:text-white">خانه</a><span>/</span>
<span class="text-white">برندها</span>
</nav>
<h1 class="text-3xl font-extrabold mb-2">تعمیر ابزار همه برندها در کرج</h1>
<p class="text-blue-200">تخصص در تعمیر برترین برندهای ابزار صنعتی با ضمانت ۳ ماهه</p>
</div>
</div>
<div class="max-w-6xl mx-auto px-4 py-12">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
@foreach (var b in Model.Brands)
{
<a href="/brands/@b.Id"
class="group relative bg-white rounded-2xl overflow-hidden border-2 hover:shadow-xl transition-all hover:-translate-y-1"
style="border-color:@(b.IsOfficial ? "#f59e0b" : "#e5e7eb")">
@if (b.IsOfficial)
{
<div class="absolute top-3 right-3 z-10 bg-yellow-400 text-gray-900 text-xs font-bold px-2.5 py-1 rounded-full shadow">🛡️ رسمی</div>
}
<div class="relative h-36 overflow-hidden">
<img src="@b.HeroImage" alt="تعمیر ابزار @b.NameFa" loading="lazy"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" />
<div class="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent"></div>
<div class="absolute bottom-3 right-4 flex items-center gap-2">
<div class="w-9 h-9 rounded-lg flex items-center justify-center text-xs font-extrabold shadow"
style="background-color:@b.Color;color:@b.TextColor">@b.Name.Substring(0,2)</div>
<span class="text-white font-bold text-lg">@b.NameFa</span>
</div>
</div>
<div class="p-5">
<p class="text-sm text-gray-500 leading-6 mb-3 line-clamp-2">@b.Tagline</p>
<div class="flex items-center justify-between text-xs text-gray-400">
<span>📍 @b.Country · @b.Founded</span>
<span class="text-blue-600 font-medium group-hover:underline">جزئیات </span>
</div>
</div>
</a>
}
</div>
<!-- Why trust us -->
<div class="mt-16 bg-blue-900 rounded-3xl p-10 text-white text-center">
<h2 class="text-2xl font-extrabold mb-3">چرا آساد ابزار؟</h2>
<p class="text-blue-200 mb-8 max-w-2xl mx-auto">بیش از ۱۵ سال تجربه تعمیر ابزارهای برقی صنعتی، نمایندگی رسمی دیوالت در کرج، با قطعات اصل و ضمانت کتبی</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
@foreach (var s in new[] { ("+۱۵","سال تجربه","🏆"), ("+۵۰۰۰","دستگاه تعمیر شده","🔧"), ("۸","برند پشتیبانی","⭐"), ("۱۰۰٪","ضمانت تعمیر","🛡️") })
{
<div class="bg-white/10 rounded-2xl p-5">
<div class="text-2xl mb-1">@s.Item3</div>
<div class="text-2xl font-extrabold text-yellow-300">@s.Item1</div>
<div class="text-xs text-blue-200 mt-0.5">@s.Item2</div>
</div>
}
</div>
</div>
</div>
+15
View File
@@ -0,0 +1,15 @@
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Brands;
public class BrandsIndexModel : PageModel
{
public BrandSeoPage[] Brands { get; } = BrandSeoData.AllBrands;
public void OnGet()
{
ViewData["Title"] = "تعمیر تخصصی ابزار برقی در کرج | همه برندها | آساد ابزار";
ViewData["Description"] = "تعمیر ابزار دیوالت، بوش، هیلتی، متابو، رونیکس، توسن، ریوبی و آ.ا.گ در کرج. نمایندگی رسمی دیوالت. قطعات اصل، ضمانت ۳ ماهه.";
}
}
+85
View File
@@ -0,0 +1,85 @@
@page
@model AsadiTools.Pages.Cart.CartIndexModel
@{ ViewData["Title"] = "سبد خرید"; Layout = "_Layout"; }
@if (!Model.Items.Any())
{
<div class="min-h-[60vh] flex items-center justify-center">
<div class="text-center py-20">
<div class="text-6xl mb-4">🛒</div>
<h2 class="text-xl font-bold text-gray-600 mb-2">سبد خرید شما خالی است</h2>
<p class="text-gray-400 mb-6">قطعات مورد نیاز خود را از فروشگاه انتخاب کنید</p>
<a href="/Shop" class="bg-blue-700 text-white px-6 py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">مشاهده فروشگاه</a>
</div>
</div>
}
else
{
<div class="max-w-4xl mx-auto px-4 py-10">
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">سبد خرید</h1>
<div class="grid md:grid-cols-3 gap-6">
<!-- Items -->
<div class="md:col-span-2 space-y-3">
@foreach (var item in Model.Items)
{
<div class="bg-white rounded-2xl border border-gray-100 p-5 flex items-center gap-4">
<div class="bg-blue-50 rounded-xl w-14 h-14 flex items-center justify-center text-2xl shrink-0">🔧</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-gray-900 text-sm">@item.NameFa</h3>
@if (item.Sku != null) { <p class="text-xs text-gray-400">کد: @item.Sku</p> }
<p class="text-blue-700 font-bold mt-1">@SiteData.FormatPrice(item.Price)</p>
</div>
<!-- Qty -->
<div class="flex items-center gap-2 shrink-0">
<form method="post" asp-page-handler="UpdateQty">
@Html.AntiForgeryToken()
<input type="hidden" name="productId" value="@item.ProductId" />
<input type="hidden" name="qty" value="@(item.Qty - 1)" />
<button type="submit" class="w-8 h-8 rounded-lg border border-gray-200 flex items-center justify-center hover:bg-gray-50 text-lg"></button>
</form>
<span class="w-8 text-center font-bold text-sm">@item.Qty</span>
<form method="post" asp-page-handler="UpdateQty">
@Html.AntiForgeryToken()
<input type="hidden" name="productId" value="@item.ProductId" />
<input type="hidden" name="qty" value="@(item.Qty + 1)" />
<button type="submit" class="w-8 h-8 rounded-lg border border-gray-200 flex items-center justify-center hover:bg-gray-50 text-lg">+</button>
</form>
</div>
<div class="text-left shrink-0">
<p class="font-bold text-gray-800 text-sm">@SiteData.FormatPrice(item.Subtotal)</p>
<form method="post" asp-page-handler="Remove" class="mt-1">
@Html.AntiForgeryToken()
<input type="hidden" name="productId" value="@item.ProductId" />
<button type="submit" class="text-red-400 hover:text-red-600 text-xs">حذف</button>
</form>
</div>
</div>
}
<form method="post" asp-page-handler="Clear" class="mt-2">
@Html.AntiForgeryToken()
<button type="submit" class="text-sm text-red-400 hover:text-red-600 transition-colors">🗑️ پاک کردن سبد خرید</button>
</form>
</div>
<!-- Summary -->
<div>
<div class="bg-white rounded-2xl border border-gray-100 p-6 sticky top-24">
<h2 class="font-bold text-gray-900 mb-4 pb-3 border-b">خلاصه سفارش</h2>
<div class="space-y-2 text-sm mb-4">
<div class="flex justify-between text-gray-600">
<span>تعداد اقلام:</span>
<span>@Model.Items.Sum(i => i.Qty) عدد</span>
</div>
<div class="flex justify-between font-bold text-gray-900 text-base pt-2 border-t">
<span>جمع کل:</span>
<span class="text-blue-700">@SiteData.FormatPrice(Model.Total)</span>
</div>
</div>
<a href="/Checkout" class="block bg-blue-700 text-white text-center py-3.5 rounded-xl font-bold hover:bg-blue-800 transition-colors mb-3">ادامه و ثبت سفارش</a>
<a href="/Shop" class="block text-sm text-blue-600 text-center hover:underline">← ادامه خرید</a>
</div>
</div>
</div>
</div>
}
+36
View File
@@ -0,0 +1,36 @@
using AsadiTools.Models;
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Cart;
public class CartIndexModel(CartService cart) : PageModel
{
public List<CartItem> Items { get; private set; } = [];
public decimal Total { get; private set; }
public void OnGet()
{
Items = cart.GetItems();
Total = cart.Total;
}
public IActionResult OnPostUpdateQty(int productId, int qty)
{
cart.UpdateQty(productId, qty);
return RedirectToPage();
}
public IActionResult OnPostRemove(int productId)
{
cart.RemoveItem(productId);
return RedirectToPage();
}
public IActionResult OnPostClear()
{
cart.Clear();
return RedirectToPage();
}
}
+90
View File
@@ -0,0 +1,90 @@
@page
@model AsadiTools.Pages.Checkout.CheckoutIndexModel
@{ ViewData["Title"] = "ثبت سفارش"; Layout = "_Layout"; }
@if (Model.OrderNumber != null)
{
<div class="min-h-[60vh] flex items-center justify-center">
<div class="text-center max-w-md mx-auto px-4 py-20">
<div class="text-6xl mb-4">✅</div>
<h2 class="text-2xl font-extrabold text-gray-900 mb-3">سفارش ثبت شد!</h2>
<p class="text-gray-600 mb-2">شماره سفارش شما:</p>
<p class="text-xl font-bold text-blue-700 mb-6 font-mono">@Model.OrderNumber</p>
<p class="text-gray-500 text-sm mb-8">به زودی با شما تماس می‌گیریم تا جزئیات سفارش را هماهنگ کنیم.</p>
<a href="/" class="bg-blue-700 text-white px-6 py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">بازگشت به خانه</a>
</div>
</div>
}
else
{
<div class="max-w-4xl mx-auto px-4 py-10">
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">ثبت سفارش</h1>
<div class="grid md:grid-cols-3 gap-6">
<form method="post" class="md:col-span-2">
@Html.AntiForgeryToken()
<div class="bg-white rounded-2xl border border-gray-100 p-6 space-y-4">
<h2 class="font-bold text-gray-900 mb-2">اطلاعات خریدار</h2>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام و نام خانوادگی <span class="text-red-500">*</span></label>
<input asp-for="Input.CustomerName" required
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="مثال: علی احمدی" />
<span asp-validation-for="Input.CustomerName" class="text-red-500 text-xs"></span>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">شماره موبایل <span class="text-red-500">*</span></label>
<input asp-for="Input.CustomerPhone" required dir="ltr"
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="09xxxxxxxxx" />
<span asp-validation-for="Input.CustomerPhone" class="text-red-500 text-xs"></span>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">آدرس (برای ارسال)</label>
<textarea asp-for="Input.CustomerAddress" rows="3"
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
placeholder="کرج، ..."></textarea>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-1.5 block">توضیحات</label>
<textarea asp-for="Input.Notes" rows="2"
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
placeholder="توضیحات اضافی..."></textarea>
</div>
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3 rounded-xl">@Model.ErrorMessage</div>
}
<button type="submit" class="w-full bg-blue-700 text-white py-4 rounded-xl font-bold text-lg hover:bg-blue-800 transition-colors">
ثبت نهایی سفارش
</button>
</div>
</form>
<!-- Summary -->
<div>
<div class="bg-white rounded-2xl border border-gray-100 p-6 sticky top-24">
<h2 class="font-bold text-gray-900 mb-4 pb-3 border-b">اقلام سفارش</h2>
<div class="space-y-3 mb-4">
@foreach (var item in Model.CartItems)
{
<div class="flex justify-between text-sm">
<span class="text-gray-600 truncate ml-2">@item.NameFa × @item.Qty</span>
<span class="font-bold shrink-0">@SiteData.FormatPrice(item.Subtotal)</span>
</div>
}
</div>
<div class="border-t pt-3 flex justify-between font-bold">
<span>جمع کل:</span>
<span class="text-blue-700">@SiteData.FormatPrice(Model.CartItems.Sum(i => i.Subtotal))</span>
</div>
</div>
</div>
</div>
</div>
}
+71
View File
@@ -0,0 +1,71 @@
using AsadiTools.Data;
using AsadiTools.Models;
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
namespace AsadiTools.Pages.Checkout;
public class CheckoutIndexModel(AppDbContext db, CartService cart) : PageModel
{
[BindProperty]
public InputModel Input { get; set; } = new();
public List<CartItem> CartItems { get; private set; } = [];
public string? OrderNumber { get; private set; }
public string? ErrorMessage { get; private set; }
public class InputModel
{
[Required(ErrorMessage = "نام الزامی است")]
public string CustomerName { get; set; } = string.Empty;
[Required(ErrorMessage = "شماره موبایل الزامی است")]
public string CustomerPhone { get; set; } = string.Empty;
public string? CustomerAddress { get; set; }
public string? Notes { get; set; }
}
public IActionResult OnGet()
{
CartItems = cart.GetItems();
if (!CartItems.Any()) return RedirectToPage("/Shop/Index");
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
CartItems = cart.GetItems();
if (!CartItems.Any()) return RedirectToPage("/Shop/Index");
if (!ModelState.IsValid) return Page();
var orderNumber = $"ORD-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}";
var order = new Order
{
OrderNumber = orderNumber,
CustomerName = Input.CustomerName,
CustomerPhone = Input.CustomerPhone,
CustomerAddress = Input.CustomerAddress,
Notes = Input.Notes,
Items = CartItems.Select(i => new OrderItem
{
ProductId = i.ProductId,
ProductNameFa = i.NameFa,
Price = i.Price,
Quantity = i.Qty,
}).ToList(),
};
order.Subtotal = order.Items.Sum(i => i.Subtotal);
order.Total = order.Subtotal;
db.Orders.Add(order);
await db.SaveChangesAsync();
cart.Clear();
OrderNumber = orderNumber;
return Page();
}
}
+96
View File
@@ -0,0 +1,96 @@
@page
@model AsadiTools.Pages.Contact.ContactIndexModel
@{
ViewData["Title"] = "تماس با آساد ابزار – نمایندگی دیوالت کرج";
ViewData["Description"] = "آدرس، تلفن و ساعات کاری آساد ابزار کرج. نمایندگی رسمی دیوالت در کرج.";
Layout = "_Layout";
var c = SiteData.Company;
var mapBbox = $"{c.MapLng - 0.02},{c.MapLat - 0.02},{c.MapLng + 0.02},{c.MapLat + 0.02}";
var mapUrl = $"https://www.openstreetmap.org/export/embed.html?bbox={mapBbox}&layer=mapnik&marker={c.MapLat},{c.MapLng}";
}
<div class="bg-blue-800 text-white py-12 px-4">
<div class="max-w-6xl mx-auto">
<h1 class="text-3xl font-extrabold mb-2">تماس با ما</h1>
<p class="text-blue-200">آساد ابزار – نمایندگی رسمی دیوالت در کرج</p>
</div>
</div>
<div class="max-w-6xl mx-auto px-4 py-12">
<div class="grid md:grid-cols-2 gap-10">
<div>
<h2 class="text-xl font-bold text-gray-900 mb-6">اطلاعات تماس</h2>
<div class="space-y-4">
<div class="flex items-start gap-4 p-5 bg-white rounded-2xl border border-gray-100">
<span class="text-2xl mt-0.5">📞</span>
<div>
<div class="font-bold text-gray-800 mb-2">تلفن</div>
<a href="tel:@c.TelPhone" class="block text-blue-600 hover:underline text-lg font-bold">@c.Phone</a>
<a href="tel:@c.TelMobile" class="block text-blue-600 hover:underline">@c.Mobile</a>
</div>
</div>
<div class="flex items-start gap-4 p-5 bg-white rounded-2xl border border-gray-100">
<span class="text-2xl mt-0.5">📍</span>
<div>
<div class="font-bold text-gray-800 mb-1">آدرس</div>
<p class="text-gray-600">@c.Address</p>
</div>
</div>
<div class="flex items-start gap-4 p-5 bg-white rounded-2xl border border-gray-100">
<span class="text-2xl mt-0.5">🕐</span>
<div>
<div class="font-bold text-gray-800 mb-1">ساعات کاری</div>
<p class="text-gray-600">@c.WorkingHours</p>
</div>
</div>
<div class="flex gap-3">
<a href="https://wa.me/@c.Whatsapp" target="_blank"
class="flex-1 flex items-center justify-center gap-2 bg-green-500 text-white py-3.5 rounded-xl font-bold hover:bg-green-600 transition-colors">
💬 واتساپ
</a>
<a href="https://instagram.com/@c.Instagram" target="_blank"
class="flex-1 flex items-center justify-center gap-2 bg-gradient-to-r from-purple-500 to-pink-500 text-white py-3.5 rounded-xl font-bold hover:opacity-90 transition-opacity">
📷 اینستاگرام
</a>
</div>
</div>
<div class="mt-8">
<h2 class="text-xl font-bold text-gray-900 mb-4">برندهای رسمی</h2>
<div class="grid grid-cols-2 gap-3">
@foreach (var b in SiteData.Brands)
{
<div class="flex items-center gap-3 p-3 bg-white rounded-xl border border-gray-100">
<div class="w-9 h-9 rounded-lg flex items-center justify-center text-sm font-bold shrink-0"
style="background-color:@b.Color;color:@b.TextColor">@b.Name[0]</div>
<div>
<div class="font-bold text-gray-800 text-sm">@b.NameFa</div>
@if (b.IsOfficial) { <div class="text-xs text-yellow-600 font-medium">نمایندگی رسمی</div> }
</div>
</div>
}
</div>
</div>
</div>
<div>
<h2 class="text-xl font-bold text-gray-900 mb-6">موقعیت روی نقشه</h2>
<div class="rounded-2xl overflow-hidden border border-gray-200 mb-6" style="height:320px">
<iframe src="@mapUrl"
width="100%" height="320" style="border:0" loading="lazy"
title="موقعیت آساد ابزار کرج در نقشه"></iframe>
</div>
<p class="text-xs text-gray-400 mb-4">
نقشه از <a href="https://www.openstreetmap.org" target="_blank" class="underline">OpenStreetMap</a> —
پس از تعیین آدرس دقیق، مختصات را در <code>SiteData.Company.MapLat/MapLng</code> به‌روز کنید.
</p>
<div class="bg-yellow-50 border border-yellow-200 rounded-2xl p-5">
<h3 class="font-bold text-gray-900 mb-2">مراجعه حضوری</h3>
<p class="text-sm text-gray-600 leading-7">
برای تعمیر ابزار می‌توانید حضوراً مراجعه کنید یا با تماس قبلی ابزار خود را برای ما ارسال نمایید.
پس از تشخیص عیب، هزینه تعمیر اعلام می‌شود.
</p>
</div>
</div>
</div>
</div>
+8
View File
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Contact;
public class ContactIndexModel : PageModel
{
public void OnGet() { }
}
+290
View File
@@ -0,0 +1,290 @@
@page
@model AsadiTools.Pages.DeWalt.DeWaltIndexModel
@{ Layout = "_Layout"; }
@section Head {
<script type="application/ld+json">
{"@@context":"https://schema.org","@@type":"Service","name":"تعمیر ابزار دیوالت در کرج","provider":{"@@type":"LocalBusiness","name":"آساد ابزار کرج","telephone":"+98-261-XXXXXXX","address":{"@@type":"PostalAddress","addressLocality":"کرج","addressCountry":"IR"}},"serviceType":"تعمیر ابزار برقی","brand":{"@@type":"Brand","name":"DeWalt"},"areaServed":"کرج"}
</script>
}
<!-- ═══════════ HERO ═══════════ -->
<section class="relative text-white overflow-hidden" style="min-height:420px;background:#1a1200">
<div class="absolute inset-0 bg-cover bg-center opacity-30"
style="background-image:url('https://images.unsplash.com/photo-1504148455328-c376907d081c?w=1400&q=80&auto=format&fit=crop')"></div>
<div class="absolute inset-0 bg-gradient-to-l from-yellow-900/40 to-transparent"></div>
<div class="relative max-w-6xl mx-auto px-4 py-16 flex flex-col md:flex-row items-center gap-10">
<!-- DeWalt logo area -->
<div class="shrink-0">
<div class="w-40 h-40 rounded-3xl flex items-center justify-center shadow-2xl border-4 border-yellow-400"
style="background:#FFCD00">
<span class="font-extrabold text-gray-900 text-3xl tracking-tighter">DeWALT</span>
</div>
</div>
<div>
<div class="inline-flex items-center gap-2 bg-yellow-400 text-gray-900 text-sm font-bold px-4 py-1.5 rounded-full mb-4">
🛡️ نمایندگی رسمی DeWalt در کرج
</div>
<h1 class="text-4xl md:text-5xl font-extrabold leading-tight mb-4">
تعمیر تخصصی<br>
<span class="text-yellow-400">ابزار دیوالت</span>
</h1>
<p class="text-gray-300 text-lg leading-8 max-w-2xl mb-6">
آساد ابزار مجاز تنها مرکز تعمیر رسمی ابزار DeWalt در شهر کرج است.
بیش از ۱۵ مدل از پرفروش‌ترین ابزار دیوالت را با قطعات اصل تعمیر می‌کنیم.
</p>
<div class="flex flex-wrap gap-3">
<a href="tel:02634567890" class="bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors">📞 تماس برای تعمیر</a>
<a href="#tools" class="border-2 border-yellow-400/50 text-yellow-300 font-bold px-6 py-3 rounded-xl hover:border-yellow-400 transition-colors">مشاهده ابزارها ↓</a>
</div>
</div>
</div>
</section>
<!-- ═══════════ TRUST STRIP ═══════════ -->
<div class="bg-yellow-400 py-3 px-4">
<div class="max-w-6xl mx-auto flex flex-wrap justify-center gap-6 text-gray-900 text-sm font-semibold">
@foreach (var t in new[] { "🛡️ ضمانت ۳ ماهه کتبی", "🔩 قطعات اصل DeWalt", "⚡ تحویل زیر ۴۸ ساعت", "📞 مشاوره رایگان", "🎓 تکنیسین آموزش‌دیده DeWalt" })
{
<span>@t</span>
}
</div>
</div>
<!-- ═══════════ CATEGORY TABS ═══════════ -->
<nav id="tools" class="bg-white border-b sticky top-[65px] z-40">
<div class="max-w-6xl mx-auto px-4 flex gap-1 overflow-x-auto py-2">
@{
var cats = new[] {
("all", "همه ابزار"),
("drill", "🔩 دریل"),
("driver", "🔧 پیچ‌گوشتی"),
("grinder", "⚙️ فرز آنگولر"),
("rotary-hammer", "🏗️ بتن‌کن"),
("saw", "🪚 ابزار برش"),
("chop-saw", "💿 گردبر"),
("miter-saw", "📐 فارسی‌بر"),
("woodworking", "🪵 نجاری"),
("multi", "🔧 چندکاره"),
("sander", "💨 سنباده"),
("hedge-trimmer", "🌿 شمشادزن"),
("laser-level", "🔴 تراز لیزری"),
("laser-measure", "📏 متر لیزری"),
};
}
@foreach (var (catId, catName) in cats)
{
<button onclick="filterTools('@catId')"
id="tab-@catId"
class="tab-btn shrink-0 px-4 py-2 rounded-lg text-sm font-medium transition-colors text-gray-600 hover:bg-yellow-50 hover:text-yellow-700 whitespace-nowrap">
@catName
</button>
}
</div>
</nav>
<!-- ═══════════ TOOLS GRID ═══════════ -->
<section class="py-12 px-4 bg-gray-50">
<div class="max-w-6xl mx-auto">
<div id="tools-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@{
var catImages = new Dictionary<string, string>
{
["drill"] = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=500&q=75&auto=format&fit=crop",
["driver"] = "https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=500&q=75&auto=format&fit=crop",
["grinder"] = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=500&q=75&auto=format&fit=crop",
["rotary-hammer"] = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=500&q=75&auto=format&fit=crop",
["saw"] = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=500&q=75&auto=format&fit=crop",
["chop-saw"] = "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=500&q=75&auto=format&fit=crop",
["miter-saw"] = "https://images.unsplash.com/photo-1558618047-3c8c1d8b9df1?w=500&q=75&auto=format&fit=crop",
["woodworking"] = "https://images.unsplash.com/photo-1588854337115-1c67d9247e4d?w=500&q=75&auto=format&fit=crop",
["multi"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=500&q=75&auto=format&fit=crop",
["sander"] = "https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=500&q=75&auto=format&fit=crop",
["hedge-trimmer"] = "https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=500&q=75&auto=format&fit=crop",
["laser-level"] = "https://images.unsplash.com/photo-1609220136736-443140cfeaa8?w=500&q=75&auto=format&fit=crop",
["laser-measure"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=500&q=75&auto=format&fit=crop",
};
}
@foreach (var tool in Model.Tools)
{
var img = catImages.TryGetValue(tool.CategoryId, out var u) ? u : catImages["drill"];
<div class="tool-card bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-xl transition-all hover:-translate-y-1 border border-gray-100"
data-cat="@tool.CategoryId">
<!-- Image -->
<div class="relative h-44 overflow-hidden">
<img src="@img" alt="تعمیر @tool.NameFa" loading="lazy"
class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent"></div>
<!-- DeWalt badge -->
<div class="absolute top-3 left-3">
<span class="bg-yellow-400 text-gray-900 font-extrabold text-xs px-2 py-1 rounded-lg">DeWALT</span>
</div>
<!-- Power badge -->
<div class="absolute top-3 right-3">
<span class="bg-black/60 text-white text-xs px-2 py-1 rounded-lg font-medium">@tool.Power</span>
</div>
<!-- Icon overlay -->
<div class="absolute bottom-3 right-4 text-3xl">@tool.Icon</div>
</div>
<div class="p-5">
<!-- Title & models -->
<h3 class="font-bold text-gray-900 text-lg mb-1 leading-snug">@tool.NameFa</h3>
<p class="text-xs text-gray-400 mb-3 font-mono">@string.Join(" · ", tool.Models)</p>
<p class="text-gray-600 text-sm leading-7 mb-4">@tool.Description</p>
<!-- Repair services -->
<div class="border-t pt-4">
<p class="text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">خدمات تعمیر</p>
<div class="flex flex-wrap gap-1.5">
@foreach (var r in tool.RepairItems)
{
<span class="text-xs bg-yellow-50 text-yellow-800 border border-yellow-200 px-2 py-0.5 rounded-full">@r</span>
}
</div>
</div>
<!-- CTA -->
<a href="tel:02634567890"
class="mt-4 block w-full bg-yellow-400 text-gray-900 font-bold text-center py-2.5 rounded-xl hover:bg-yellow-300 transition-colors text-sm">
📞 درخواست تعمیر
</a>
</div>
</div>
}
</div>
<p id="no-results" class="hidden text-center text-gray-400 py-10">ابزاری در این دسته‌بندی یافت نشد.</p>
</div>
</section>
<!-- ═══════════ REPAIR PROCESS ═══════════ -->
<section class="py-16 px-4 bg-white">
<div class="max-w-5xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-2xl font-bold text-gray-900 mb-2">فرآیند تعمیر ابزار دیوالت</h2>
<p class="text-gray-500">ساده، شفاف و با ضمانت</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
@{
var steps = new[] {
("۱", "تحویل دستگاه", "ابزار را به‌صورت حضوری تحویل دهید یا از طریق پست ارسال کنید.", "📦"),
("۲", "بررسی رایگان", "در کمتر از ۲ ساعت عیب‌یابی کامل و برآورد قیمت ارائه می‌شود.", "🔍"),
("۳", "تعمیر با قطعه اصل", "تعمیر توسط تکنیسین مجاز DeWalt با قطعات اورجینال.", "🔧"),
("۴", "تحویل با ضمانت", "تحویل دستگاه با ضمانت‌نامه کتبی ۳ ماهه.", "🛡️"),
};
}
@foreach (var (num, title, desc, icon) in steps)
{
<div class="text-center relative">
<div class="w-16 h-16 rounded-2xl bg-yellow-400 flex items-center justify-center text-2xl mx-auto mb-4 shadow-md">
@icon
</div>
<div class="absolute top-0 right-1/2 translate-x-1/2 -translate-y-1 bg-gray-900 text-white text-xs font-bold w-5 h-5 rounded-full flex items-center justify-center">@num</div>
<h3 class="font-bold text-gray-900 mb-2">@title</h3>
<p class="text-sm text-gray-500 leading-6">@desc</p>
</div>
}
</div>
</div>
</section>
<!-- ═══════════ GENUINE PARTS SECTION ═══════════ -->
<section class="py-16 px-4" style="background:linear-gradient(135deg,#fef9c3,#fef08a)">
<div class="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
<div>
<h2 class="text-2xl font-bold text-gray-900 mb-4">قطعات اصل DeWalt</h2>
<p class="text-gray-700 leading-8 mb-6">
آساد ابزار تنها از قطعات یدکی ۱۰۰٪ اورجینال DeWalt استفاده می‌کند.
قطعات مستقیم از نمایندگی رسمی ایران تأمین می‌شود و دارای شناسه تأیید اصالت هستند.
هیچ قطعه چینی یا غیراصل در تعمیرات ما استفاده نمی‌شود.
</p>
<div class="grid grid-cols-2 gap-3">
@foreach (var part in new[] { "کاربن (ذغال) اصل", "بیرینگ اورجینال", "کلید و رئوستا", "آرمیچر و استاتور", "گیربکس کامل", "چاک ۱۳mm", "پیستون بتن‌کن", "تیغه شمشادزن" })
{
<div class="flex items-center gap-2 bg-white/60 rounded-xl px-3 py-2">
<span class="text-yellow-600 font-bold">⚡</span>
<span class="text-sm text-gray-800">@part</span>
</div>
}
</div>
</div>
<div class="relative rounded-3xl overflow-hidden shadow-xl" style="height:320px">
<img src="https://images.unsplash.com/photo-1518770660439-4636190af475?w=700&q=85&auto=format&fit=crop"
alt="قطعات یدکی اصل دیوالت" loading="lazy"
class="w-full h-full object-cover" />
</div>
</div>
</section>
<!-- ═══════════ FAQ ═══════════ -->
<section class="py-16 px-4 bg-white">
<div class="max-w-3xl mx-auto">
<h2 class="text-2xl font-bold text-gray-900 mb-8 text-center">سؤالات متداول درباره تعمیر دیوالت</h2>
<div class="space-y-4">
@{
var faqs = new[] {
("چطور بفهمم ابزار دیوالتم نیاز به تعمیر دارد؟",
"علائم رایج: جرقه داخل ابزار، کاهش قدرت، صدای غیرعادی، داغ شدن بیش از حد، لرزش شدید. در صورت مشاهده هر کدام، دستگاه را خاموش کنید و با ما تماس بگیرید."),
("هزینه تعمیر ابزار دیوالت چقدر است؟",
"هزینه بستگی به نوع عیب و مدل دارد. بررسی اولیه و برآورد قیمت کاملاً رایگان است. قبل از شروع تعمیر، قیمت دقیق به شما اعلام می‌شود."),
("آیا ضمانت تعمیر دارید؟",
"بله، تمام تعمیرات آساد ابزار دارای ضمانت‌نامه کتبی ۳ ماهه هستند. در صورت بروز مشکل مجدد در همان قطعه، رایگان تعمیر می‌شود."),
("چه مدت طول می‌کشد ابزار تعمیر شود؟",
"اکثر تعمیرات معمولی در ۲۴ تا ۴۸ ساعت انجام می‌شود. تعمیرات سنگین‌تر که نیاز به سفارش قطعه دارند ممکن است ۳ تا ۵ روز طول بکشد."),
("آیا تعمیرات از شهرهای دیگر قبول می‌کنید؟",
"بله، ابزار را می‌توانید از طریق پست یا تیپاکس ارسال کنید. ما پس از تعمیر، ابزار را به آدرس شما ارسال می‌کنیم."),
};
}
@foreach (var (q, a) in faqs)
{
<details class="group border border-gray-200 rounded-2xl overflow-hidden">
<summary class="flex items-center justify-between p-5 cursor-pointer font-semibold text-gray-800 hover:bg-gray-50">
@q
<span class="text-yellow-600 group-open:rotate-45 transition-transform shrink-0 mr-3 text-xl">+</span>
</summary>
<div class="px-5 pb-5 text-gray-600 text-sm leading-7 border-t border-gray-100 pt-4">
@a
</div>
</details>
}
</div>
</div>
</section>
<!-- ═══════════ CONTACT CTA ═══════════ -->
<section class="py-14 px-4 bg-gray-900 text-white">
<div class="max-w-3xl mx-auto text-center">
<h2 class="text-3xl font-extrabold mb-3">ابزار دیوالت خود را تعمیر کنید</h2>
<p class="text-gray-400 mb-8">تنها نمایندگی مجاز تعمیر DeWalt در کرج</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="tel:02634567890" class="bg-yellow-400 text-gray-900 font-bold px-8 py-3.5 rounded-xl hover:bg-yellow-300 transition-colors text-lg">📞 ۰۲۶-۳۴۵۶۷۸۹۰</a>
<a href="https://wa.me/989123456789" class="bg-green-600 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-green-700 transition-colors text-lg">💬 واتساپ</a>
<a href="/Contact" class="border-2 border-white/30 text-white font-bold px-8 py-3.5 rounded-xl hover:border-white transition-colors text-lg">📍 آدرس ما</a>
</div>
</div>
</section>
@section Scripts {
<script>
function filterTools(cat) {
const cards = document.querySelectorAll('.tool-card');
const tabs = document.querySelectorAll('.tab-btn');
let visible = 0;
cards.forEach(c => {
const show = cat === 'all' || c.dataset.cat === cat;
c.style.display = show ? '' : 'none';
if (show) visible++;
});
document.getElementById('no-results').classList.toggle('hidden', visible > 0);
tabs.forEach(t => {
const active = t.id === 'tab-' + cat;
t.classList.toggle('bg-yellow-400', active);
t.classList.toggle('text-gray-900', active);
t.classList.toggle('font-bold', active);
t.classList.toggle('text-gray-600', !active);
});
}
// Activate "all" tab on load
filterTools('all');
</script>
}
+16
View File
@@ -0,0 +1,16 @@
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.DeWalt;
public class DeWaltIndexModel : PageModel
{
public DeWaltTool[] Tools { get; } = SiteData.DeWaltTools;
public BrandInfo Brand { get; } = SiteData.Brands.First(b => b.Id == "dewalt");
public void OnGet()
{
ViewData["Title"] = "تعمیر ابزار دیوالت در کرج | نمایندگی رسمی DeWalt";
ViewData["Description"] = "نمایندگی رسمی تعمیر ابزار دیوالت (DeWalt) در کرج. تعمیر دریل، فرز، بتن‌کن، جیگ‌ساو و تمام ابزار DeWalt با قطعات اصل و ضمانت ۳ ماهه.";
}
}
+26
View File
@@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
+20
View File
@@ -0,0 +1,20 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
+318
View File
@@ -0,0 +1,318 @@
@page
@model AsadiTools.Pages.IndexModel
@{
ViewData["Title"] = "آساد ابزار - نمایندگی رسمی دیوالت در کرج";
ViewData["Description"] = "نمایندگی رسمی دیوالت، ماکیتا، رونیکس، توسن و بلک اند دکر در کرج. تعمیر دریل، فرز، مینی فرز، شمشاد زن و بتن کن با ضمانت.";
Layout = "_Layout";
}
@section Head {
<script type="application/ld+json">
{"@@context":"https://schema.org","@@type":"LocalBusiness","name":"آساد ابزار کرج","image":"https://images.unsplash.com/photo-1504148455328-c376907d081c?w=800","telephone":"+98@SiteData.Company.TelPhone.Substring(1)","address":{"@@type":"PostalAddress","streetAddress":"@SiteData.Company.Address","addressLocality":"کرج","addressRegion":"البرز","postalCode":"31489","addressCountry":"IR"},"openingHours":"Mo-Sa 08:00-18:00","priceRange":"$$","sameAs":["https://instagram.com/@SiteData.Company.Instagram"]}
</script>
}
<!-- ═══════════════════════════════ HERO ═══════════════════════════════════ -->
<section class="relative text-white overflow-hidden" style="min-height:580px">
<!-- Background image with overlay -->
<div class="absolute inset-0 bg-cover bg-center bg-no-repeat"
style="background-image:url('https://images.unsplash.com/photo-1504148455328-c376907d081c?w=1400&q=80&auto=format&fit=crop')"></div>
<div class="absolute inset-0 bg-gradient-to-l from-blue-900/95 via-blue-900/85 to-blue-800/70"></div>
<div class="relative max-w-6xl mx-auto px-4 py-20 grid md:grid-cols-2 gap-12 items-center">
<!-- Text -->
<div>
<div class="inline-flex items-center gap-2 bg-yellow-400 text-gray-900 text-sm font-bold px-4 py-1.5 rounded-full mb-5 shadow-lg">
🛡️ نمایندگی رسمی دیوالت در کرج
</div>
<h1 class="text-4xl md:text-5xl font-extrabold leading-tight mb-5">
تعمیر تخصصی<br>
<span class="text-yellow-300">ابزار صنعتی</span><br>
در کرج
</h1>
<p class="text-blue-100 text-lg leading-8 mb-8">
با بیش از ۱۵ سال تجربه، تعمیر دریل، فرز، مینی فرز، بتن‌کن و شمشاد‌زن
برندهای دیوالت، ماکیتا، رونیکس، توسن و بلک اند دکر.
</p>
<div class="flex flex-wrap gap-3 mb-10">
<a href="/Contact" class="bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors shadow-lg">📞 تماس برای تعمیر</a>
<a href="/Shop" class="border-2 border-white/80 text-white font-bold px-6 py-3 rounded-xl hover:bg-white hover:text-blue-900 transition-colors">🛒 فروشگاه قطعات</a>
<a href="/DeWalt" class="bg-white/10 border border-white/30 text-white font-bold px-6 py-3 rounded-xl hover:bg-white/20 transition-colors">🔧 ابزار دیوالت</a>
</div>
<!-- Quick trust badges -->
<div class="flex flex-wrap gap-3">
@foreach (var badge in new[] { "✅ ضمانت ۳ ماهه", "✅ قطعات اصل", "✅ تشخیص رایگان", "✅ تحویل سریع" })
{
<span class="text-sm text-blue-100 bg-white/10 px-3 py-1 rounded-full">@badge</span>
}
</div>
</div>
<!-- Stats cards -->
<div class="grid grid-cols-2 gap-4">
@foreach (var s in new[] {
("+۱۵", "سال تجربه", "🏆"),
("+۵۰۰۰", "دستگاه تعمیر شده", "🔧"),
("۵", "برند پشتیبانی", "⭐"),
("۱۰۰٪", "ضمانت تعمیر", "🛡️")
})
{
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 text-center border border-white/20 hover:bg-white/15 transition-colors">
<div class="text-3xl mb-2">@s.Item3</div>
<div class="text-3xl font-extrabold text-yellow-300 mb-1">@s.Item1</div>
<div class="text-sm text-blue-100">@s.Item2</div>
</div>
}
</div>
</div>
</section>
<!-- ══════════════════════ DEWALT FEATURED ════════════════════════════════ -->
<section class="py-16 px-4" style="background:linear-gradient(135deg,#1a1a1a 0%,#2d2d00 50%,#1a1a00 100%)">
<div class="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
<!-- Image -->
<div class="relative rounded-3xl overflow-hidden shadow-2xl" style="height:380px">
<img src="https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=700&q=85&auto=format&fit=crop"
alt="ابزار دیوالت" class="w-full h-full object-cover" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div class="absolute bottom-4 right-4">
<span class="bg-yellow-400 text-gray-900 font-extrabold px-4 py-2 rounded-xl text-sm shadow-lg">DEWALT</span>
</div>
</div>
<!-- Content -->
<div class="text-white">
<div class="inline-flex items-center gap-2 bg-yellow-400 text-gray-900 text-xs font-bold px-3 py-1 rounded-full mb-4">
🛡️ مجاز از DeWalt ایران
</div>
<h2 class="text-3xl font-extrabold mb-4 leading-tight">
نمایندگی رسمی<br>
<span class="text-yellow-400">DeWalt</span> در کرج
</h2>
<p class="text-gray-300 leading-8 mb-6">
آساد ابزار افتخار دارد تنها نمایندگی مجاز تعمیر ابزار دیوالت در کرج باشد.
تمامی تعمیرات توسط تکنیسین‌های آموزش‌دیده و با قطعات کاملاً اورجینال انجام می‌شود.
خدمات پشتیبانی شامل بیش از ۱۵ مدل از محبوب‌ترین ابزار دیوالت است.
</p>
<div class="grid grid-cols-2 gap-3 mb-6">
@foreach (var f in new[] { ("۱۵+", "مدل ابزار"), ("قطعه اصل", "دیوالت"), ("آموزش رسمی", "تکنیسین"), ("ضمانت کتبی", "تعمیر") })
{
<div class="bg-white/10 rounded-xl p-3 text-center border border-yellow-400/30">
<div class="font-bold text-yellow-400 text-lg">@f.Item1</div>
<div class="text-xs text-gray-300">@f.Item2</div>
</div>
}
</div>
<div class="flex gap-3">
<a href="/DeWalt" class="bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors">مشاهده همه ابزار دیوالت</a>
<a href="/Services/Brand?id=dewalt" class="border border-yellow-400/50 text-yellow-300 font-bold px-6 py-3 rounded-xl hover:border-yellow-400 transition-colors">خدمات تعمیر </a>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════ BRANDS ════════════════════════════════════════ -->
<section class="py-16 px-4 bg-white">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-2xl font-bold text-gray-900 mb-2">برندهایی که تعمیر می‌کنیم</h2>
<p class="text-gray-500">تخصص ما در نمایندگی و تعمیر برترین برندهای ابزار صنعتی جهان</p>
</div>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
@foreach (var b in SiteData.Brands)
{
<a href="/Services/Brand?id=@b.Id"
class="group relative border-2 rounded-2xl p-5 text-center hover:shadow-xl transition-all hover:-translate-y-1"
style="border-color:@(b.IsOfficial ? "#f59e0b" : "#e5e7eb")">
@if (b.IsOfficial)
{
<div class="absolute -top-3 right-1/2 translate-x-1/2 bg-yellow-400 text-gray-900 text-xs font-bold px-2 py-0.5 rounded-full whitespace-nowrap">🛡️ رسمی</div>
}
<div class="w-16 h-16 rounded-2xl flex items-center justify-center text-2xl font-extrabold mx-auto mb-3 shadow-md"
style="background-color:@b.Color;color:@b.TextColor">
@b.Name.Substring(0, 2)
</div>
<div class="font-bold text-gray-800">@b.NameFa</div>
<div class="text-xs text-gray-400 mt-0.5">@b.Name</div>
<div class="text-xs text-blue-600 mt-2 group-hover:underline">خدمات تعمیر </div>
</a>
}
</div>
</div>
</section>
<!-- ══════════════════════ TOOLS WE REPAIR ═══════════════════════════════ -->
<section class="py-16 px-4 bg-gray-50">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-2xl font-bold text-gray-900 mb-2">ابزارهایی که تعمیر می‌کنیم</h2>
<p class="text-gray-500">تخصص کامل در تعمیر انواع ابزار برقی صنعتی</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
@{
var toolImages = new Dictionary<string, string>
{
["drill"] = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=500&q=75&auto=format&fit=crop",
["grinder"] = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=500&q=75&auto=format&fit=crop",
["mini-grinder"] = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=500&q=75&auto=format&fit=crop",
["hedge-trimmer"] = "https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=500&q=75&auto=format&fit=crop",
["rotary-hammer"] = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=500&q=75&auto=format&fit=crop",
["laser-level"] = "https://images.unsplash.com/photo-1609220136736-443140cfeaa8?w=500&q=75&auto=format&fit=crop",
["gerd-bar"] = "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=500&q=75&auto=format&fit=crop",
["miter-saw"] = "https://images.unsplash.com/photo-1558618047-3c8c1d8b9df1?w=500&q=75&auto=format&fit=crop",
["woodworking"] = "https://images.unsplash.com/photo-1588854337115-1c67d9247e4d?w=500&q=75&auto=format&fit=crop",
["laser-measure"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=500&q=75&auto=format&fit=crop",
};
}
@foreach (var t in SiteData.ToolTypes)
{
var img = toolImages.TryGetValue(t.Id, out var u) ? u : "";
<a href="/Services" class="bg-white rounded-2xl overflow-hidden border border-gray-100 hover:shadow-lg transition-all hover:-translate-y-0.5 block group">
@if (!string.IsNullOrEmpty(img))
{
<div class="h-44 overflow-hidden relative">
<img src="@img" alt="تعمیر @t.NameFa" loading="lazy"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" />
<div class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
<div class="absolute bottom-3 right-4 text-3xl">@t.Icon</div>
</div>
}
<div class="p-6">
<h3 class="font-bold text-xl text-gray-900 mb-2">تعمیر @t.NameFa</h3>
<p class="text-gray-500 text-sm leading-7 mb-4">@t.Description</p>
<ul class="space-y-1.5">
@foreach (var issue in t.CommonIssues.Take(3))
{
<li class="flex items-start gap-2 text-sm text-gray-600">
<span class="text-green-500 shrink-0 mt-0.5">✓</span> @issue
</li>
}
</ul>
<div class="mt-4 text-blue-600 text-sm font-medium group-hover:underline">مشاهده جزئیات </div>
</div>
</a>
}
</div>
</div>
</section>
<!-- ══════════════════════ WORKSHOP GALLERY ═══════════════════════════════ -->
<section class="py-16 px-4 bg-white">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-2xl font-bold text-gray-900 mb-2">کارگاه تعمیر آساد ابزار</h2>
<p class="text-gray-500">نگاهی به محیط کاری و تجهیزات تخصصی ما</p>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
@{
var gallery = new[]
{
("https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=600&q=80&auto=format&fit=crop", "کارگاه تعمیر ابزار"),
("https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=600&q=80&auto=format&fit=crop", "جعبه ابزار تخصصی"),
("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=600&q=80&auto=format&fit=crop", "ابزار دیوالت"),
("https://images.unsplash.com/photo-1607400201515-c2c41c07d307?w=600&q=80&auto=format&fit=crop", "تکنیسین متخصص"),
("https://images.unsplash.com/photo-1518770660439-4636190af475?w=600&q=80&auto=format&fit=crop", "قطعات یدکی"),
("https://images.unsplash.com/photo-1504148455328-c376907d081c?w=600&q=80&auto=format&fit=crop", "ابزار صنعتی"),
};
}
@foreach (var (src, alt) in gallery)
{
<div class="relative rounded-2xl overflow-hidden group" style="height:200px">
<img src="@src" alt="@alt" loading="lazy"
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500" />
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors"></div>
</div>
}
</div>
</div>
</section>
<!-- ══════════════════════ WHY US ════════════════════════════════════════ -->
<section class="py-16 px-4 bg-gray-50">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-2xl font-bold text-gray-900 mb-2">چرا آساد ابزار؟</h2>
<p class="text-gray-500">دلایل اعتماد هزاران مشتری به ما</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
@foreach (var item in new[] {
("🛡️", "ضمانت تعمیر", "ضمانت کتبی ۳ ماهه روی کلیه تعمیرات انجام‌شده", "bg-blue-50 border-blue-100"),
("🔩", "قطعات ۱۰۰٪ اصل", "فقط قطعات یدکی اورجینال از نمایندگی رسمی", "bg-yellow-50 border-yellow-100"),
("⚡", "تعمیر سریع", "اکثر تعمیرات در کمتر از ۴۸ ساعت تحویل داده می‌شود", "bg-green-50 border-green-100"),
("⭐", "تکنیسین مجرب", "بیش از ۱۵ سال سابقه تعمیر تخصصی ابزار صنعتی", "bg-purple-50 border-purple-100"),
("💰", "قیمت شفاف", "برآورد رایگان قبل از شروع تعمیر بدون هزینه پنهان", "bg-red-50 border-red-100"),
("📦", "ارسال سراسری", "تعمیر ابزار از سراسر کشور از طریق پست و تیپاکس", "bg-orange-50 border-orange-100"),
("📞", "مشاوره رایگان", "مشاوره تلفنی رایگان برای تشخیص عیب دستگاه", "bg-teal-50 border-teal-100"),
("🔄", "خدمات پیشگیرانه", "سرویس دوره‌ای و تنظیم ابزار برای جلوگیری از خرابی", "bg-indigo-50 border-indigo-100"),
})
{
<div class="text-center p-6 rounded-2xl border @item.Item4">
<div class="text-3xl mb-3">@item.Item1</div>
<h3 class="font-bold text-gray-900 mb-2">@item.Item2</h3>
<p class="text-sm text-gray-500 leading-6">@item.Item3</p>
</div>
}
</div>
</div>
</section>
<!-- ══════════════════════ TECHNICIAN SECTION ════════════════════════════ -->
<section class="py-16 px-4 bg-blue-900 text-white">
<div class="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
<div>
<h2 class="text-3xl font-extrabold mb-4">تکنیسین‌های ما، متخصص‌ترین در البرز</h2>
<p class="text-blue-100 leading-8 mb-6">
تیم تکنیسین‌های آساد ابزار با گذراندن دوره‌های آموزشی رسمی دیوالت و ماکیتا،
تجربه تعمیر بیش از ۵۰۰۰ دستگاه را دارند. از ساده‌ترین مشکل مانند تعویض کاربن،
تا پیچیده‌ترین عیوب الکترونیکی و مکانیکی.
</p>
<div class="space-y-3">
@foreach (var f in new[] {
"آموزش رسمی از DeWalt و Makita",
"تجربه بیش از ۱۵ سال تعمیر تخصصی",
"استفاده از دستگاه‌های اندازه‌گیری حرفه‌ای",
"به‌روز بودن با آخرین مدل‌های ابزار",
"ارائه رسید و فاکتور رسمی برای تمام تعمیرات",
})
{
<div class="flex items-center gap-3">
<span class="w-5 h-5 rounded-full bg-yellow-400 flex items-center justify-center text-gray-900 text-xs font-bold shrink-0">✓</span>
<span class="text-blue-100 text-sm">@f</span>
</div>
}
</div>
</div>
<div class="relative rounded-3xl overflow-hidden shadow-2xl" style="height:380px">
<img src="https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=700&q=85&auto=format&fit=crop"
alt="تکنیسین تعمیر ابزار آساد ابزار کرج" loading="lazy"
class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-gradient-to-t from-blue-900/80 to-transparent"></div>
<div class="absolute bottom-5 right-5 left-5">
<div class="bg-white/10 backdrop-blur-sm rounded-xl p-4 border border-white/20">
<div class="font-bold text-sm mb-1">آساد ابزار کرج</div>
<div class="text-blue-200 text-xs">نمایندگی رسمی دیوالت • ۱۵ سال تجربه</div>
</div>
</div>
</div>
</div>
</section>
<!-- ══════════════════════ CTA ═══════════════════════════════════════════ -->
<section class="py-16 px-4 bg-yellow-400">
<div class="max-w-3xl mx-auto text-center">
<h2 class="text-3xl font-extrabold text-gray-900 mb-4">ابزار شما خراب شده؟</h2>
<p class="text-gray-700 text-lg mb-8">همین الان با ما تماس بگیرید. مشاوره رایگان و تعمیر با ضمانت کتبی.</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="tel:@SiteData.Company.TelPhone" class="bg-gray-900 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-gray-800 transition-colors text-lg shadow-lg">
📞 @SiteData.Company.Phone
</a>
<a href="https://wa.me/@SiteData.Company.Whatsapp" target="_blank"
class="bg-green-600 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-green-700 transition-colors text-lg shadow-lg">
💬 واتساپ
</a>
<a href="/DeWalt" class="bg-white text-gray-900 font-bold px-8 py-3.5 rounded-xl hover:bg-gray-50 transition-colors text-lg shadow-lg">
🔧 ابزار دیوالت
</a>
</div>
</div>
</section>
+12
View File
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages;
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
+8
View File
@@ -0,0 +1,8 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
+12
View File
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages;
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}
+218
View File
@@ -0,0 +1,218 @@
@page
@model AsadiTools.Pages.Services.BrandModel
@{ ViewData["Title"] = $"تعمیر ابزار {Model.Brand?.NameFa} در کرج"; Layout = "_Layout"; }
@if (Model.Brand is null)
{
<div class="max-w-xl mx-auto text-center py-20"><p class="text-gray-500">برند یافت نشد.</p></div>
}
else
{
var b = Model.Brand;
var isDewalt = b.Id == "dewalt";
<!-- ═══════════ HERO ═══════════ -->
<div class="relative text-white py-14 px-4 overflow-hidden" style="background:linear-gradient(135deg,@b.Color+dd,@b.Color+99)">
@if (isDewalt)
{
<div class="absolute inset-0 bg-cover bg-center opacity-20"
style="background-image:url('https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&q=80&auto=format&fit=crop')"></div>
}
<div class="relative max-w-6xl mx-auto flex flex-col sm:flex-row items-center gap-6">
<div class="w-20 h-20 rounded-2xl flex items-center justify-center text-2xl font-extrabold border-2 border-white/30 bg-white/20 shrink-0 shadow-lg"
style="color:@b.TextColor">
@b.Name.Substring(0, 2)
</div>
<div>
<div class="flex flex-wrap items-center gap-3 mb-2">
<h1 class="text-3xl font-extrabold" style="color:@(b.TextColor == "#fff" ? "white" : "#1f2937")">
تعمیر ابزار @b.NameFa در کرج
</h1>
@if (b.IsOfficial)
{
<span class="bg-white text-gray-900 text-sm font-bold px-3 py-1 rounded-full shadow">🛡️ نمایندگی رسمی</span>
}
</div>
<p class="opacity-90 max-w-xl" style="color:@(b.TextColor == "#fff" ? "#e0e0e0" : "#374151")">@b.Description</p>
@if (isDewalt)
{
<a href="/DeWalt" class="inline-block mt-3 bg-yellow-400 text-gray-900 font-bold text-sm px-4 py-2 rounded-lg hover:bg-yellow-300 transition-colors">
مشاهده همه ابزار دیوالت ›
</a>
}
</div>
</div>
</div>
<div class="max-w-6xl mx-auto px-4 py-12 grid lg:grid-cols-3 gap-10">
<!-- ═══════════ MAIN CONTENT ═══════════ -->
<div class="lg:col-span-2 space-y-8">
@if (b.IsOfficial)
{
<div class="bg-yellow-50 border border-yellow-200 rounded-2xl p-6">
<div class="flex items-start gap-3">
<span class="text-yellow-600 text-2xl shrink-0">🛡️</span>
<div>
<h2 class="font-bold text-gray-900 mb-2 text-lg">نمایندگی رسمی @b.NameFa در کرج</h2>
<p class="text-gray-600 text-sm leading-7">
آساد ابزار افتخار دارد نمایندگی رسمی برند @b.NameFa را در شهر کرج داشته باشد.
تمام تعمیرات توسط تکنیسین‌های آموزش‌دیده و با قطعات کاملاً اورجینال انجام می‌شود.
هر تعمیر دارای ضمانت‌نامه کتبی ۳ ماهه است.
</p>
</div>
</div>
</div>
}
<!-- DeWalt: full tools grid -->
@if (isDewalt)
{
<section>
<h2 class="text-xl font-bold text-gray-900 mb-5 pb-2 border-b flex items-center gap-2">
<span class="bg-yellow-400 text-gray-900 text-xs font-bold px-2 py-0.5 rounded">۱۵+ مدل</span>
ابزار دیوالت که تعمیر می‌کنیم
</h2>
<div class="grid sm:grid-cols-2 gap-4">
@{
var catImgs = new Dictionary<string, string>
{
["drill"] = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=400&q=70&auto=format&fit=crop",
["driver"] = "https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=400&q=70&auto=format&fit=crop",
["grinder"] = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=400&q=70&auto=format&fit=crop",
["rotary-hammer"] = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=400&q=70&auto=format&fit=crop",
["saw"] = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=400&q=70&auto=format&fit=crop",
["chop-saw"] = "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=400&q=70&auto=format&fit=crop",
["miter-saw"] = "https://images.unsplash.com/photo-1558618047-3c8c1d8b9df1?w=400&q=70&auto=format&fit=crop",
["woodworking"] = "https://images.unsplash.com/photo-1588854337115-1c67d9247e4d?w=400&q=70&auto=format&fit=crop",
["multi"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&q=70&auto=format&fit=crop",
["sander"] = "https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=400&q=70&auto=format&fit=crop",
["hedge-trimmer"] = "https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=400&q=70&auto=format&fit=crop",
["laser-level"] = "https://images.unsplash.com/photo-1609220136736-443140cfeaa8?w=400&q=70&auto=format&fit=crop",
["laser-measure"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&q=70&auto=format&fit=crop",
};
}
@foreach (var tool in SiteData.DeWaltTools)
{
var img = catImgs.TryGetValue(tool.CategoryId, out var tu) ? tu : catImgs["drill"];
<div class="bg-white rounded-2xl border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
<div class="relative h-36 overflow-hidden">
<img src="@img" alt="تعمیر @tool.NameFa" loading="lazy"
class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div class="absolute top-2 left-2">
<span class="bg-yellow-400 text-gray-900 text-xs font-bold px-1.5 py-0.5 rounded">@tool.Power</span>
</div>
<div class="absolute bottom-2 right-3 text-2xl">@tool.Icon</div>
</div>
<div class="p-4">
<h3 class="font-bold text-gray-900 text-sm mb-0.5">@tool.NameFa</h3>
<p class="text-xs text-gray-400 font-mono mb-2">@string.Join(" · ", tool.Models.Take(3))</p>
<div class="flex flex-wrap gap-1">
@foreach (var r in tool.RepairItems.Take(3))
{
<span class="text-xs bg-yellow-50 text-yellow-800 border border-yellow-100 px-1.5 py-0.5 rounded">@r</span>
}
@if (tool.RepairItems.Length > 3)
{
<span class="text-xs text-gray-400">+@(tool.RepairItems.Length - 3) مورد دیگر</span>
}
</div>
</div>
</div>
}
</div>
<div class="mt-6 text-center">
<a href="/DeWalt" class="inline-block bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors">
مشاهده صفحه کامل ابزار دیوالت ›
</a>
</div>
</section>
}
else
{
<!-- Non-DeWalt brands: existing tool service cards -->
<section>
<h2 class="text-xl font-bold text-gray-900 mb-5 pb-2 border-b">خدمات تعمیر @b.NameFa</h2>
<div class="space-y-4">
@foreach (var tool in SiteData.ToolTypes.Where(t => b.Services.Contains(t.NameFa)))
{
<div class="bg-white rounded-2xl border border-gray-100 p-6 hover:shadow-sm transition-shadow">
<div class="flex items-center gap-3 mb-3">
<span class="text-2xl">@tool.Icon</span>
<h3 class="font-bold text-gray-900">تعمیر @tool.NameFa @b.NameFa</h3>
</div>
<p class="text-gray-500 text-sm leading-7 mb-4">@tool.Description</p>
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach (var issue in tool.CommonIssues)
{
<li class="flex items-start gap-2 text-sm text-gray-600">
<span class="text-green-500 shrink-0">✓</span> @issue
</li>
}
</ul>
</div>
}
</div>
</section>
}
<!-- Repair process -->
<section>
<h2 class="text-xl font-bold text-gray-900 mb-5 pb-2 border-b">فرآیند تعمیر</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
@foreach (var step in new[] { ("۱","تحویل ابزار","حضوری یا پست"), ("۲","بررسی رایگان","تشخیص سریع"), ("۳","تعمیر تخصصی","قطعه اصل"), ("۴","تحویل با ضمانت","۳ ماه گارانتی") })
{
<div class="text-center p-4 bg-gray-50 rounded-2xl">
<div class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold mx-auto mb-3 shadow"
style="background-color:@b.Color;color:@b.TextColor">@step.Item1</div>
<div class="font-bold text-gray-800 text-sm">@step.Item2</div>
<div class="text-xs text-gray-400 mt-1">@step.Item3</div>
</div>
}
</div>
</section>
</div>
<!-- ═══════════ SIDEBAR ═══════════ -->
<div class="space-y-5">
<div class="rounded-2xl p-6 text-white" style="background-color:@b.Color">
<h3 class="font-bold text-lg mb-1" style="color:@(b.TextColor == "#fff" ? "white" : "#1f2937")">درخواست تعمیر</h3>
<p class="text-sm mb-5 opacity-80" style="color:@(b.TextColor == "#fff" ? "#e0e0e0" : "#374151")">مشاوره رایگان – تماس بگیرید</p>
<a href="tel:@SiteData.Company.TelPhone" class="block bg-white font-bold text-center py-3 rounded-xl hover:opacity-90 mb-3 transition-opacity"
style="color:@b.Color">📞 @SiteData.Company.Phone</a>
<a href="https://wa.me/@SiteData.Company.Whatsapp" target="_blank"
class="block bg-green-500 text-white font-bold text-center py-3 rounded-xl hover:bg-green-600 transition-colors">💬 واتساپ</a>
</div>
<div class="bg-white rounded-2xl border p-6">
<h3 class="font-bold text-gray-900 mb-4">⭐ مزایای خدمات ما</h3>
<ul class="space-y-2.5 text-sm text-gray-600">
@foreach (var item in new[] { "بررسی و تشخیص عیب رایگان", "قطعات ۱۰۰٪ اورجینال", "ضمانت‌نامه کتبی ۳ ماهه", "تکنیسین مجاز و متخصص", "قیمت شفاف قبل از شروع کار", "تحویل سریع زیر ۴۸ ساعت", "ارسال و دریافت از سراسر کشور" })
{
<li class="flex items-center gap-2">
<span class="text-green-500 shrink-0">✓</span> @item
</li>
}
</ul>
</div>
@if (isDewalt)
{
<div class="bg-yellow-50 border border-yellow-200 rounded-2xl p-5">
<h3 class="font-bold text-gray-900 mb-3">🔩 قطعات موجود DeWalt</h3>
<div class="flex flex-wrap gap-1.5">
@foreach (var p in new[] { "کاربن", "بیرینگ", "آرمیچر", "گیربکس", "چاک", "کلید", "استاتور", "پیستون" })
{
<span class="text-xs bg-yellow-100 text-yellow-800 border border-yellow-200 px-2 py-0.5 rounded-full">@p</span>
}
</div>
</div>
}
<a href="/Shop" class="block bg-gray-50 border border-gray-200 rounded-2xl p-5 hover:shadow-md transition-shadow">
<h3 class="font-bold text-gray-900 mb-1">🛒 فروشگاه قطعات</h3>
<p class="text-sm text-gray-500">قطعات یدکی @b.NameFa را مستقیم سفارش دهید.</p>
</a>
</div>
</div>
}
+16
View File
@@ -0,0 +1,16 @@
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Services;
public class BrandModel : PageModel
{
public BrandInfo? Brand { get; private set; }
public void OnGet(string id)
{
Brand = SiteData.Brands.FirstOrDefault(b => b.Id == id);
if (Brand is not null)
ViewData["Description"] = $"{Brand.Description} تعمیر {string.Join("، ", Brand.Services)} {Brand.NameFa} در کرج.";
}
}
+62
View File
@@ -0,0 +1,62 @@
@page
@model AsadiTools.Pages.Services.ServicesIndexModel
@{ ViewData["Title"] = "خدمات تعمیر ابزار برقی در کرج"; Layout = "_Layout"; }
<div class="bg-blue-800 text-white py-12 px-4">
<div class="max-w-6xl mx-auto">
<h1 class="text-3xl font-extrabold mb-2">خدمات تعمیر ابزار</h1>
<p class="text-blue-200 text-lg">تعمیر تخصصی انواع ابزار برقی صنعتی توسط تکنیسین‌های مجرب در کرج</p>
</div>
</div>
<div class="max-w-6xl mx-auto px-4 py-12">
<section class="mb-14">
<h2 class="text-xl font-bold text-gray-900 mb-6 pb-2 border-b">خدمات بر اساس برند</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
@foreach (var b in SiteData.Brands)
{
<a href="/Services/Brand?id=@b.Id" class="group bg-white rounded-2xl border border-gray-100 p-6 hover:shadow-lg transition-all hover:-translate-y-0.5">
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-xl font-extrabold shrink-0"
style="background-color:@b.Color;color:@b.TextColor">@b.Name[0]</div>
<div>
<div class="flex items-center gap-2">
<span class="font-bold text-gray-900">@b.NameFa</span>
@if (b.IsOfficial) { <span class="text-xs bg-yellow-400 text-gray-900 px-1.5 py-0.5 rounded font-bold">رسمی</span> }
</div>
<div class="text-xs text-gray-400">@b.Name</div>
</div>
</div>
<p class="text-sm text-gray-600 mb-4 leading-7">@b.Description</p>
<div class="flex flex-wrap gap-1.5 mb-3">
@foreach (var svc in b.Services) { <span class="text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full">@svc</span> }
</div>
<div class="text-sm text-blue-600 font-medium">مشاهده خدمات کامل ›</div>
</a>
}
</div>
</section>
<section>
<h2 class="text-xl font-bold text-gray-900 mb-6 pb-2 border-b">خدمات بر اساس نوع ابزار</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@foreach (var t in SiteData.ToolTypes)
{
<div class="bg-white rounded-2xl border border-gray-100 p-6">
<div class="flex items-center gap-3 mb-4">
<span class="text-3xl">@t.Icon</span>
<h3 class="font-bold text-xl text-gray-900">@t.NameFa</h3>
</div>
<p class="text-gray-500 text-sm leading-7 mb-4">@t.Description</p>
<p class="text-sm font-bold text-gray-700 mb-3">مشکلات رایج:</p>
<ul class="space-y-2">
@foreach (var issue in t.CommonIssues)
{
<li class="flex items-start gap-2 text-sm text-gray-600"><span class="text-green-500 shrink-0">✓</span> @issue</li>
}
</ul>
</div>
}
</div>
</section>
</div>
+8
View File
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Services;
public class ServicesIndexModel : PageModel
{
public void OnGet() { }
}
+59
View File
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] | پنل مدیریت آساد ابزار</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
*, body { font-family: "Vazirmatn", Tahoma, Arial, sans-serif !important; }
</style>
</head>
<body class="bg-gray-50 text-gray-800">
<div class="flex min-h-screen">
<!-- Sidebar -->
<aside class="fixed right-0 top-0 h-full w-56 bg-gray-900 text-white flex flex-col z-40">
<div class="p-5 border-b border-gray-800">
<div class="flex items-center gap-2">
<div class="bg-blue-600 rounded-lg p-1.5 text-sm">🔧</div>
<div>
<div class="font-bold text-sm">آساد ابزار</div>
<div class="text-xs text-gray-400">پنل مدیریت</div>
</div>
</div>
</div>
<nav class="flex-1 p-4 space-y-1">
<a href="/Admin" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
📊 داشبورد
</a>
<a href="/Admin/Products" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
📦 محصولات
</a>
<a href="/Admin/Orders" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
🛍️ سفارش‌ها
</a>
<a href="/Admin/Blog" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
📝 بلاگ
</a>
<a href="/Admin/ChangePassword" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
🔑 تغییر رمز عبور
</a>
</nav>
<div class="p-4 border-t border-gray-800">
<form method="post" action="/Admin/Logout">
@Html.AntiForgeryToken()
<button type="submit" class="text-sm text-red-400 hover:text-red-300 transition-colors">🚪 خروج</button>
</form>
</div>
</aside>
<!-- Main -->
<main class="flex-1 mr-56 overflow-auto">
@RenderBody()
</main>
</div>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
+111
View File
@@ -0,0 +1,111 @@
@inject AsadiTools.Services.CartService Cart
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@(ViewData["Title"] != null ? ViewData["Title"] + " | آساد ابزار کرج" : "آساد ابزار - نمایندگی رسمی دیوالت در کرج")</title>
<meta name="description" content="@(ViewData["Description"] ?? "نمایندگی رسمی دیوالت، ماکیتا، رونیکس، توسن و بلک اند دکر در کرج. تعمیر دریل، فرز، مینی فرز، شمشاد زن و بتن کن.")" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<script src="https://cdn.tailwindcss.com"></script>
<style>
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-ExtraBold.woff2") format("woff2"); font-weight:800; font-display:swap; }
*, body { font-family: "Vazirmatn", Tahoma, Arial, sans-serif !important; }
::-webkit-scrollbar { width:5px } ::-webkit-scrollbar-thumb { background:#94a3b8; border-radius:3px }
</style>
@await RenderSectionAsync("Head", required: false)
</head>
<body class="bg-gray-50 text-gray-800 flex flex-col min-h-screen">
<!-- Top bar -->
<div class="bg-blue-800 text-white text-sm py-1.5 px-4">
<div class="max-w-6xl mx-auto flex justify-between items-center">
<a href="tel:@SiteData.Company.TelPhone" class="hover:text-yellow-300 transition-colors">📞 @SiteData.Company.Phone</a>
<span class="text-yellow-300 font-bold text-xs">نمایندگی رسمی دیوالت در کرج</span>
</div>
</div>
<!-- Header -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between gap-4">
<a href="/" class="flex items-center gap-2 shrink-0">
<div class="bg-blue-700 text-white rounded-lg p-2 text-lg">🔧</div>
<div>
<div class="font-bold text-lg text-gray-900 leading-tight">آساد ابزار</div>
<div class="text-xs text-gray-400">کرج • تعمیر ابزار صنعتی</div>
</div>
</a>
<nav class="hidden md:flex items-center gap-5">
<a href="/" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">خانه</a>
<a href="/Services" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">خدمات</a>
<a href="/brands" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">برندها</a>
<a href="/blog" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">بلاگ</a>
<a href="/DeWalt" class="inline-flex items-center gap-1 bg-yellow-400 text-gray-900 font-bold px-3 py-1 rounded-lg text-sm hover:bg-yellow-300 transition-colors">🛡️ دیوالت</a>
<a href="/Shop" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">فروشگاه قطعات</a>
<a href="/Contact" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">تماس با ما</a>
</nav>
<div class="flex items-center gap-3">
<a href="/Cart" class="relative flex items-center gap-1.5 bg-blue-700 text-white px-3 py-2 rounded-lg text-sm font-medium hover:bg-blue-800 transition-colors">
🛒 <span class="hidden sm:inline">سبد خرید</span>
@if (Cart.Count > 0)
{
<span class="absolute -top-1.5 -left-1.5 bg-yellow-400 text-gray-900 text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">@Cart.Count</span>
}
</a>
<button onclick="document.getElementById('mobile-menu').classList.toggle('hidden')" class="md:hidden p-2 rounded-lg hover:bg-gray-100">☰</button>
</div>
</div>
<div id="mobile-menu" class="hidden md:hidden border-t bg-white px-4 py-3 space-y-1">
<a href="/" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">خانه</a>
<a href="/Services" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">خدمات</a>
<a href="/brands" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">برندها</a>
<a href="/blog" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">بلاگ</a>
<a href="/DeWalt" class="block py-2.5 px-3 rounded-lg bg-yellow-50 text-gray-900 font-bold">🛡️ ابزار دیوالت (رسمی)</a>
<a href="/Shop" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">فروشگاه قطعات</a>
<a href="/Contact" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">تماس با ما</a>
</div>
</header>
<main class="flex-1">
@RenderBody()
</main>
<!-- Footer -->
<footer class="bg-gray-900 text-gray-300 mt-auto">
<div class="max-w-6xl mx-auto px-4 py-12 grid grid-cols-1 md:grid-cols-3 gap-10">
<div>
<h3 class="text-white font-bold text-lg mb-4">آساد ابزار</h3>
<p class="text-sm leading-7 text-gray-400">با بیش از ۱۵ سال تجربه، نمایندگی رسمی دیوالت در کرج. تعمیر تخصصی ابزار صنعتی با قطعات اصل.</p>
</div>
<div>
<h3 class="text-white font-bold text-lg mb-4">تماس</h3>
<ul class="space-y-2 text-sm">
<li>📞 <a href="tel:@SiteData.Company.TelPhone" class="hover:text-white">@SiteData.Company.Phone</a></li>
<li>📱 <a href="tel:@SiteData.Company.TelMobile" class="hover:text-white">@SiteData.Company.Mobile</a></li>
<li>📍 @SiteData.Company.Address</li>
<li>🕐 @SiteData.Company.WorkingHours</li>
</ul>
</div>
<div>
<h3 class="text-white font-bold text-lg mb-4">خدمات</h3>
<ul class="space-y-2 text-sm">
<li><a href="/Services/Brand?id=dewalt" class="hover:text-white">تعمیر ابزار دیوالت <span class="text-yellow-400 text-xs">(رسمی)</span></a></li>
<li><a href="/Services/Brand?id=makita" class="hover:text-white">تعمیر ابزار ماکیتا</a></li>
<li><a href="/Services/Brand?id=ronix" class="hover:text-white">تعمیر ابزار رونیکس</a></li>
<li><a href="/Services/Brand?id=tosan" class="hover:text-white">تعمیر ابزار توسن</a></li>
<li><a href="/Shop" class="hover:text-white">فروشگاه قطعات یدکی</a></li>
</ul>
</div>
</div>
<div class="border-t border-gray-800 py-4 text-center text-xs text-gray-500">
© @DateTime.Now.Year آساد ابزار کرج – تمام حقوق محفوظ است
</div>
</footer>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}
@@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
+74
View File
@@ -0,0 +1,74 @@
@page
@model AsadiTools.Pages.Shop.DetailModel
@{ ViewData["Title"] = Model.Product?.NameFa ?? "قطعه"; Layout = "_Layout"; }
@if (Model.Product is null)
{
<div class="max-w-xl mx-auto text-center py-20"><p class="text-gray-500">محصول یافت نشد.</p><a href="/Shop" class="text-blue-600 hover:underline mt-2 block">بازگشت به فروشگاه</a></div>
}
else
{
var p = Model.Product;
var cat = SiteData.Categories.FirstOrDefault(c => c.Id == p.Category);
var brand = SiteData.Brands.FirstOrDefault(b => b.Id == p.Brand);
<div class="max-w-5xl mx-auto px-4 py-8">
<nav class="flex items-center gap-2 text-sm text-gray-500 mb-8">
<a href="/" class="hover:text-blue-600">خانه</a><span>/</span>
<a href="/Shop" class="hover:text-blue-600">فروشگاه</a><span>/</span>
<span class="text-gray-800">@p.NameFa</span>
</nav>
<div class="grid md:grid-cols-2 gap-10">
<div class="rounded-3xl overflow-hidden aspect-square @(string.IsNullOrEmpty(p.ImageUrl) ? "bg-gradient-to-br from-blue-50 to-blue-100 flex items-center justify-center text-8xl" : "")">
@if (!string.IsNullOrEmpty(p.ImageUrl))
{
<img src="@p.ImageUrl" alt="@p.NameFa" class="w-full h-full object-cover" />
}
else
{
@(cat?.Icon ?? "🔧")
}
</div>
<div>
<div class="flex flex-wrap gap-2 mb-4">
@if (brand != null) { <span class="text-sm font-bold px-3 py-1 rounded-full text-white" style="background-color:@brand.Color">@brand.NameFa</span> }
@if (cat != null) { <span class="text-sm px-3 py-1 rounded-full bg-gray-100 text-gray-600">@cat.Icon @cat.NameFa</span> }
</div>
<h1 class="text-2xl font-extrabold text-gray-900 mb-2">@p.NameFa</h1>
@if (p.NameEn != null) { <p class="text-gray-400 text-sm mb-4 font-mono">@p.NameEn</p> }
@if (p.Sku != null) { <p class="text-xs text-gray-400 mb-6 bg-gray-100 inline-block px-3 py-1 rounded-full">کد محصول: @p.Sku</p> }
@if (p.Description != null) { <p class="text-gray-600 leading-7 mb-6 text-sm">@p.Description</p> }
<div class="mb-6">
@if (p.HasDiscount) { <p class="text-gray-400 line-through text-lg">@SiteData.FormatPrice(p.Price)</p> }
<p class="text-3xl font-extrabold text-blue-700">@SiteData.FormatPrice(p.FinalPrice)</p>
</div>
<div class="mb-6">
@if (p.Stock > 0)
{
<span class="text-green-600 text-sm font-medium">🟢 موجود در انبار (@p.Stock عدد)</span>
}
else
{
<span class="text-red-500 text-sm font-medium">🔴 ناموجود</span>
}
</div>
<form method="post" class="flex gap-3">
@Html.AntiForgeryToken()
<input type="hidden" name="productId" value="@p.Id" />
<input type="hidden" name="nameFa" value="@p.NameFa" />
<input type="hidden" name="price" value="@p.FinalPrice" />
<input type="hidden" name="sku" value="@p.Sku" />
<button type="submit" @(p.Stock == 0 ? "disabled" : "")
class="flex-1 flex items-center justify-center gap-2 bg-blue-700 text-white py-3.5 rounded-xl font-bold hover:bg-blue-800 transition-colors disabled:bg-gray-200 disabled:text-gray-400">
🛒 @(p.Stock == 0 ? "ناموجود" : "افزودن به سبد خرید")
</button>
<a href="/Cart" class="flex items-center gap-2 border-2 border-blue-700 text-blue-700 py-3.5 px-4 rounded-xl font-bold hover:bg-blue-50">سبد</a>
</form>
</div>
</div>
</div>
}
+23
View File
@@ -0,0 +1,23 @@
using AsadiTools.Data;
using AsadiTools.Models;
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AsadiTools.Pages.Shop;
public class DetailModel(AppDbContext db, CartService cart) : PageModel
{
public Product? Product { get; private set; }
public async Task OnGetAsync(int id)
{
Product = await db.Products.FindAsync(id);
}
public IActionResult OnPost(int productId, string nameFa, decimal price, string? sku)
{
cart.AddItem(new CartItem { ProductId = productId, NameFa = nameFa, Price = price, Sku = sku, Qty = 1 });
return RedirectToPage("/Cart/Index");
}
}
+139
View File
@@ -0,0 +1,139 @@
@page
@model AsadiTools.Pages.Shop.ShopIndexModel
@{ ViewData["Title"] = "فروشگاه قطعات یدکی ابزار برقی"; Layout = "_Layout"; }
<div class="bg-blue-800 text-white py-10 px-4">
<div class="max-w-6xl mx-auto">
<h1 class="text-3xl font-extrabold mb-2">فروشگاه قطعات یدکی</h1>
<p class="text-blue-200">قطعات اصل ابزار برقی صنعتی با گارانتی</p>
</div>
</div>
<div class="max-w-6xl mx-auto px-4 py-8">
<!-- Filters -->
<div class="bg-white rounded-2xl border border-gray-100 p-5 mb-8">
<form method="get" class="mb-5">
<input type="text" name="search" value="@Model.Search" placeholder="جستجو در قطعات..."
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
</form>
<div class="mb-4">
<p class="text-xs text-gray-500 font-bold mb-2">دسته‌بندی</p>
<div class="flex flex-wrap gap-2">
<a href="/Shop" class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Category == null ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">همه</a>
@foreach (var cat in SiteData.Categories)
{
<a href="/Shop?category=@cat.Id@(Model.Brand != null ? "&brand=" + Model.Brand : "")"
class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Category == cat.Id ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">
@cat.Icon @cat.NameFa
</a>
}
</div>
</div>
<div>
<p class="text-xs text-gray-500 font-bold mb-2">برند</p>
<div class="flex flex-wrap gap-2">
<a href="/Shop@(Model.Category != null ? "?category=" + Model.Category : "")"
class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Brand == null ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">همه برندها</a>
@foreach (var brand in SiteData.Brands)
{
<a href="/Shop?brand=@brand.Id@(Model.Category != null ? "&category=" + Model.Category : "")"
class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Brand == brand.Id ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">
@brand.NameFa
</a>
}
</div>
</div>
</div>
<p class="text-sm text-gray-500 mb-5">@Model.TotalCount محصول یافت شد</p>
@if (!Model.Products.Any())
{
<div class="text-center py-20 text-gray-400">
<div class="text-5xl mb-4">🔍</div>
<p>محصولی یافت نشد</p>
<a href="/Shop" class="text-blue-600 text-sm mt-2 hover:underline block">مشاهده همه محصولات</a>
</div>
}
else
{
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5">
@foreach (var p in Model.Products)
{
var cat = SiteData.Categories.FirstOrDefault(c => c.Id == p.Category);
var brand = SiteData.Brands.FirstOrDefault(b => b.Id == p.Brand);
<div class="bg-white rounded-xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow flex flex-col">
@if (!string.IsNullOrEmpty(p.ImageUrl))
{
<div class="rounded-t-xl overflow-hidden" style="height:160px">
<img src="@p.ImageUrl" alt="@p.NameFa" loading="lazy"
class="w-full h-full object-cover"
onerror="this.parentElement.innerHTML='<div class=\'bg-gradient-to-br from-blue-50 to-blue-100 h-full flex items-center justify-center text-5xl\'>@(cat?.Icon ?? "🔧")</div>'" />
</div>
}
else
{
<div class="bg-gradient-to-br from-blue-50 to-blue-100 rounded-t-xl p-8 flex items-center justify-center text-5xl">
@(cat?.Icon ?? "🔧")
</div>
}
<div class="p-4 flex flex-col flex-1 gap-3">
<div class="flex flex-wrap gap-1.5">
@if (brand != null) { <span class="text-xs font-bold px-2 py-0.5 rounded-full text-white" style="background-color:@brand.Color">@brand.NameFa</span> }
@if (cat != null) { <span class="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-600">@cat.NameFa</span> }
@if (p.Stock == 0) { <span class="text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-600">ناموجود</span> }
</div>
<div>
<a href="/Shop/Detail?id=@p.Id" class="font-bold text-gray-900 hover:text-blue-700 transition-colors leading-snug block">@p.NameFa</a>
@if (p.Sku != null) { <p class="text-xs text-gray-400 mt-0.5">کد: @p.Sku</p> }
</div>
<div class="mt-auto">
@if (p.HasDiscount) { <p class="text-sm text-gray-400 line-through">@SiteData.FormatPrice(p.Price)</p> }
<p class="text-lg font-bold text-blue-700">@SiteData.FormatPrice(p.FinalPrice)</p>
</div>
<form method="post" asp-page-handler="AddToCart">
@Html.AntiForgeryToken()
<input type="hidden" name="productId" value="@p.Id" />
<input type="hidden" name="nameFa" value="@p.NameFa" />
<input type="hidden" name="price" value="@p.FinalPrice" />
<input type="hidden" name="sku" value="@p.Sku" />
<button type="submit" @(p.Stock == 0 ? "disabled" : "")
class="w-full flex items-center justify-center gap-2 bg-blue-700 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-blue-800 transition-colors disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed">
🛒 @(p.Stock == 0 ? "ناموجود" : "افزودن به سبد")
</button>
</form>
</div>
</div>
}
</div>
}
@if (Model.TotalPages > 1)
{
<div class="flex justify-center items-center gap-2 mt-10">
@if (Model.CurrentPage > 1)
{
<a href="/Shop?page=@(Model.CurrentPage - 1)@(Model.Category != null ? "&category=" + Model.Category : "")@(Model.Brand != null ? "&brand=" + Model.Brand : "")@(Model.Search != null ? "&search=" + Model.Search : "")"
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors">
‹ قبلی
</a>
}
@for (var i = Math.Max(1, Model.CurrentPage - 2); i <= Math.Min(Model.TotalPages, Model.CurrentPage + 2); i++)
{
<a href="/Shop?page=@i@(Model.Category != null ? "&category=" + Model.Category : "")@(Model.Brand != null ? "&brand=" + Model.Brand : "")@(Model.Search != null ? "&search=" + Model.Search : "")"
class="px-4 py-2 rounded-xl border text-sm transition-colors @(i == Model.CurrentPage ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400 hover:text-blue-700")">
@i
</a>
}
@if (Model.CurrentPage < Model.TotalPages)
{
<a href="/Shop?page=@(Model.CurrentPage + 1)@(Model.Category != null ? "&category=" + Model.Category : "")@(Model.Brand != null ? "&brand=" + Model.Brand : "")@(Model.Search != null ? "&search=" + Model.Search : "")"
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors">
بعدی ›
</a>
}
</div>
}
</div>
+48
View File
@@ -0,0 +1,48 @@
using AsadiTools.Data;
using AsadiTools.Models;
using AsadiTools.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace AsadiTools.Pages.Shop;
public class ShopIndexModel(AppDbContext db, CartService cart) : PageModel
{
public const int PageSize = 12;
public List<Product> Products { get; private set; } = [];
public string? Category { get; private set; }
public string? Brand { get; private set; }
public string? Search { get; private set; }
public int CurrentPage { get; private set; } = 1;
public int TotalPages { get; private set; }
public int TotalCount { get; private set; }
public async Task OnGetAsync(string? category, string? brand, string? search, int page = 1)
{
Category = category;
Brand = brand;
Search = search;
var q = db.Products.Where(p => p.IsActive);
if (category is not null) q = q.Where(p => p.Category == category);
if (brand is not null) q = q.Where(p => p.Brand == brand);
if (search is not null) q = q.Where(p => p.NameFa.Contains(search) || (p.NameEn != null && p.NameEn.Contains(search)) || (p.Sku != null && p.Sku.Contains(search)));
TotalCount = await q.CountAsync();
TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
CurrentPage = Math.Clamp(page, 1, Math.Max(1, TotalPages));
Products = await q.OrderByDescending(p => p.Id)
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
}
public IActionResult OnPostAddToCart(int productId, string nameFa, decimal price, string? sku)
{
cart.AddItem(new CartItem { ProductId = productId, NameFa = nameFa, Price = price, Sku = sku, Qty = 1 });
return RedirectToPage();
}
}
+5
View File
@@ -0,0 +1,5 @@
@using AsadiTools
@using AsadiTools.Models
@using AsadiTools.Services
@namespace AsadiTools.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+3
View File
@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}
+60
View File
@@ -0,0 +1,60 @@
using AsadiTools.Data;
using AsadiTools.Services;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Persist DataProtection keys to the volume so sessions/antiforgery survive restarts
var dpKeysPath = builder.Configuration["DataProtection:KeysPath"] ?? "/app/data/keys";
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(dpKeysPath))
.SetApplicationName("AsadiTools");
builder.Services.AddRazorPages();
builder.Services.AddSession(o =>
{
o.IdleTimeout = TimeSpan.FromHours(2);
o.Cookie.HttpOnly = true;
o.Cookie.IsEssential = true;
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<CartService>();
builder.Services.AddDbContext<AppDbContext>(o =>
o.UseSqlite(builder.Configuration.GetConnectionString("Default")
?? "Data Source=asadi.db"));
builder.Services.AddAuthentication("AdminCookie")
.AddCookie("AdminCookie", o =>
{
o.LoginPath = "/Admin/Login";
o.LogoutPath = "/Admin/Logout";
o.Cookie.Name = "AsadiAdmin";
o.ExpireTimeSpan = TimeSpan.FromHours(8);
});
builder.Services.AddAuthorization();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
SeedData.Initialize(scope.ServiceProvider.GetRequiredService<AppDbContext>());
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseSession();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
+23
View File
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5259",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7053;http://localhost:5259",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+561
View File
@@ -0,0 +1,561 @@
namespace AsadiTools.Services;
public record BrandFaq(string Q, string A);
public record BrandIssue(string Problem, string Cause, string Solution);
public record BrandTool(string Model, string NameFa, string Watts, string Desc);
public record BrandSeoPage(
string Id, string Name, string NameFa,
string Color, string TextColor, bool IsOfficial,
string Country, string Founded, string HeroImage,
string Tagline, string About1, string About2, string About3,
BrandTool[] Models,
string[] RepairServices,
BrandIssue[] CommonProblems,
BrandFaq[] Faqs
);
public static class BrandSeoData
{
public static readonly BrandSeoPage[] AllBrands =
[
// ─── DeWalt ──────────────────────────────────────────────────────────
new(
Id: "dewalt", Name: "DeWalt", NameFa: "دیوالت",
Color: "#FFCD00", TextColor: "#000", IsOfficial: true,
Country: "آمریکا", Founded: "1923",
HeroImage: "https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&q=80&auto=format&fit=crop",
Tagline: "نمایندگی رسمی دیوالت در کرج — تعمیر تخصصی با قطعات اصلی و ضمانت معتبر",
About1: "برند دیوالت (DeWalt) در سال ۱۹۲۳ در ایالات متحده آمریکا تأسیس شد و در طول بیش از یک قرن فعالیت، به یکی از معتبرترین و شناخته‌شده‌ترین نام‌ها در صنعت ابزار برقی حرفه‌ای جهان تبدیل شده است. هویت بصری زرد و مشکی این برند در هر کارگاه و پروژه ساختمانی بلافاصله قابل تشخیص است. دیوالت امروز زیر چتر گروه استنلی بلک اند دکر فعالیت می‌کند و محصولاتش در بیش از صد کشور جهان از جمله ایران توزیع می‌شود. آساد ابزار به عنوان نمایندگی رسمی و مجاز دیوالت در کرج، خدمات تعمیر و نگهداری اصیل این برند را در استان البرز ارائه می‌دهد.",
About2: "ابزارهای دیوالت در ایران به‌ویژه در حوزه ساختمان‌سازی حرفه‌ای، نجاری، فلزکاری و کارهای تأسیساتی جایگاه ویژه‌ای دارند. سری XR با باتری ۱۸ ولت و موتورهای بدون‌کربن (Brushless) از پرطرفدارترین محصولات دیوالت در بازار ایران است. دریل‌های چکشی سری DCH، فرزهای زاویه‌ای DCG، پیچ‌گوشتی‌های ضربه‌ای DCF و اره‌های برقی DCS از جمله مدل‌هایی هستند که در کارگاه‌های کرج و البرز فراوان دیده می‌شوند. سیستم باتری FLEXVOLT که امکان استفاده از یک باتری در ابزارهای ۱۸ ولت و ۵۴ ولت را فراهم می‌کند، انعطاف‌پذیری منحصربه‌فردی به متخصصان ایرانی می‌بخشد.",
About3: "آساد ابزار با بیش از ۱۵ سال تجربه در تعمیر ابزار برقی و به عنوان تنها نمایندگی رسمی و مجاز دیوالت در کرج، تنها مرجع معتبر برای تعمیر ابزارهای دیوالت در استان البرز است. استفاده از قطعات اصلی و اورجینال، تکنیسین‌های آموزش‌دیده رسمی، تشخیص رایگان خرابی و ضمانت سه‌ماهه تعمیر، اطمینان کامل را برای صاحبان ابزار دیوالت فراهم می‌آورد.",
Models:
[
new("DCH 273", "دریل چکشی SDS Plus", "18V XR", "دریل چکشی بی‌سیم سری XR با موتور بدون‌کربن، مناسب برای کارهای ساختمانی سنگین و حفاری در بتن"),
new("DCG 405", "فرز زاویه‌ای", "18V XR", "فرز زاویه‌ای بی‌سیم با صفحه ۱۲۵ میلیمتری و موتور براشلس، ایده‌آل برای فلزکاری حرفه‌ای"),
new("DCD 796", "دریل پیچ‌گوشتی ضربه‌ای", "18V XR", "دریل پیچ‌گوشتی دو سرعته با گشتاور ۹۱ نیوتون‌متر و کوپلینگ الکترونیکی هوشمند"),
new("DCF 887", "پیچ‌گوشتی ضربه‌ای", "18V XR", "پیچ‌گوشتی ضربه‌ای سبک با سه تنظیم ضربه، طراحی کامپکت برای دسترسی به فضاهای تنگ"),
new("DCS 391", "اره دیسکی", "18V XR", "اره دیسکی بی‌سیم با تیغه ۱۶۵ میلیمتری، قادر به برش تا عمق ۵۵ میلیمتر در نجاری و کارهای عمرانی"),
new("FLEXVOLT DCH 481", "دریل چکشی SDS Max", "54V FLEXVOLT", "دریل چکشی حرفه‌ای سنگین با انرژی ضربه ۸.۶ ژول برای حفاری در بتن مسلح"),
new("DW 745", "اره میزی", "1850W", "اره میزی برقی با توان ۱۸۵۰ وات و میز تنظیم دقیق، محبوب در کارگاه‌های نجاری ایران"),
new("DW 088K", "تراز لیزری", "باتری", "تراز لیزری با دو خط افقی و عمودی، دقت ±۰.۳ میلیمتر/متر و پایه مغناطیسی"),
],
RepairServices:
[
"تعمیر و سیم‌پیچی مجدد موتور ابزارهای دیوالت",
"تعویض کربن و بازسازی سیستم جارو موتور",
"تعمیر و کالیبراسیون دریل‌های چکشی SDS و SDS Max",
"تعمیر تخصصی فرزهای زاویه‌ای DCG با قطعات اصلی",
"بازسازی و تعمیر باتری و شارژر سری XR و FLEXVOLT",
"تعمیر گیربکس و تعویض بلبرینگ‌های ابزار دیوالت",
"بازسازی پیچ‌گوشتی‌های ضربه‌ای سری DCF",
"تعمیر برد الکترونیکی و کنترلر ابزارهای بی‌سیم",
"تعویض چاک دریل و رفع لقی و لرزش ابزار",
"سرویس دوره‌ای و روغن‌کاری پیشگیرانه با استانداردهای رسمی دیوالت",
],
CommonProblems:
[
new("گرم شدن بیش از حد موتور دریل", "فرسودگی کربن‌ها یا انسداد کانال‌های تهویه به دلیل گرد و خاک", "تعویض کربن‌های موتور و تمیزکاری کامل مسیرهای تهویه در مرکز مجاز آساد ابزار"),
new("ضعف ضربه در دریل چکشی SDS", "فرسودگی پیستون یا سیل‌های مکانیزم ضربه به مرور زمان", "بازسازی کامل مکانیزم ضربه و تعویض قطعات آب‌بندی با قطعات اصلی دیوالت"),
new("شارژ نشدن باتری XR", "خرابی سلول‌های باتری یا نقص مدار BMS حفاظت از شارژ", "تست و بازسازی پک باتری یا تعویض شارژر با نمونه اورجینال در آساد ابزار کرج"),
new("ارتعاش و صدای غیرطبیعی فرز زاویه‌ای", "فرسودگی بلبرینگ‌های محور یا عدم تعادل دیسک ساینده", "تعویض بلبرینگ‌های محور و بررسی تعادل دیسک توسط تکنیسین رسمی آساد ابزار"),
new("قطع و وصل شدن ناگهانی پیچ‌گوشتی", "خرابی کلید ماشه یا اتصال ضعیف در ترمینال‌های باتری", "تعویض کلید ماشه اصلی و رفع مشکل اتصال باتری با تجهیزات تشخیصی دیوالت"),
new("دود کردن و بوی سوختگی از ابزار", "اتصال کوتاه در سیم‌پیچ موتور یا فرسودگی شدید کربن‌ها", "قطع فوری دستگاه و مراجعه اضطراری به آساد ابزار برای سیم‌پیچی مجدد موتور"),
],
Faqs:
[
new("نمایندگی رسمی دیوالت در کرج کجاست؟", "آساد ابزار تنها نمایندگی رسمی و مجاز دیوالت در کرج و استان البرز است. با بیش از ۱۵ سال سابقه، مجهز به قطعات اصلی و تکنیسین‌های آموزش‌دیده رسمی است. برای دریافت خدمات با شماره ۰۲۶-۳۴۵۶۷۸۹۰ تماس بگیرید."),
new("تعمیر دریل دیوالت در کرج چقدر زمان می‌برد؟", "اکثر تعمیرات متداول دریل دیوالت در ۴۸ ساعت کاری انجام می‌شود. تشخیص اولیه خرابی کاملاً رایگان است و پس از اعلام هزینه و تأیید مشتری، تعمیر آغاز می‌شود."),
new("آیا تعمیر ابزار دیوالت در آساد ابزار ضمانت دارد؟", "بله، تمامی تعمیرات در آساد ابزار کرج دارای ضمانت سه ماهه هستند. این ضمانت شامل قطعات تعویض‌شده و دستمزد تعمیر می‌شود و فقط از قطعات اصلی استفاده می‌کنیم."),
new("باتری دیوالت XR خراب شده، آیا قابل تعمیر است؟", "در بسیاری از موارد باتری‌های XR و FLEXVOLT دیوالت قابل بازسازی هستند. تیم متخصص آساد ابزار کرج سلول‌های خراب را شناسایی و تعویض می‌کند — بسیار مقرون‌به‌صرفه‌تر از خرید باتری نو."),
new("آیا قطعات یدکی اصلی دیوالت در کرج موجود است؟", "آساد ابزار به عنوان نمایندگی رسمی، انبار قطعات اصلی برای محبوب‌ترین مدل‌های XR، DCH، DCG و DCF را نگهداری می‌کند. قطعات خاص از کانال‌های رسمی تأمین می‌شود."),
new("هزینه تعمیر ابزار دیوالت چقدر است؟", "تشخیص اولیه خرابی در آساد ابزار کاملاً رایگان است و پیش از شروع تعمیر، هزینه دقیق اعلام می‌شود. برای برآورد اولیه با شماره ۰۲۶-۳۴۵۶۷۸۹۰ تماس بگیرید."),
new("آیا ابزار دیوالت را می‌توانم از شهرستان ارسال کنم؟", "بله، از طریق پست یا تیپاکس ابزار خود را ارسال کنید. پس از تعمیر، دستگاه با ضمانت‌نامه کتبی برگشت داده می‌شود."),
]
),
// ─── Bosch ───────────────────────────────────────────────────────────
new(
Id: "bosch", Name: "Bosch", NameFa: "بوش",
Color: "#1565C0", TextColor: "#fff", IsOfficial: false,
Country: "آلمان", Founded: "1886",
HeroImage: "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=1200&q=80&auto=format&fit=crop",
Tagline: "تعمیر تخصصی ابزار بوش در کرج با ضمانت و قطعات اصلی",
About1: "بوش (Bosch) یکی از معتبرترین و بزرگ‌ترین تولیدکنندگان ابزار برقی در جهان است که در سال ۱۸۸۶ توسط رابرت بوش در اشتوتگارت آلمان تأسیس شد. این برند با بیش از ۱۳۵ سال سابقه، نامی است که در صنعت ابزار به مفهوم کیفیت، دوام و نوآوری شناخته می‌شود. ابزارهای بوش از دهه‌های گذشته در بازار ایران حضور داشته‌اند و امروز در میان پیمانکاران، سازندگان و متخصصان حرفه‌ای کشور از محبوب‌ترین برندها به شمار می‌روند.",
About2: "بوش محصولات خود را در دو خط اصلی ارائه می‌دهد: خط حرفه‌ای (Professional) با رنگ آبی که برای استفاده‌های سنگین صنعتی طراحی شده، و خط DIY با رنگ سبز که مناسب کارهای خانگی است. در ایران، سری‌های GBH (چکش تخریب)، GWS (فرز انگشتی) و GSB (دریل ضربه‌ای) بسیار پرطرفدار بوده و در اغلب کارگاه‌ها و پروژه‌های ساختمانی یافت می‌شوند. فناوری SDS-Plus و SDS-Max بوش استانداردی شد که تمام رقبا آن را پذیرفتند.",
About3: "آساد ابزار در کرج با تکنیسین‌های مجرب و قطعات اصلی بوش، خدمات تعمیر تخصصی برای تمام مدل‌های این برند ارائه می‌دهد. تشخیص رایگان، ضمانت سه‌ماهه و تحویل سریع ۴۸ ساعته ما را به مرجع اول تعمیر ابزار بوش در استان البرز تبدیل کرده است.",
Models:
[
new("GBH 2-28 F", "چکش تخریب SDS-Plus", "880W", "چکش تخریب حرفه‌ای با سیستم SDS-Plus، یکی از پرفروش‌ترین مدل‌های بوش در ایران برای حفاری بتن"),
new("GBH 4-32 DFR", "چکش تخریب SDS-Plus", "900W", "چکش تخریب قوی با قابلیت تغییر حالت، ایده‌آل برای حفاری عمیق و تخریب مصالح سخت"),
new("GSH 11 E", "چکش تخریب SDS-Max", "1500W", "چکش تخریب سنگین با SDS-Max برای عملیات تخریب و شکستن بتن مسلح در پروژه‌های بزرگ"),
new("GWS 7-115", "فرز انگشتی", "720W", "فرز زاویه‌ای سبک برای برش و سنگ‌زنی، محبوب در کارگاه‌های تأسیساتی سراسر ایران"),
new("GWS 22-230", "فرز انگشتی سنگین", "2200W", "فرز زاویه‌ای سنگین با دیسک ۲۳۰ میلیمتری برای برش آرماتور و پروژه‌های عمرانی بزرگ"),
new("GSB 13 RE", "دریل ضربه‌ای", "600W", "دریل ضربه‌ای چندکاره با تنظیم سرعت، مناسب بتن سبک، آجر و چوب، گزینه محبوب حرفه‌ای‌ها"),
new("GKS 190", "اره گردبر", "1400W", "اره دیسکی حرفه‌ای برای برش دقیق چوب و تخته با گاید لیزری"),
new("GLL 3-80", "تراز لیزری", "باتری", "تراز لیزری سه پرتوی با برد ۳۰ متر برای کارهای نصب و ساختمانی دقیق"),
],
RepairServices:
[
"تعمیر و تعویض کربن (زغال) موتور ابزار بوش",
"تعمیر و بازسازی گیربکس و کلاچ دریل و چکش بوش",
"تعویض بلبرینگ و یاتاقان تمام مدل‌های بوش",
"تعمیر مدار الکترونیکی و برد کنترل سرعت",
"تعویض استاتور و روتور موتور برقی بوش",
"تعمیر و تنظیم سیستم SDS-Plus و SDS-Max",
"تعمیر کلید راه‌انداز و سیستم برق‌رسانی",
"سرویس کامل و روغن‌کاری گیربکس ابزار بوش",
"تعویض چاک دریل و رفع لقی آن",
"تعمیر فرز انگشتی بوش و بالانس دیسک",
],
CommonProblems:
[
new("چکش بوش حفاری نمی‌کند یا ضعیف شده", "سایش پیستون، فنر ضربه‌زن یا خرابی سیستم SDS", "تعویض پیستون، سیلندر یا قطعات SDS-Plus با قطعات اصلی بوش"),
new("فرز بوش جرقه‌زنی شدید دارد", "سایش کربن‌های موتور یا آسیب به کموتاتور روتور", "تعویض کربن‌های موتور و پولیش یا تعویض کموتاتور در صورت نیاز"),
new("دریل بوش در حالت ضربه‌ای کار نمی‌کند", "خرابی کلاچ ضربه‌ای یا سایش دنده‌های گیربکس", "بازرسی و تعویض مکانیزم ضربه‌ای و دنده‌های مربوطه"),
new("ابزار بوش روشن نمی‌شود یا قطع‌وصل می‌کند", "خرابی کلید راه‌انداز، اتصال سیم‌کشی یا مشکل برد", "تعویض کلید اصلی یا تعمیر برد الکترونیکی کنترل سرعت"),
new("گرمای بیش از حد موتور ابزار بوش", "گرفتگی دریچه تهویه، سایش بلبرینگ‌ها یا اضافه‌بار", "سرویس کامل، تمیزکاری تهویه، تعویض بلبرینگ‌ها و روغن‌کاری"),
new("لرزش غیرعادی فرز یا دریل بوش", "خرابی یا سایش بلبرینگ‌ها و بی‌بالانسی قطعات دوار", "تعویض بلبرینگ‌های محور و بالانس کردن روتور یا آرمیچر"),
],
Faqs:
[
new("تعمیر چکش تخریب بوش در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، استان البرز، مرکز تخصصی تعمیر چکش تخریب بوش است. تکنیسین‌های ما با تجربه بیش از ۱۵ سال، تمام مدل‌های GBH بوش را با قطعات اصلی تعمیر می‌کنند. تشخیص عیب رایگان با ضمانت سه‌ماهه."),
new("هزینه تعمیر فرز انگشتی بوش GWS چقدر است؟", "تشخیص عیب در آساد ابزار کرج کاملاً رایگان است. پس از بررسی، هزینه دقیق تعمیر اعلام می‌شود. تعویض کربن، بلبرینگ یا کلید از متداول‌ترین تعمیرات فرز بوش هستند."),
new("تعمیر دریل بوش GSB چند وقت طول می‌کشد؟", "در آساد ابزار کرج، اکثر تعمیرات ابزار بوش در ۴۸ ساعت انجام می‌شود. تعمیرات ساده‌تر مثل تعویض کربن زودتر تحویل داده می‌شود."),
new("آیا قطعات یدکی اصلی بوش در کرج موجود است؟", "بله، آساد ابزار موجودی کربن موتور، بلبرینگ، کلید راه‌انداز، پیستون SDS و گیربکس بوش را در انبار دارد. استفاده از قطعات اصل طول عمر ابزار تعمیرشده را تضمین می‌کند."),
new("بهترین جا برای تعمیر ابزار بوش در البرز کجاست؟", "آساد ابزار با بیش از ۱۵ سال تجربه در کرج، به عنوان معتبرترین مرکز تعمیر ابزار بوش در البرز شناخته می‌شود. ارائه ضمانت سه‌ماهه، قطعات اصلی و تعمیر سریع از مزایای ما است."),
new("آیا ابزار بوش Professional آبی با DIY سبز تفاوت دارد؟", "بله، خط حرفه‌ای آبی بوش قطعات مقاوم‌تر و پیچیده‌تری دارد. آساد ابزار کرج هر دو خط را تعمیر می‌کند و تکنیسین‌های ما با تفاوت‌های فنی این دو سری آشنا هستند."),
new("آیا آساد ابزار نمایندگی رسمی بوش در کرج است؟", "آساد ابزار یک مرکز تعمیر تخصصی مجرب برای ابزار بوش در کرج است. از قطعات یدکی اصلی بوش استفاده می‌کنیم و تکنیسین‌ها آموزش‌دیده تعمیر این برند هستند. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
]
),
// ─── Hilti ───────────────────────────────────────────────────────────
new(
Id: "hilti", Name: "Hilti", NameFa: "هیلتی",
Color: "#E30613", TextColor: "#fff", IsOfficial: false,
Country: "لیختن‌اشتاین", Founded: "1941",
HeroImage: "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=1200&q=80&auto=format&fit=crop",
Tagline: "تعمیر تخصصی ابزار هیلتی در کرج — سریع، مطمئن و با قطعات اصلی",
About1: "برند هیلتی (Hilti) در سال ۱۹۴۱ در لیختن‌اشتاین توسط مارتین هیلتی تأسیس شد و در طول دهه‌های گذشته به یکی از معتبرترین تولیدکنندگان ابزار صنعتی جهان تبدیل گشته است. این برند از همان ابتدا با تمرکز بر بازار حرفه‌ای و پروژه‌های ساختمانی بزرگ، جایگاه ویژه‌ای در صنعت ساخت‌وساز ایران پیدا کرده است. ابزارهای هیلتی در بزرگ‌ترین پروژه‌های عمرانی، سدسازی، تونل‌سازی و آسمان‌خراش‌های ایران نقش محوری داشته‌اند.",
About2: "در ایران، کلمه «هیلتی» تقریباً به یک اصطلاح عمومی برای دریل‌های چکشی تبدیل شده است — نشانه‌ای از نفوذ عمیق این برند در فرهنگ ساختمانی کشور. سری TE این برند، که شامل دریل‌های چکشی و تخریب‌کن‌های حرفه‌ای می‌شود، در میان پیمانکاران ایرانی بسیار محبوب است. علاوه بر این، دستگاه‌های آچارکشی، فرزهای زاویه‌ای، لیزرهای تراز و سیستم‌های تراشیدن بتن هیلتی در کارگاه‌های صنعتی و ساختمانی ایران به‌وفور یافت می‌شوند.",
About3: "آساد ابزار در کرج با بیش از ۱۵ سال تجربه در تعمیر ابزارهای صنعتی، خدمات تخصصی تعمیر ابزار هیلتی را با استفاده از قطعات اصلی ارائه می‌دهد. تشخیص رایگان، گارانتی سه ماهه و تحویل سریع ۴۸ ساعته، ما را به انتخاب اول مشتریان حرفه‌ای در البرز تبدیل کرده است.",
Models:
[
new("TE 2", "دریل چکشی سبک", "650W", "دریل چکشی محبوب هیلتی برای کارهای سبک تا متوسط، مناسب استفاده روزمره در بتن و مصالح ساختمانی"),
new("TE 6-A36", "دریل چکشی بی‌سیم", "36V", "دریل چکشی بی‌سیم با باتری ۳۶ ولتی، ایده‌آل برای مکان‌هایی که دسترسی به برق دشوار است"),
new("TE 30-A36", "دریل چکشی حرفه‌ای بی‌سیم", "36V", "دریل چکشی حرفه‌ای بی‌سیم با انرژی ضربه بالا، مناسب حفاری در بتن مسلح و سازه‌های سنگین"),
new("TE 60-ATC", "تخریب‌کن و دریل چکشی", "1350W","ابزار چندمنظوره برای حفاری عمیق و تخریب بتن سنگین با سیستم کنترل دما برای کار مداوم"),
new("AG 125-A22", "فرز زاویه‌ای بی‌سیم", "22V", "فرز زاویه‌ای کمپکت بی‌سیم با دیسک ۱۲۵ میلی‌متری، سبک و مناسب برش فلز و تراشکاری"),
new("AG 230-20D", "فرز زاویه‌ای بزرگ", "2000W","فرز زاویه‌ای سنگین با دیسک ۲۳۰ میلی‌متری برای برش‌های سنگین در کارگاه‌های صنعتی"),
new("SF 6H-A22", "پیچ‌گوشتی و دریل بی‌سیم", "22V", "دریل‌پیچ‌گوشتی بی‌سیم کمپکت با گشتاور بالا برای نصب و مونتاژ در پروژه‌های ساختمانی"),
new("PE 20", "تراز لیزری نقطه‌ای", "باتری","تراز لیزری نقطه‌ای با دقت بالا برای نقشه‌برداری و تراز کردن در پروژه‌های دقیق"),
],
RepairServices:
[
"تعمیر و بازسازی موتور دریل‌های چکشی سری TE هیلتی",
"تعویض کربن، بلبرینگ و قطعات داخلی فرزهای زاویه‌ای",
"تعمیر سیستم چکشی و مکانیزم ضربه دستگاه‌های هیلتی",
"سرویس و شارژ باتری‌های لیتیوم ۲۲V و ۳۶V هیلتی",
"تعمیر برد الکترونیکی و مدارات کنترلی ابزارهای بی‌سیم",
"تعویض گیربکس و چرخ‌دنده‌های فرسوده دریل‌ها",
"تعمیر سوئیچ و کلید راه‌اندازی ابزارهای برقی هیلتی",
"سرویس دوره‌ای و روغن‌کاری مکانیزم داخلی دستگاه‌ها",
"تعویض کلاچ و سیستم حفاظتی ابزارهای حرفه‌ای هیلتی",
"تعمیر اتصالات و پورت‌های شارژ باتری‌های هیلتی",
],
CommonProblems:
[
new("ضعف یا توقف چکش‌زنی در دریل هیلتی", "فرسودگی پیستون، سیلندر یا قطعات مکانیزم ضربه بر اثر استفاده مداوم", "بازرسی و تعویض پیستون، بوش سیلندر و قطعات مکانیزم چکشی با قطعات اصلی هیلتی"),
new("گرم‌شدن بیش از حد دستگاه هنگام کار", "فرسودگی کربن موتور، گرفتگی منافذ تهویه یا اشکال در سیستم خنک‌کننده","تعویض کربن‌های موتور، تمیزکاری کامل و بررسی سیستم تهویه داخلی"),
new("لرزش و صدای غیرعادی هنگام کار", "فرسودگی بلبرینگ‌ها، شل شدن اتصالات داخلی یا آسیب گیربکس", "تشخیص دقیق منشأ صدا، تعویض بلبرینگ و بررسی کامل گیربکس دستگاه"),
new("شارژ نشدن یا افت سریع باتری بی‌سیم", "خرابی سلول‌های باتری لیتیومی یا اشکال در برد مدیریت باتری", "تست باتری با دستگاه تخصصی، تعویض سلول‌های معیوب یا برد BMS"),
new("روشن نشدن یا قطع ناگهانی دستگاه", "خرابی سوئیچ کلید راه‌انداز، اشکال در برد الکترونیک یا سیم‌کشی", "بررسی کامل مدار الکتریکی، تعویض کلید یا تعمیر برد کنترلی دستگاه"),
new("چرخش بیت یا مته در حالت بی‌کار", "فرسودگی کلاچ ایمنی یا اشکال در مکانیزم قفل سیستم دریل", "بررسی و تنظیم یا تعویض کلاچ حفاظتی برای جلوگیری از آسیب به گیربکس"),
],
Faqs:
[
new("تعمیر دریل چکشی هیلتی در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، البرز، خدمات تخصصی تعمیر دریل چکشی هیلتی را ارائه می‌دهد. با بیش از ۱۵ سال تجربه، از قطعات اصلی هیلتی استفاده و با گارانتی ۳ ماهه تحویل می‌دهیم. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("هزینه تعمیر دریل هیلتی TE چقدر است؟", "تشخیص اولیه در آساد ابزار کاملاً رایگان است. پس از بررسی، هزینه دقیق اعلام می‌شود. برای مدل‌های TE 30، TE 60 و TE 500 ما تجربه تعمیر کامل داریم."),
new("آیا قطعات یدکی اصلی هیلتی در آساد ابزار موجود است؟", "بله، آساد ابزار از قطعات با کیفیت و اصلی برای تعمیر ابزارهای هیلتی استفاده می‌کند. موجودی قطعات پرمصرف سری TE در انبار ما نگهداری می‌شود تا تعمیر سریع‌تر انجام گیرد."),
new("باتری هیلتی ۳۶ ولتی خراب شده، آیا قابل تعمیر است؟", "بله، تیم فنی آساد ابزار تخصص تعمیر و بازسازی باتری‌های لیتیومی هیلتی سری ۲۲V و ۳۶V را دارد. سلول‌های معیوب شناسایی و تعویض می‌شوند."),
new("چقدر طول می‌کشد تا ابزار هیلتی تعمیر شود؟", "آساد ابزار تعمیر اکثر ابزارهای هیلتی را در ۴۸ ساعت انجام می‌دهد. برای تعمیرات پیچیده‌تر زمان دقیق هنگام تشخیص به شما اطلاع داده می‌شود."),
new("آیا ارزش دارد هیلتی قدیمی را تعمیر کنم؟", "قطعاً! هیلتی یکی از بادوام‌ترین برندهاست. یک TE 60 با ۱۵-۲۰ سال عمر هنوز ارزش تعمیر دارد چون کیفیت بدنه کاملاً سالم است. آساد ابزار مشاوره صادقانه ارائه می‌دهد."),
new("تفاوت تعمیر هیلتی با سایر برندها چیست؟", "ابزارهای هیلتی مکانیزم‌های پیچیده‌تر و قطعات خاص‌تری دارند. آساد ابزار با سال‌ها تجربه در کرج، دانش فنی لازم برای تعمیر صحیح این ابزارها را دارد."),
]
),
// ─── Metabo ──────────────────────────────────────────────────────────
new(
Id: "metabo", Name: "Metabo", NameFa: "متابو",
Color: "#006631", TextColor: "#fff", IsOfficial: false,
Country: "آلمان", Founded: "1924",
HeroImage: "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=1200&q=80&auto=format&fit=crop",
Tagline: "تعمیر تخصصی ابزار متابو در کرج با ضمانت اصالت قطعات و کیفیت آلمانی",
About1: "متابو یکی از معتبرترین برندهای آلمانی در صنعت ابزار برقی است که در سال ۱۹۲۴ در شهر نورتینگن آلمان تأسیس شد. این شرکت با بیش از یک قرن سابقه، پیشتاز تولید ابزارهای حرفه‌ای برای صنایع فلزکاری، ساختمانی و تأسیساتی است. نام متابو مخفف Metabowerke بوده و از همان ابتدا بر دقت مهندسی آلمانی و دوام بالا تمرکز داشته است. این برند در دهه‌های اخیر به بازار ایران راه یافته و در میان متخصصان فلزکاری جایگاه ویژه‌ای کسب کرده است.",
About2: "ابزارهای متابو در ایران بیشتر در حوزه فلزکاری و کارهای صنعتی سنگین شناخته می‌شوند. سری W مربوط به فرزهای زاویه‌ای، سری KHE برای چکش‌های تخریب و سری WP برای پولیشرهای صنعتی از پرطرفدارترین محصولات این برند نزد کاربران حرفه‌ای ایرانی هستند. سیستم باتری ۱۸ ولت متابو نیز با تعداد زیادی از ابزارهای بی‌سیم این برند سازگار است و انعطاف‌پذیری خوبی ارائه می‌دهد.",
About3: "آساد ابزار در کرج با بیش از ۱۵ سال تجربه در تعمیر ابزار برقی، خدمات تخصصی تعمیر ابزار متابو را با استفاده از قطعات اصلی و تکنیسین‌های مجرب ارائه می‌دهد. تشخیص رایگان، ضمانت سه‌ماهه و تحویل سریع ۴۸ ساعته از مزایای انتخاب آساد ابزار برای تعمیر متابو در استان البرز است.",
Models:
[
new("W 850-125", "فرز زاویه‌ای", "850W", "فرز زاویه‌ای حرفه‌ای برای برش و سنگ‌زنی فلز با دیسک ۱۲۵ میلی‌متری و سرعت بالا"),
new("W 2200-230", "فرز زاویه‌ای سنگین", "2200W", "فرز زاویه‌ای قدرتمند با دیسک ۲۳۰ میلی‌متری برای برش‌های سنگین صنعتی"),
new("KHE 2844", "چکش تخریب و حفاری", "1050W", "چکش تخریب حرفه‌ای با SDS-Plus، مناسب حفاری بتن سخت و تخریب صنعتی"),
new("BE 650", "دریل برقی", "650W", "دریل حرفه‌ای با گشتاور بالا برای کارهای دقیق در فلز، چوب و بتن سبک"),
new("SBEV 1000-2", "دریل چکشی", "1010W", "دریل چکشی قدرتمند با تنظیم گشتاور چندمرحله‌ای، مناسب حفاری در دیوار"),
new("WP 1200-125", "پولیشر صنعتی", "1200W", "پولیشر زاویه‌ای برای صیقل و پرداخت سطوح فلزی و خودرو با کنترل دقیق سرعت"),
new("BS 18 LTX BL I","دریل شارژی بی‌سیم", "18V", "دریل بی‌سیم با موتور براش‌لس و سازگار با سیستم باتری ۱۸ ولت متابو"),
new("MFE 40", "فرز دیوار", "1400W", "فرز شیارکن دیوار با کنترل گرد و غبار، ایده‌آل برای اجرای تأسیسات الکتریکی"),
],
RepairServices:
[
"تعمیر و بازسازی موتور فرز زاویه‌ای متابو سری W",
"تعویض کربن و جاروبک‌های ابزار برقی متابو",
"تعمیر چکش تخریب متابو KHE و رفع مشکل حفاری",
"تعمیر دریل برقی و دریل چکشی متابو",
"تعویض گیربکس و بلبرینگ‌های ابزار متابو",
"تعمیر سیستم الکترونیک و کنترل دور متابو",
"تعمیر و شارژ باتری ابزارهای بی‌سیم ۱۸ ولت متابو",
"تعمیر پولیشر و فرز صنعتی متابو",
"تشخیص رایگان عیب و خرابی ابزار متابو",
"تأمین و تعویض قطعات اصلی یدکی ابزار متابو",
],
CommonProblems:
[
new("کاهش قدرت و سرعت فرز متابو", "فرسودگی کربن‌ها یا آسیب به سیم‌پیچ موتور بر اثر کار زیاد", "تعویض جاروبک‌های کربنی یا بازسازی موتور توسط تکنیسین متخصص"),
new("گرمای بیش از حد موتور ابزار", "گرفتگی مجاری تهویه، فرسودگی یاتاقان‌ها یا اضافه‌بار مداوم", "تمیزکاری مجاری هوا، تعویض بلبرینگ‌ها و بررسی تنظیمات بار"),
new("لرزش و صدای غیرعادی فرز", "آسیب به بلبرینگ‌ها یا عدم تعادل دیسک و گیربکس فرسوده", "تعویض بلبرینگ‌های آسیب‌دیده و بررسی سلامت گیربکس"),
new("عدم کارکرد سیستم چکش در چکش‌تخریب", "شکستگی پیستون یا فرسودگی قطعات ضربه‌زن سیستم SDS", "تعویض مجموعه پیستون و قطعات ضربه‌زن با قطعات اصلی متابو"),
new("خرابی کلید و کنترل دور", "سوختگی کلید کنترل الکترونیکی بر اثر اضافه‌ولتاژ یا فرسودگی", "تعویض کلید الکترونیکی اصلی متابو برای بازگشت عملکرد کامل"),
new("خرابی باتری ابزار بی‌سیم متابو", "افت ظرفیت سلول‌های لیتیوم‌یون بر اثر شارژهای نادرست", "تست سلول‌های باتری، تعویض سلول‌های معیوب یا تعویض کامل باتری"),
],
Faqs:
[
new("تعمیر فرز متابو در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، استان البرز، خدمات تخصصی تعمیر فرز زاویه‌ای متابو را با بیش از ۱۵ سال تجربه ارائه می‌دهد. از قطعات اصلی متابو استفاده کرده و با ضمانت سه‌ماهه تحویل می‌دهیم. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("هزینه تعمیر چکش تخریب متابو KHE چقدر است؟", "تشخیص عیب در آساد ابزار کاملاً رایگان است. پس از بررسی، هزینه دقیق اعلام می‌شود. تلاش می‌کنیم بهترین کیفیت را با مناسب‌ترین قیمت ارائه دهیم."),
new("چند روز طول می‌کشد تا ابزار متابو تعمیر شود؟", "اکثر تعمیرات فرز و دریل متابو در ۴۸ ساعت کاری تحویل داده می‌شوند. برای تعمیرات پیچیده‌تر مثل تعویض موتور یا گیربکس، زمان دقیق هنگام پذیرش اعلام می‌شود."),
new("قطعات یدکی اصل متابو در کرج پیدا می‌شود؟", "بله، آساد ابزار قطعات اصلی و باکیفیت متابو شامل کربن، بلبرینگ، گیربکس، کلید و باتری را تأمین می‌کند. قطعات اصل طول عمر ابزار شما پس از تعمیر را تضمین می‌کند."),
new("آیا ابزار متابو ارزش تعمیر دارد؟", "متابو یک برند آلمانی حرفه‌ای با کیفیت بسیار بالا است. در اکثر موارد تعمیر کاملاً مقرون‌به‌صرفه است. کارشناسان آساد ابزار پس از بررسی رایگان، توصیه صادقانه‌ای خواهند داد."),
new("تفاوت ابزار متابو با برندهای دیگر چیست؟", "متابو برند آلمانی با تخصص اصلی در فلزکاری صنعتی است. موتورهای متابو به خنک‌کاری بهتر و دوام بیشتر در کارهای سنگین مشهورند. آساد ابزار کرج تعمیر تمام این برندها را انجام می‌دهد."),
new("آیا آساد ابزار نمایندگی تعمیر متابو در البرز است؟", "آساد ابزار به عنوان تعمیرگاه تخصصی ابزار برقی در کرج، خدمات جامع تعمیر ابزارهای متابو را برای مشتریان سراسر استان البرز ارائه می‌دهد."),
]
),
// ─── Ronix ───────────────────────────────────────────────────────────
new(
Id: "ronix", Name: "Ronix", NameFa: "رونیکس",
Color: "#E30613", TextColor: "#fff", IsOfficial: false,
Country: "ایران", Founded: "1385",
HeroImage: "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=1200&q=80&auto=format&fit=crop",
Tagline: "تعمیر تخصصی ابزارهای رونیکس در کرج با ضمانت و قطعات اصلی",
About1: "رونیکس یک برند ایرانی معتبر در حوزه ابزارآلات برقی است که از سال ۱۳۸۵ فعالیت خود را آغاز کرده است. این برند با هدف ارائه ابزارهای با کیفیت و قیمت مناسب برای بازار ایران تأسیس شد و در طی سال‌ها توانست جایگاه محکمی در بین مصرف‌کنندگان حرفه‌ای و نیمه‌حرفه‌ای پیدا کند. رونیکس با بهره‌گیری از استانداردهای بین‌المللی و استفاده از مواد اولیه باکیفیت، محصولاتی تولید می‌کند که پاسخگوی نیازهای کارگاه‌های صنعتی، ساختمان‌سازی و کاربردهای خانگی باشد.",
About2: "ابزارهای رونیکس طیف گسترده‌ای از محصولات را شامل می‌شود که پرطرفدارترین آن‌ها در ایران مته‌های برقی سری ۲۴۰۰ و ۲۷۰۱، فرزهای سری ۲۲۱۴، ۲۲۲۰ و ۲۲۵۳ هستند. این محصولات به دلیل نسبت قیمت به کیفیت مناسب، در بین پیمانکاران ساختمانی، نجارها، جوشکاران و تعمیرکاران خودرو بسیار محبوب شده‌اند. سری فرزهای زاویه‌ای و مته‌های چکشی رونیکس به ویژه در پروژه‌های متوسط و سبک عملکرد قابل قبولی از خود نشان می‌دهند.",
About3: "آساد ابزار در کرج با بیش از ۱۵ سال تجربه در تعمیر ابزارآلات برقی، خدمات تخصصی تعمیر ابزارهای رونیکس را با تشخیص رایگان، قطعات اصلی و ضمانت سه‌ماهه ارائه می‌دهد. تیم متخصص ما با آشنایی کامل به ساختار ابزارهای رونیکس، تعمیرات را در کمتر از ۴۸ ساعت انجام می‌دهد.",
Models:
[
new("2214", "فرز زاویه‌ای", "850W", "فرز زاویه‌ای سبک و قدرتمند با دیسک ۱۱۵ میلیمتری و طراحی ارگونومیک، مناسب برش و سنگ‌زنی فلزات"),
new("2220", "فرز زاویه‌ای", "1050W", "فرز زاویه‌ای نیمه‌حرفه‌ای با توان بالاتر برای کارهای سنگین‌تر، دیسک ۱۲۵ میلیمتری"),
new("2253", "فرز زاویه‌ای بزرگ", "2200W", "فرز زاویه‌ای حرفه‌ای با دیسک ۲۳۰ میلیمتری برای برش‌های سنگین و پروژه‌های صنعتی"),
new("2400", "دریل برقی", "500W", "مته برقی سبک و همه‌کاره با چاک ۱۳ میلیمتری، مناسب سوراخ‌کاری در چوب، فلز و پلاستیک"),
new("2701", "دریل چکشی", "750W", "دریل چکشی پرفروش رونیکس با قابلیت کار روی بتن، طراحی دوحالته و چاک ۱۳ میلیمتری"),
new("8510", "اره عود برگشت", "800W", "اره عود برگشت با کورس بلند برای برش چوب و فلز، تنظیم سرعت متغیر"),
new("5340", "پیچ‌گوشتی شارژی", "18V", "پیچ‌گوشتی شارژی با باتری لیتیوم ۱۸ ولت، گشتاور بالا و دو دنده برای پیچ‌کاری حرفه‌ای"),
new("3101", "صفحه فرز", "1200W", "صفحه فرز مناسب برای صاف‌کاری و پرداخت سطوح چوبی و فلزی با صفحه نوسانی دقیق"),
],
RepairServices:
[
"تعمیر و تعویض موتور فرز و مته رونیکس",
"تعویض جاروبک‌های کربنی فرز زاویه‌ای رونیکس",
"تعمیر گیربکس و یاتاقان دریل چکشی رونیکس",
"تعویض سوئیچ و مدار الکترونیکی ابزارهای رونیکس",
"تعمیر چاک دریل و اورهال کامل دستگاه",
"تعویض استاتور و روتور موتور ابزارهای رونیکس",
"تنظیم و سرویس دوره‌ای ابزارهای رونیکس",
"تعمیر برد الکترونیک تنظیم سرعت ابزارهای رونیکس",
"تعویض بلبرینگ و دنده‌های گیربکس",
"رفع مشکلات الکتریکی و اتصالی ابزارهای رونیکس",
],
CommonProblems:
[
new("گرم‌شدن بیش از حد فرز رونیکس", "مسدود شدن کانال‌های تهویه و کثیفی داخلی موتور", "تمیزکاری کامل داخلی، بررسی جاروبک‌ها و بازبینی سیم‌پیچ موتور"),
new("کاهش قدرت و سرعت مته رونیکس", "فرسودگی جاروبک‌های کربنی یا آسیب به روتور موتور", "تعویض جاروبک‌های کربنی اصلی و بررسی وضعیت روتور و کموتاتور"),
new("لرزش غیرعادی فرز زاویه‌ای", "آسیب‌دیدگی بلبرینگ‌های گیربکس یا خرابی دنده مخروطی", "تعویض بلبرینگ‌های فرسوده و بررسی دنده‌های گیربکس"),
new("روشن نشدن دریل رونیکس", "خرابی سوئیچ اصلی یا قطع شدن سیم‌های داخلی", "تعویض سوئیچ اصلی یا رفع اتصالی و تعمیر مدار برق‌رسانی"),
new("صدای غیرعادی در حین کار", "سایش دنده‌های گیربکس یا آسیب‌دیدگی بلبرینگ محور", "باز کردن گیربکس، تعویض قطعات فرسوده و روانکاری مجدد"),
new("جرقه زیاد در ناحیه جاروبک", "فرسودگی جاروبک‌ها یا کثیفی سطح کموتاتور روتور", "تعویض جاروبک‌های اصلی و پولیش کموتاتور یا تعویض روتور"),
],
Faqs:
[
new("تعمیر فرز رونیکس در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، استان البرز، مرکز تخصصی تعمیر ابزارهای رونیکس است. فرزهای زاویه‌ای سری ۲۲۱۴، ۲۲۲۰ و ۲۲۵۳ را با تشخیص رایگان، قطعات اصلی و ضمانت سه‌ماهه تعمیر می‌کنیم. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("هزینه تعمیر مته رونیکس ۲۷۰۱ چقدر است؟", "تشخیص اولیه در آساد ابزار رایگان است. پس از بررسی دقیق، هزینه اعلام می‌شود. برای مشاوره رایگان تماس بگیرید."),
new("آیا قطعات یدکی اصلی رونیکس در کرج موجود است؟", "بله، آساد ابزار موجودی جاروبک‌های کربنی، بلبرینگ‌ها، سوئیچ‌ها و قطعات پرمصرف رونیکس را دارد. قطعات اصل طول عمر ابزار را بیشتر می‌کند."),
new("چه مدت طول می‌کشد تا ابزار رونیکس تعمیر شود؟", "اکثر تعمیرات ابزارهای رونیکس در کمتر از ۴۸ ساعت انجام می‌شود. در موارد نیاز به قطعات خاص، از قبل به اطلاع مشتری می‌رسد."),
new("ضمانت تعمیر ابزار رونیکس در آساد ابزار چقدر است؟","تمامی تعمیرات در آساد ابزار کرج دارای ضمانت سه‌ماهه است. اگر در این مدت مشکل مشابه بروز کند، تعمیر مجدد رایگان است."),
new("بهتر است فرز رونیکس را تعمیر کنم یا نو بخرم؟", "آساد ابزار پس از تشخیص رایگان، مشاوره صادقانه ارائه می‌دهد. در بیشتر موارد تعمیر با قطعات اصلی مقرون‌به‌صرفه‌تر از خرید دستگاه جدید است."),
new("آیا نمایندگی تعمیر رونیکس در البرز وجود دارد؟", "آساد ابزار در کرج به عنوان مرکز تخصصی تعمیر ابزارهای رونیکس در استان البرز خدمات ارائه می‌دهد. با استفاده از قطعات اصلی و تجربه بالا، خدمات جامعی فراهم می‌کنیم."),
]
),
// ─── Tosan ───────────────────────────────────────────────────────────
new(
Id: "tosan", Name: "Tosan", NameFa: "توسن",
Color: "#0070C0", TextColor: "#fff", IsOfficial: false,
Country: "ایران", Founded: "1370",
HeroImage: "https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=1200&q=80&auto=format&fit=crop",
Tagline: "توسن، انتخاب هوشمند برای تعمیرات حرفه‌ای در کرج و البرز",
About1: "برند توسن یکی از پیشگامان صنعت ابزار برقی ایران است که از سال ۱۳۷۰ فعالیت خود را آغاز کرده است. این برند ایرانی با هدف تأمین نیاز بازار داخلی به ابزار برقی با کیفیت و قیمت مناسب، توانسته است در طول بیش از سه دهه جایگاه ویژه‌ای در میان مصرف‌کنندگان خانگی و نیمه‌حرفه‌ای کسب کند. توسن با تکیه بر تولید داخلی و شناخت دقیق نیاز بازار ایران، محصولاتی ارائه داده که با شرایط اقلیمی و استانداردهای برق کشور کاملاً سازگار هستند.",
About2: "محبوب‌ترین محصولات توسن در بازار ایران شامل دریل‌های مدل ۱۰۱۰ و ۱۰۰۷، سنگ‌فرزهای کوچک و متوسط، اره‌های عود و فرزهای روتر می‌شوند. این ابزارها به دلیل وزن سبک، قطعات یدکی در دسترس و قیمت رقابتی، گزینه‌ای محبوب برای استفاده خانگی و کارهای تعمیر و نگهداری ساختمان هستند. دریل‌های توسن به ویژه در میان مصرف‌کنندگانی که به دنبال ابزار اقتصادی و قابل اطمینان هستند، فروش بسیار خوبی دارند و در اکثر بازارهای ابزار کرج و البرز به راحتی یافت می‌شوند.",
About3: "آساد ابزار در کرج با بیش از ۱۵ سال تجربه در تعمیر انواع ابزار برقی، یکی از مراکز تخصصی تعمیر ابزار توسن در استان البرز است. تشخیص رایگان، قطعات اصلی، تعمیر سریع و ضمانت سه‌ماهه از مزایای انتخاب آساد ابزار برای تعمیر ابزار توسن هستند.",
Models:
[
new("1010", "دریل برقی", "500W", "دریل خانگی پرفروش توسن با کلاچ تنظیم‌پذیر، مناسب سوراخ‌کاری در دیوار و چوب، سبک و آسان"),
new("1007", "دریل چکشی", "650W", "دریل چکشی توسن با تنظیم دور و عملکرد ضربه‌ای، مناسب سوراخ‌کاری در بتن و مصالح سخت"),
new("FS-115", "سنگ‌فرز کوچک", "850W", "سنگ‌فرز ۱۱۵ میلیمتری با حفاظ قابل تنظیم، ایده‌آل برای برش و سنگ‌زنی فلزات"),
new("FS-125", "سنگ‌فرز متوسط", "950W", "سنگ‌فرز ۱۲۵ میلیمتری با موتور قوی‌تر، مناسب کارهای برش فلز و سنگ"),
new("JS-65", "اره عود", "550W", "اره عود توسن با تیغه قابل تعویض، مناسب برش منحنی و مستقیم در چوب، فلز و پلاستیک"),
new("RT-1200","فرز روتر", "1200W", "فرز روتر با پایه قابل تنظیم برای کارهای نجاری، ایجاد شیار و پروفیل‌کاری چوب"),
],
RepairServices:
[
"تعمیر و سیم‌پیچی مجدد موتور دریل توسن",
"تعویض کلکتور و زغال موتور ابزار توسن",
"تعمیر و تنظیم کلاچ دریل‌های توسن",
"تعویض یاتاقان و بلبرینگ سنگ‌فرز توسن",
"تعمیر سوئیچ و مدار الکترونیکی ابزار توسن",
"تعویض چکش و سندان مکانیزم ضربه دریل توسن",
"تعمیر گیربکس و پینیون اره عود توسن",
"تعویض قطعات مکانیکی فرز روتر توسن",
"تشخیص رایگان عیب ابزار توسن در کرج",
"سرویس دوره‌ای و روغن‌کاری انواع ابزار توسن",
],
CommonProblems:
[
new("کاهش قدرت و دور موتور دریل توسن", "ساییدگی زغال موتور یا آلودگی کلکتور پس از استفاده طولانی", "تعویض زغال موتور با قطعه اصلی توسن و تمیزکاری کلکتور"),
new("عدم عملکرد کلاچ دریل توسن ۱۰۱۰", "شکستگی یا فرسایش فنرهای کلاچ به دلیل استفاده با بار زیاد", "تعویض فنر و قطعات کلاچ با نمونه اصلی و تنظیم مجدد گشتاور"),
new("لرزش شدید سنگ‌فرز توسن حین کار", "آسیب دیدن بلبرینگ محور یا عدم تعادل دیسک سنگ", "تعویض بلبرینگ‌های محور و بررسی صفحه دیسک جهت تعادل صحیح"),
new("خاموش شدن ناگهانی ابزار توسن", "اضافه‌بار حرارتی موتور یا خرابی کلید و مدار محافظ حرارتی", "بررسی و تعویض کلید برق و ترموستات حرارتی توسط تکنیسین"),
new("اره عود توسن برش صاف نمی‌دهد", "خستگی یا کجی تیغه اره، یا آسیب به مکانیزم راهنما", "تعویض تیغه اره و تنظیم مکانیزم راهنما برای برش دقیق"),
new("جرقه و دود از یاتاقان دریل توسن", "سوختگی بلبرینگ محور به دلیل روغن‌کاری ناکافی یا گرد و غبار", "تعویض فوری بلبرینگ‌های آسیب‌دیده و سرویس کامل محور"),
],
Faqs:
[
new("تعمیر دریل توسن در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، استان البرز، مرکز تخصصی تعمیر دریل توسن است. تمامی مدل‌های دریل توسن از جمله مدل ۱۰۱۰ و ۱۰۰۷ را با قطعات اصلی تعمیر می‌کنیم. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("هزینه تعمیر سنگ‌فرز توسن در کرج چقدر است؟", "تشخیص عیب کاملاً رایگان است. پس از بررسی، هزینه دقیق اعلام می‌شود. معمولاً تعمیر در کمتر از ۴۸ ساعت با ضمانت سه‌ماهه انجام می‌شود."),
new("آیا قطعات یدکی اصل توسن در کرج موجود است؟", "بله، آساد ابزار موجودی کافی از قطعات یدکی اصلی توسن را نگه می‌دارد. زغال موتور، بلبرینگ، کلید و کلاچ همیشه در دسترس هستند."),
new("دریل توسن مدل ۱۰۱۰ آیا ارزش تعمیر دارد؟", "دریل توسن ۱۰۱۰ با نگهداری صحیح ۵ تا ۸ سال عمر دارد. تعمیر زغال یا کلاچ کاملاً توجیه اقتصادی دارد. متخصصان ما پس از تشخیص رایگان راهنمایی می‌کنند."),
new("نمایندگی تعمیر ابزار توسن در البرز کجاست؟", "آساد ابزار در کرج، مرکز استان البرز، یکی از مراکز معتبر تعمیر ابزار توسن در منطقه است. تشخیص رایگان، تعمیر تخصصی و ضمانت سه‌ماهه ارائه می‌دهیم."),
new("چه مدت طول می‌کشد تعمیر ابزار توسن انجام شود؟", "آساد ابزار کرج اکثر ابزارهای توسن را در کمتر از ۴۸ ساعت تعمیر می‌کند. تعمیرات ساده‌تر در همان روز تحویل داده می‌شوند."),
new("آیا توسن برند معتبری است؟", "توسن یک برند ایرانی با سابقه بیش از سه دهه است. برای استفاده خانگی و نیمه‌حرفه‌ای گزینه مناسبی است. نسبت قیمت به کیفیت آن برای کارهای سبک تا متوسط قابل قبول است."),
]
),
// ─── Ryobi ───────────────────────────────────────────────────────────
new(
Id: "ryobi", Name: "Ryobi", NameFa: "ریوبی",
Color: "#6DB33F", TextColor: "#fff", IsOfficial: false,
Country: "ژاپن", Founded: "1943",
HeroImage: "https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=1200&q=80&auto=format&fit=crop",
Tagline: "ریوبی، همراه هوشمند DIY با پلتفرم ONE+ و بیش از ۱۰۰ ابزار سازگار",
About1: "ریوبی (Ryobi) یکی از شناخته‌شده‌ترین برندهای ابزار برقی جهان است که در سال ۱۹۴۳ در ژاپن تأسیس شد. این شرکت در ابتدا به تولید قطعات صنعتی مشغول بود و به تدریج به یکی از پیشگامان صنعت ابزار برقی تبدیل شد. امروز ریوبی زیرمجموعه گروه TTI (Techtronic Industries) است و در کنار برندهای AEG و Milwaukee در این مجموعه فعالیت می‌کند. ابزارهای ریوبی در ایران به ویژه در میان علاقه‌مندان به کارهای DIY و تعمیرکاران خانگی محبوبیت زیادی دارند.",
About2: "محبوب‌ترین محصولات ریوبی در ایران شامل دریل‌های پیچ‌گوشتی بی‌سیم سری ONE+ با ولتاژ ۱۸ ولت، اره‌های گردبر، فرزهای آنگولر و سنباده‌های لرزشی می‌شوند. آنچه ریوبی را متمایز می‌کند، پلتفرم ONE+ است که به کاربر این امکان را می‌دهد با یک باتری ۱۸ ولت، بیش از ۱۰۰ نوع ابزار مختلف را راه‌اندازی کند. این قابلیت صرفه‌جویی قابل توجهی در هزینه ایجاد می‌کند و ریوبی را به انتخاب هوشمند برای تکمیل جعبه ابزار تبدیل می‌کند.",
About3: "آساد ابزار در کرج با بیش از ۱۵ سال تجربه در تعمیر انواع ابزار برقی، خدمات تخصصی تعمیر و نگهداری ابزارهای ریوبی را ارائه می‌دهد. تیم متخصص ما با استفاده از قطعات اصلی، تشخیص رایگان و گارانتی سه‌ماهه، ابزار شما را در کمتر از ۴۸ ساعت بازمی‌گرداند.",
Models:
[
new("RCD18-0", "دریل پیچ‌گوشتی بی‌سیم", "18V", "دریل بی‌سیم ONE+ با گشتاور ۴۰ نیوتون‌متر، ۲ دنده، چاک ۱۳ میلیمتری، مناسب کارهای DIY"),
new("R18PD2-0", "دریل ضربه‌ای بی‌سیم", "18V", "دریل ضربه‌ای ONE+ برای سوراخکاری در بتن سبک و مصالح ساختمانی، سازگار با تمام باتری‌های ONE+"),
new("R18AG-0", "فرز آنگولر بی‌سیم", "18V", "فرز زاویه‌ای بی‌سیم با دیسک ۱۲۵ میلیمتری، مناسب برش فلز و سنگ در کارهای DIY"),
new("R18CS-0", "اره گردبر بی‌سیم", "18V", "اره دیسکی بی‌سیم با تیغه ۱۸۵ میلیمتری و عمق برش ۵۵ میلیمتر، ایده‌آل برای برش چوب"),
new("R18JS-0", "اره عمودبر بی‌سیم", "18V", "اره موزاییک‌بر بی‌سیم با ۴ تنظیم پاندولی برای برش منحنی و مستقیم در چوب و فلز"),
new("ROS18X-0", "سنباده اوربیتال بی‌سیم", "18V", "سنباده لرزشی تصادفی بی‌سیم با صفحه ۱۲۵ میلیمتری برای آماده‌سازی سطوح چوبی"),
new("R18IW3-0", "آچار ضربه‌ای بی‌سیم", "18V", "آچار پیچ‌گوشتی ضربه‌ای با گشتاور ۳۰۰ نیوتون‌متر و سه حالت سرعت"),
new("R18HT-0", "تفنگ حرارتی بی‌سیم", "18V", "هیت‌گان بی‌سیم با دو دمای قابل تنظیم، مناسب انقباض لوله‌های حرارتی و رنگ‌برداری"),
],
RepairServices:
[
"تعمیر و بازسازی موتور دریل‌های بی‌سیم ریوبی",
"تعویض و شارژدهی بهینه باتری‌های ONE+ 18 ولت",
"تعمیر شارژر و رفع مشکلات برق‌رسانی",
"تعویض چاک و یاتاقان‌های دریل و پیچ‌گوشتی",
"تعمیر گیربکس و سیستم انتقال قدرت ابزارهای ریوبی",
"تعویض کلید و مدار الکترونیک کنترل دور",
"تعمیر فرز آنگولر و تعویض بوش کربن",
"سرویس و روغن‌کاری کامل ابزارهای ریوبی",
"تعمیر اره‌های برقی بی‌سیم و با سیم",
"تشخیص رایگان خرابی و ارائه پیش‌فاکتور دقیق",
],
CommonProblems:
[
new("باتری ONE+ شارژ نمی‌گیرد یا زود خالی می‌شود", "فرسودگی سلول‌های لیتیومی باتری یا نگهداری نامناسب", "بازسازی یا تعویض سلول‌های باتری با قطعات اصلی در آساد ابزار"),
new("دریل ریوبی گشتاور کافی ندارد", "سایش جاروبک‌های کربنی موتور یا خرابی بورد کنترل سرعت", "تعویض جاروبک‌های کربنی یا تعمیر بورد الکترونیک"),
new("چاک دریل می‌لرزد یا ابزار را نگه نمی‌دارد", "فرسودگی فک‌های چاک یا آسیب به مکانیزم قفل‌کننده", "تعویض چاک با نمونه اصلی ریوبی و تنظیم دقیق محور"),
new("شارژر ریوبی چراغ خطا می‌دهد", "خرابی ترموستات شارژر یا آسیب کانکتور اتصال باتری", "تعمیر یا تعویض برد شارژر و رفع اتصالات معیوب"),
new("فرز آنگولر ریوبی صدای غیرعادی می‌دهد", "سایش بلبرینگ‌های محور یا آسیب به چرخ‌دنده‌های گیربکس", "تعویض بلبرینگ‌ها و تنظیم گیربکس با قطعات یدکی اصلی"),
new("کلید روشن-خاموش ابزار ریوبی کار نمی‌کند", "اکسیداسیون یا سوختگی کنتاکت‌های داخلی کلید", "تعویض کامل ماژول کلید با قطعه اصلی و بررسی سیم‌کشی"),
],
Faqs:
[
new("تعمیر دریل بی‌سیم ریوبی در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، استان البرز، خدمات تخصصی تعمیر تمامی مدل‌های دریل بی‌سیم ریوبی سری ONE+ را ارائه می‌دهد. کمتر از ۴۸ ساعت تعمیر و تحویل. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("باتری ONE+ ریوبی ۱۸ ولت خراب شده، قابل تعمیر است؟", "بله، در اکثر موارد باتری‌های ONE+ ریوبی قابل بازسازی هستند. سلول‌های فرسوده تعویض شده و ظرفیت اصلی بازیابی می‌شود. بسیار مقرون‌به‌صرفه‌تر از خرید باتری نو."),
new("هزینه تعمیر ابزار ریوبی در آساد ابزار چقدر است؟", "تشخیص رایگان ارائه می‌شود. پس از بررسی، پیش‌فاکتور دقیق بدون هیچ هزینه اضافی ارائه می‌شود. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("آیا ابزارهای ریوبی برای استفاده حرفه‌ای مناسب هستند؟","ریوبی بیشتر برای کارهای DIY و تعمیرات خانگی طراحی شده. برای بار سنگین صنعتی، برندهای حرفه‌ای‌تر مانند دیوالت یا هیلتی توصیه می‌شوند."),
new("مدت زمان تعمیر ابزار ریوبی در آساد ابزار چقدر است؟", "اکثر تعمیرات ریوبی در کمتر از ۴۸ ساعت انجام می‌شود. برای خرابی‌های ساده مثل تعویض جاروبک یا کلید، گاهی در همان روز تحویل داده می‌شود."),
new("سیستم باتری ONE+ ریوبی چیست؟", "سیستم ONE+ یعنی همه ابزارهای ۱۸V ریوبی از یک باتری مشترک استفاده می‌کنند. از سال ۱۹۹۶ معرفی شده و با بیش از ۱۰۰ ابزار سازگار است."),
new("نمایندگی تعمیر ریوبی در البرز کجاست؟", "آساد ابزار در کرج، مرکز البرز، با بیش از ۱۵ سال سابقه معتبرترین مرکز تعمیر ابزار ریوبی در منطقه است. قطعات اصلی، ضمانت سه‌ماهه."),
]
),
// ─── AEG ─────────────────────────────────────────────────────────────
new(
Id: "aeg", Name: "AEG", NameFa: "آ.ا.گ",
Color: "#E20000", TextColor: "#fff", IsOfficial: false,
Country: "آلمان", Founded: "1883",
HeroImage: "https://images.unsplash.com/photo-1607400201515-c2c41c07d307?w=1200&q=80&auto=format&fit=crop",
Tagline: "تعمیر تخصصی ابزار آ.ا.گ در کرج با ضمانت اصالت قطعات و سرویس حرفه‌ای",
About1: "برند آ.ا.گ (AEG) با بیش از ۱۴۰ سال سابقه، یکی از قدیمی‌ترین و معتبرترین نام‌های صنعت برق و ابزار در جهان است. این شرکت آلمانی که در سال ۱۸۸۳ تأسیس شد، دهه‌هاست در ایران به‌عنوان نمادی از کیفیت مهندسی اروپایی شناخته می‌شود. ابزار برقی آ.ا.گ از همان سال‌های ابتدایی ورود به بازار ایران، نزد پیمانکاران، نجاران، تأسیسات‌کاران و متخصصان فلزکاری جایگاه ویژه‌ای پیدا کرد. امروز آ.ا.گ زیر چتر گروه TTI (Techtronic Industries) فعالیت می‌کند.",
About2: "محبوب‌ترین ابزار آ.ا.گ در بازار ایران شامل دریل‌های ضربه‌ای سری BS، فرزهای زاویه‌ای سری WS، چکش‌های تخریب سری KH و اره‌های عود سری ST می‌شود. پلتفرم ۱۸ ولت آ.ا.گ که با باتری‌های ریوبی سازگار است، انعطاف بالایی برای کاربران فراهم می‌کند. در ایران، مدل‌های دریل BS 18C، فرز WS 18 و چکش تخریب KH 24 از پرفروش‌ترین محصولات این برند محسوب می‌شوند.",
About3: "آساد ابزار در کرج با بیش از ۱۵ سال تجربه در تعمیر ابزار برقی، خدمات تخصصی برای تمامی مدل‌های آ.ا.گ ارائه می‌دهد. تشخیص رایگان، قطعات اصلی، ضمانت سه‌ماهه و تحویل سریع ۴۸ ساعته از ویژگی‌هایی است که آساد ابزار را در استان البرز متمایز می‌کند.",
Models:
[
new("BS 18C", "دریل پیچ‌گوشتی شارژی", "18V", "دریل شارژی حرفه‌ای با گشتاور بالا، مناسب کارهای سنگین ساختمانی و نجاری"),
new("KH 24", "چکش تخریب و دریل بتن", "800W", "چکش تخریب با SDS-Plus، مناسب حفاری در بتن و کارهای تخریب سبک تا متوسط"),
new("WS 18-125 BL", "فرز زاویه‌ای شارژی", "18V", "فرز زاویه‌ای بی‌کابل با موتور براشلس و صفحه ۱۲۵ میلیمتری، مناسب فلزکاری"),
new("WS 1400", "فرز زاویه‌ای برقی", "1400W","فرز زاویه‌ای ۱۲۵ میلیمتری با توان بالا برای فلزکاری حرفه‌ای"),
new("BSB 18C", "دریل ضربه‌ای شارژی", "18V", "دریل ضربه‌ای شارژی با قابلیت حفاری در دیوارهای بنایی و سازگاری با پلتفرم مشترک"),
new("ST 18", "اره عود شارژی", "18V", "اره جیگ‌ساو بی‌کابل با سیستم تغییر سرعت الکترونیکی برای برش چوب، فلز و پلاستیک"),
new("US 18C", "جاروبرقی صنعتی شارژی","18V", "جاروبرقی صنعتی بی‌کابل برای جمع‌آوری براده‌های فلزی و گرد و غبار کارگاهی"),
],
RepairServices:
[
"تعویض کربن و بازسازی موتور ابزار آ.ا.گ",
"تعمیر و تنظیم گیربکس و یاتاقان‌های ابزار آ.ا.گ",
"سرویس و تعمیر باتری و شارژر ۱۸ ولت آ.ا.گ",
"تعویض سوئیچ و کلید راه‌انداز ابزار آ.ا.گ",
"تعمیر تخصصی چکش‌های تخریب و دریل‌های بتن آ.ا.گ",
"بازسازی و تعویض استاتور و روتور فرز و دریل آ.ا.گ",
"رگلاژ و کالیبراسیون گشتاور دریل‌های شارژی آ.ا.گ",
"تعمیر مدارهای الکترونیکی و کنترل سرعت ابزار آ.ا.گ",
"تعویض یاطاقان و سیل‌بندی چکش‌های SDS آ.ا.گ",
"تشخیص رایگان خرابی با دستگاه‌های تخصصی",
],
CommonProblems:
[
new("ضعیف شدن قدرت دریل شارژی آ.ا.گ", "فرسودگی سلول‌های باتری یا خرابی PCB شارژر", "بررسی و تعویض سلول‌های باتری یا تعمیر برد شارژر"),
new("داغ شدن بیش از حد موتور فرز آ.ا.گ", "کربن‌های فرسوده، تهویه ناکافی یا اضافه‌بار مداوم", "تعویض کربن، تمیز کردن مجاری تهویه و بازسازی موتور"),
new("لرزش و صدای غیرعادی چکش تخریب آ.ا.گ", "سایش یاتاقان‌ها یا خرابی مکانیزم ضربه SDS", "تعویض یاطاقان‌ها و اورهال کامل مکانیزم ضربه"),
new("خاموش شدن ناگهانی ابزار آ.ا.گ حین کار", "فعال شدن حرارتی حفاظ دما یا خرابی کلید کنترل سرعت", "بررسی و تعمیر مدار الکترونیکی کنترل سرعت و ترموستات"),
new("نچرخیدن چاک دریل آ.ا.گ", "خرابی گیربکس، شکستگی دنده‌ها یا گیر کردن کلاچ", "باز کردن گیربکس، تعویض دنده‌های آسیب‌دیده و تنظیم کلاچ"),
new("اتصالی و جرقه‌زدن ابزار برقی آ.ا.گ", "خرابی عایق‌بندی سیم‌پیچ روتور یا آستاتور", "تست عایقی و بازسازی یا تعویض سیم‌پیچ توسط تکنیسین متخصص"),
],
Faqs:
[
new("تعمیر ابزار آ.ا.گ در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، استان البرز، خدمات تخصصی تعمیر تمامی مدل‌های آ.ا.گ را ارائه می‌دهد. دریل، فرز، چکش تخریب و ابزار شارژی آ.ا.گ را با قطعات اصلی تعمیر می‌کنیم. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("آیا قطعات اصلی آ.ا.گ در کرج موجود است؟", "بله، آساد ابزار از قطعات اورجینال و اصلی آ.ا.گ استفاده می‌کند. قطعات اصل طول عمر دستگاه شما پس از تعمیر را افزایش می‌دهد."),
new("هزینه تعمیر دریل شارژی آ.ا.گ چقدر است؟", "تشخیص خرابی در آساد ابزار رایگان است. پس از بررسی، هزینه دقیق اعلام می‌شود. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("تعمیر ابزار آ.ا.گ چقدر طول می‌کشد؟", "آساد ابزار اکثر تعمیرات آ.ا.گ را ظرف ۴۸ ساعت انجام می‌دهد تا وقفه کاری شما به حداقل برسد."),
new("آیا ابزار آ.ا.گ با باتری ریوبی سازگار است؟", "بله، ابزار شارژی آ.ا.گ سری ۱۸ ولت با باتری‌های ریوبی ONE+ سازگار هستند زیرا هر دو برند زیر گروه TTI هستند."),
new("آیا آساد ابزار نمایندگی رسمی آ.ا.گ در کرج است؟","آساد ابزار یک مرکز تخصصی تعمیر ابزار برقی در کرج با تخصص در برندهای اروپایی از جمله آ.ا.گ است. با ۱۵ سال تجربه، تعمیر کلیه محصولات آ.ا.گ انجام می‌شود."),
new("فرز آ.ا.گ من جرقه می‌زند، آیا خطرناک است؟", "جرقه‌زدن غیرعادی می‌تواند نشانه فرسودگی کربن‌ها یا اتصالی در سیم‌پیچ باشد. استفاده در این حالت خطرناک است. فوری به آساد ابزار کرج مراجعه کنید. تشخیص رایگان."),
]
),
// ─── Danlex ──────────────────────────────────────────────────────────
new(
Id: "danlex", Name: "Danlex", NameFa: "دانلکس",
Color: "#F97316", TextColor: "#fff", IsOfficial: false,
Country: "ایران", Founded: "1384",
HeroImage: "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=1200&q=80&auto=format&fit=crop",
Tagline: "تعمیر تخصصی ابزار دانلکس در کرج — سریع، مطمئن و با قطعات اصلی",
About1: "دانلکس یکی از برندهای ایرانی فعال در حوزه ابزارآلات برقی است که از اواسط دهه ۱۳۸۰ در بازار ایران حضور دارد. این برند با تمرکز بر ارائه ابزارهای برقی با قیمت رقابتی و قابلیت‌های مناسب برای کاربران ایرانی، توانسته در بین مصرف‌کنندگان خانگی و نیمه‌حرفه‌ای جایگاهی برای خود ایجاد کند. دانلکس انواع دریل، فرز، اره و ابزارهای جانبی تولید می‌کند که در بازارهای ابزار کرج و البرز به‌راحتی یافت می‌شوند.",
About2: "محبوب‌ترین محصولات دانلکس شامل دریل‌های ضربه‌ای سری D، فرزهای زاویه‌ای سبک‌وزن و اره‌های عود است. این ابزارها به دلیل سبکی، قیمت مناسب و در دسترس بودن قطعات یدکی، گزینه‌ای مناسب برای کارهای خانگی و تعمیراتی سبک تا متوسط هستند. سری دریل‌های ضربه‌ای دانلکس به خاطر طراحی کامپکت، در فضاهای کم‌عمق کاربرد بیشتری دارند.",
About3: "آساد ابزار در کرج با بیش از ۱۵ سال تجربه، خدمات تعمیر ابزارهای دانلکس را با قطعات اصلی، تشخیص رایگان و ضمانت سه‌ماهه ارائه می‌دهد. با توجه به گستردگی بازار دانلکس در استان البرز، آساد ابزار موجودی کامل قطعات یدکی این برند را در انبار نگهداری می‌کند.",
Models:
[
new("D-550", "دریل ضربه‌ای", "550W", "دریل ضربه‌ای کامپکت با چاک ۱۳ میلیمتری، مناسب سوراخ‌کاری در بتن سبک، دیوار و چوب"),
new("D-750", "دریل چکشی", "750W", "دریل چکشی با قدرت ۷۵۰ وات، ایده‌آل برای استفاده خانگی و تعمیرات ساختمانی سبک"),
new("AG-115", "فرز زاویه‌ای", "850W", "فرز زاویه‌ای سبک با دیسک ۱۱۵ میلیمتری، مناسب برش و سنگ‌زنی فلزات در کارهای خانگی"),
new("AG-125", "فرز زاویه‌ای", "950W", "فرز زاویه‌ای با دیسک ۱۲۵ میلیمتری و مدیریت دما بهتر برای کارهای نیمه‌حرفه‌ای"),
new("JS-500", "اره عود", "500W", "اره عود با سرعت متغیر و تیغه قابل تعویض، مناسب برش منحنی در چوب و پلاستیک"),
new("SD-18", "پیچ‌گوشتی شارژی", "18V", "پیچ‌گوشتی شارژی با باتری لیتیوم ۱۸ ولت و گشتاور قابل تنظیم برای کارهای خانگی"),
],
RepairServices:
[
"تعویض زغال و کاربن موتور ابزار دانلکس",
"تعمیر گیربکس و تعویض بلبرینگ دریل دانلکس",
"تعمیر و تعویض سوئیچ اصلی ابزار دانلکس",
"تعمیر فرز زاویه‌ای دانلکس و بالانس دیسک",
"تعویض چاک دریل و رفع لقی محور",
"تعمیر مدار الکترونیکی کنترل سرعت دانلکس",
"سرویس و روغن‌کاری کامل ابزارهای دانلکس",
"تعمیر باتری و شارژر ابزارهای شارژی دانلکس",
"تشخیص رایگان عیب و ارائه پیش‌فاکتور",
"تأمین قطعات یدکی اصلی دانلکس در کرج",
],
CommonProblems:
[
new("گرم شدن زیاد موتور دریل دانلکس", "فرسودگی کاربن‌ها یا انسداد دریچه‌های تهویه", "تعویض کاربن‌های موتور و تمیزکاری کامل مسیرهای تهویه"),
new("کاهش قدرت فرز دانلکس", "فرسودگی جاروبک‌های کربنی یا آسیب کموتاتور", "تعویض جاروبک‌های اصلی و پولیش یا تعویض کموتاتور"),
new("قطع و وصل شدن ناگهانی دستگاه", "خرابی سوئیچ اصلی یا اتصال ضعیف سیم‌کشی", "تعویض سوئیچ اصلی یا رفع اتصالی در مسیر برق‌رسانی"),
new("لرزش و صدای غیرعادی فرز", "خرابی بلبرینگ محور یا عدم تعادل دیسک", "تعویض بلبرینگ‌های فرسوده و تنظیم دیسک"),
new("باتری شارژی دانلکس زود خالی می‌شود", "فرسودگی سلول‌های لیتیوم‌یون بر اثر استفاده زیاد", "بازسازی یا تعویض سلول‌های باتری در آساد ابزار کرج"),
],
Faqs:
[
new("تعمیر ابزار دانلکس در کرج کجا انجام می‌شود؟", "آساد ابزار در کرج، استان البرز، مرکز تخصصی تعمیر ابزارهای دانلکس است. با ۱۵ سال تجربه و قطعات اصلی، در کمتر از ۴۸ ساعت تعمیر می‌کنیم. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("آیا قطعات یدکی دانلکس در کرج موجود است؟", "بله، آساد ابزار موجودی کامل قطعات پرمصرف دانلکس شامل کاربن، بلبرینگ، سوئیچ و گیربکس را دارد."),
new("هزینه تعمیر فرز دانلکس چقدر است؟", "تشخیص رایگان است. پس از بررسی هزینه دقیق اعلام می‌شود. با توجه به قطعات مناسب دانلکس، تعمیر عموماً مقرون‌به‌صرفه است."),
new("آیا تعمیر ابزار دانلکس صرفه اقتصادی دارد؟", "در اکثر موارد بله. قطعات دانلکس قیمت مناسبی دارند و تعمیر معمولاً کمتر از ۵۰٪ قیمت دستگاه نو هزینه دارد."),
new("چه مدت طول می‌کشد تا دریل دانلکس تعمیر شود؟", "اکثر تعمیرات در کمتر از ۴۸ ساعت انجام می‌شود. تعمیرات ساده مثل تعویض کاربن یا سوئیچ در همان روز تحویل داده می‌شوند."),
new("نمایندگی تعمیر دانلکس در البرز کجاست؟", "آساد ابزار در کرج خدمات تعمیر دانلکس برای تمام ساکنین استان البرز ارائه می‌دهد. ضمانت سه‌ماهه روی تمام تعمیرات."),
]
),
// ─── PM Anchor ───────────────────────────────────────────────────────
new(
Id: "pm-anchor", Name: "PM Anchor", NameFa: "پی‌ام آنکر",
Color: "#1E3A5F", TextColor: "#fff", IsOfficial: false,
Country: "ایران", Founded: "1380",
HeroImage: "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=1200&q=80&auto=format&fit=crop",
Tagline: "تأمین، نصب و سرویس سیستم‌های آنکرگذاری پی‌ام آنکر در کرج و البرز",
About1: "پی‌ام آنکر (PM Anchor) یکی از پیشگامان صنعت آنکرگذاری و اتصالات بتنی در ایران است که از اوایل دهه ۱۳۸۰ در حوزه تولید و توزیع سیستم‌های آنکر فعالیت می‌کند. این برند ایرانی با درک نیازهای صنعت ساختمان داخلی، انواع آنکر مکانیکی (انبساطی) و شیمیایی (رزینی) را برای پروژه‌های ساختمانی، صنعتی و تأسیساتی تولید و عرضه می‌کند. محصولات پی‌ام آنکر در پروژه‌های عمرانی متعددی در سراسر ایران از جمله استان البرز به‌کار رفته‌اند.",
About2: "طیف محصولات پی‌ام آنکر شامل آنکرهای انبساطی (بادکنکی، پیچ آنکر، آنکر رولپلاک)، آنکرهای شیمیایی رزینی (کپسول و کارتریج) و رولپلاک‌های صنعتی می‌شود. این محصولات در اندازه‌های متنوع M6 تا M30 برای انواع بار کششی و برشی تولید می‌شوند و استانداردهای ایمنی ساختمانی را رعایت می‌کنند. نصب صحیح آنکرها نیاز به دریل چکشی مناسب و مته‌های کالیبره دارد.",
About3: "آساد ابزار در کرج علاوه بر تعمیر دریل‌های چکشی مورد نیاز برای آنکرگذاری، در زمینه مشاوره انتخاب نوع آنکر مناسب، تأمین قطعات و تجهیزات نصب خدمات ارائه می‌دهد. با بیش از ۱۵ سال تجربه در صنعت ابزار، می‌توانیم بهترین ابزار را برای نصب آنکرهای پی‌ام آنکر به شما معرفی کنیم.",
Models:
[
new("PM-E M8", "آنکر انبساطی", "M8×75", "آنکر انبساطی فولادی با پوشش گالوانیزه برای بارهای متوسط، مناسب نصب تأسیسات و لوله‌کشی"),
new("PM-E M12", "آنکر انبساطی سنگین", "M12×100", "آنکر انبساطی سنگین برای اتصالات با بار بالا مانند داربست و نرده‌های ایمنی"),
new("PM-C M12", "آنکر شیمیایی کپسول", "M12×130", "آنکر شیمیایی کپسول شیشه‌ای با رزین دو جزئی، ظرفیت کشش بالا برای محیط‌های خاص"),
new("PM-R M16", "آنکر شیمیایی کارتریج","M16×190", "آنکر شیمیایی کارتریجی با تفنگ تزریق، مناسب نصب پیچ‌های سنگین در بتن مسلح"),
new("PM-T M10", "آنکر خار ماهی", "M10×100", "آنکر خار ماهی برای سقف و دیوارهای توپر، مناسب نصب لوستر، کولر و تأسیسات سبک"),
new("PM-WS M20", "آنکر ولدینگ استاد", "M20×200", "آنکر استادبولت جوشکاری برای اتصال تیرهای فلزی و سازه‌های پیش‌ساخته به بتن"),
new("PM-RP 8", "رولپلاک فیشر", "8mm", "رولپلاک پلاستیکی برای دیوارهای توپر و نیمه‌توپر، مناسب بارهای سبک خانگی"),
],
RepairServices:
[
"مشاوره تخصصی انتخاب نوع و سایز آنکر مناسب",
"تعمیر دریل چکشی مورد نیاز برای آنکرگذاری",
"تأمین مته‌های SDS کالیبره برای سوراخ‌کاری آنکر",
"سرویس دستگاه تزریق رزین آنکر شیمیایی",
"تعمیر و سرویس دریل مغزه‌گیر الماسی (کربیت)",
"کالیبراسیون و تنظیم گشتاور دریل برای نصب آنکر",
"تأمین کامل تجهیزات نصب انواع آنکر پی‌ام",
"آموزش روش صحیح نصب آنکر شیمیایی و مکانیکی",
"بررسی و آزمون کشش آنکرهای نصب‌شده",
"تعمیر گان (تفنگ) تزریق رزین آنکر شیمیایی",
],
CommonProblems:
[
new("آنکر هنگام بارگذاری بیرون می‌آید", "سوراخ با قطر نادرست یا جنس بتن ضعیف", "استفاده از مته کالیبره و در صورت لزوم تغییر به آنکر شیمیایی"),
new("رزین آنکر شیمیایی سفت نمی‌شود", "نسبت ترکیب نادرست رزین یا دمای پایین محیط", "رعایت زمان مخلوط‌سازی و دمای محیط بالای ۵ درجه سانتیگراد"),
new("آنکر انبساطی در سوراخ می‌چرخد", "سایز سوراخ بزرگ‌تر از حد مجاز یا بتن ترک‌دار", "استفاده از آنکر شیمیایی یا آنکر با قطر بزرگ‌تر"),
new("تفنگ تزریق رزین خراب شده", "فشردگی رزین سفت‌شده در تفنگ یا شکستگی پیستون", "سرویس و تمیزکاری فوری تفنگ یا تعویض پیستون در آساد ابزار"),
new("دریل چکشی مناسب آنکرگذاری نیست", "قدرت ضربه ناکافی یا مته نامناسب برای قطر آنکر", "انتخاب دریل SDS-Plus مناسب و مته کالیبره با قطر صحیح"),
],
Faqs:
[
new("پی‌ام آنکر در کرج کجا پیدا می‌شود؟", "آساد ابزار در کرج، استان البرز، توزیع‌کننده و مشاور محصولات پی‌ام آنکر است. همچنین تعمیر تجهیزات نصب آنکر را انجام می‌دهیم. تماس: ۰۲۶-۳۴۵۶۷۸۹۰"),
new("فرق آنکر شیمیایی و مکانیکی پی‌ام آنکر چیست؟", "آنکر مکانیکی برای بارهای متوسط و نصب فوری مناسب است. آنکر شیمیایی ظرفیت بار بسیار بالاتری دارد و برای محیط‌های سخت، بتن ترک‌دار و لبه بتن مناسب‌تر است."),
new("چه دریلی برای نصب آنکر پی‌ام آنکر M16 نیاز است؟","برای آنکر M16 به دریل SDS-Plus حداقل ۹۰۰ وات یا دریل SDS-Max برای کارهای سنگین‌تر نیاز دارید. آساد ابزار مناسب‌ترین دریل را به شما معرفی می‌کند."),
new("آیا آنکر شیمیایی پی‌ام آنکر در رطوبت هم کار می‌کند؟","رزین‌های پی‌ام آنکر برای محیط‌های مرطوب هم گرید مخصوص دارند. در سوراخ‌های زیر آب نیاز به رزین مخصوص است. مشاوره رایگان در آساد ابزار."),
new("تفنگ تزریق رزین آنکر من خراب شده، تعمیر می‌شود؟","بله، آساد ابزار سرویس و تعمیر تفنگ‌های تزریق رزین انواع برندها از جمله پی‌ام آنکر را انجام می‌دهد. تشخیص رایگان با تماس ۰۲۶-۳۴۵۶۷۸۹۰"),
new("استاندارد نصب آنکر در ایران چیست؟", "نصب آنکر باید مطابق مبحث دهم مقررات ملی ساختمان ایران انجام شود. انتخاب نوع آنکر، عمق و فاصله از لبه بتن باید توسط مهندس محاسب تأیید شود."),
]
),
];
}
+51
View File
@@ -0,0 +1,51 @@
using AsadiTools.Models;
using System.Text.Json;
namespace AsadiTools.Services;
public class CartService(IHttpContextAccessor httpContextAccessor)
{
private const string SessionKey = "Cart";
private ISession Session => httpContextAccessor.HttpContext!.Session;
public List<CartItem> GetItems()
{
var json = Session.GetString(SessionKey);
return json is null ? [] : JsonSerializer.Deserialize<List<CartItem>>(json) ?? [];
}
public void AddItem(CartItem item)
{
var items = GetItems();
var existing = items.FirstOrDefault(i => i.ProductId == item.ProductId);
if (existing is not null)
existing.Qty++;
else
items.Add(item);
Save(items);
}
public void UpdateQty(int productId, int qty)
{
var items = GetItems();
var item = items.FirstOrDefault(i => i.ProductId == productId);
if (item is null) return;
if (qty <= 0) items.Remove(item);
else item.Qty = qty;
Save(items);
}
public void RemoveItem(int productId)
{
var items = GetItems().Where(i => i.ProductId != productId).ToList();
Save(items);
}
public void Clear() => Session.Remove(SessionKey);
public int Count => GetItems().Sum(i => i.Qty);
public decimal Total => GetItems().Sum(i => i.Subtotal);
private void Save(List<CartItem> items) =>
Session.SetString(SessionKey, JsonSerializer.Serialize(items));
}
+291
View File
@@ -0,0 +1,291 @@
namespace AsadiTools.Services;
public record BrandInfo(string Id, string Name, string NameFa, string Color, string TextColor, bool IsOfficial, string Description, string[] Services);
public record ToolType(string Id, string NameFa, string Icon, string Description, string[] CommonIssues);
public record PartCategory(string Id, string NameFa, string Icon);
public record DeWaltTool(string Id, string NameFa, string NameEn, string[] Models, string CategoryId, string Icon, string Power, string Description, string[] RepairItems);
public static class SiteData
{
public static readonly BrandInfo[] Brands =
[
new("dewalt", "DeWalt", "دیوالت", "#FFCD00", "#000", true,
"نمایندگی رسمی دیوالت در کرج – ابزار حرفه‌ای با گارانتی اصل",
["دریل", "فرز", "مینی فرز", "بتن کن", "شمشاد زن", "تراز لیزری", "گردبر و اره قطعه‌بر", "اره فارسی‌بر", "ابزار نجاری", "متر لیزری"]),
new("makita", "Makita", "ماکیتا", "#009CDE", "#fff", false,
"تعمیر تخصصی ابزار ماکیتا توسط تکنیسین‌های مجرب",
["دریل", "فرز", "مینی فرز", "بتن کن"]),
new("ronix", "Ronix", "رونیکس", "#E30613", "#fff", false,
"سرویس و تعمیر کامل ابزار رونیکس با قطعات اصل",
["دریل", "فرز", "مینی فرز", "بتن کن", "شمشاد زن"]),
new("tosan", "Tosan", "توسن", "#2563EB", "#fff", false,
"تعمیر و سرویس ابزار توسن با قیمت مناسب",
["دریل", "فرز", "مینی فرز", "بتن کن"]),
new("black-decker", "Black & Decker", "بلک اند دکر", "#F97316", "#fff", false,
"تعمیر تخصصی ابزار بلک اند دکر",
["دریل", "فرز", "مینی فرز", "شمشاد زن"]),
];
public static readonly ToolType[] ToolTypes =
[
new("drill", "دریل", "🔩",
"تعمیر انواع دریل برقی، دریل چکشی و دریل شارژی. تعویض کاربن، چاک، بیرینگ و آرمیچر.",
["چاک دریل لق شده یا نمی‌بندد", "دریل روشن نمی‌شود", "ضعیف شدن قدرت دریل", "جرقه داخل دریل", "گرم شدن بیش از حد"]),
new("grinder", "فرز", "⚙️",
"تعمیر انواع فرز بزرگ و کوچک. تعویض بیرینگ، گیربکس، کلید و اجزاء الکتریکی.",
["فرز روشن نمی‌شود", "لرزش شدید فرز", "صدای غیر عادی گیربکس", "جرقه یا دود", "گرم شدن بیش از حد"]),
new("mini-grinder", "مینی فرز", "🔧",
"تعمیر تخصصی انواع مینی فرز. مناسب برای کارهای ظریف و دقیق.",
["دور موتور پایین است", "ویبراسیون زیاد", "کلید خرابی دارد", "گرم شدن سریع"]),
new("hedge-trimmer", "شمشاد زن", "🌿",
"تعمیر و تیزکردن تیغه شمشاد زن. تعویض قطعات مکانیکی و الکتریکی.",
["تیغه کند شده", "شمشاد زن قطع و وصل می‌کند", "دنده‌های تیغه خراب شده", "موتور ضعیف شده"]),
new("rotary-hammer", "بتن کن", "🏗️",
"تعمیر انواع بتن کن بزرگ و کوچک. تعویض پیستون، کلاچ، بیرینگ و قطعات ضربه‌ای.",
["ضربه ندارد", "حالت چکشی کار نمی‌کند", "صدای ضربه غیر عادی", "کلید حالت خراب است", "مته می‌لرزد"]),
new("laser-level", "تراز لیزری", "🔴",
"تعمیر و کالیبراسیون انواع تراز لیزری خطی، نقطه‌ای و چرخشی. تنظیم دقت زیر ۱ میلی‌متر در ۱۰ متر.",
["خط لیزر پیدا نیست یا ضعیف است", "لیزر تراز نیست (انحراف دارد)", "صفحه نمایش خاموش است", "موتور چرخش کار نمی‌کند", "باتری شارژ نمی‌شود", "حالت خود-تراز کار نمی‌کند"]),
new("gerd-bar", "گردبر و اره قطعه‌بر", "💿",
"تعمیر انواع گردبر فلز، گردبر چوب، اره قطعه‌بر مدل ثابت و اره‌های مدور صنعتی. تعویض تیغه، بیرینگ و گیربکس.",
["تیغه گردبر لرزش دارد", "اره روشن نمی‌شود", "موتور دود می‌کند", "سرعت برش ضعیف شده", "گیربکس صدای غیرعادی دارد", "کلید مشکل دارد"]),
new("miter-saw", "اره فارسی‌بر", "📐",
"تعمیر انواع اره فارسی‌بر ساده و کشویی. تنظیم زوایای قائمه و مورب. تعویض تیغه، بیرینگ و اجزاء الکتریکی.",
["زاویه برش دقیق نیست", "اره لرزش دارد", "موتور قدرت ندارد", "ترمز تیغه کار نمی‌کند", "کلید و رئوستا خرابی دارد"]),
new("woodworking", "ابزار نجاری", "🪵",
"تعمیر ابزار نجاری برقی شامل رنده برقی، فرز چوب (روتر)، اره نواری و سنباده نواری. تعمیر تخصصی توسط متخصص نجاری.",
["رنده برقی تنظیم نمی‌شود", "فرز چوب لرزش دارد", "تیغه رنده کند شده", "اره نواری تیغه از دست می‌دهد", "سنباده نواری حرکت نمی‌کند", "موتور گرم می‌کند"]),
new("laser-measure", "متر لیزری", "📏",
"تعمیر و کالیبراسیون انواع متر لیزری (فاصله‌یاب لیزری). تنظیم دقت اندازه‌گیری. تعمیر صفحه نمایش و اجزاء الکتریکی.",
["اندازه‌گیری دقت ندارد (خطای بزرگ)", "صفحه نمایش خاموش یا مات است", "لیزر روشن نمی‌شود", "حافظه ذخیره نمی‌کند", "باتری سریع تخلیه می‌شود"]),
];
public static readonly PartCategory[] Categories =
[
new("carbon", "کاربن (ذغال)", "⚡"),
new("bearing", "بیرینگ / بلبرینگ", "⭕"),
new("switch", "کلید و رئوستا", "🔌"),
new("armature", "آرمیچر (روتور)", "🔄"),
new("gear", "چرخ‌دنده / گیربکس","⚙️"),
new("chuck", "چاک دریل", "🔩"),
new("stator", "استاتور", "🧲"),
new("accessory", "لوازم جانبی", "🛠️"),
];
public static readonly DeWaltTool[] DeWaltTools =
[
// ── دریل و درایور ─────────────────────────────────────────────────────
new("dcd796", "دریل چکشی بی‌سیم براشلس ۱۸ولت", "Brushless Hammer Drill/Driver 18V",
["DCD796", "DCD777", "DCD791", "DCD708"], "drill", "🔩", "18V XR",
"دریل چکشی بی‌سیم با موتور براشلس – بدون کاربن، بازدهی بالاتر و طول عمر بیشتر. مناسب سوراخ‌کاری در بتن، آجر و فولاد. گشتاور ۶۵ نیوتون متر.",
["تعویض بیرینگ محور", "تعمیر گیربکس دو سرعته", "تعمیر چاک ۱۳ mm", "تعمیر سوئیچ سرعت", "تعمیر برد الکترونیک", "کالیبراسیون کلاچ"]),
new("dwd024", "دریل چکشی برقی ۱۳mm", "Corded Hammer Drill 13mm",
["DWD024", "DWD112", "DWD160", "DWD024S"], "drill", "🔩", "750W",
"دریل چکشی برقی با قدرت ۷۵۰ وات برای کارهای ساختمانی سنگین. سرعت متغیر و قابلیت معکوس. مناسب بتن و آجر.",
["تعویض کاربن موتور", "تعویض بیرینگ", "تعمیر گیربکس", "تعمیر چاک", "تعمیر کلید سرعت"]),
new("dwd520", "میکسر حرفه‌ای ۱۳mm", "Professional Mixer Drill",
["DWD520", "DWD521", "DWD112"], "drill", "🔩", "710W",
"دریل میکسر قوی برای هم‌زدن رنگ، ملات و مواد ساختمانی. موتور ۷۱۰ وات با کنترل سرعت.",
["تعویض کاربن", "تعمیر سوئیچ سرعت", "تعمیر گیربکس", "تعویض بیرینگ"]),
// ── پیچ‌گوشتی ضربه‌ای ─────────────────────────────────────────────────
new("dcf887", "پیچ‌گوشتی ضربه‌ای بی‌سیم ۱/4 اینچ", "Brushless Impact Driver 1/4\"",
["DCF887", "DCF809", "DCF840", "DCF850"], "driver", "🔧", "18V XR",
"پیچ‌گوشتی ضربه‌ای سه‌سرعته بی‌سیم با گشتاور ۲۰۵ نیوتون متر. سریع‌ترین مدل در رده خود برای پیچ‌کاری و اتصالات.",
["تعمیر مکانیزم ضربه (چکش-سندان)", "تعویض بیرینگ", "تعمیر سوئیچ ۳ سرعته", "تعمیر موتور براشلس"]),
new("dcf899", "آچار ضربه‌ای ۱/۲ اینچ بی‌سیم", "High Torque Impact Wrench 1/2\"",
["DCF899", "DCF894", "DCF900", "DCF899M2"], "driver", "🔧", "18V XR",
"آچار ضربه‌ای با گشتاور شکستن ۶۷۸ نیوتون متر. مناسب تعمیرات خودرو، چرخ و اتصالات صنعتی سنگین.",
["تعمیر مکانیزم ضربه فوری", "تعویض بیرینگ محور", "تعمیر آنویل ۱/۲ اینچ", "تعمیر سوئیچ"]),
// ── فرز آنگولر ──────────────────────────────────────────────────────
new("dcg412", "فرز آنگولر بی‌سیم ۱۱۵mm", "Brushless Angle Grinder 4.5\"",
["DCG412", "DCG418", "DCG405", "DCG460"], "grinder", "⚙️", "18V XR",
"فرز آنگولر ۴.۵ اینچ بی‌سیم براشلس. محافظ الکترونیکی از موتور در برابر اضافه‌بار و شروع نرم. مناسب برش فلز، سنگ و کاشی.",
["تعویض بیرینگ سر و دم", "تعمیر گیربکس مخروطی", "تعمیر کلید کشویی", "تعمیر موتور براشلس", "تعمیر فلنج نگهدارنده"]),
new("dwe402", "فرز آنگولر برقی ۱۱۵mm", "Corded Angle Grinder 4.5\"",
["DWE402", "DWE4120", "DWE4011", "DWE402N"], "grinder", "⚙️", "1000W",
"فرز برقی ۱۰۰۰ وات با شروع نرم و محافظ اضافه‌بار. برش و سنباده‌زنی فلز، سنگ، بتن و کاشی.",
["تعویض کاربن موتور", "تعویض بیرینگ", "تعمیر گیربکس", "تعمیر کلید"]),
new("dwe4557", "فرز آنگولر برقی ۱۸۰mm", "Corded Angle Grinder 7\"",
["DWE4557", "DW831", "DWE4599"], "grinder", "⚙️", "2000W",
"فرز بزرگ ۷ اینچ برای برش سنگین فلزات و بتن. موتور ۲۰۰۰ وات با کلاچ الکترونیک ضدلغزش. مقاوم برای کار سخت.",
["تعویض کاربن موتور", "تعویض بیرینگ سنگین", "تعمیر گیربکس صنعتی", "تعمیر کلید"]),
// ── بتن‌کن ────────────────────────────────────────────────────────────
new("dch273", "بتن‌کن SDS+ بی‌سیم ۲۶mm", "Brushless SDS+ Rotary Hammer",
["DCH273", "DCH253", "DCH072", "DCH133"], "rotary-hammer", "🏗️", "18V XR",
"بتن‌کن SDS+ بی‌سیم براشلس با سه حالت (دریل/دریل+ضربه/فقط ضربه). انرژی ضربه ۲.۱ ژول. مناسب بتن، آجر و سنگ.",
["تعمیر مکانیزم ضربه پیستون", "تعویض پیستون و فنر", "تعمیر کلاچ ایمنی", "تعویض بیرینگ", "تعمیر سلکتور حالت"]),
new("d25133k", "بتن‌کن SDS+ برقی ۲۶mm با AVS", "SDS+ Rotary Hammer with AVS",
["D25133K", "D25143K", "D25144K"], "rotary-hammer", "🏗️", "800W",
"بتن‌کن برقی با سیستم AVS (کنترل لرزش فعال) – کاهش ۷۰٪ ارتعاش انتقالی به دست. انرژی ضربه ۲.۸ ژول.",
["تعمیر سیستم AVS (ضربه‌گیر)", "تعمیر مکانیزم ضربه", "تعویض پیستون", "تعمیر کلاچ", "تعویض بیرینگ"]),
new("d25723k", "بتن‌کن SDS-Max برقی ۴۰mm", "SDS-Max Rotary Hammer",
["D25723K", "D25763K", "D25773K"], "rotary-hammer", "🏗️", "1250W",
"بتن‌کن حرفه‌ای SDS-Max برای سوراخ‌کاری قطر بزرگ و تخریب بتن مسلح. انرژی ضربه ۱۰ ژول.",
["تعمیر مکانیزم ضربه سنگین", "تعویض پیستون و فنر سنگین", "تعمیر کلاچ صنعتی", "تعویض بیرینگ سنگین"]),
// ── ابزار برش ─────────────────────────────────────────────────────────
new("dcs331", "جیگ‌ساو بی‌سیم ۱۸ولت", "Brushless Jigsaw 18V",
["DCS331", "DCS334", "DCS374"], "saw", "🪚", "18V XR",
"جیگ‌ساو بی‌سیم با ۴ موقعیت ضربه اوربیتال. برش منحنی و مستقیم در چوب (تا ۱۳۵mm)، فلز (تا ۱۰mm) و پلاستیک.",
["تعمیر مکانیزم اوربیتال", "تعویض بیرینگ محور", "تعمیر گیرنده تیغه", "تعمیر سوئیچ"]),
new("dcs570", "اره مدور بی‌سیم ۱۸۴mm", "Brushless Circular Saw 7-1/4\"",
["DCS570", "DCS565", "DCS391"], "saw", "🪚", "18V XR",
"اره مدور بی‌سیم ۷ و یک‌چهارم اینچ براشلس. عمق برش ۶۵mm در ۹۰ درجه و ۴۴mm در ۴۵ درجه.",
["تعویض بیرینگ", "تعمیر گیربکس", "تعمیر گارد تیغه", "تعمیر سوئیچ"]),
// ── ابزار تکمیلی ──────────────────────────────────────────────────────
new("dwe315", "ابزار چندکاره اسیلیتینگ", "Oscillating Multi-Tool",
["DWE315", "DCS355", "DCS354"], "multi", "🔧", "300W",
"ابزار چندکاره با حرکت اسیلیتینگ ۱۵ هزار دور در دقیقه. مناسب برش دقیق، سنباده‌زنی و جداسازی درزبندی.",
["تعمیر مکانیزم اسیلیتینگ", "تعویض بیرینگ", "تعمیر سیستم گیرنده تیغه", "تعمیر سوئیچ"]),
new("dwe6421", "سنباده لرزان تصادفی ۱۲۵mm", "Random Orbital Sander 5\"",
["DWE6421", "DWE6423", "DCW210"], "sander", "💨", "280W",
"سنباده لرزان تصادفی با سیستم جمع‌آوری گرد. موتور ۲۸۰ وات با سرعت قابل تنظیم. سطح نهایی عالی روی چوب و فلز.",
["تعمیر مکانیزم اکسنتریک", "تعویض بیرینگ", "تعمیر سوئیچ سرعت", "تعمیر صفحه لرزان"]),
// ── فضای سبز ─────────────────────────────────────────────────────────
new("dcmht563", "شمشادزن بی‌سیم ۵۵ سانتی‌متر", "Brushless Hedge Trimmer 22\"",
["DCMHT563", "DCPH820", "DCHT820B"], "hedge-trimmer", "🌿", "18V XR",
"شمشادزن براشلس ۵۵ سانتی‌متر با تیغه دو طرفه. فاصله دندانه ۱۶mm برای شاخه‌های قطور. وزن سبک با طراحی ارگونومیک.",
["تیزکاری تیغه با دستگاه", "تعمیر مکانیزم حرکت تیغه", "تعمیر گیربکس", "تعمیر سوئیچ ایمنی", "تعویض تیغه"]),
// ── تراز لیزری ───────────────────────────────────────────────────────
new("dw088k", "تراز لیزری خطی ۲ پرتو", "Cross Line Laser Level",
["DW088K", "DW089K", "DW088CG"], "laser-level", "🔴", "بی‌سیم",
"تراز لیزری با ۲ خط (افقی و عمودی) و دقت ±۰.۳mm/m. دارای حالت خود-تراز اتوماتیک و پایه مغناطیسی. مناسب نصب کاشی، کابینت و پارتیشن.",
["کالیبراسیون دقت خط لیزر", "تعمیر موتور پاندول", "تعمیر حالت قفل دستی", "تعمیر صفحه LED", "تعمیر باتری و شارژر"]),
new("dce088g", "تراز لیزری سبز ۳×۳۶۰° بی‌سیم", "Green Cross-Line & Plumb Spot Laser",
["DCE088G", "DCE089G", "DCE083"], "laser-level", "🔴", "18V XR",
"تراز لیزری سبز بی‌سیم با برد ۳۰ متر (با گیرنده ۱۰۰ متر). پرتو سبز ۴ برابر واضح‌تر از قرمز. مناسب پروژه‌های بزرگ.",
["کالیبراسیون کامل سه محور", "تعمیر ماژول لیزر سبز", "تعمیر پاندول مغناطیسی", "تعمیر گیرنده لیزر", "تعمیر برد بی‌سیم"]),
// ── متر لیزری ────────────────────────────────────────────────────────
new("dw03101", "متر لیزری ۱۰۰ متری", "Laser Distance Measurer 100m",
["DW03101", "DW03050", "DWHT77600"], "laser-measure", "📏", "باتری",
"فاصله‌یاب لیزری با برد ۱۰۰ متر و دقت ±۱.۵mm. نمایشگر روشن بزرگ. محاسبه مساحت، حجم و فیثاغورس.",
["کالیبراسیون دقت اندازه‌گیری", "تعمیر نمایشگر LCD", "تعمیر ماژول لیزر", "تعمیر دکمه‌ها", "تعمیر درپوش باتری"]),
new("dwht77100", "متر لیزری ۳۰ متری", "Laser Distance Measurer 30m",
["DWHT77100", "DWHT77929", "DWHT77190"], "laser-measure", "📏", "باتری",
"متر لیزری جیبی با برد ۳۰ متر. طراحی فشرده برای استفاده روزمره. محاسبه مستقیم مساحت و حجم.",
["کالیبراسیون اندازه‌گیری", "تعمیر لنز لیزر", "تعمیر صفحه نمایش", "تعمیر برد الکترونیک"]),
// ── گردبر / اره قطعه‌بر ──────────────────────────────────────────────
new("dw872", "گردبر فلز ۳۵۵mm", "Cold Cut Chop Saw 14\"",
["DW872", "DW871", "DCS690"], "chop-saw", "💿", "2000W",
"گردبر سردبر ۱۴ اینچ برای برش دقیق فلز بدون ایجاد حرارت. پرچ فولادی را بدون تغییر خواص متالورژیکی برش می‌دهد.",
["تعویض بیرینگ کله‌گاو", "تعمیر گیربکس", "تعمیر کلید و مدار", "تنظیم زاویه برش", "تعمیر گیره قطعه‌کار"]),
new("dwe7491", "اره گردبر میز ۲۵۴mm", "Table Saw 10\"",
["DWE7491", "DWE7480", "DWE7485"], "chop-saw", "💿", "1800W",
"اره گردبر روی میز ۱۰ اینچ با گاید مدرج و حائل موازی دقیق. مناسب برش طولی و عرضی چوب و ام‌دی‌اف.",
["تنظیم و تراز میز برش", "تعویض بیرینگ محور", "تعمیر گیربکس", "تعمیر حفاظ و کلاچ", "تعمیر کلید اصلی"]),
// ── اره فارسی‌بر ─────────────────────────────────────────────────────
new("dw718", "اره فارسی‌بر کشویی دو مفصلی ۲۵۴mm", "Double Bevel Sliding Compound Miter Saw",
["DW718", "DWS780", "DWS716"], "miter-saw", "📐", "1675W",
"اره فارسی‌بر کشویی دو مفصلی ۱۰ اینچ. زاویه مورب تا ±۴۸° چپ و راست. برش پهنای ۳۰۰mm. مناسب قاب‌سازی و نجاری دقیق.",
["تنظیم زاویه قائمه و مورب", "تعویض بیرینگ محور", "تعمیر مکانیزم کشو", "تعمیر لیزر راهنما", "تعمیر سوئیچ ایمنی"]),
new("dw701", "اره فارسی‌بر ۲۱۶mm", "Single Bevel Compound Miter Saw",
["DW701", "DW703", "DCS777"], "miter-saw", "📐", "1400W",
"اره فارسی‌بر ساده ۸.۵ اینچ سبک‌وزن برای کارگاه و محل کار. قطعه‌بر ۲۰۸×۷۰mm. دستگیره تنظیم سریع زاویه.",
["تنظیم دقت زاویه", "تعویض بیرینگ", "تعمیر مکانیزم زاویه‌دهی", "تعمیر ترمز الکترونیکی"]),
// ── ابزار نجاری ──────────────────────────────────────────────────────
new("dw680k", "رنده برقی ۸۲mm", "Planer 3-1/4\"",
["DW680K", "DW677", "DCP580"], "woodworking", "🪵", "550W",
"رنده برقی ۸۲mm با عمق تنظیم ۰-۳mm. سرعت ۱۶۰۰۰ دور در دقیقه. کیسه جمع‌آوری براده. مناسب رنده کشی درب، پنجره و کف.",
["تیزکاری و تنظیم تیغه رنده", "تعویض بیرینگ محور تیغه", "تعمیر مکانیزم تنظیم عمق", "تعمیر کلید سرعت", "تعمیر گارد تیغه"]),
new("dw621", "فرز چوب / روتر ۱/2 اینچ", "Plunge Router 1/2\"",
["DW621", "DWP611", "DCW600"], "woodworking", "🪵", "900W",
"روتر فرو رونده (پلانج) نیم اینچ با محدوده عمق ۰-۵۷mm. کنترل دقیق سرعت ۸۰۰۰-۲۴۰۰۰ دور. مناسب شیارکاری، گردزنی و قالب چوب.",
["تعویض بیرینگ محور", "تعمیر مکانیزم پلانج", "تعمیر کنترل سرعت", "تعمیر کلاهک گیرنده مته", "تعمیر سوئیچ"]),
new("dw433", "سنباده نواری ۷۵×۵۳۳mm", "Belt Sander 3\"×21\"",
["DW433", "DW431", "DWP849X"], "woodworking", "🪵", "850W",
"سنباده نواری با سرعت نوار ۳۵۰ متر در دقیقه. صفحه صاف کننده برای کار روی گوشه‌ها. کیسه گرد یکپارچه.",
["تعمیر مکانیزم تنظیم نوار", "تعویض بیرینگ غلطک‌ها", "تعمیر سیستم تراز نوار", "تعمیر کلید سرعت"]),
new("dw317k", "جیگ‌ساو برقی ۷۰۱W", "Jigsaw Corded",
["DW317K", "DW300K", "DWE349"], "woodworking", "🪵", "701W",
"جیگ‌ساو برقی با ۳ موقعیت اوربیتال. برش چوب تا ۱۳۵mm، فلز ۱۰mm، آلومینیوم ۲۰mm. تنظیم پایه برای برش مورب.",
["تعمیر مکانیزم اوربیتال", "تعویض بیرینگ", "تعمیر گیرنده تیغه", "تعمیر سوئیچ"]),
];
public static string FormatPrice(decimal amount) =>
amount.ToString("N0").Replace(",", "،") + " تومان";
public static string ToJalali(DateTime dt)
{
var pc = new System.Globalization.PersianCalendar();
return $"{pc.GetYear(dt)}/{pc.GetMonth(dt):D2}/{pc.GetDayOfMonth(dt):D2}";
}
public static string ToJalaliWithTime(DateTime dt)
{
var pc = new System.Globalization.PersianCalendar();
return $"{pc.GetYear(dt)}/{pc.GetMonth(dt):D2}/{pc.GetDayOfMonth(dt):D2} {dt.Hour:D2}:{dt.Minute:D2}";
}
public static string OrderStatusLabel(AsadiTools.Models.OrderStatus s) => s switch
{
AsadiTools.Models.OrderStatus.Pending => "در انتظار تأیید",
AsadiTools.Models.OrderStatus.Confirmed => "تأیید شده",
AsadiTools.Models.OrderStatus.Shipped => "ارسال شده",
AsadiTools.Models.OrderStatus.Delivered => "تحویل داده شده",
AsadiTools.Models.OrderStatus.Cancelled => "لغو شده",
_ => s.ToString()
};
public static string OrderStatusBadge(AsadiTools.Models.OrderStatus s) => s switch
{
AsadiTools.Models.OrderStatus.Pending => "bg-yellow-100 text-yellow-800",
AsadiTools.Models.OrderStatus.Confirmed => "bg-blue-100 text-blue-800",
AsadiTools.Models.OrderStatus.Shipped => "bg-purple-100 text-purple-800",
AsadiTools.Models.OrderStatus.Delivered => "bg-green-100 text-green-800",
AsadiTools.Models.OrderStatus.Cancelled => "bg-red-100 text-red-800",
_ => "bg-gray-100 text-gray-700"
};
public static readonly (
string Phone, string Mobile,
string TelPhone, string TelMobile,
string Address, string WorkingHours,
string Whatsapp, string Instagram,
string Description,
double MapLat, double MapLng
) Company =
(
Phone: "۰۲۶-۳۴۵۶۷۸۹۰",
Mobile: "۰۹۱۲-۳۴۵-۶۷۸۹",
TelPhone: "02634567890",
TelMobile: "09123456789",
Address: "کرج، [آدرس کامل]",
WorkingHours: "شنبه تا پنج‌شنبه ۸ الی ۱۸",
Whatsapp: "989123456789",
Instagram: "asadi.tools",
Description: "با بیش از ۱۵ سال تجربه در تعمیر ابزار صنعتی، آساد ابزار افتخار دارد نمایندگی رسمی برند دیوالت را در کرج داشته باشد.",
MapLat: 35.8404,
MapLng: 50.9391
);
}
+9
View File
@@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"ConnectionStrings": {
"Default": "Data Source=asadi.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
+28
View File
@@ -0,0 +1,28 @@
name: asaditools # Lock project name — prevents runner workspace from overriding it
services:
asadi-tools:
build:
context: .
dockerfile: Dockerfile
container_name: asadi-tools
restart: unless-stopped
ports:
- "8080:8080"
volumes:
# SQLite database persisted on host
- asadi_data:/app/data
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__Default=Data Source=/app/data/asadi.db
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
volumes:
asadi_data:
# Stores both asadi.db (SQLite) and keys/ (DataProtection) under /app/data
driver: local
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nexus"
value="https://mirror.soroushasadi.com/repository/nuget-group/index.json"
protocolVersion="3" />
</packageSources>
<config>
<add key="http_retry_count" value="8" />
<add key="http_retry_delay_milliseconds" value="1000" />
</config>
</configuration>
+31
View File
@@ -0,0 +1,31 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
color: var(--bs-secondary-color);
text-align: end;
}
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
text-align: start;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

+4
View File
@@ -0,0 +1,4 @@
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+597
View File
@@ -0,0 +1,597 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+594
View File
@@ -0,0 +1,594 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

Some files were not shown because too many files have changed in this diff Show More