Harden UX & accessibility (ui-ux-pro-max pass)
deploy / deploy (push) Successful in 24s

Audited the site with the ui-ux-pro-max skill. It validated the brand blue
(#2563EB == its SaaS primary) but flagged real high-severity gaps:

- Contrast: muted grays were zinc-400 (~2.8:1, fails WCAG AA). Bumped the
  muted token + all text-zinc-400 to zinc-500 (#71717a, ~4.6:1).
- Touch targets: social buttons 38px -> 44x44 (meets 44pt minimum).
- Cursor + disabled: cursor-pointer on buttons; disabled state dims + blocks.
- Form a11y: required-field asterisks (name/service/budget/message),
  autocomplete on name/company, and role=status aria-live=polite on the
  submit status so screen readers announce success/error.

Kept Syne + system fonts and the blue accent (skill suggested Inter + an
AI-purple palette its own anti-patterns reject). Rebuilt Tailwind bundles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-26 05:33:44 +03:30
parent 93f7873dd1
commit 8896740895
6 changed files with 21 additions and 17 deletions
+1 -1
View File
@@ -20,7 +20,7 @@
<a href="/blog/@post.Slug" class="group reveal grid grid-cols-1 gap-2 border-t border-zinc-200 py-6 sm:grid-cols-[8rem_1fr] sm:gap-8"> <a href="/blog/@post.Slug" class="group reveal grid grid-cols-1 gap-2 border-t border-zinc-200 py-6 sm:grid-cols-[8rem_1fr] sm:gap-8">
<div class="flex items-baseline justify-between sm:flex-col sm:gap-1"> <div class="flex items-baseline justify-between sm:flex-col sm:gap-1">
<span class="kicker">@post.Category</span> <span class="kicker">@post.Category</span>
<span class="text-[.78rem] text-zinc-400">@post.ReadTime @(fa ? "دقیقه" : "min")</span> <span class="text-[.78rem] text-zinc-500">@post.ReadTime @(fa ? "دقیقه" : "min")</span>
</div> </div>
<div> <div>
<h2 class="text-[1.1rem] font-semibold transition-colors group-hover:text-accent @(fa ? "font-fa" : "")">@post.Title</h2> <h2 class="text-[1.1rem] font-semibold transition-colors group-hover:text-accent @(fa ? "font-fa" : "")">@post.Title</h2>
+1 -1
View File
@@ -21,7 +21,7 @@
<header class="mb-8"> <header class="mb-8">
<span class="kicker">@Model.Category</span> <span class="kicker">@Model.Category</span>
<h1 class="mt-3 @(fa ? "font-fa" : "")" style="font-size:clamp(1.8rem,4vw,2.5rem)">@Model.Title</h1> <h1 class="mt-3 @(fa ? "font-fa" : "")" style="font-size:clamp(1.8rem,4vw,2.5rem)">@Model.Title</h1>
<p class="mt-3 text-sm text-zinc-400">@Model.ReadTime @(fa ? "دقیقه مطالعه" : "min read")</p> <p class="mt-3 text-sm text-zinc-500">@Model.ReadTime @(fa ? "دقیقه مطالعه" : "min read")</p>
</header> </header>
<article class="prose-custom"> <article class="prose-custom">
+10 -10
View File
@@ -68,7 +68,7 @@
@foreach (var (id, title, desc, tags) in services) @foreach (var (id, title, desc, tags) in services)
{ {
<article class="svc reveal border-t border-zinc-200 pt-6" style="transition-delay:@(si * 60)ms"> <article class="svc reveal border-t border-zinc-200 pt-6" style="transition-delay:@(si * 60)ms">
<span class="svc-icon text-zinc-400" aria-hidden="true">@Html.Raw(ServiceIcon(id))</span> <span class="svc-icon text-zinc-500" aria-hidden="true">@Html.Raw(ServiceIcon(id))</span>
<h3 class="mt-5 text-lg font-semibold @(fa ? "font-fa" : "")">@title</h3> <h3 class="mt-5 text-lg font-semibold @(fa ? "font-fa" : "")">@title</h3>
<p class="mt-2.5 text-[.95rem] leading-relaxed text-zinc-600">@desc</p> <p class="mt-2.5 text-[.95rem] leading-relaxed text-zinc-600">@desc</p>
<div class="mt-4 flex flex-wrap gap-1.5"> <div class="mt-4 flex flex-wrap gap-1.5">
@@ -110,7 +110,7 @@
{ {
stepN++; stepN++;
<li class="reveal border-t border-zinc-200 pt-4" style="transition-delay:@((stepN-1) * 40)ms"> <li class="reveal border-t border-zinc-200 pt-4" style="transition-delay:@((stepN-1) * 40)ms">
<span class="font-display text-sm text-zinc-400">@stepN.ToString("D2")</span> <span class="font-display text-sm text-zinc-500">@stepN.ToString("D2")</span>
<h3 class="mt-2 text-base font-semibold @(fa ? "font-fa" : "")">@nlabel</h3> <h3 class="mt-2 text-base font-semibold @(fa ? "font-fa" : "")">@nlabel</h3>
<p class="mt-1.5 text-[.85rem] leading-relaxed text-zinc-600">@ndesc</p> <p class="mt-1.5 text-[.85rem] leading-relaxed text-zinc-600">@ndesc</p>
</li> </li>
@@ -268,7 +268,7 @@
<a href="/blog/@slug" class="group reveal grid grid-cols-1 gap-2 border-t border-zinc-200 py-6 sm:grid-cols-[8rem_1fr] sm:gap-8"> <a href="/blog/@slug" class="group reveal grid grid-cols-1 gap-2 border-t border-zinc-200 py-6 sm:grid-cols-[8rem_1fr] sm:gap-8">
<div class="flex items-baseline justify-between sm:flex-col sm:gap-1"> <div class="flex items-baseline justify-between sm:flex-col sm:gap-1">
<span class="kicker">@cat</span> <span class="kicker">@cat</span>
<span class="text-[.78rem] text-zinc-400">@readTime @(fa ? "دقیقه" : "min")</span> <span class="text-[.78rem] text-zinc-500">@readTime @(fa ? "دقیقه" : "min")</span>
</div> </div>
<div> <div>
<h3 class="text-[1.1rem] font-semibold transition-colors group-hover:text-accent @(fa ? "font-fa" : "")">@btitle</h3> <h3 class="text-[1.1rem] font-semibold transition-colors group-hover:text-accent @(fa ? "font-fa" : "")">@btitle</h3>
@@ -299,18 +299,18 @@
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div> <div>
<label class="flabel" for="name">@(fa ? "نام" : "Name")</label> <label class="flabel" for="name">@(fa ? "نام" : "Name")<span class="text-red-600" aria-hidden="true"> *</span></label>
<input id="name" name="name" type="text" required placeholder="@(fa ? "نام و نام خانوادگی" : "Full name")" class="field" /> <input id="name" name="name" type="text" required autocomplete="name" placeholder="@(fa ? "نام و نام خانوادگی" : "Full name")" class="field" />
</div> </div>
<div> <div>
<label class="flabel" for="company">@(fa ? "سازمان" : "Company")</label> <label class="flabel" for="company">@(fa ? "سازمان" : "Company")</label>
<input id="company" name="company" type="text" placeholder="@(fa ? "نام سازمان" : "Organization")" class="field" /> <input id="company" name="company" type="text" autocomplete="organization" placeholder="@(fa ? "نام سازمان" : "Organization")" class="field" />
</div> </div>
</div> </div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div> <div>
<label class="flabel" for="service">@(fa ? "خدمت" : "Service")</label> <label class="flabel" for="service">@(fa ? "خدمت" : "Service")<span class="text-red-600" aria-hidden="true"> *</span></label>
<select id="service" name="service" required class="field"> <select id="service" name="service" required class="field">
<option value="" disabled selected>@(fa ? "انتخاب کنید" : "Select…")</option> <option value="" disabled selected>@(fa ? "انتخاب کنید" : "Select…")</option>
@if (fa) @if (fa)
@@ -334,7 +334,7 @@
</select> </select>
</div> </div>
<div> <div>
<label class="flabel" for="budget">@(fa ? "بودجه (تقریبی)" : "Budget (rough)")</label> <label class="flabel" for="budget">@(fa ? "بودجه (تقریبی)" : "Budget (rough)")<span class="text-red-600" aria-hidden="true"> *</span></label>
<select id="budget" name="budget" required class="field"> <select id="budget" name="budget" required class="field">
<option value="" disabled selected>@(fa ? "انتخاب کنید" : "Select…")</option> <option value="" disabled selected>@(fa ? "انتخاب کنید" : "Select…")</option>
<option>Under $10k</option> <option>Under $10k</option>
@@ -346,12 +346,12 @@
</div> </div>
<div> <div>
<label class="flabel" for="message">@(fa ? "پیام" : "Message")</label> <label class="flabel" for="message">@(fa ? "پیام" : "Message")<span class="text-red-600" aria-hidden="true"> *</span></label>
<textarea id="message" name="message" required rows="4" placeholder="@(fa ? "هدف، بازه‌ی زمانی، و چیزی که الان گیرتان انداخته…" : "Goal, timeline, current blockers…")" class="field resize-none"></textarea> <textarea id="message" name="message" required rows="4" placeholder="@(fa ? "هدف، بازه‌ی زمانی، و چیزی که الان گیرتان انداخته…" : "Goal, timeline, current blockers…")" class="field resize-none"></textarea>
</div> </div>
<button type="submit" class="btn w-full">@(fa ? "ارسال پیام" : "Send request")</button> <button type="submit" class="btn w-full">@(fa ? "ارسال پیام" : "Send request")</button>
<p id="contact-status" class="mt-1 text-sm text-zinc-500">@(fa ? "معمولاً ظرف ۲۴ ساعت کاری جواب می‌دهم." : "Typical reply within 24 working hours.")</p> <p id="contact-status" role="status" aria-live="polite" class="mt-1 text-sm text-zinc-500">@(fa ? "معمولاً ظرف ۲۴ ساعت کاری جواب می‌دهم." : "Typical reply within 24 working hours.")</p>
</form> </form>
</div> </div>
</section> </section>
+2 -2
View File
@@ -150,8 +150,8 @@
</div> </div>
<div class="mx-auto mt-12 flex max-w-6xl flex-col items-center gap-2 border-t border-zinc-200 pt-6 text-center sm:flex-row sm:justify-between sm:text-start"> <div class="mx-auto mt-12 flex max-w-6xl flex-col items-center gap-2 border-t border-zinc-200 pt-6 text-center sm:flex-row sm:justify-between sm:text-start">
<p class="text-[.78rem] text-zinc-400">© 2026 Soroush Asadi. @(fa ? "تمام حقوق محفوظ است." : "All rights reserved.")</p> <p class="text-[.78rem] text-zinc-500">© 2026 Soroush Asadi. @(fa ? "تمام حقوق محفوظ است." : "All rights reserved.")</p>
<p class="text-[.78rem] text-zinc-400">@(fa ? "ساخته‌شده با دقت در تهران." : "Built with care in Tehran.")</p> <p class="text-[.78rem] text-zinc-500">@(fa ? "ساخته‌شده با دقت در تهران." : "Built with care in Tehran.")</p>
</div> </div>
</footer> </footer>
+6 -2
View File
@@ -10,7 +10,7 @@
--surface: #ffffff; --surface: #ffffff;
--text: #18181b; /* zinc-900 */ --text: #18181b; /* zinc-900 */
--text-2: #52525b; /* zinc-600 */ --text-2: #52525b; /* zinc-600 */
--text-3: #a1a1aa; /* zinc-400 */ --text-3: #71717a; /* zinc-500 - meets WCAG AA 4.5:1 on the off-white bg */
--line: #e4e4e7; /* zinc-200 */ --line: #e4e4e7; /* zinc-200 */
--line-strong: #d4d4d8; /* zinc-300 */ --line-strong: #d4d4d8; /* zinc-300 */
--accent: #2563eb; /* blue-600 - the single accent */ --accent: #2563eb; /* blue-600 - the single accent */
@@ -108,6 +108,10 @@ body.site {
.btn-ghost:hover { border-color: var(--text); background: #fff; } .btn-ghost:hover { border-color: var(--text); background: #fff; }
.btn-ghost:active { transform: translateY(1px); } .btn-ghost:active { transform: translateY(1px); }
/* pointer affordance + disabled semantics (ui-ux-pro-max: cursor-pointer, disabled-states) */
.site button { cursor: pointer; }
.site button:disabled { cursor: not-allowed; opacity: .55; }
/* ─── Availability status ────────────────────────────────────────────── */ /* ─── Availability status ────────────────────────────────────────────── */
.status { .status {
display: inline-flex; align-items: center; gap: .5rem; display: inline-flex; align-items: center; gap: .5rem;
@@ -121,7 +125,7 @@ body.site {
/* ─── Social ─────────────────────────────────────────────────────────── */ /* ─── Social ─────────────────────────────────────────────────────────── */
.social { .social {
display: inline-flex; align-items: center; justify-content: center; display: inline-flex; align-items: center; justify-content: center;
width: 38px; height: 38px; border: 1px solid var(--line-strong); border-radius: var(--radius); width: 44px; height: 44px; border: 1px solid var(--line-strong); border-radius: var(--radius);
color: var(--text-2); transition: color .18s ease, border-color .18s ease, transform .18s ease; color: var(--text-2); transition: color .18s ease, border-color .18s ease, transform .18s ease;
} }
.social:hover { color: var(--text); border-color: var(--text); transform: translateY(-2px); } .social:hover { color: var(--text); border-color: var(--text); transform: translateY(-2px); }
File diff suppressed because one or more lines are too long