feat(seo): add eight landing pages for the clusters we had nothing for

Keyword research put the head demand on four phrases — clinic management
software, doctor's office software, online booking, and the price/comparison
questions that precede a purchase — and we only had a page for one of them.
The new pages cover the head terms (clinic management, online booking), the
buying intent (price, how to choose, download), the positioning that separates
a hosted product from the installed ones that dominate these results (web
based, no install), and two long-tails where competition is thin (clinic
accounting, psychology practice).

The download page deliberately answers the intent rather than the wording:
someone searching for an installer wants to try something without paying, and
the page gives them that while explaining why there is nothing to install.

Copy is written per page rather than templated — eight pages saying the same
thing in different words is duplicate content, and every claim on them maps to
something the product actually does. Internal links from the older pages were
repointed at the new head-term pages, and the registry tests no longer hardcode
the page count: they now assert that every declared slug has a page, and that
titles, headings and descriptions stay unique.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-18 18:16:58 +03:30
co-authored by Claude Opus 5
parent 6e3d574e38
commit 8e4bb3ca7b
3 changed files with 218 additions and 14 deletions
+16 -6
View File
@@ -7,7 +7,7 @@ use App\Shared\Landing\LandingRegistry;
use PHPUnit\Framework\TestCase;
/**
* قواعدی که اگر بشکنند، هفت صفحهٔ فرود به‌جای کمک به رتبه، به هم آسیب می‌زنند:
* قواعدی که اگر بشکنند، صفحه‌های فرود به‌جای کمک به رتبه، به هم آسیب می‌زنند:
* عنوان یا h1 تکراری یعنی محتوای تکراری، و توضیحات بیرون از بازهٔ استاندارد در
* نتایج جستجو بریده می‌شود.
*/
@@ -24,9 +24,15 @@ class LandingRegistryTest extends TestCase
{
$slugs = array_keys($this->registry->all());
self::assertCount(7, $slugs);
self::assertContains(LandingRegistry::BEAUTY, $slugs);
self::assertContains(LandingRegistry::CRM, $slugs);
// تعداد صفحه‌ها با هر خوشهٔ کلیدواژهٔ تازه بالا می‌رود، پس عدد ثابت اینجا فقط
// یک تستِ شکننده می‌سازد. آنچه باید ثابت بماند این است که هر ثابتِ کلاس
// واقعاً صفحه‌ای دارد — ثابتی که ساخته نشده باشد، لینک داخلیِ ۴۰۴ می‌سازد.
$constants = (new \ReflectionClass(LandingRegistry::class))->getConstants();
self::assertSame(count($constants), count($slugs));
foreach ($constants as $name => $slug) {
self::assertContains($slug, $slugs, "ثابت {$name} صفحه‌ای ندارد");
}
}
public function testUnknownSlugReturnsNull(): void
@@ -47,8 +53,12 @@ class LandingRegistryTest extends TestCase
$titles = array_map(static fn (LandingPage $p): string => $p->metaTitle, $this->registry->all());
$h1s = array_map(static fn (LandingPage $p): string => $p->h1, $this->registry->all());
self::assertCount(7, array_unique($titles), 'عنوان تکراری یعنی محتوای تکراری');
self::assertCount(7, array_unique($h1s), 'h1 تکراری یعنی محتوای تکراری');
$descriptions = array_map(static fn (LandingPage $p): string => $p->metaDescription, $this->registry->all());
self::assertCount(count($titles), array_unique($titles), 'عنوان تکراری یعنی محتوای تکراری');
self::assertCount(count($h1s), array_unique($h1s), 'h1 تکراری یعنی محتوای تکراری');
// توضیحات تکراری همان پیام را می‌دهد: دو صفحه که یک چیز را می‌گویند.
self::assertCount(count($descriptions), array_unique($descriptions), 'توضیحات تکراری یعنی محتوای تکراری');
}
/** توضیحات کوتاه‌تر از ۱۲۰ فرصت را هدر می‌دهد و بلندتر از ۱۷۰ در نتایج بریده می‌شود. */