Compare commits
2 Commits
dev
...
feat/401-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b83049462c | ||
| be314c577f |
@ -942,6 +942,8 @@ ## Active Technologies
|
|||||||
- PHP 8.4 (Laravel 12) + Filament v5 + Livewire v4
|
- PHP 8.4 (Laravel 12) + Filament v5 + Livewire v4
|
||||||
- PostgreSQL (Sail)
|
- PostgreSQL (Sail)
|
||||||
- Tailwind CSS v4
|
- Tailwind CSS v4
|
||||||
|
- TypeScript 5.9, Astro 6 static components, HTML, CSS + Astro 6.0.0, Tailwind CSS 4.2.2 through CSS-first `@theme` and `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, Playwright 1.59.1 (400-tenantial-homepage-visual-rebuild)
|
||||||
|
- TypeScript 5.9.3, Astro 6.0.0, Tailwind CSS v4.2.2 via `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, and Playwright smoke tests for the static website; no database, CMS, API, customer data, tenant data, or runtime persistence. (401-tenantial-platform-page)
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
- 066-rbac-ui-enforcement-helper-v2-session-1769732329: Planned UiEnforcement v2 (spec + plan + design artifacts)
|
- 066-rbac-ui-enforcement-helper-v2-session-1769732329: Planned UiEnforcement v2 (spec + plan + design artifacts)
|
||||||
|
|||||||
@ -4,11 +4,14 @@ import tailwindcss from '@tailwindcss/vite';
|
|||||||
import icon from 'astro-icon';
|
import icon from 'astro-icon';
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example';
|
const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantial.example';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [icon()],
|
integrations: [icon()],
|
||||||
output: 'static',
|
output: 'static',
|
||||||
|
redirects: {
|
||||||
|
'/product': '/platform',
|
||||||
|
},
|
||||||
site: publicSiteUrl,
|
site: publicSiteUrl,
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
|
||||||
<rect width="64" height="64" rx="18" fill="#17120F" />
|
<rect width="96" height="96" rx="22" fill="#111516" />
|
||||||
|
<rect x="14" y="14" width="20" height="20" rx="5.5" fill="#FFF7E1" />
|
||||||
<path
|
<path
|
||||||
d="M17 18H47V25H36V46H28V25H17V18Z"
|
d="M40 84H23C18.0294 84 14 79.9706 14 75V56.5C14 25.2959 39.2959 0 70.5 0H84C89.5229 0 94 4.47715 94 10V21C94 26.5228 89.5229 31 84 31H70.5C56.4167 31 45 42.4167 45 56.5V79C45 81.7614 42.7614 84 40 84Z"
|
||||||
fill="#FFF7F1"
|
fill="#FFF7E1"
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M44 17C50.0751 17 55 21.9249 55 28C55 34.0751 50.0751 39 44 39H39V32H44C46.2091 32 48 30.2091 48 28C48 25.7909 46.2091 24 44 24H39V17H44Z"
|
|
||||||
fill="#CC5F2C"
|
|
||||||
/>
|
/>
|
||||||
|
<circle cx="78" cy="72" r="12" fill="#FFF7E1" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 517 B |
@ -1,70 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="500" viewBox="0 0 800 500" fill="none">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="bg" x1="60" y1="40" x2="740" y2="460" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0" stop-color="#F8FAFC" />
|
|
||||||
<stop offset="1" stop-color="#E2E8F0" />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="topbar" x1="0" y1="0" x2="800" y2="0" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0" stop-color="#1D4ED8" />
|
|
||||||
<stop offset="1" stop-color="#4F46E5" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<rect x="8" y="8" width="784" height="484" rx="20" fill="url(#bg)" stroke="#D7E2EE" stroke-width="2" />
|
|
||||||
<rect x="8" y="8" width="784" height="52" rx="20" fill="url(#topbar)" />
|
|
||||||
<rect x="8" y="30" width="784" height="30" fill="url(#topbar)" />
|
|
||||||
<text x="32" y="40" font-family="system-ui" font-size="15" font-weight="700" fill="#FFFFFF">TenantAtlas</text>
|
|
||||||
<circle cx="742" cy="34" r="6" fill="#FFFFFF" fill-opacity="0.7" />
|
|
||||||
<circle cx="762" cy="34" r="6" fill="#FFFFFF" fill-opacity="0.45" />
|
|
||||||
<rect x="24" y="76" width="168" height="396" rx="18" fill="#F3F6FB" stroke="#D7E2EE" />
|
|
||||||
<text x="44" y="108" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">WORKSPACE</text>
|
|
||||||
<rect x="36" y="128" width="144" height="34" rx="10" fill="#DBEAFE" />
|
|
||||||
<text x="52" y="149" font-family="system-ui" font-size="12" font-weight="700" fill="#1D4ED8">Change history</text>
|
|
||||||
<text x="52" y="194" font-family="system-ui" font-size="12" fill="#475569">Restore preview</text>
|
|
||||||
<text x="52" y="230" font-family="system-ui" font-size="12" fill="#475569">Review queue</text>
|
|
||||||
<text x="52" y="266" font-family="system-ui" font-size="12" fill="#475569">Evidence</text>
|
|
||||||
<text x="52" y="302" font-family="system-ui" font-size="12" fill="#475569">Assignments</text>
|
|
||||||
<rect x="36" y="344" width="144" height="96" rx="14" fill="#FFFFFF" stroke="#D7E2EE" />
|
|
||||||
<text x="52" y="370" font-family="system-ui" font-size="11" font-weight="700" fill="#334155">Current tenant</text>
|
|
||||||
<text x="52" y="394" font-family="system-ui" font-size="12" fill="#0F172A">Northwind Services</text>
|
|
||||||
<text x="52" y="418" font-family="system-ui" font-size="11" fill="#64748B">Inventory linked to reviewable history</text>
|
|
||||||
<rect x="212" y="76" width="564" height="396" rx="18" fill="#FFFFFF" stroke="#D7E2EE" />
|
|
||||||
<text x="236" y="108" font-family="system-ui" font-size="12" font-weight="700" fill="#334155">Recent tenant changes</text>
|
|
||||||
<rect x="236" y="124" width="90" height="24" rx="12" fill="#EFF6FF" />
|
|
||||||
<text x="252" y="140" font-family="system-ui" font-size="10" font-weight="700" fill="#2563EB">Policies</text>
|
|
||||||
<rect x="336" y="124" width="82" height="24" rx="12" fill="#F8FAFC" stroke="#D7E2EE" />
|
|
||||||
<text x="356" y="140" font-family="system-ui" font-size="10" font-weight="700" fill="#475569">Drift</text>
|
|
||||||
<rect x="428" y="124" width="108" height="24" rx="12" fill="#F8FAFC" stroke="#D7E2EE" />
|
|
||||||
<text x="448" y="140" font-family="system-ui" font-size="10" font-weight="700" fill="#475569">Assignments</text>
|
|
||||||
<rect x="236" y="164" width="320" height="236" rx="16" fill="#F8FAFC" stroke="#D7E2EE" />
|
|
||||||
<text x="256" y="192" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">CHANGE RECORD</text>
|
|
||||||
<line x1="256" y1="210" x2="536" y2="210" stroke="#E2E8F0" />
|
|
||||||
<text x="256" y="236" font-family="system-ui" font-size="13" font-weight="700" fill="#0F172A">Windows Compliance Baseline</text>
|
|
||||||
<text x="256" y="256" font-family="system-ui" font-size="11" fill="#475569">Version diff prepared for review</text>
|
|
||||||
<rect x="454" y="222" width="78" height="22" rx="11" fill="#DCFCE7" />
|
|
||||||
<text x="469" y="237" font-family="system-ui" font-size="10" font-weight="700" fill="#15803D">Ready</text>
|
|
||||||
<line x1="256" y1="274" x2="536" y2="274" stroke="#E2E8F0" />
|
|
||||||
<text x="256" y="300" font-family="system-ui" font-size="13" font-weight="700" fill="#0F172A">Conditional Access MFA</text>
|
|
||||||
<text x="256" y="320" font-family="system-ui" font-size="11" fill="#475569">Assignment drift surfaced before rollout</text>
|
|
||||||
<rect x="438" y="286" width="94" height="22" rx="11" fill="#FEF3C7" />
|
|
||||||
<text x="452" y="301" font-family="system-ui" font-size="10" font-weight="700" fill="#B45309">Needs review</text>
|
|
||||||
<line x1="256" y1="338" x2="536" y2="338" stroke="#E2E8F0" />
|
|
||||||
<text x="256" y="364" font-family="system-ui" font-size="13" font-weight="700" fill="#0F172A">BitLocker policy</text>
|
|
||||||
<text x="256" y="384" font-family="system-ui" font-size="11" fill="#475569">Restore candidate linked to prior snapshot</text>
|
|
||||||
<rect x="420" y="350" width="112" height="22" rx="11" fill="#DBEAFE" />
|
|
||||||
<text x="436" y="365" font-family="system-ui" font-size="10" font-weight="700" fill="#1D4ED8">Snapshot linked</text>
|
|
||||||
<rect x="576" y="164" width="176" height="112" rx="16" fill="#F8FAFC" stroke="#D7E2EE" />
|
|
||||||
<text x="596" y="192" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">RESTORE PREVIEW</text>
|
|
||||||
<text x="596" y="220" font-family="system-ui" font-size="12" fill="#0F172A">Scope validated</text>
|
|
||||||
<text x="596" y="242" font-family="system-ui" font-size="12" fill="#0F172A">Assignments included</text>
|
|
||||||
<text x="596" y="264" font-family="system-ui" font-size="12" fill="#0F172A">Confirmation required</text>
|
|
||||||
<circle cx="580" cy="217" r="4" fill="#16A34A" />
|
|
||||||
<circle cx="580" cy="239" r="4" fill="#16A34A" />
|
|
||||||
<circle cx="580" cy="261" r="4" fill="#F59E0B" />
|
|
||||||
<rect x="576" y="292" width="176" height="108" rx="16" fill="#F8FAFC" stroke="#D7E2EE" />
|
|
||||||
<text x="596" y="320" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">REVIEW QUEUE</text>
|
|
||||||
<text x="596" y="346" font-family="system-ui" font-size="12" fill="#0F172A">Conditional Access drift</text>
|
|
||||||
<text x="596" y="368" font-family="system-ui" font-size="12" fill="#0F172A">Restore plan awaiting approval</text>
|
|
||||||
<text x="596" y="390" font-family="system-ui" font-size="12" fill="#0F172A">Evidence attached to change record</text>
|
|
||||||
<rect x="236" y="420" width="516" height="28" rx="14" fill="#EEF2FF" />
|
|
||||||
<text x="256" y="438" font-family="system-ui" font-size="11" font-weight="700" fill="#4338CA">Change history, restore preview, and review queue stay connected on one screen.</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 6.1 KiB |
BIN
apps/website/public/images/tenantial-logo-transparent-clean.png
Normal file
BIN
apps/website/public/images/tenantial-logo-transparent-clean.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
46
apps/website/public/images/tenantial-wave-mesh.svg
Normal file
46
apps/website/public/images/tenantial-wave-mesh.svg
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 420" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="rowFade" x1="0" x2="1600" y1="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FFF7E1" stop-opacity="0" />
|
||||||
|
<stop offset="0.18" stop-color="#FFF7E1" stop-opacity="0.18" />
|
||||||
|
<stop offset="0.5" stop-color="#FFF7E1" stop-opacity="0.46" />
|
||||||
|
<stop offset="0.82" stop-color="#FFF7E1" stop-opacity="0.2" />
|
||||||
|
<stop offset="1" stop-color="#FFF7E1" stop-opacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="mintFade" x1="0" x2="1600" y1="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6FE5BF" stop-opacity="0" />
|
||||||
|
<stop offset="0.42" stop-color="#6FE5BF" stop-opacity="0.2" />
|
||||||
|
<stop offset="0.7" stop-color="#6FE5BF" stop-opacity="0.08" />
|
||||||
|
<stop offset="1" stop-color="#6FE5BF" stop-opacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
<mask id="surfaceFade">
|
||||||
|
<rect width="1600" height="420" fill="url(#maskGradient)" />
|
||||||
|
</mask>
|
||||||
|
<linearGradient id="maskGradient" x1="800" x2="800" y1="0" y2="420" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white" stop-opacity="0" />
|
||||||
|
<stop offset="0.2" stop-color="white" stop-opacity="0.45" />
|
||||||
|
<stop offset="0.62" stop-color="white" stop-opacity="1" />
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g mask="url(#surfaceFade)" stroke-linecap="round" stroke-width="3.8" stroke-dasharray="0.1 23">
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 52 C160 10 300 86 520 42 C770 -8 910 106 1160 58 C1360 20 1490 42 1680 8" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 77 C170 31 308 113 532 68 C774 19 930 132 1170 82 C1378 39 1518 71 1680 33" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 103 C174 58 330 136 548 93 C786 46 946 159 1190 107 C1406 60 1516 100 1680 61" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 130 C180 85 344 158 572 120 C804 81 954 182 1204 132 C1414 90 1520 126 1680 92" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 158 C194 116 362 180 590 150 C826 119 972 207 1224 162 C1426 126 1538 154 1680 126" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 188 C214 149 382 206 620 182 C850 158 1000 234 1240 196 C1444 164 1550 188 1680 164" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 220 C230 184 410 236 648 218 C884 198 1028 266 1270 232 C1468 204 1560 224 1680 204" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 254 C252 222 436 270 682 256 C918 242 1054 298 1306 270 C1494 250 1574 266 1680 248" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 290 C278 264 472 306 724 296 C960 286 1094 332 1344 310 C1512 296 1586 306 1680 294" />
|
||||||
|
<path stroke="url(#rowFade)" d="M-80 328 C304 306 514 346 772 336 C1006 328 1136 366 1390 350 C1536 340 1600 350 1680 340" />
|
||||||
|
<path stroke="url(#mintFade)" d="M-80 198 C250 160 450 214 710 190 C960 168 1110 240 1370 204 C1510 184 1594 194 1680 184" />
|
||||||
|
<path stroke="url(#mintFade)" d="M-80 250 C270 222 500 260 768 242 C1030 224 1180 284 1426 262 C1548 252 1610 260 1680 252" />
|
||||||
|
</g>
|
||||||
|
<g mask="url(#surfaceFade)" stroke="url(#rowFade)" stroke-linecap="round" stroke-width="1.2" opacity="0.3">
|
||||||
|
<path d="M-80 130 C180 85 344 158 572 120 C804 81 954 182 1204 132 C1414 90 1520 126 1680 92" />
|
||||||
|
<path d="M-80 188 C214 149 382 206 620 182 C850 158 1000 234 1240 196 C1444 164 1550 188 1680 164" />
|
||||||
|
<path d="M-80 254 C252 222 436 270 682 256 C918 242 1054 298 1306 270 C1494 250 1574 266 1680 248" />
|
||||||
|
<path d="M-80 328 C304 306 514 346 772 336 C1006 328 1136 366 1390 350 C1536 340 1600 350 1680 340" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.7 KiB |
1096
apps/website/src/components/content/DashboardPreview.astro
Normal file
1096
apps/website/src/components/content/DashboardPreview.astro
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,6 @@ const sizeClasses = {
|
|||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<Tag class:list={['m-0 text-[var(--color-ink-900)] [&>.accent]:text-[var(--color-primary)]', sizeClasses[size], className]}>
|
<Tag class:list={['m-0 text-[var(--color-foreground)] [&>.accent]:text-[var(--color-primary)]', sizeClasses[size], className]}>
|
||||||
<slot />
|
<slot />
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|||||||
@ -1,467 +0,0 @@
|
|||||||
---
|
|
||||||
---
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="hero-dashboard"
|
|
||||||
role="img"
|
|
||||||
aria-label="TenantAtlas — change history, restore preview, and a review queue with baseline drift and evidence links"
|
|
||||||
>
|
|
||||||
<div class="dashboard-chrome">
|
|
||||||
<div class="dashboard-titlebar">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="flex gap-1.5">
|
|
||||||
<span class="dot dot-red"></span>
|
|
||||||
<span class="dot dot-yellow"></span>
|
|
||||||
<span class="dot dot-green"></span>
|
|
||||||
</div>
|
|
||||||
<span class="titlebar-label">TenantAtlas — Governance Surface</span>
|
|
||||||
</div>
|
|
||||||
<div class="titlebar-url">
|
|
||||||
<span class="url-text">app.tenantatlas.com/admin/governance/change-history</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboard-body">
|
|
||||||
<div class="dashboard-sidebar">
|
|
||||||
<div class="sidebar-logo">TA</div>
|
|
||||||
<div class="sidebar-nav">
|
|
||||||
<div class="nav-item active">
|
|
||||||
<div class="nav-dot"></div>
|
|
||||||
<span>Change history</span>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item">
|
|
||||||
<div class="nav-dot"></div>
|
|
||||||
<span>Restore preview</span>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item">
|
|
||||||
<div class="nav-dot"></div>
|
|
||||||
<span>Review queue</span>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item">
|
|
||||||
<div class="nav-dot"></div>
|
|
||||||
<span>Evidence</span>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item">
|
|
||||||
<div class="nav-dot"></div>
|
|
||||||
<span>Assignments</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-divider"></div>
|
|
||||||
<div class="sidebar-nav">
|
|
||||||
<div class="nav-item">
|
|
||||||
<div class="nav-dot muted"></div>
|
|
||||||
<span>Settings</span>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item">
|
|
||||||
<div class="nav-dot muted"></div>
|
|
||||||
<span>Audit Log</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboard-main">
|
|
||||||
<div class="stats-row">
|
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Baseline drift</span>
|
|
||||||
<span class="stat-value">2 items need review</span>
|
|
||||||
<span class="stat-change neutral">Windows baseline and Conditional Access</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Restore preview</span>
|
|
||||||
<span class="stat-value">Scope and assignments validated</span>
|
|
||||||
<span class="stat-change positive">Confirmation required before execution</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Evidence linked</span>
|
|
||||||
<span class="stat-value">3 change records attached</span>
|
|
||||||
<span class="stat-change neutral">Review context stays with the operator path</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activity-grid">
|
|
||||||
<div class="activity-section">
|
|
||||||
<div class="activity-header">
|
|
||||||
<span class="activity-title">Change record</span>
|
|
||||||
<span class="activity-badge">Review active</span>
|
|
||||||
</div>
|
|
||||||
<div class="activity-table">
|
|
||||||
<div class="table-row">
|
|
||||||
<span class="row-status warning"></span>
|
|
||||||
<span class="row-name">Windows Compliance Baseline</span>
|
|
||||||
<span class="row-type">Baseline drift</span>
|
|
||||||
<span class="row-time">Needs review</span>
|
|
||||||
</div>
|
|
||||||
<div class="table-row">
|
|
||||||
<span class="row-status success"></span>
|
|
||||||
<span class="row-name">BitLocker policy restore</span>
|
|
||||||
<span class="row-type">Restore preview</span>
|
|
||||||
<span class="row-time">Assignments in scope</span>
|
|
||||||
</div>
|
|
||||||
<div class="table-row">
|
|
||||||
<span class="row-status success"></span>
|
|
||||||
<span class="row-name">Conditional Access MFA</span>
|
|
||||||
<span class="row-type">Evidence linked</span>
|
|
||||||
<span class="row-time">Ready for approval</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="queue-column">
|
|
||||||
<div class="queue-card">
|
|
||||||
<span class="queue-label">Restore preview</span>
|
|
||||||
<p class="queue-title">Scope and assignment edges stay visible before execution.</p>
|
|
||||||
<ul class="queue-list">
|
|
||||||
<li>Scope validated</li>
|
|
||||||
<li>Assignments included</li>
|
|
||||||
<li>Confirmation required</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="queue-card">
|
|
||||||
<span class="queue-label">Review queue</span>
|
|
||||||
<ul class="queue-list">
|
|
||||||
<li>Baseline drift awaiting reviewer</li>
|
|
||||||
<li>Restore plan queued for approval</li>
|
|
||||||
<li>Evidence pack linked to the change record</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="evidence-strip">Evidence stays linked to the change record before an operator acts.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.hero-dashboard {
|
|
||||||
border-radius: 1rem;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid rgba(20, 20, 20, 0.08);
|
|
||||||
box-shadow:
|
|
||||||
0 24px 80px rgba(20, 20, 20, 0.1),
|
|
||||||
0 8px 24px rgba(0, 0, 0, 0.05);
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-chrome {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-titlebar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.65rem 1rem;
|
|
||||||
background: #fafafa;
|
|
||||||
border-bottom: 1px solid rgba(20, 20, 20, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot {
|
|
||||||
display: block;
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot-red { background: #ff5f57; }
|
|
||||||
.dot-yellow { background: #febc2e; }
|
|
||||||
.dot-green { background: #28c840; }
|
|
||||||
|
|
||||||
.titlebar-label {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: rgba(20, 20, 20, 0.45);
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar-url {
|
|
||||||
font-size: 0.65rem;
|
|
||||||
color: rgba(20, 20, 20, 0.35);
|
|
||||||
background: rgba(20, 20, 20, 0.04);
|
|
||||||
padding: 0.2rem 0.6rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.url-text {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-body {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 140px 1fr;
|
|
||||||
min-height: 260px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-sidebar {
|
|
||||||
padding: 0.75rem;
|
|
||||||
background: #f8f9fb;
|
|
||||||
border-right: 1px solid rgba(20, 20, 20, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logo {
|
|
||||||
width: 1.75rem;
|
|
||||||
height: 1.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background: var(--color-ink-900);
|
|
||||||
color: white;
|
|
||||||
font-size: 0.6rem;
|
|
||||||
font-weight: 700;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-nav {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-divider {
|
|
||||||
height: 1px;
|
|
||||||
background: rgba(20, 20, 20, 0.06);
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.4rem;
|
|
||||||
padding: 0.3rem 0.4rem;
|
|
||||||
font-size: 0.65rem;
|
|
||||||
color: rgba(20, 20, 20, 0.5);
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item.active {
|
|
||||||
background: rgba(40, 60, 120, 0.06);
|
|
||||||
color: var(--color-ink-900);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-dot {
|
|
||||||
width: 0.35rem;
|
|
||||||
height: 0.35rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--color-brand-500);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-dot.muted {
|
|
||||||
background: rgba(20, 20, 20, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item.active .nav-dot {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-main {
|
|
||||||
padding: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.65rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.15rem;
|
|
||||||
padding: 0.5rem 0.6rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: 1px solid rgba(20, 20, 20, 0.05);
|
|
||||||
background: #fbfbfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
font-size: 0.55rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
color: rgba(20, 20, 20, 0.45);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 0.94rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-ink-900);
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
line-height: 1.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-change {
|
|
||||||
font-size: 0.55rem;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.35;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-change.positive {
|
|
||||||
color: var(--color-mint-700);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-change.neutral {
|
|
||||||
color: rgba(20, 20, 20, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-section {
|
|
||||||
border: 1px solid rgba(20, 20, 20, 0.05);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1.28fr) minmax(14rem, 0.92fr);
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.45rem 0.6rem;
|
|
||||||
background: #fafafa;
|
|
||||||
border-bottom: 1px solid rgba(20, 20, 20, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-title {
|
|
||||||
font-size: 0.65rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-ink-900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-badge {
|
|
||||||
font-size: 0.55rem;
|
|
||||||
font-weight: 600;
|
|
||||||
background: rgba(40, 60, 120, 0.07);
|
|
||||||
color: var(--color-brand-500);
|
|
||||||
padding: 0.1rem 0.4rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-table {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-column {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-card {
|
|
||||||
border: 1px solid rgba(20, 20, 20, 0.05);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background: #fbfbfd;
|
|
||||||
padding: 0.65rem 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-label {
|
|
||||||
display: inline-flex;
|
|
||||||
font-size: 0.55rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: rgba(20, 20, 20, 0.42);
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-title {
|
|
||||||
margin: 0.45rem 0 0;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.45;
|
|
||||||
color: var(--color-ink-900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-list {
|
|
||||||
margin: 0.55rem 0 0;
|
|
||||||
padding-left: 1rem;
|
|
||||||
display: grid;
|
|
||||||
gap: 0.35rem;
|
|
||||||
font-size: 0.64rem;
|
|
||||||
line-height: 1.45;
|
|
||||||
color: rgba(20, 20, 20, 0.56);
|
|
||||||
}
|
|
||||||
|
|
||||||
.evidence-strip {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background: rgba(40, 60, 120, 0.06);
|
|
||||||
color: var(--color-ink-800);
|
|
||||||
font-size: 0.64rem;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.4;
|
|
||||||
padding: 0.5rem 0.65rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 0.5rem 1fr auto auto;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
font-size: 0.6rem;
|
|
||||||
border-bottom: 1px solid rgba(20, 20, 20, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-status {
|
|
||||||
width: 0.45rem;
|
|
||||||
height: 0.45rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-status.success {
|
|
||||||
background: var(--color-mint-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-status.warning {
|
|
||||||
background: #febc2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-name {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-ink-900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-type {
|
|
||||||
color: rgba(20, 20, 20, 0.4);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-time {
|
|
||||||
color: rgba(20, 20, 20, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.dashboard-body {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-sidebar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar-url {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
35
apps/website/src/components/content/TenantialLogo.astro
Normal file
35
apps/website/src/components/content/TenantialLogo.astro
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
imageClass?: string;
|
||||||
|
invert?: boolean;
|
||||||
|
label?: string;
|
||||||
|
markClass?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
class: className = '',
|
||||||
|
imageClass,
|
||||||
|
invert = true,
|
||||||
|
label = 'Tenantial',
|
||||||
|
markClass = 'h-9 w-9',
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const resolvedImageClass = imageClass ?? markClass;
|
||||||
|
---
|
||||||
|
|
||||||
|
<span class:list={['inline-flex items-center', className]} data-brand-lockup>
|
||||||
|
<img
|
||||||
|
src="/images/tenantial-logo-transparent-clean.png"
|
||||||
|
alt={label}
|
||||||
|
width="3720"
|
||||||
|
height="920"
|
||||||
|
decoding="async"
|
||||||
|
class:list={[
|
||||||
|
'block shrink-0 object-contain',
|
||||||
|
resolvedImageClass,
|
||||||
|
invert ? 'brightness-0 invert' : '',
|
||||||
|
]}
|
||||||
|
data-brand-mark
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
||||||
|
import TenantialLogo from '@/components/content/TenantialLogo.astro';
|
||||||
import Container from '@/components/primitives/Container.astro';
|
import Container from '@/components/primitives/Container.astro';
|
||||||
import { getFooterLead, getFooterNavigationGroups, siteMetadata } from '@/lib/site';
|
import { getFooterLead, getFooterNavigationGroups, siteMetadata } from '@/lib/site';
|
||||||
|
|
||||||
@ -11,50 +12,83 @@ const { currentPath: _currentPath } = Astro.props;
|
|||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
const footerLead = getFooterLead(_currentPath);
|
const footerLead = getFooterLead(_currentPath);
|
||||||
const footerNavigationGroups = await getFooterNavigationGroups();
|
const footerNavigationGroups = await getFooterNavigationGroups();
|
||||||
|
const isQuietFooter = _currentPath === '/platform';
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer class="section-divider px-[var(--space-page-x)] pt-10 sm:pt-12" data-footer-intent={footerLead.intent}>
|
<footer class="section-divider px-[var(--space-page-x)] pt-10 sm:pt-12" data-footer-intent={isQuietFooter ? 'quiet' : footerLead.intent}>
|
||||||
<Container width="wide">
|
<Container width="wide">
|
||||||
<div class="surface-card-muted grid gap-8 rounded-[var(--radius-panel)] p-6 lg:grid-cols-[1.3fr,1fr] lg:p-8">
|
{isQuietFooter ? (
|
||||||
<div class="space-y-5">
|
<div class="grid gap-8 py-3 lg:grid-cols-[0.85fr_1.15fr] lg:items-start">
|
||||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
<div class="space-y-4">
|
||||||
{footerLead.eyebrow}
|
<TenantialLogo imageClass="h-[2.25rem] w-auto" />
|
||||||
</p>
|
<p class="m-0 max-w-sm text-[0.95rem] leading-7 text-[var(--color-copy)]">
|
||||||
<h2 class="m-0 max-w-xl font-[var(--font-display)] text-3xl leading-[0.98] text-[var(--color-ink-900)] sm:text-4xl">
|
Evidence-first governance for Microsoft tenants.
|
||||||
{footerLead.title}
|
</p>
|
||||||
</h2>
|
</div>
|
||||||
<p class="m-0 max-w-xl text-base leading-7 text-[var(--color-copy)]">
|
|
||||||
{footerLead.description}
|
|
||||||
</p>
|
|
||||||
<PrimaryCTA cta={footerLead.primaryCta} size="sm" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-6 sm:grid-cols-2 xl:grid-cols-4">
|
<div class="grid gap-6 sm:grid-cols-2 xl:grid-cols-5">
|
||||||
{
|
{
|
||||||
footerNavigationGroups.map((group) => (
|
footerNavigationGroups.map((group) => (
|
||||||
<div>
|
<div>
|
||||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-ink-900)]">
|
<p class="m-0 text-[0.88rem] font-semibold uppercase text-[var(--color-foreground)]">
|
||||||
{group.title}
|
{group.title}
|
||||||
</p>
|
</p>
|
||||||
<ul class="mt-4 space-y-3 p-0 text-sm text-[var(--color-copy)]">
|
<ul class="mt-4 space-y-3 p-0 text-[0.92rem] text-[var(--color-copy)]">
|
||||||
{group.items.map((item) => (
|
{group.items.map((item) => (
|
||||||
<li class="list-none">
|
<li class="list-none">
|
||||||
<a class="transition hover:text-[var(--color-brand)]" href={item.href}>
|
<a class="transition hover:text-[var(--color-brand)]" href={item.href}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<div class="surface-card-muted grid gap-8 rounded-[var(--radius-panel)] p-6 lg:grid-cols-[1.3fr,1fr] lg:p-8">
|
||||||
|
<div class="space-y-5">
|
||||||
|
<p class="m-0 text-sm font-semibold uppercase text-[var(--color-brand)]">
|
||||||
|
{footerLead.eyebrow}
|
||||||
|
</p>
|
||||||
|
<h2 class="m-0 max-w-xl font-[var(--font-display)] text-3xl leading-tight text-[var(--color-foreground)] sm:text-4xl">
|
||||||
|
{footerLead.title}
|
||||||
|
</h2>
|
||||||
|
<p class="m-0 max-w-xl text-base leading-7 text-[var(--color-copy)]">
|
||||||
|
{footerLead.description}
|
||||||
|
</p>
|
||||||
|
<PrimaryCTA cta={footerLead.primaryCta} size="sm" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 py-6 text-sm text-[var(--color-copy)] sm:flex-row sm:items-center sm:justify-between">
|
<div class="grid gap-6 sm:grid-cols-2 xl:grid-cols-4">
|
||||||
<p class="m-0">© {currentYear} {siteMetadata.siteName}. Core public route foundation.</p>
|
{
|
||||||
|
footerNavigationGroups.map((group) => (
|
||||||
|
<div>
|
||||||
|
<p class="m-0 text-sm font-semibold uppercase text-[var(--color-foreground)]">
|
||||||
|
{group.title}
|
||||||
|
</p>
|
||||||
|
<ul class="mt-4 space-y-3 p-0 text-sm text-[var(--color-copy)]">
|
||||||
|
{group.items.map((item) => (
|
||||||
|
<li class="list-none">
|
||||||
|
<a class="transition hover:text-[var(--color-brand)]" href={item.href}>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3 py-6 text-[0.9rem] text-[var(--color-copy)] sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<p class="m-0">© {currentYear} {siteMetadata.siteName}. Evidence-first governance for Microsoft tenants.</p>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
Built as a static Astro track with no platform auth, session, or API coupling.
|
Static public website with sample preview values only.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
||||||
|
import TenantialLogo from '@/components/content/TenantialLogo.astro';
|
||||||
import Container from '@/components/primitives/Container.astro';
|
import Container from '@/components/primitives/Container.astro';
|
||||||
import { getHeaderCta, getPrimaryNavigation, isActiveNavigationPath, siteMetadata } from '@/lib/site';
|
import { getHeaderCta, getPrimaryNavigation, isActiveNavigationPath, siteMetadata } from '@/lib/site';
|
||||||
|
|
||||||
@ -12,39 +13,31 @@ const headerCta = getHeaderCta(currentPath);
|
|||||||
const primaryNavigation = await getPrimaryNavigation();
|
const primaryNavigation = await getPrimaryNavigation();
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class="sticky top-0 z-30 px-[var(--space-page-x)] pt-4 sm:pt-6">
|
<header class="relative z-30 px-[var(--space-page-x)] pt-5 sm:pt-7">
|
||||||
<Container width="wide">
|
<Container width="wide" class="!max-w-[96rem]">
|
||||||
<div
|
<div
|
||||||
class="shell-panel flex items-center justify-between gap-4 rounded-[var(--radius-panel)] px-4 py-3 sm:px-5"
|
class="flex items-center justify-between gap-4 py-1"
|
||||||
data-shell-surface="header"
|
data-shell-surface="header"
|
||||||
|
data-visual-tone="dark"
|
||||||
>
|
>
|
||||||
<a href="/" class="flex min-w-0 items-center gap-3 no-underline">
|
<a href="/" class="group flex min-w-0 no-underline" aria-label="Tenantial home">
|
||||||
<span
|
<TenantialLogo
|
||||||
class="inline-flex h-11 w-11 items-center justify-center rounded-full bg-[linear-gradient(135deg,var(--color-primary),#8eaed1)] font-[var(--font-display)] text-lg text-white shadow-[var(--shadow-inline)]"
|
label={siteMetadata.siteName}
|
||||||
>
|
imageClass="h-[2.75rem] w-auto sm:h-12"
|
||||||
TA
|
/>
|
||||||
</span>
|
|
||||||
<span class="min-w-0">
|
|
||||||
<span class="block truncate text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-ink-900)]">
|
|
||||||
{siteMetadata.siteName}
|
|
||||||
</span>
|
|
||||||
<span class="block truncate text-sm text-[var(--color-copy)]">
|
|
||||||
{siteMetadata.siteTagline}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<nav class="hidden items-center gap-1 lg:flex" aria-label="Primary">
|
<nav class="hidden items-center gap-9 lg:flex" aria-label="Primary">
|
||||||
{
|
{
|
||||||
primaryNavigation.map((item) => (
|
primaryNavigation.map((item) => (
|
||||||
<a
|
<a
|
||||||
href={item.href}
|
href={item.href}
|
||||||
aria-current={isActiveNavigationPath(currentPath, item.href) ? 'page' : undefined}
|
aria-current={isActiveNavigationPath(currentPath, item.href) ? 'page' : undefined}
|
||||||
class:list={[
|
class:list={[
|
||||||
'rounded-full px-4 py-2 text-sm font-medium transition',
|
'text-sm font-medium transition',
|
||||||
isActiveNavigationPath(currentPath, item.href)
|
isActiveNavigationPath(currentPath, item.href)
|
||||||
? 'bg-[var(--color-accent)] text-[var(--color-accent-foreground)]'
|
? 'text-[var(--color-foreground)]'
|
||||||
: 'text-[var(--color-ink-800)] hover:bg-white/70',
|
: 'text-[var(--color-stone-200)] hover:text-[var(--color-primary)]',
|
||||||
]}
|
]}
|
||||||
data-nav-link="primary"
|
data-nav-link="primary"
|
||||||
>
|
>
|
||||||
@ -54,14 +47,23 @@ const primaryNavigation = await getPrimaryNavigation();
|
|||||||
}
|
}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="hidden lg:block">
|
<div class="hidden items-center gap-5 lg:flex">
|
||||||
<SecondaryCTA cta={headerCta} size="sm" />
|
<span
|
||||||
|
class="text-sm font-medium text-[var(--color-stone-200)]"
|
||||||
|
data-nav-state="deferred"
|
||||||
|
title="Sign in is not available from the public website yet."
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</span>
|
||||||
|
<div data-header-cta>
|
||||||
|
<PrimaryCTA cta={headerCta} size="sm" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="relative lg:hidden" data-mobile-nav>
|
<details class="relative lg:hidden" data-mobile-nav>
|
||||||
<summary
|
<summary
|
||||||
aria-label="Open navigation menu"
|
aria-label="Open navigation menu"
|
||||||
class="flex h-11 w-11 cursor-pointer list-none items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 text-[var(--color-ink-900)]"
|
class="flex h-11 w-11 cursor-pointer list-none items-center justify-center rounded-full border border-[color:var(--color-line)] bg-[rgba(255,247,225,0.06)] text-[var(--color-foreground)]"
|
||||||
data-mobile-nav-trigger
|
data-mobile-nav-trigger
|
||||||
>
|
>
|
||||||
<span class="sr-only">Open navigation menu</span>
|
<span class="sr-only">Open navigation menu</span>
|
||||||
@ -84,7 +86,7 @@ const primaryNavigation = await getPrimaryNavigation();
|
|||||||
'rounded-[1rem] px-4 py-3 text-sm',
|
'rounded-[1rem] px-4 py-3 text-sm',
|
||||||
isActiveNavigationPath(currentPath, item.href)
|
isActiveNavigationPath(currentPath, item.href)
|
||||||
? 'bg-[var(--color-accent)] text-[var(--color-accent-foreground)]'
|
? 'bg-[var(--color-accent)] text-[var(--color-accent-foreground)]'
|
||||||
: 'text-[var(--color-ink-800)] hover:bg-white/75',
|
: 'text-[var(--color-muted-foreground)] hover:bg-[var(--surface-muted)] hover:text-[var(--color-foreground)]',
|
||||||
]}
|
]}
|
||||||
data-nav-link="mobile-primary"
|
data-nav-link="mobile-primary"
|
||||||
>
|
>
|
||||||
@ -97,8 +99,14 @@ const primaryNavigation = await getPrimaryNavigation();
|
|||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
<div class="mt-2 rounded-[1rem] bg-[rgba(47,111,183,0.08)] p-3">
|
<span
|
||||||
<SecondaryCTA cta={headerCta} size="sm" showHelper />
|
class="rounded-[1rem] px-4 py-3 text-sm font-medium text-[var(--color-muted-foreground)] opacity-75"
|
||||||
|
data-nav-state="deferred"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</span>
|
||||||
|
<div class="mt-2 rounded-[1rem] bg-[var(--surface-accent)] p-3" data-header-cta>
|
||||||
|
<PrimaryCTA cta={headerCta} size="sm" showHelper />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -37,10 +37,15 @@ const pageDefinition = getPageDefinition(currentPath);
|
|||||||
data-surface-group={pageDefinition.surfaceGroup}
|
data-surface-group={pageDefinition.surfaceGroup}
|
||||||
data-journey-stage={pageDefinition.journeyStage}
|
data-journey-stage={pageDefinition.journeyStage}
|
||||||
>
|
>
|
||||||
<div
|
<div class="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[36rem] bg-[linear-gradient(180deg,rgba(255,247,225,0.018),transparent_74%)]"></div>
|
||||||
class="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[34rem] bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.68),transparent_28%),radial-gradient(circle_at_top_right,rgba(47,111,183,0.14),transparent_26%)]"
|
{
|
||||||
>
|
pageDefinition.pageRole === 'home' && (
|
||||||
</div>
|
<>
|
||||||
|
<div class="home-ambient-wash" aria-hidden="true"></div>
|
||||||
|
<div class="home-dot-field" aria-hidden="true"></div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Navbar currentPath={currentPath} />
|
<Navbar currentPath={currentPath} />
|
||||||
<main id="content" class="foundation-main pb-20 sm:pb-24">
|
<main id="content" class="foundation-main pb-20 sm:pb-24">
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@ -34,10 +34,10 @@ const sizeClasses = {
|
|||||||
|
|
||||||
const variantClasses = {
|
const variantClasses = {
|
||||||
primary:
|
primary:
|
||||||
'border-transparent bg-[var(--color-ink-900)] text-white shadow-[0_2px_8px_rgba(0,0,0,0.12)] hover:bg-[var(--color-ink-800)] hover:shadow-[0_4px_16px_rgba(0,0,0,0.18)] active:scale-[0.98]',
|
'border-transparent bg-[var(--color-primary)] text-[var(--color-primary-foreground)] shadow-[0_2px_14px_rgba(111,229,191,0.2)] hover:bg-[var(--color-mint-300)] hover:shadow-[0_4px_20px_rgba(111,229,191,0.26)] active:scale-[0.98]',
|
||||||
secondary:
|
secondary:
|
||||||
'border-[color:var(--color-border)] bg-white text-[var(--color-secondary-foreground)] hover:border-[var(--color-border-strong)] hover:bg-[var(--surface-muted)] active:scale-[0.98]',
|
'border-[color:var(--color-border)] bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)] hover:border-[var(--color-border-strong)] hover:bg-[var(--surface-muted-strong)] active:scale-[0.98]',
|
||||||
ghost: 'border-transparent bg-transparent text-[var(--color-ink-800)] hover:bg-white/70',
|
ghost: 'border-transparent bg-transparent text-[var(--color-muted-foreground)] hover:bg-[var(--surface-muted)] hover:text-[var(--color-foreground)]',
|
||||||
};
|
};
|
||||||
|
|
||||||
const classes = [baseClass, sizeClasses[size], variantClasses[variant], className];
|
const classes = [baseClass, sizeClasses[size], variantClasses[variant], className];
|
||||||
|
|||||||
70
apps/website/src/components/sections/FeaturePillars.astro
Normal file
70
apps/website/src/components/sections/FeaturePillars.astro
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
import Container from '@/components/primitives/Container.astro';
|
||||||
|
import Section from '@/components/primitives/Section.astro';
|
||||||
|
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||||
|
import type { FeatureItemContent } from '@/types/site';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
items: FeatureItemContent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { items } = Astro.props;
|
||||||
|
|
||||||
|
const lucideMap: Record<string, string> = {
|
||||||
|
archive: 'lucide:archive',
|
||||||
|
refresh: 'lucide:refresh-cw',
|
||||||
|
'git-branch': 'lucide:git-branch',
|
||||||
|
'file-check': 'lucide:file-check',
|
||||||
|
clipboard: 'lucide:clipboard-list',
|
||||||
|
shield: 'lucide:shield-check',
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<Section data-section="feature-pillars">
|
||||||
|
<Container width="wide">
|
||||||
|
<div class="grid gap-8 lg:grid-cols-[0.78fr_1.22fr] lg:items-start">
|
||||||
|
<SectionHeader
|
||||||
|
eyebrow="Tenantial capabilities"
|
||||||
|
title="The operating loop buyers need to verify."
|
||||||
|
description="Each pillar stays tied to evidence, safety, and review context instead of unsupported proof claims."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="grid gap-4 sm:grid-cols-2">
|
||||||
|
{
|
||||||
|
items.map((item) => {
|
||||||
|
const iconName = item.icon ? lucideMap[item.icon] : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
class="rounded-[var(--radius-lg)] border border-[color:var(--color-border)] bg-[var(--color-card)] p-5 shadow-[var(--shadow-soft)]"
|
||||||
|
data-surface="tenantial-pillar"
|
||||||
|
>
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
{iconName && (
|
||||||
|
<div class="feature-icon" aria-hidden="true">
|
||||||
|
<Icon name={iconName} size={20} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h2 class="m-0 text-lg font-semibold text-[var(--color-foreground)]">
|
||||||
|
{item.title}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-[var(--color-copy)]">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
{item.meta && (
|
||||||
|
<p class="mt-3 text-sm font-semibold text-[var(--color-primary)]">
|
||||||
|
{item.meta}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</Section>
|
||||||
@ -3,12 +3,13 @@ import Badge from '@/components/primitives/Badge.astro';
|
|||||||
import Card from '@/components/primitives/Card.astro';
|
import Card from '@/components/primitives/Card.astro';
|
||||||
import Cluster from '@/components/primitives/Cluster.astro';
|
import Cluster from '@/components/primitives/Cluster.astro';
|
||||||
import Container from '@/components/primitives/Container.astro';
|
import Container from '@/components/primitives/Container.astro';
|
||||||
|
import DashboardPreview from '@/components/content/DashboardPreview.astro';
|
||||||
import Headline from '@/components/content/Headline.astro';
|
import Headline from '@/components/content/Headline.astro';
|
||||||
import HeroDashboard from '@/components/content/HeroDashboard.astro';
|
|
||||||
import Lead from '@/components/content/Lead.astro';
|
import Lead from '@/components/content/Lead.astro';
|
||||||
import Metric from '@/components/content/Metric.astro';
|
import Metric from '@/components/content/Metric.astro';
|
||||||
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
||||||
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
import type { HeroContent, MetricItem } from '@/types/site';
|
import type { HeroContent, MetricItem } from '@/types/site';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -23,11 +24,13 @@ const isHomepageHero = Astro.url.pathname === '/';
|
|||||||
const heroHeadlineSize = isHomepageHero ? 'page' : 'display';
|
const heroHeadlineSize = isHomepageHero ? 'page' : 'display';
|
||||||
const heroLeadSize = isHomepageHero ? 'body' : 'lead';
|
const heroLeadSize = isHomepageHero ? 'body' : 'lead';
|
||||||
const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
||||||
|
const trustSignalIcons = ['lucide:shield-check', 'lucide:lock', 'lucide:check-circle'];
|
||||||
|
const audienceLabels = ['MSP operators', 'Endpoint teams', 'Security reviewers', 'Audit owners', 'Cloud operations'];
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
class:list={[
|
class:list={[
|
||||||
isHomepageHero ? 'hero-gradient pt-2 sm:pt-8 lg:pt-14' : 'pt-8 sm:pt-10 lg:pt-14',
|
isHomepageHero ? 'hero-gradient pt-10 sm:pt-14 lg:pt-12' : 'pt-8 sm:pt-10 lg:pt-14',
|
||||||
]}
|
]}
|
||||||
data-hero-root
|
data-hero-root
|
||||||
data-hero-surface={isHomepageHero ? 'homepage' : 'page'}
|
data-hero-surface={isHomepageHero ? 'homepage' : 'page'}
|
||||||
@ -35,12 +38,12 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
|||||||
data-hero-primary-anchor={isHomepageHero && heroPrimaryAnchor === 'composition' ? 'composition' : undefined}
|
data-hero-primary-anchor={isHomepageHero && heroPrimaryAnchor === 'composition' ? 'composition' : undefined}
|
||||||
data-section={isHomepageHero ? 'hero' : undefined}
|
data-section={isHomepageHero ? 'hero' : undefined}
|
||||||
>
|
>
|
||||||
<Container width="wide">
|
<Container width="wide" class={isHomepageHero ? '!max-w-[96rem]' : ''}>
|
||||||
{isHomepageHero ? (
|
{isHomepageHero ? (
|
||||||
<div class="space-y-6 sm:space-y-8 lg:space-y-10" data-disclosure-layer="1" data-hero-layout>
|
<div class="space-y-8 sm:space-y-10" data-disclosure-layer="1" data-hero-layout>
|
||||||
<div class="grid gap-6 lg:grid-cols-[minmax(0,0.82fr)_minmax(22rem,1.18fr)] lg:items-start lg:gap-10">
|
<div class="grid gap-8 xl:grid-cols-[minmax(24rem,0.68fr)_minmax(42rem,1.32fr)] xl:items-start xl:gap-12">
|
||||||
<div class="motion-rise flex flex-col gap-5 sm:gap-6 lg:max-w-[40rem]" data-hero-panel="text">
|
<div class="motion-rise flex flex-col gap-7 pt-1 sm:gap-8 xl:max-w-[39rem] xl:pt-14" data-hero-panel="text">
|
||||||
<div class="space-y-5" data-hero-anchor-group>
|
<div class="space-y-6" data-hero-anchor-group>
|
||||||
<div data-hero-text-core>
|
<div data-hero-text-core>
|
||||||
<div data-hero-eyebrow data-hero-segment="eyebrow">
|
<div data-hero-eyebrow data-hero-segment="eyebrow">
|
||||||
<Badge>{hero.eyebrow}</Badge>
|
<Badge>{hero.eyebrow}</Badge>
|
||||||
@ -54,7 +57,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
|||||||
<Headline
|
<Headline
|
||||||
as="h1"
|
as="h1"
|
||||||
size="page"
|
size="page"
|
||||||
class="max-w-[13ch] text-balance text-[length:clamp(2.7rem,4.6vw,4.8rem)] leading-[0.94] tracking-[-0.045em]"
|
class="max-w-[15ch] text-balance text-[3.1rem] font-medium leading-[1.04] tracking-normal sm:text-[3.8rem] lg:text-[4.15rem]"
|
||||||
>
|
>
|
||||||
{hero.titleHtml ? <Fragment set:html={hero.titleHtml} /> : hero.title}
|
{hero.titleHtml ? <Fragment set:html={hero.titleHtml} /> : hero.title}
|
||||||
</Headline>
|
</Headline>
|
||||||
@ -64,7 +67,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
|||||||
data-hero-supporting-copy
|
data-hero-supporting-copy
|
||||||
data-hero-segment="supporting-copy"
|
data-hero-segment="supporting-copy"
|
||||||
>
|
>
|
||||||
<Lead class="max-w-[36rem] text-[1.02rem] leading-8 text-[var(--color-copy)] sm:text-[1.08rem]">
|
<Lead class="max-w-[35rem] text-[1.04rem] leading-8 text-[var(--color-copy)] sm:text-[1.12rem]">
|
||||||
{hero.description}
|
{hero.description}
|
||||||
</Lead>
|
</Lead>
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +75,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
|||||||
</div>
|
</div>
|
||||||
{(hero.primaryCta || hero.secondaryCta) && (
|
{(hero.primaryCta || hero.secondaryCta) && (
|
||||||
<div data-hero-cta-pair data-hero-segment="cta-pair">
|
<div data-hero-cta-pair data-hero-segment="cta-pair">
|
||||||
<Cluster data-cta-cluster gap="sm" class="items-center sm:gap-[var(--space-cluster)]">
|
<Cluster data-cta-cluster gap="sm" class="items-center sm:gap-[var(--space-cluster-lg)]">
|
||||||
<PrimaryCTA cta={hero.primaryCta} size="lg" />
|
<PrimaryCTA cta={hero.primaryCta} size="lg" />
|
||||||
{hero.secondaryCta && (
|
{hero.secondaryCta && (
|
||||||
<SecondaryCTA cta={hero.secondaryCta} />
|
<SecondaryCTA cta={hero.secondaryCta} />
|
||||||
@ -81,9 +84,37 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{hero.trustSubclaims && hero.trustSubclaims.length > 0 && (
|
||||||
|
<div class="space-y-6" data-hero-segment="trust-subclaims" data-hero-trust-signals>
|
||||||
|
<ul class="grid gap-4 p-0 sm:grid-cols-3 lg:grid-cols-3">
|
||||||
|
{hero.trustSubclaims.map((claim, index) => (
|
||||||
|
<li class="list-none border-r border-[color:var(--color-border-strong)] pr-4 text-sm leading-6 text-[var(--color-copy)] last:border-r-0">
|
||||||
|
<Icon
|
||||||
|
name={trustSignalIcons[index] ?? 'lucide:check-circle'}
|
||||||
|
class="mb-3 h-5 w-5 text-[var(--color-stone-400)]"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{claim}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div class="space-y-3" data-hero-audience-strip>
|
||||||
|
<p class="m-0 text-[0.68rem] font-semibold uppercase tracking-[var(--tracking-eyebrow)] text-[var(--color-muted-foreground)]">
|
||||||
|
Built for operator-led teams
|
||||||
|
</p>
|
||||||
|
<ul class="flex flex-wrap items-center gap-x-7 gap-y-2 p-0">
|
||||||
|
{audienceLabels.map((label) => (
|
||||||
|
<li class="list-none text-sm font-semibold text-[var(--color-muted-foreground)] opacity-70">
|
||||||
|
{label}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="motion-rise lg:pt-1"
|
class="motion-rise min-w-0 xl:-mr-4"
|
||||||
style="animation-delay: 120ms;"
|
style="animation-delay: 120ms;"
|
||||||
data-hero-panel="dashboard"
|
data-hero-panel="dashboard"
|
||||||
data-hero-primary-anchor={heroPrimaryAnchor === 'product-visual' ? 'product-visual' : undefined}
|
data-hero-primary-anchor={heroPrimaryAnchor === 'product-visual' ? 'product-visual' : undefined}
|
||||||
@ -91,39 +122,9 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
|||||||
data-hero-visual-style="governance-surface"
|
data-hero-visual-style="governance-surface"
|
||||||
data-hero-segment="product-near-visual"
|
data-hero-segment="product-near-visual"
|
||||||
>
|
>
|
||||||
<div class="overflow-hidden rounded-[2rem] border border-[color:var(--color-border)] bg-[linear-gradient(180deg,rgba(255,255,255,0.9),rgba(246,248,252,0.94))] p-3 shadow-[var(--shadow-panel-strong)] sm:p-4">
|
<DashboardPreview />
|
||||||
{hero.visualFocus && (
|
|
||||||
<div class="mb-3 rounded-[1.45rem] border border-[color:var(--color-border-subtle)] bg-white/88 px-4 py-4 sm:px-5">
|
|
||||||
<p class="m-0 text-[0.72rem] font-semibold uppercase tracking-[var(--tracking-eyebrow)] text-[var(--color-brand-500)]">
|
|
||||||
{hero.visualFocus.eyebrow}
|
|
||||||
</p>
|
|
||||||
<p class="mt-2 max-w-[42rem] text-sm font-semibold leading-6 text-[var(--color-ink-900)] sm:text-[0.98rem]">
|
|
||||||
{hero.visualFocus.title}
|
|
||||||
</p>
|
|
||||||
<ul class="mt-3 grid gap-2 p-0 sm:grid-cols-3">
|
|
||||||
{hero.visualFocus.points.map((point) => (
|
|
||||||
<li class="list-none rounded-[1rem] border border-[color:var(--color-border-subtle)] bg-[var(--surface-muted)] px-3 py-2 text-sm font-medium leading-5 text-[var(--color-ink-800)]">
|
|
||||||
{point}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<HeroDashboard />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{hero.trustSubclaims && hero.trustSubclaims.length > 0 && (
|
|
||||||
<div class="motion-rise space-y-2" data-hero-segment="trust-subclaims" data-hero-trust-signals>
|
|
||||||
<ul class="flex flex-wrap gap-3 p-0">
|
|
||||||
{hero.trustSubclaims.map((claim) => (
|
|
||||||
<li class="list-none rounded-full border border-[color:var(--color-border)] bg-white/80 px-4 py-1.5 text-sm font-medium text-[var(--color-ink-800)]">
|
|
||||||
{claim}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<!-- Subpage hero: card-based 2-col layout -->
|
<!-- Subpage hero: card-based 2-col layout -->
|
||||||
|
|||||||
30
apps/website/src/components/sections/TrustBar.astro
Normal file
30
apps/website/src/components/sections/TrustBar.astro
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
import Container from '@/components/primitives/Container.astro';
|
||||||
|
import Section from '@/components/primitives/Section.astro';
|
||||||
|
import type { TrustPrincipleContent } from '@/types/site';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
statements: TrustPrincipleContent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { statements } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Section density="compact" data-section="trustbar">
|
||||||
|
<Container width="wide">
|
||||||
|
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{
|
||||||
|
statements.map((statement) => (
|
||||||
|
<article class="rounded-[var(--radius-lg)] border border-[color:var(--color-border)] bg-[var(--surface-muted)] p-4">
|
||||||
|
<h2 class="m-0 text-base font-semibold text-[var(--color-foreground)]">
|
||||||
|
{statement.title}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-[var(--color-copy)]">
|
||||||
|
{statement.description}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</Section>
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import type { HeroContent, PageSeo } from '@/types/site';
|
import type { HeroContent, PageSeo } from '@/types/site';
|
||||||
|
|
||||||
export const changelogSeo: PageSeo = {
|
export const changelogSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Changelog',
|
title: 'Tenantial | Changelog',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas uses a dedicated changelog to show dated public progress without pretending a broader editorial or resources program is already live.',
|
'Tenantial uses a dedicated changelog to show dated public progress without pretending a broader editorial or resources program is already live.',
|
||||||
path: '/changelog',
|
path: '/changelog',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||||
|
|
||||||
export const contactSeo: PageSeo = {
|
export const contactSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Contact',
|
title: 'Tenantial | Contact',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas uses one clear contact path for serious product, trust, and rollout conversations instead of splitting first contact across vague demo flows.',
|
'Tenantial uses one clear contact path for serious product, trust, and rollout conversations instead of splitting first contact across vague demo flows.',
|
||||||
path: '/contact',
|
path: '/contact',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13,8 +13,8 @@ export const contactHero: HeroContent = {
|
|||||||
description:
|
description:
|
||||||
'The contact path should help serious buyers explain who they are, what governance questions they are trying to solve, and what kind of follow-up would actually be useful.',
|
'The contact path should help serious buyers explain who they are, what governance questions they are trying to solve, and what kind of follow-up would actually be useful.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
href: 'mailto:hello@tenantatlas.example?subject=TenantAtlas%20working%20session',
|
href: 'mailto:hello@tenantial.example?subject=Tenantial%20working%20session',
|
||||||
label: 'Email the TenantAtlas team',
|
label: 'Email the Tenantial team',
|
||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
secondaryCta: {
|
secondaryCta: {
|
||||||
@ -50,7 +50,7 @@ export const contactPrompts = [
|
|||||||
|
|
||||||
export const contactPreview = {
|
export const contactPreview = {
|
||||||
message:
|
message:
|
||||||
'We operate Microsoft tenant governance across multiple environments and want to understand how TenantAtlas approaches version history, safer restore flows, drift visibility, and review evidence.',
|
'We operate Microsoft tenant governance across multiple environments and want to understand how Tenantial approaches version history, safer restore flows, drift visibility, and review evidence.',
|
||||||
topic: 'Environment and operating model summary',
|
topic: 'Environment and operating model summary',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,171 +1,112 @@
|
|||||||
import type {
|
import type {
|
||||||
CapabilityClusterContent,
|
|
||||||
CtaLink,
|
CtaLink,
|
||||||
|
FeatureItemContent,
|
||||||
HeroContent,
|
HeroContent,
|
||||||
IntegrationEntry,
|
|
||||||
OutcomeSectionContent,
|
|
||||||
PageSeo,
|
PageSeo,
|
||||||
ProgressTeaserContent,
|
TrustPrincipleContent,
|
||||||
TrustSignalGroupContent,
|
|
||||||
} from '@/types/site';
|
} from '@/types/site';
|
||||||
|
|
||||||
export const homeSeo: PageSeo = {
|
export const homeSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Governance of record for Microsoft tenant operations',
|
title: 'Tenantial - Evidence-first governance for Microsoft tenants',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas helps teams understand Microsoft tenant change history, restore posture, trust boundaries, and the next evaluation step without a bloated public sitemap.',
|
'Tenantial helps Microsoft tenant teams govern backup, restore, drift detection, snapshot-backed audit context, verifiable evidence, and structured governance reviews from one evidence-first surface.',
|
||||||
|
ogTitle: 'Evidence-first governance for Microsoft tenants.',
|
||||||
|
ogDescription:
|
||||||
|
'Evidence-oriented restore discipline, drift detection, and governance review workflows for Microsoft tenants without unsupported proof claims.',
|
||||||
path: '/',
|
path: '/',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const homeHero: HeroContent = {
|
export const homeHero: HeroContent = {
|
||||||
eyebrow: 'Microsoft tenant governance',
|
eyebrow: 'Governance that earns trust',
|
||||||
title: 'TenantAtlas gives Microsoft tenant teams one operating record for change history, drift review, and restore planning.',
|
title: 'Evidence-first governance for Microsoft tenants.',
|
||||||
description:
|
description:
|
||||||
'Security, endpoint, and platform teams use TenantAtlas to see what changed, preview restores, and move reviews forward without stitching governance across exports and memory.',
|
'Backup and restore with confidence. Detect drift before it becomes audit work. Preserve snapshot history and review context for governance conversations that need evidence.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Request a working session',
|
label: 'Book a demo',
|
||||||
},
|
},
|
||||||
secondaryCta: {
|
secondaryCta: {
|
||||||
href: '/product',
|
href: '/platform',
|
||||||
label: 'See the product model',
|
label: 'Explore the platform',
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
},
|
},
|
||||||
productVisual: {
|
|
||||||
src: '/images/hero-product-visual.svg',
|
|
||||||
alt: 'TenantAtlas screen showing change history, restore preview, and a review queue for Microsoft tenant policies',
|
|
||||||
},
|
|
||||||
trustSubclaims: [
|
trustSubclaims: [
|
||||||
'Tenant-scoped boundaries',
|
'Built for Microsoft 365 and Azure AD',
|
||||||
'Reviewable change history',
|
'Snapshot-first history with review context.',
|
||||||
'Preview before restore',
|
'Designed for audit-ready workflows.',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const homeOutcome: OutcomeSectionContent = {
|
export const homeTrustBar: TrustPrincipleContent[] = [
|
||||||
title: 'Calmer operations start with visible governance.',
|
|
||||||
description:
|
|
||||||
'TenantAtlas replaces fragmented spreadsheets and manual audit trails with one connected record of tenant change, restore safety, and review context.',
|
|
||||||
audienceBias: 'MSP and enterprise operations teams',
|
|
||||||
outcomes: [
|
|
||||||
{
|
|
||||||
title: 'Understand what changed and why it matters.',
|
|
||||||
description:
|
|
||||||
'Immutable version history gives every tenant configuration a traceable timeline, so drift and unexpected changes surface before they become incidents.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Restore with confidence, not guesswork.',
|
|
||||||
description:
|
|
||||||
'Preview-first restore flows validate scope and impact before execution, turning risky rollbacks into reviewable operations.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Reduce the operational risk of governance gaps.',
|
|
||||||
description:
|
|
||||||
'Connected findings, evidence, and review workflows keep audit context in one place instead of scattered across tools and memory.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const homeCapabilities: CapabilityClusterContent[] = [
|
|
||||||
{
|
{
|
||||||
title: 'Backup & Version History',
|
title: 'Microsoft tenant focused',
|
||||||
description: 'Immutable snapshots of tenant configuration state with full version lineage.',
|
description: 'Built around the governance surfaces Microsoft tenant teams already need to inspect.',
|
||||||
capabilities: ['Automated backup', 'Immutable snapshots', 'Version comparison', 'Change timeline'],
|
|
||||||
href: '/product',
|
|
||||||
meta: 'Core safety net',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Restore & Recovery',
|
title: 'Evidence-oriented workflows',
|
||||||
description: 'Preview-first restore with selective scope and explicit confirmation.',
|
description: 'Backup, drift, restore, and review context stay connected to the decision trail.',
|
||||||
capabilities: ['Dry-run preview', 'Selective restore', 'Rollback safety', 'Conflict detection'],
|
|
||||||
href: '/product',
|
|
||||||
meta: 'Safer change operations',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Inventory & Drift Visibility',
|
title: 'Designed for audit review',
|
||||||
description: 'Connected view of what exists, what drifted, and what needs attention.',
|
description: 'Claims stay grounded in reviewable evidence rather than unverified certification language.',
|
||||||
capabilities: ['Policy inventory', 'Drift detection', 'Assignment visibility', 'Cross-tenant comparison'],
|
|
||||||
href: '/product',
|
|
||||||
meta: 'Operational clarity',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Governance & Evidence',
|
title: 'Operator-led governance',
|
||||||
description: 'Audit-ready evidence, findings, and review workflows for tenant operations.',
|
description: 'High-risk actions stay framed by preview, confirmation, and explicit accountability.',
|
||||||
capabilities: ['Audit trails', 'Evidence linkage', 'Exception tracking', 'Review workflows'],
|
|
||||||
href: '/product',
|
|
||||||
meta: 'Accountability and review',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const homeTrustSignals: TrustSignalGroupContent = {
|
export const homeFeaturePillars: FeatureItemContent[] = [
|
||||||
title: 'Trust is a first-read concern, not a footnote.',
|
{
|
||||||
description:
|
title: 'Backup',
|
||||||
'Tenant isolation, access boundaries, and operating discipline are product rules, not marketing language. Every public claim routes back to one bounded trust surface.',
|
description: 'Preserve full Microsoft tenant configuration snapshots so recovery starts from known evidence.',
|
||||||
supportRoute: '/trust',
|
icon: 'archive',
|
||||||
signals: [
|
meta: 'Snapshot record',
|
||||||
{
|
|
||||||
title: 'Tenant isolation is a product boundary.',
|
|
||||||
description:
|
|
||||||
'Tenant-scoped data, access, and workflow boundaries are enforced as deliberate product rules.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Access stays bounded and purpose-specific.',
|
|
||||||
description:
|
|
||||||
'Microsoft tenant-facing access follows least-privilege scoping tied to the governance operations that require it.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Restore operations require preview and confirmation.',
|
|
||||||
description:
|
|
||||||
'High-risk changes go through validation, selective scope, and explicit confirmation instead of one-click execution.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const homeProgressTeaser: ProgressTeaserContent = {
|
|
||||||
title: 'Product movement you can verify.',
|
|
||||||
description:
|
|
||||||
'Real progress shows up as dated changelog entries, not vague promises. Check the changelog for the latest updates.',
|
|
||||||
entries: [],
|
|
||||||
cta: {
|
|
||||||
href: '/changelog',
|
|
||||||
label: 'Read the full changelog',
|
|
||||||
},
|
},
|
||||||
};
|
{
|
||||||
|
title: 'Restore',
|
||||||
|
description: 'Preview scope, assignments, and conflicts before an operator commits to a restore path.',
|
||||||
|
icon: 'refresh',
|
||||||
|
meta: 'Dry-run first',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Drift Detection',
|
||||||
|
description: 'Surface meaningful configuration change so review work starts before drift becomes incident work.',
|
||||||
|
icon: 'git-branch',
|
||||||
|
meta: 'Review required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Evidence',
|
||||||
|
description: 'Keep screenshots, snapshots, findings, and review notes attached to the governance decision.',
|
||||||
|
icon: 'file-check',
|
||||||
|
meta: 'Decision context',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Audit Trail',
|
||||||
|
description: 'Show who changed, reviewed, backed up, or restored tenant configuration with clear timestamps.',
|
||||||
|
icon: 'clipboard',
|
||||||
|
meta: 'Traceable activity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Governance Reviews',
|
||||||
|
description: 'Move structured tenant reviews from memory and exports into a repeatable operating rhythm.',
|
||||||
|
icon: 'shield',
|
||||||
|
meta: 'Review cadence',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const homeCtaSection = {
|
export const homeCtaSection = {
|
||||||
eyebrow: 'Next step',
|
eyebrow: 'Next step',
|
||||||
title: 'Move from first read into a working session.',
|
title: 'Build tenant governance on evidence, not assumptions.',
|
||||||
description:
|
description:
|
||||||
'Once you understand the product model, the trust posture, and the progress record, the next step is a conversation about your governance reality.',
|
'Use a focused walkthrough to map Tenantial against your Microsoft tenant backup, restore, drift, evidence, and review workflows.',
|
||||||
primary: {
|
primary: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Request a working session',
|
label: 'Book a demo',
|
||||||
} as CtaLink,
|
} as CtaLink,
|
||||||
secondary: {
|
secondary: {
|
||||||
href: '/product',
|
href: '/platform',
|
||||||
label: 'See the product model',
|
label: 'Explore the platform',
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
} as CtaLink,
|
} as CtaLink,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const homeEcosystem: IntegrationEntry[] = [
|
|
||||||
{
|
|
||||||
category: 'Microsoft',
|
|
||||||
name: 'Microsoft Graph',
|
|
||||||
summary: 'Graph-backed inventory and restore without implying the public website depends on live tenant access.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: 'Identity',
|
|
||||||
name: 'Entra ID',
|
|
||||||
summary: 'Identity context where change control, tenant access, and review posture intersect.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: 'Endpoint',
|
|
||||||
name: 'Intune',
|
|
||||||
summary: 'Configuration state, backup, restore posture, and drift visibility stay central to the product story.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: 'Governance',
|
|
||||||
name: 'Review workflows',
|
|
||||||
summary: 'Exceptions, evidence, and reviews stay connected to operational reality.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||||
|
|
||||||
export const imprintSeo: PageSeo = {
|
export const imprintSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Imprint',
|
title: 'Tenantial | Imprint',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas uses the Imprint route as the canonical public legal notice and publisher baseline for the website.',
|
'Tenantial uses the Imprint route as the canonical public legal notice and publisher baseline for the website.',
|
||||||
path: '/imprint',
|
path: '/imprint',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const imprintHero: HeroContent = {
|
export const imprintHero: HeroContent = {
|
||||||
eyebrow: 'Canonical legal notice',
|
eyebrow: 'Canonical legal notice',
|
||||||
title: 'Imprint and public legal notice baseline for the TenantAtlas website.',
|
title: 'Imprint and public legal notice baseline for the Tenantial website.',
|
||||||
description:
|
description:
|
||||||
'This route is the canonical public notice surface for publisher identity and jurisdiction-specific disclosure details. During controlled evaluation, it also makes clear which launch-ready fields still need to be finalized before broader publication.',
|
'This route is the canonical public notice surface for publisher identity and jurisdiction-specific disclosure details. During controlled evaluation, it also makes clear which launch-ready fields still need to be finalized before broader publication.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
@ -32,7 +32,7 @@ export const imprintSections: LegalSection[] = [
|
|||||||
{
|
{
|
||||||
title: 'Current publication status',
|
title: 'Current publication status',
|
||||||
body: [
|
body: [
|
||||||
'TenantAtlas is still tightening its launch-ready publisher and jurisdiction details for the broader public website.',
|
'Tenantial is still tightening its launch-ready publisher and jurisdiction details for the broader public website.',
|
||||||
'Until those fields are finalized, this route makes the intended legal-notice location explicit so the public IA stays honest about where the canonical notice belongs.',
|
'Until those fields are finalized, this route makes the intended legal-notice location explicit so the public IA stays honest about where the canonical notice belongs.',
|
||||||
],
|
],
|
||||||
bullets: [
|
bullets: [
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import type {
|
|||||||
} from '@/types/site';
|
} from '@/types/site';
|
||||||
|
|
||||||
export const integrationsSeo: PageSeo = {
|
export const integrationsSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Integrations',
|
title: 'Tenantial | Integrations',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas keeps ecosystem-fit detail available as a supporting page without pretending integrations belong in the primary navigation.',
|
'Tenantial keeps ecosystem-fit detail available as a supporting page without pretending integrations belong in the primary navigation.',
|
||||||
path: '/integrations',
|
path: '/integrations',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export const integrationsHero: HeroContent = {
|
|||||||
eyebrow: 'Retained supporting page',
|
eyebrow: 'Retained supporting page',
|
||||||
title: 'Keep ecosystem fit detail visible without pretending it is the first thing every buyer needs.',
|
title: 'Keep ecosystem fit detail visible without pretending it is the first thing every buyer needs.',
|
||||||
description:
|
description:
|
||||||
'This page shows the real systems TenantAtlas is built around, the workflows it expects to support, and the deliberate boundaries where speculative integration language would create more noise than trust.',
|
'This page shows the real systems Tenantial is built around, the workflows it expects to support, and the deliberate boundaries where speculative integration language would create more noise than trust.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Plan the working session',
|
label: 'Plan the working session',
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||||
|
|
||||||
export const legalSeo: PageSeo = {
|
export const legalSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Legal',
|
title: 'Tenantial | Legal',
|
||||||
description:
|
description:
|
||||||
'The TenantAtlas legal baseline keeps privacy, terms, imprint, and trust routing accessible without promoting the legal hub into the primary navigation.',
|
'The Tenantial legal baseline keeps privacy, terms, imprint, and trust routing accessible without promoting the legal hub into the primary navigation.',
|
||||||
path: '/legal',
|
path: '/legal',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
304
apps/website/src/content/pages/platform.ts
Normal file
304
apps/website/src/content/pages/platform.ts
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
import type { CtaLink, FeatureItemContent, HeroContent, PageSeo } from '@/types/site';
|
||||||
|
|
||||||
|
export interface GovernanceLoopItem {
|
||||||
|
description: string;
|
||||||
|
label: string;
|
||||||
|
tone?: 'default' | 'focus' | 'source';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OperatingModelStep {
|
||||||
|
badge: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformBoundaryContent {
|
||||||
|
description: string;
|
||||||
|
headline: string;
|
||||||
|
items: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformCapabilityPrimary {
|
||||||
|
ctaLabel: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
proofPoints: string[];
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformHeroTrustSignal {
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformWorkflowContent {
|
||||||
|
audience: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TruthLayerContent {
|
||||||
|
description: string;
|
||||||
|
question: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const platformSeo: PageSeo = {
|
||||||
|
title: 'Tenantial Platform | Evidence-first governance for Microsoft tenants',
|
||||||
|
description:
|
||||||
|
'Tenantial Platform connects backup, restore, drift detection, findings, evidence, audit trails, exceptions, and reviews for Microsoft tenant governance.',
|
||||||
|
path: '/platform',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const platformHero: HeroContent = {
|
||||||
|
eyebrow: 'TENANTIAL PLATFORM',
|
||||||
|
title: 'Govern Microsoft tenants through evidence, drift context, and reviewable decisions.',
|
||||||
|
description:
|
||||||
|
'Tenantial continuously maps Microsoft tenant configuration into reviewable records, so backup records, configuration drift, findings, evidence, audit trails, and structured reviews stay connected.',
|
||||||
|
primaryCta: {
|
||||||
|
href: '/contact',
|
||||||
|
label: 'Book a demo',
|
||||||
|
},
|
||||||
|
secondaryCta: {
|
||||||
|
href: '#governance-loop',
|
||||||
|
label: 'See the governance loop',
|
||||||
|
variant: 'secondary',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const platformHeroTrustSignals: PlatformHeroTrustSignal[] = [
|
||||||
|
{
|
||||||
|
icon: 'lucide:cloud-cog',
|
||||||
|
label: 'Microsoft tenant focused',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'lucide:file-check-2',
|
||||||
|
label: 'Evidence-oriented workflows',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'lucide:refresh-cw',
|
||||||
|
label: 'Reviewable decisions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'lucide:users-round',
|
||||||
|
label: 'Operator-led governance',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const operatingModelSteps: OperatingModelStep[] = [
|
||||||
|
{
|
||||||
|
badge: 'Observed',
|
||||||
|
icon: 'lucide:camera',
|
||||||
|
label: 'Snapshot',
|
||||||
|
description: 'Capture the current state of policies, identities, roles, and configuration.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badge: 'Detected',
|
||||||
|
icon: 'lucide:activity',
|
||||||
|
label: 'Drift',
|
||||||
|
description: 'Detect meaningful movement against known baselines and recent evidence.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badge: 'Qualified',
|
||||||
|
icon: 'lucide:triangle-alert',
|
||||||
|
label: 'Finding',
|
||||||
|
description: 'Qualify drift with risk context, affected scope, and remediation options.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badge: 'Decided',
|
||||||
|
icon: 'lucide:users-round',
|
||||||
|
label: 'Review',
|
||||||
|
description: 'Let the responsible operator inspect context before the next action is chosen.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badge: 'Documented',
|
||||||
|
icon: 'lucide:file-check-2',
|
||||||
|
label: 'Evidence',
|
||||||
|
description: 'Attach artifacts, screenshots, notes, and logs to the review record.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badge: 'Recorded',
|
||||||
|
icon: 'lucide:shield-check',
|
||||||
|
label: 'Audit trail',
|
||||||
|
description: 'Retain who decided what, when, and which evidence supported the decision.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const governanceLoopIntro = {
|
||||||
|
eyebrow: 'Governance loop',
|
||||||
|
title: 'Context, evidence, and auditability stay connected.',
|
||||||
|
description:
|
||||||
|
'Every decision is grounded in verifiable data. Nothing gets lost in handoffs or memory, and review work stays tied to the source material that created it.',
|
||||||
|
bullets: [
|
||||||
|
'End-to-end traceability from change to decision',
|
||||||
|
'Immutable evidence with screenshots and logs',
|
||||||
|
'Clear accountability for every action',
|
||||||
|
'Ready for internal and external reviews',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const governanceLoop: GovernanceLoopItem[] = [
|
||||||
|
{
|
||||||
|
label: 'Source of truth',
|
||||||
|
description: 'Graph, Entra ID, SharePoint, Exchange, Defender, Azure AD, and operator scripts.',
|
||||||
|
tone: 'source',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Snapshot',
|
||||||
|
description: 'Point-in-time state captured.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Diff',
|
||||||
|
description: 'Baseline comparison highlights drift.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Finding',
|
||||||
|
description: 'Risk context and suggested remediation.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Exception',
|
||||||
|
description: 'Accepted risk with owner and expiry.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Review',
|
||||||
|
description: 'Operator reviews context and decides.',
|
||||||
|
tone: 'focus',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Evidence',
|
||||||
|
description: 'Artifacts and notes attach to the decision.',
|
||||||
|
tone: 'focus',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Audit trail',
|
||||||
|
description: 'Decision history stays traceable.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const platformCapabilityPrimary: PlatformCapabilityPrimary = {
|
||||||
|
icon: 'lucide:archive-restore',
|
||||||
|
title: 'Backup & Restore',
|
||||||
|
description:
|
||||||
|
'Protect critical tenant configurations and data. Preview, validate, and restore with confidence from preserved configuration evidence.',
|
||||||
|
ctaLabel: 'Explore backups',
|
||||||
|
proofPoints: ['Point-in-time records', 'Restore preview context', 'Evidence for each decision'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const platformCapabilities: FeatureItemContent[] = [
|
||||||
|
{
|
||||||
|
icon: 'git-branch',
|
||||||
|
title: 'Drift Detection',
|
||||||
|
description: 'Detect policy, access, and configuration changes before they turn into audit work.',
|
||||||
|
meta: 'Detected',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'search',
|
||||||
|
title: 'Findings',
|
||||||
|
description: 'Risk-rank drift with context, impact, and recommended actions.',
|
||||||
|
meta: 'Qualified',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'file-check',
|
||||||
|
title: 'Evidence',
|
||||||
|
description: 'Capture and organize artifacts that prove every decision.',
|
||||||
|
meta: 'Documented',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'clipboard',
|
||||||
|
title: 'Audit Trail',
|
||||||
|
description: 'Maintain history of changes, decisions, and operator actions.',
|
||||||
|
meta: 'Traceable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'flag',
|
||||||
|
title: 'Exceptions',
|
||||||
|
description: 'Document accepted risks with owners and review cadence.',
|
||||||
|
meta: 'Owned',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'users',
|
||||||
|
title: 'Governance Reviews',
|
||||||
|
description: 'Run structured reviews with tasks, approvals, and reporting.',
|
||||||
|
meta: 'Review cadence',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const truthLayers: TruthLayerContent[] = [
|
||||||
|
{
|
||||||
|
title: 'Execution truth',
|
||||||
|
description: 'What is happening right now in the tenant.',
|
||||||
|
question: 'What changed?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Artifact truth',
|
||||||
|
description: 'The evidence and logs that prove it.',
|
||||||
|
question: 'What proves it?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Backup truth',
|
||||||
|
description: 'What we have protected and can restore.',
|
||||||
|
question: 'What can we recover?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Recovery evidence',
|
||||||
|
description: 'Proof of restores, tests, and recovery actions.',
|
||||||
|
question: 'How do we restore?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Operator next action',
|
||||||
|
description: 'What the operator should do or review next.',
|
||||||
|
question: 'What should we do now?',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const operatorWorkflows: PlatformWorkflowContent[] = [
|
||||||
|
{
|
||||||
|
audience: 'MSP Operator',
|
||||||
|
icon: 'lucide:user-round',
|
||||||
|
description: 'Manage multiple clients with consistent guardrails, backups, and reviews.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
audience: 'Enterprise IT',
|
||||||
|
icon: 'lucide:building-2',
|
||||||
|
description: 'Maintain secure posture with drift context and clear actions.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
audience: 'Security Reviewer',
|
||||||
|
icon: 'lucide:shield-check',
|
||||||
|
description: 'Investigate, validate, and ensure issues are remediated.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
audience: 'Compliance / Audit',
|
||||||
|
icon: 'lucide:scroll-text',
|
||||||
|
description: 'Access evidence and reports to meet internal and external needs.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const platformBoundary: PlatformBoundaryContent = {
|
||||||
|
headline: 'Built for governance of record and recovery context.',
|
||||||
|
description:
|
||||||
|
'Tenantial is designed to preserve context, evidence, and governance control so operators can act with confidence.',
|
||||||
|
items: [
|
||||||
|
'We do not take actions on your behalf.',
|
||||||
|
'We do not manage devices.',
|
||||||
|
'We do not replace your ITSM, SIEM, ticketing, or Microsoft admin centers.',
|
||||||
|
'We support reviewable decisions.',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const platformFinalCta = {
|
||||||
|
eyebrow: 'Evaluate the loop',
|
||||||
|
title: 'See how evidence-first governance fits your Microsoft tenant operations.',
|
||||||
|
description:
|
||||||
|
'Explore a focused demo of Tenantial and see how your team can turn signal into trusted decisions.',
|
||||||
|
primary: {
|
||||||
|
href: '/contact',
|
||||||
|
label: 'Book a demo',
|
||||||
|
} as CtaLink,
|
||||||
|
secondary: {
|
||||||
|
href: '#governance-loop',
|
||||||
|
label: 'Explore the platform',
|
||||||
|
variant: 'secondary',
|
||||||
|
} as CtaLink,
|
||||||
|
};
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||||
|
|
||||||
export const privacySeo: PageSeo = {
|
export const privacySeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Privacy',
|
title: 'Tenantial | Privacy',
|
||||||
description:
|
description:
|
||||||
'Public-site privacy overview for TenantAtlas inquiries, including how contact details and evaluation context are handled on the public website.',
|
'Public-site privacy overview for Tenantial inquiries, including how contact details and evaluation context are handled on the public website.',
|
||||||
path: '/privacy',
|
path: '/privacy',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const privacyHero: HeroContent = {
|
export const privacyHero: HeroContent = {
|
||||||
eyebrow: 'Privacy',
|
eyebrow: 'Privacy',
|
||||||
title: 'Public-site privacy overview for TenantAtlas inquiries.',
|
title: 'Public-site privacy overview for Tenantial inquiries.',
|
||||||
description:
|
description:
|
||||||
'This page explains the privacy expectations for the public website and the contact path, rather than promising a full product-tenant data processing agreement from a static marketing surface.',
|
'This page explains the privacy expectations for the public website and the contact path, rather than promising a full product-tenant data processing agreement from a static marketing surface.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
@ -32,7 +32,7 @@ export const privacySections: LegalSection[] = [
|
|||||||
{
|
{
|
||||||
title: 'Scope',
|
title: 'Scope',
|
||||||
body: [
|
body: [
|
||||||
'This privacy overview applies to the public TenantAtlas website and to information a visitor intentionally shares through the public contact path.',
|
'This privacy overview applies to the public Tenantial website and to information a visitor intentionally shares through the public contact path.',
|
||||||
'It does not describe tenant data processing inside the product itself, which belongs in product-specific legal and contractual materials.',
|
'It does not describe tenant data processing inside the product itself, which belongs in product-specific legal and contractual materials.',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import type {
|
|||||||
} from '@/types/site';
|
} from '@/types/site';
|
||||||
|
|
||||||
export const productSeo: PageSeo = {
|
export const productSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Product',
|
title: 'Tenantial | Product',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas explains backup, restore, version history, drift, findings, evidence, and reviews as one operating model rather than a loose feature list.',
|
'Tenantial explains backup, restore, version history, drift, findings, evidence, and reviews as one operating model rather than a loose feature list.',
|
||||||
path: '/product',
|
path: '/product',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ export const productHero: HeroContent = {
|
|||||||
eyebrow: 'Product model',
|
eyebrow: 'Product model',
|
||||||
title: 'Explain the product as one operating model before asking a buyer to trust the route map around it.',
|
title: 'Explain the product as one operating model before asking a buyer to trust the route map around it.',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas treats Microsoft tenant governance as one connected system: observe the current state, preserve immutable history, detect meaningful change, and support reviews or restores with the context operators actually need.',
|
'Tenantial treats Microsoft tenant governance as one connected system: observe the current state, preserve immutable history, detect meaningful change, and support reviews or restores with the context operators actually need.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
href: '/trust',
|
href: '/trust',
|
||||||
label: 'Review the trust posture',
|
label: 'Review the trust posture',
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import type {
|
|||||||
} from '@/types/site';
|
} from '@/types/site';
|
||||||
|
|
||||||
export const securityTrustSeo: PageSeo = {
|
export const securityTrustSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Security & Trust',
|
title: 'Tenantial | Security & Trust',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas frames trust through substantiated product posture, safer restore discipline, and operational clarity rather than inflated guarantees.',
|
'Tenantial frames trust through substantiated product posture, safer restore discipline, and operational clarity rather than inflated guarantees.',
|
||||||
path: '/security-trust',
|
path: '/security-trust',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import type {
|
|||||||
} from '@/types/site';
|
} from '@/types/site';
|
||||||
|
|
||||||
export const solutionsSeo: PageSeo = {
|
export const solutionsSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Solutions',
|
title: 'Tenantial | Solutions',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas keeps MSP and enterprise outcome framing available as a supporting page without requiring it for the first public read.',
|
'Tenantial keeps MSP and enterprise outcome framing available as a supporting page without requiring it for the first public read.',
|
||||||
path: '/solutions',
|
path: '/solutions',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export const solutionsSignals: FeatureItemContent[] = [
|
|||||||
eyebrow: 'Where it lands',
|
eyebrow: 'Where it lands',
|
||||||
title: 'The product belongs in the operating layer, not just the reporting layer.',
|
title: 'The product belongs in the operating layer, not just the reporting layer.',
|
||||||
description:
|
description:
|
||||||
'Visitors should understand that TenantAtlas helps teams make safer decisions about configuration state rather than merely summarize activity afterward.',
|
'Visitors should understand that Tenantial helps teams make safer decisions about configuration state rather than merely summarize activity afterward.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
eyebrow: 'How it reads',
|
eyebrow: 'How it reads',
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||||
|
|
||||||
export const termsSeo: PageSeo = {
|
export const termsSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Terms',
|
title: 'Tenantial | Terms',
|
||||||
description:
|
description:
|
||||||
'Website terms for the public TenantAtlas surface, covering informational use of the site and the limits of public product statements.',
|
'Website terms for the public Tenantial surface, covering informational use of the site and the limits of public product statements.',
|
||||||
path: '/terms',
|
path: '/terms',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const termsHero: HeroContent = {
|
export const termsHero: HeroContent = {
|
||||||
eyebrow: 'Website terms',
|
eyebrow: 'Website terms',
|
||||||
title: 'Website terms for the public TenantAtlas surface.',
|
title: 'Website terms for the public Tenantial surface.',
|
||||||
description:
|
description:
|
||||||
'These terms describe the public website itself: informational use of the content, basic conduct expectations, and the fact that a public product site is not the same thing as a signed service agreement.',
|
'These terms describe the public website itself: informational use of the content, basic conduct expectations, and the fact that a public product site is not the same thing as a signed service agreement.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
@ -32,7 +32,7 @@ export const termsSections: LegalSection[] = [
|
|||||||
{
|
{
|
||||||
title: 'Informational website use',
|
title: 'Informational website use',
|
||||||
body: [
|
body: [
|
||||||
'The public TenantAtlas website is provided to explain the product category, trust posture, integrations direction, and contact path for evaluation conversations.',
|
'The public Tenantial website is provided to explain the product category, trust posture, integrations direction, and contact path for evaluation conversations.',
|
||||||
'Nothing on the public site should be interpreted as a guarantee of product availability, certification, or commercial commitment unless it is later confirmed in signed agreements.',
|
'Nothing on the public site should be interpreted as a guarantee of product availability, certification, or commercial commitment unless it is later confirmed in signed agreements.',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import type {
|
|||||||
} from '@/types/site';
|
} from '@/types/site';
|
||||||
|
|
||||||
export const trustSeo: PageSeo = {
|
export const trustSeo: PageSeo = {
|
||||||
title: 'TenantAtlas | Trust',
|
title: 'Tenantial | Trust',
|
||||||
description:
|
description:
|
||||||
'TenantAtlas uses the Trust surface to explain tenant isolation, access boundaries, operating discipline, and the limits of public claims in one explicit place.',
|
'Tenantial uses the Trust surface to explain tenant isolation, access boundaries, operating discipline, and the limits of public claims in one explicit place.',
|
||||||
path: '/trust',
|
path: '/trust',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -16,11 +16,11 @@ import type {
|
|||||||
} from '@/types/site';
|
} from '@/types/site';
|
||||||
|
|
||||||
export const siteMetadata: SiteMetadata = {
|
export const siteMetadata: SiteMetadata = {
|
||||||
siteName: 'TenantAtlas',
|
siteName: 'Tenantial',
|
||||||
siteTagline: 'Governance of record for Microsoft tenant operations.',
|
siteTagline: 'Evidence-first governance for Microsoft tenants.',
|
||||||
siteDescription:
|
siteDescription:
|
||||||
'TenantAtlas helps MSP and enterprise teams keep Microsoft tenant change history observable, reviewable, and safer to operate.',
|
'Tenantial helps MSP and enterprise teams govern Microsoft tenant backup, restore, drift, evidence, audit trails, and structured reviews from one evidence-first surface.',
|
||||||
siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example',
|
siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantial.example',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const visualFoundationContract: VisualFoundationContract = {
|
export const visualFoundationContract: VisualFoundationContract = {
|
||||||
@ -68,17 +68,17 @@ interface FooterGroupSeed {
|
|||||||
|
|
||||||
export const contactCta: CtaLink = {
|
export const contactCta: CtaLink = {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Request a working session',
|
label: 'Book a demo',
|
||||||
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
|
helper: 'Bring tenant governance questions, restore concerns, or audit-readiness goals.',
|
||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
};
|
};
|
||||||
|
|
||||||
const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
||||||
landing: {
|
landing: {
|
||||||
eyebrow: 'Keep the next move obvious',
|
eyebrow: 'Next step',
|
||||||
title: 'Product, trust, progress, and contact should stay connected.',
|
title: 'Build tenant governance on evidence, not assumptions.',
|
||||||
description:
|
description:
|
||||||
'Landing pages should move visitors from orientation into product understanding, trust review, changelog proof, or the contact path without fake maturity signals.',
|
'Use a focused demo conversation to map Tenantial against your Microsoft tenant backup, restore, drift, evidence, and review needs.',
|
||||||
intent: 'conversion',
|
intent: 'conversion',
|
||||||
primaryCta: contactCta,
|
primaryCta: contactCta,
|
||||||
},
|
},
|
||||||
@ -90,7 +90,7 @@ const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
|||||||
intent: 'guidance',
|
intent: 'guidance',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Discuss trust requirements',
|
label: 'Book a demo',
|
||||||
helper: 'Bring current review, legal, or rollout questions into one working conversation.',
|
helper: 'Bring current review, legal, or rollout questions into one working conversation.',
|
||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
@ -103,7 +103,7 @@ const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
|||||||
intent: 'legal',
|
intent: 'legal',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Continue the evaluation path',
|
label: 'Book a demo',
|
||||||
helper: 'Move from the reading surface back into a product or trust conversation.',
|
helper: 'Move from the reading surface back into a product or trust conversation.',
|
||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
@ -113,19 +113,19 @@ const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
|||||||
const headerCtaByFamily: Record<PageFamily, CtaLink> = {
|
const headerCtaByFamily: Record<PageFamily, CtaLink> = {
|
||||||
landing: {
|
landing: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Request a working session',
|
label: 'Book a demo',
|
||||||
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
|
helper: 'Discuss the tenant governance surface with the Tenantial team.',
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
},
|
},
|
||||||
trust: {
|
trust: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Discuss trust requirements',
|
label: 'Book a demo',
|
||||||
helper: 'Route trust, legal, or rollout questions into one conversation.',
|
helper: 'Route trust, legal, or rollout questions into one conversation.',
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Start the contact path',
|
label: 'Book a demo',
|
||||||
helper: 'Turn the reading path into a concrete next step.',
|
helper: 'Turn the reading path into a concrete next step.',
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
},
|
},
|
||||||
@ -133,7 +133,7 @@ const headerCtaByFamily: Record<PageFamily, CtaLink> = {
|
|||||||
|
|
||||||
export const canonicalCoreRoutes: SitePath[] = [
|
export const canonicalCoreRoutes: SitePath[] = [
|
||||||
'/',
|
'/',
|
||||||
'/product',
|
'/platform',
|
||||||
'/trust',
|
'/trust',
|
||||||
'/changelog',
|
'/changelog',
|
||||||
'/contact',
|
'/contact',
|
||||||
@ -142,7 +142,7 @@ export const canonicalCoreRoutes: SitePath[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const retainedSecondaryRoutes: SitePath[] = ['/legal', '/terms', '/solutions', '/integrations'];
|
export const retainedSecondaryRoutes: SitePath[] = ['/legal', '/terms', '/solutions', '/integrations'];
|
||||||
export const compatibilityRoutes: SitePath[] = ['/security-trust'];
|
export const compatibilityRoutes: SitePath[] = ['/product', '/security-trust'];
|
||||||
export const primaryConversionRoute: SitePath = '/contact';
|
export const primaryConversionRoute: SitePath = '/contact';
|
||||||
export const unpublishedEditorialCollections: CollectionName[] = ['articles'];
|
export const unpublishedEditorialCollections: CollectionName[] = ['articles'];
|
||||||
|
|
||||||
@ -157,38 +157,55 @@ export async function getSurfaceAvailability(): Promise<SurfaceAvailability> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const primaryNavigationSeeds: CollectionGatedNavigationItem[] = [
|
const primaryNavigationSeeds: CollectionGatedNavigationItem[] = [
|
||||||
{ href: '/product', label: 'Product', description: 'See how the product connects backup, restore, drift, and evidence.' },
|
{ href: '/platform', label: 'Platform', description: 'Explore the evidence-first governance surface.' },
|
||||||
{ href: '/trust', label: 'Trust', description: 'Review the operating posture and bounded public claims.' },
|
{ href: '/solutions', label: 'Solutions', description: 'Review MSP and enterprise governance fit.' },
|
||||||
{ href: '/changelog', label: 'Changelog', description: 'Inspect dated product progress instead of placeholder content.' },
|
{ href: '/changelog', label: 'Resources', description: 'Use the changelog as the current public resource baseline.' },
|
||||||
{ href: '/resources', label: 'Resources', description: 'Optional deeper content when substantive material exists.', collection: 'resources' },
|
{ href: '/contact', label: 'Pricing', description: 'Pricing is handled through a scoped demo conversation.' },
|
||||||
{ href: '/contact', label: 'Contact', description: 'Move into a working session with one clear next step.' },
|
{ href: '/contact', label: 'Company', description: 'Contact is the current company and team introduction path.' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const footerNavigationGroupSeeds: FooterGroupSeed[] = [
|
const footerNavigationGroupSeeds: FooterGroupSeed[] = [
|
||||||
{
|
{
|
||||||
title: 'Product',
|
title: 'Platform',
|
||||||
items: [
|
items: [{ href: '/platform', label: 'Explore the platform' }],
|
||||||
{ href: '/product', label: 'Product' },
|
|
||||||
{ href: '/changelog', label: 'Changelog' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Trust & Legal',
|
title: 'Solutions',
|
||||||
items: [
|
items: [{ href: '/solutions', label: 'Solutions' }],
|
||||||
{ href: '/trust', label: 'Trust' },
|
},
|
||||||
{ href: '/privacy', label: 'Privacy' },
|
{
|
||||||
{ href: '/imprint', label: 'Imprint' },
|
title: 'Resources',
|
||||||
{ href: '/terms', label: 'Terms' },
|
items: [{ href: '/changelog', label: 'Changelog' }],
|
||||||
],
|
},
|
||||||
|
{
|
||||||
|
title: 'Pricing',
|
||||||
|
items: [{ href: '/contact', label: 'Book a demo' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Company',
|
||||||
|
items: [{ href: '/contact', label: 'Contact' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Contact',
|
title: 'Contact',
|
||||||
items: [{ href: '/contact', label: 'Contact' }],
|
items: [{ href: '/contact', label: 'Contact' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Content',
|
title: 'Legal',
|
||||||
collection: 'resources',
|
items: [
|
||||||
items: [{ href: '/resources', label: 'Resources', collection: 'resources' }],
|
{ href: '/legal', label: 'Legal' },
|
||||||
|
{ href: '/imprint', label: 'Imprint' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Privacy',
|
||||||
|
items: [{ href: '/privacy', label: 'Privacy' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Security',
|
||||||
|
items: [
|
||||||
|
{ href: '/trust', label: 'Trust' },
|
||||||
|
{ href: '/terms', label: 'Terms' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -237,10 +254,10 @@ export const pageDefinitions: Record<SitePath, PageDefinition> = {
|
|||||||
surfaceGroup: 'core',
|
surfaceGroup: 'core',
|
||||||
inSitemap: true,
|
inSitemap: true,
|
||||||
},
|
},
|
||||||
'/product': {
|
'/platform': {
|
||||||
path: '/product',
|
path: '/platform',
|
||||||
canonicalPath: '/product',
|
canonicalPath: '/platform',
|
||||||
pageRole: 'product',
|
pageRole: 'platform',
|
||||||
family: 'landing',
|
family: 'landing',
|
||||||
shellTone: 'brand',
|
shellTone: 'brand',
|
||||||
priority: 'required',
|
priority: 'required',
|
||||||
@ -248,6 +265,17 @@ export const pageDefinitions: Record<SitePath, PageDefinition> = {
|
|||||||
surfaceGroup: 'core',
|
surfaceGroup: 'core',
|
||||||
inSitemap: true,
|
inSitemap: true,
|
||||||
},
|
},
|
||||||
|
'/product': {
|
||||||
|
path: '/product',
|
||||||
|
canonicalPath: '/platform',
|
||||||
|
pageRole: 'product',
|
||||||
|
family: 'landing',
|
||||||
|
shellTone: 'brand',
|
||||||
|
priority: 'compatibility',
|
||||||
|
journeyStage: 'first-clarification',
|
||||||
|
surfaceGroup: 'compatibility',
|
||||||
|
inSitemap: false,
|
||||||
|
},
|
||||||
'/trust': {
|
'/trust': {
|
||||||
path: '/trust',
|
path: '/trust',
|
||||||
canonicalPath: '/trust',
|
canonicalPath: '/trust',
|
||||||
|
|||||||
@ -1,87 +1,24 @@
|
|||||||
---
|
---
|
||||||
import PageShell from '@/components/layout/PageShell.astro';
|
import PageShell from '@/components/layout/PageShell.astro';
|
||||||
import CapabilityGrid from '@/components/sections/CapabilityGrid.astro';
|
|
||||||
import CTASection from '@/components/sections/CTASection.astro';
|
import CTASection from '@/components/sections/CTASection.astro';
|
||||||
import LogoStrip from '@/components/sections/LogoStrip.astro';
|
import FeaturePillars from '@/components/sections/FeaturePillars.astro';
|
||||||
import OutcomeSection from '@/components/sections/OutcomeSection.astro';
|
|
||||||
import PageHero from '@/components/sections/PageHero.astro';
|
import PageHero from '@/components/sections/PageHero.astro';
|
||||||
import ProgressTeaser from '@/components/sections/ProgressTeaser.astro';
|
import TrustBar from '@/components/sections/TrustBar.astro';
|
||||||
import TrustGrid from '@/components/sections/TrustGrid.astro';
|
|
||||||
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
|
||||||
import Container from '@/components/primitives/Container.astro';
|
|
||||||
import Section from '@/components/primitives/Section.astro';
|
|
||||||
import { getRecentChangelogEntries } from '@/lib/changelog';
|
|
||||||
import {
|
import {
|
||||||
homeCapabilities,
|
|
||||||
homeCtaSection,
|
homeCtaSection,
|
||||||
homeEcosystem,
|
homeFeaturePillars,
|
||||||
homeHero,
|
homeHero,
|
||||||
homeOutcome,
|
|
||||||
homeProgressTeaser,
|
|
||||||
homeSeo,
|
homeSeo,
|
||||||
homeTrustSignals,
|
homeTrustBar,
|
||||||
} from '@/content/pages/home';
|
} from '@/content/pages/home';
|
||||||
|
|
||||||
const recentChangelog = await getRecentChangelogEntries(3);
|
|
||||||
const progressContent = {
|
|
||||||
...homeProgressTeaser,
|
|
||||||
entries: recentChangelog.length > 0 ? recentChangelog : homeProgressTeaser.entries,
|
|
||||||
};
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<PageShell currentPath="/" title={homeSeo.title} description={homeSeo.description}>
|
<PageShell currentPath="/" title={homeSeo.title} description={homeSeo.description}>
|
||||||
<PageHero hero={homeHero} />
|
<PageHero hero={homeHero} />
|
||||||
|
|
||||||
<LogoStrip
|
<TrustBar statements={homeTrustBar} />
|
||||||
eyebrow="Ecosystem fit"
|
|
||||||
title="Built around the Microsoft tenant reality buyers already need to govern."
|
|
||||||
items={homeEcosystem}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<OutcomeSection
|
<FeaturePillars items={homeFeaturePillars} />
|
||||||
content={homeOutcome}
|
|
||||||
titleHtml='Teams that <span class="accent">understand their tenant</span> make better decisions.'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<CapabilityGrid
|
|
||||||
eyebrow="What TenantAtlas covers"
|
|
||||||
title="A connected product model, not a feature wall."
|
|
||||||
titleHtml='A <span class="accent">connected product model</span>, not a feature wall.'
|
|
||||||
description="TenantAtlas groups backup, restore, inventory, drift, and governance into connected clusters instead of listing isolated features."
|
|
||||||
items={homeCapabilities}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div data-section="trust">
|
|
||||||
<TrustGrid
|
|
||||||
eyebrow="Trust posture"
|
|
||||||
title={homeTrustSignals.title}
|
|
||||||
titleHtml='Trust is a <span class="accent">first-read concern</span>, not a footnote.'
|
|
||||||
description={homeTrustSignals.description}
|
|
||||||
items={homeTrustSignals.signals}
|
|
||||||
/>
|
|
||||||
<Section density="compact">
|
|
||||||
<Container width="wide">
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<PrimaryCTA cta={{ href: '/trust', label: 'Review the full trust posture', variant: 'secondary' }} />
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</Section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{progressContent.entries.length > 0 && (
|
|
||||||
<ProgressTeaser content={progressContent} />
|
|
||||||
)}
|
|
||||||
{progressContent.entries.length === 0 && (
|
|
||||||
<Section layer="2" data-section="progress">
|
|
||||||
<Container width="wide">
|
|
||||||
<div class="text-center">
|
|
||||||
<p class="text-[var(--color-copy)]">
|
|
||||||
Follow product progress on the <a href="/changelog" class="text-link font-semibold">changelog</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</Section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div data-section="cta">
|
<div data-section="cta">
|
||||||
<CTASection
|
<CTASection
|
||||||
|
|||||||
1826
apps/website/src/pages/platform.astro
Normal file
1826
apps/website/src/pages/platform.astro
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,63 +0,0 @@
|
|||||||
---
|
|
||||||
import Callout from '@/components/content/Callout.astro';
|
|
||||||
import PageShell from '@/components/layout/PageShell.astro';
|
|
||||||
import Container from '@/components/primitives/Container.astro';
|
|
||||||
import Grid from '@/components/primitives/Grid.astro';
|
|
||||||
import Section from '@/components/primitives/Section.astro';
|
|
||||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
|
||||||
import CTASection from '@/components/sections/CTASection.astro';
|
|
||||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
|
||||||
import PageHero from '@/components/sections/PageHero.astro';
|
|
||||||
import {
|
|
||||||
productHero,
|
|
||||||
productMetrics,
|
|
||||||
productModelBlocks,
|
|
||||||
productNarrative,
|
|
||||||
productSeo,
|
|
||||||
} from '@/content/pages/product';
|
|
||||||
---
|
|
||||||
|
|
||||||
<PageShell currentPath="/product" title={productSeo.title} description={productSeo.description}>
|
|
||||||
<PageHero
|
|
||||||
hero={productHero}
|
|
||||||
metrics={productMetrics}
|
|
||||||
calloutTitle="Connected governance model"
|
|
||||||
calloutDescription="TenantAtlas connects present-state inventory, immutable snapshots, restore posture, drift, exceptions, and evidence so teams can explain what happened before they decide what to do next."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FeatureGrid
|
|
||||||
eyebrow="Connected governance model"
|
|
||||||
title="Explain what the product does before asking for buyer trust."
|
|
||||||
titleHtml='Explain what the product does before asking for <span class="accent">buyer trust</span>.'
|
|
||||||
description="This page should explain how the pieces fit together so visitors do not mistake the product for a loose collection of backup, reporting, and restore features."
|
|
||||||
items={productModelBlocks}
|
|
||||||
tone="tinted"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Section tone="muted" density="base" layer="3">
|
|
||||||
<Container width="wide">
|
|
||||||
<div class="space-y-8">
|
|
||||||
<SectionHeader
|
|
||||||
eyebrow="Narrative"
|
|
||||||
title="Keep the path from product truth into trust and action readable."
|
|
||||||
description="The public product page should make it obvious how the product helps a team move from current-state understanding into trust review, visible progress, and reviewable action."
|
|
||||||
/>
|
|
||||||
<Grid cols="3">
|
|
||||||
{productNarrative.map((block) => <Callout content={block} />)}
|
|
||||||
</Grid>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<CTASection
|
|
||||||
eyebrow="Continue"
|
|
||||||
title="Trust review and visible progress should follow the product explanation cleanly."
|
|
||||||
description="Once the product model is clear, the next useful moves are to inspect the Trust surface, verify current progress, and start the contact path."
|
|
||||||
primary={{ href: '/changelog', label: 'Read the changelog' }}
|
|
||||||
secondary={{
|
|
||||||
href: '/contact',
|
|
||||||
label: 'Start the working session',
|
|
||||||
variant: 'secondary',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</PageShell>
|
|
||||||
@ -2,18 +2,19 @@
|
|||||||
@import "./tokens.css";
|
@import "./tokens.css";
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
overflow-x: clip;
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
overflow-x: clip;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top left, rgba(255, 255, 255, 0.88), transparent 30%),
|
linear-gradient(180deg, #050607 0%, #050607 58%, #07090d 100%);
|
||||||
linear-gradient(180deg, var(--color-background) 0%, var(--color-background-elevated) 48%, var(--color-muted) 100%);
|
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@ -29,7 +30,8 @@ a:not([data-button-variant]) {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img,
|
||||||
|
svg {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
@ -39,12 +41,12 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background: rgba(40, 60, 120, 0.12);
|
background: rgba(111, 229, 191, 0.28);
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
:where(a, button, input, textarea, summary):focus-visible {
|
:where(a, button, input, textarea, summary):focus-visible {
|
||||||
outline: 3px solid rgba(40, 60, 120, 0.22);
|
outline: 3px solid rgba(111, 229, 191, 0.58);
|
||||||
outline-offset: 4px;
|
outline-offset: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,41 +61,85 @@ .foundation-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.foundation-page::before {
|
.foundation-page::before {
|
||||||
position: absolute;
|
content: none;
|
||||||
inset: 0;
|
|
||||||
z-index: -2;
|
|
||||||
content: "";
|
|
||||||
background:
|
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.68), transparent 16%),
|
|
||||||
radial-gradient(circle at 20% 18%, rgba(255, 255, 255, 0.72), transparent 28%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.foundation-page[data-shell-tone='brand']::before {
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 8% 0%, rgba(255, 255, 255, 0.82), transparent 30%),
|
|
||||||
radial-gradient(circle at 86% 0%, rgba(40, 60, 120, 0.04), transparent 28%),
|
|
||||||
radial-gradient(circle at 72% 22%, rgba(40, 60, 120, 0.03), transparent 26%),
|
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.62), transparent 18%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.foundation-page[data-shell-tone='trust']::before {
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 10% 0%, rgba(255, 255, 255, 0.8), transparent 28%),
|
|
||||||
radial-gradient(circle at 82% 8%, rgba(40, 60, 120, 0.04), transparent 30%),
|
|
||||||
radial-gradient(circle at 74% 30%, rgba(100, 80, 140, 0.03), transparent 26%),
|
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.62), transparent 18%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.foundation-page::after {
|
.foundation-page::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ambient-wash,
|
||||||
|
.home-dot-field {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: clamp(0.7rem, 1vw, 1.2rem);
|
right: 0;
|
||||||
|
left: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
content: "";
|
|
||||||
border: 1px solid var(--color-frame);
|
|
||||||
border-radius: calc(var(--radius-panel) + 0.45rem);
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-ambient-wash {
|
||||||
|
top: 4rem;
|
||||||
|
height: 52rem;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 30rem 35rem at 62% 44%, rgba(255, 247, 225, 0.22), transparent 68%),
|
||||||
|
linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
transparent 30%,
|
||||||
|
rgba(255, 247, 225, 0.032) 40%,
|
||||||
|
rgba(255, 247, 225, 0.2) 58%,
|
||||||
|
rgba(255, 247, 225, 0.09) 74%,
|
||||||
|
transparent 94%
|
||||||
|
),
|
||||||
|
linear-gradient(180deg, transparent 0%, rgba(111, 229, 191, 0.052) 56%, transparent 100%);
|
||||||
|
filter: blur(30px);
|
||||||
|
mask-image:
|
||||||
|
linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.88) 16%, rgba(0, 0, 0, 0.78) 76%, transparent 100%),
|
||||||
|
linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.95) 34%, rgba(0, 0, 0, 0.95) 88%, transparent 100%);
|
||||||
|
mask-composite: intersect;
|
||||||
|
opacity: 0.78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-dot-field {
|
||||||
|
top: clamp(37rem, 73vh, 45rem);
|
||||||
|
height: 29rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-dot-field::before {
|
||||||
|
position: absolute;
|
||||||
|
top: -1rem;
|
||||||
|
left: 50%;
|
||||||
|
width: min(126rem, 158vw);
|
||||||
|
height: 31rem;
|
||||||
|
content: "";
|
||||||
|
background-image: url("/images/tenantial-wave-mesh.svg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center top;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
transform: translateX(-50%) perspective(44rem) rotateX(52deg) scaleX(1.18);
|
||||||
|
transform-origin: top center;
|
||||||
|
mask-image:
|
||||||
|
linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.6) 14%, rgba(0, 0, 0, 0.96) 50%, transparent 100%),
|
||||||
|
radial-gradient(86% 56% at 52% 42%, rgba(0, 0, 0, 0.98), transparent 80%);
|
||||||
|
mask-composite: intersect;
|
||||||
|
filter: drop-shadow(0 0 8px rgba(111, 229, 191, 0.06));
|
||||||
|
opacity: 0.96;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-dot-field::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 4.5rem;
|
||||||
|
left: 50%;
|
||||||
|
width: min(84rem, 112vw);
|
||||||
|
height: 10rem;
|
||||||
|
content: "";
|
||||||
|
background: radial-gradient(ellipse at center, rgba(111, 229, 191, 0.12), transparent 68%);
|
||||||
|
filter: blur(22px);
|
||||||
|
transform: translateX(-50%) perspective(38rem) rotateX(52deg);
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
.skip-link {
|
.skip-link {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
@ -101,9 +147,9 @@ .skip-link {
|
|||||||
z-index: 40;
|
z-index: 40;
|
||||||
transform: translateY(-200%);
|
transform: translateY(-200%);
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
background: var(--color-foreground);
|
background: var(--color-primary);
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
color: white;
|
color: var(--color-primary-foreground);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: transform 140ms ease;
|
transition: transform 140ms ease;
|
||||||
}
|
}
|
||||||
@ -120,36 +166,29 @@ .site-shell {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shell-panel {
|
.shell-panel,
|
||||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
|
||||||
background: linear-gradient(180deg, var(--surface-shell-strong), var(--surface-shell));
|
|
||||||
box-shadow: var(--shadow-panel-strong);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-panel {
|
.glass-panel {
|
||||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
border: 1px solid var(--color-border);
|
||||||
background: linear-gradient(180deg, var(--surface-shell-strong), var(--surface-shell));
|
background: linear-gradient(180deg, var(--surface-shell-strong), var(--surface-shell));
|
||||||
box-shadow: var(--shadow-panel-strong);
|
box-shadow: var(--shadow-panel-strong);
|
||||||
backdrop-filter: blur(18px);
|
backdrop-filter: blur(18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.surface-card {
|
.surface-card,
|
||||||
|
.surface-card-muted,
|
||||||
|
.surface-card-accent {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
background: linear-gradient(180deg, var(--color-card), var(--surface-card-soft));
|
background: linear-gradient(180deg, var(--color-card), var(--surface-card-soft));
|
||||||
box-shadow: var(--shadow-card);
|
box-shadow: var(--shadow-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.surface-card-muted {
|
.surface-card-muted {
|
||||||
border: 1px solid var(--color-border-subtle);
|
|
||||||
background: linear-gradient(180deg, var(--surface-muted-strong), var(--surface-muted));
|
background: linear-gradient(180deg, var(--surface-muted-strong), var(--surface-muted));
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.surface-card-accent {
|
.surface-card-accent {
|
||||||
border: 1px solid var(--color-border-strong);
|
border-color: var(--color-border-strong);
|
||||||
background: linear-gradient(180deg, var(--surface-accent-strong), var(--surface-accent));
|
background: linear-gradient(180deg, rgba(111, 229, 191, 0.12), rgba(20, 28, 36, 0.9));
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-density-compact {
|
.section-density-compact {
|
||||||
@ -164,22 +203,32 @@ .section-density-spacious {
|
|||||||
padding-block: var(--space-section-spacious);
|
padding-block: var(--space-section-spacious);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-shell-muted {
|
.section-shell-muted,
|
||||||
|
.section-shell-emphasis,
|
||||||
|
.section-tinted,
|
||||||
|
.section-warm {
|
||||||
border: 1px solid var(--color-border-subtle);
|
border: 1px solid var(--color-border-subtle);
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.52), rgba(243, 247, 251, 0.4));
|
border-radius: var(--radius-lg);
|
||||||
border-radius: calc(var(--radius-lg) + 0.15rem);
|
}
|
||||||
|
|
||||||
|
.section-shell-muted,
|
||||||
|
.section-tinted {
|
||||||
|
background: var(--surface-section-tinted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-shell-emphasis {
|
.section-shell-emphasis {
|
||||||
border: 1px solid var(--color-border-strong);
|
border-color: var(--color-border-strong);
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.56), rgba(40, 60, 120, 0.03));
|
background: rgba(111, 229, 191, 0.08);
|
||||||
border-radius: calc(var(--radius-lg) + 0.15rem);
|
}
|
||||||
|
|
||||||
|
.section-warm {
|
||||||
|
background: var(--surface-section-warm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-link {
|
.text-link {
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-color: rgba(17, 36, 58, 0.2);
|
text-decoration-color: rgba(111, 229, 191, 0.32);
|
||||||
text-underline-offset: 0.3em;
|
text-underline-offset: 0.3em;
|
||||||
transition:
|
transition:
|
||||||
color 140ms ease,
|
color 140ms ease,
|
||||||
@ -188,7 +237,7 @@ .text-link {
|
|||||||
|
|
||||||
.text-link:hover {
|
.text-link:hover {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-decoration-color: rgba(40, 60, 120, 0.35);
|
text-decoration-color: rgba(111, 229, 191, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legal-prose p {
|
.legal-prose p {
|
||||||
@ -212,18 +261,18 @@ .legal-prose li + li {
|
|||||||
margin-top: 0.6rem;
|
margin-top: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.foundation-page [data-disclosure-layer='1'] {
|
.foundation-page [data-disclosure-layer='1'],
|
||||||
|
.foundation-page [data-disclosure-layer='2'],
|
||||||
|
.foundation-page [data-disclosure-layer='3'] {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.foundation-page [data-disclosure-layer='1'] {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.foundation-page [data-disclosure-layer='2'] {
|
.foundation-page [data-disclosure-layer='2'],
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.foundation-page [data-disclosure-layer='3'] {
|
.foundation-page [data-disclosure-layer='3'] {
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,26 +292,21 @@ @keyframes rise-in {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Section tint bands ── */
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.section-tinted {
|
*,
|
||||||
background: var(--surface-section-tinted);
|
*::before,
|
||||||
border-radius: calc(var(--radius-lg) + 0.15rem);
|
*::after {
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
animation-duration: 1ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 1ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-warm {
|
|
||||||
background: var(--surface-section-warm);
|
|
||||||
border-radius: calc(var(--radius-lg) + 0.15rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Hero gradient band ── */
|
|
||||||
.hero-gradient {
|
.hero-gradient {
|
||||||
background:
|
|
||||||
radial-gradient(ellipse 80% 60% at 20% 40%, rgba(40, 60, 120, 0.02), transparent),
|
|
||||||
radial-gradient(ellipse 60% 50% at 80% 20%, rgba(40, 60, 120, 0.015), transparent);
|
|
||||||
padding-bottom: var(--space-section);
|
padding-bottom: var(--space-section);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Card hover lift ── */
|
|
||||||
.surface-card,
|
.surface-card,
|
||||||
.surface-card-muted,
|
.surface-card-muted,
|
||||||
.surface-card-accent {
|
.surface-card-accent {
|
||||||
@ -276,7 +320,6 @@ .card-hoverable:hover {
|
|||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Callout accent bar ── */
|
|
||||||
.callout-bar {
|
.callout-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 1.25rem;
|
padding-left: 1.25rem;
|
||||||
@ -285,8 +328,8 @@ .callout-bar {
|
|||||||
.callout-bar::before {
|
.callout-bar::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
@ -301,25 +344,19 @@ .callout-bar[data-bar-tone="warm"]::before {
|
|||||||
background: var(--color-warning);
|
background: var(--color-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Accent text highlight ── */
|
|
||||||
.text-accent-word {
|
.text-accent-word {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Feature icon circle ── */
|
|
||||||
.feature-icon {
|
.feature-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 2.75rem;
|
width: 2.75rem;
|
||||||
height: 2.75rem;
|
height: 2.75rem;
|
||||||
|
border: 1px solid rgba(111, 229, 191, 0.24);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(135deg, var(--surface-accent), var(--surface-accent-strong));
|
background: var(--surface-accent);
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-icon svg {
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,117 +3,109 @@ @theme {
|
|||||||
--font-display: "Inter", "Avenir Next", "Segoe UI", sans-serif;
|
--font-display: "Inter", "Avenir Next", "Segoe UI", sans-serif;
|
||||||
--font-mono: "IBM Plex Mono", "SFMono-Regular", monospace;
|
--font-mono: "IBM Plex Mono", "SFMono-Regular", monospace;
|
||||||
|
|
||||||
/* Cool slate neutrals — shadcn / Apex direction */
|
--color-obsidian-950: oklch(0.115 0.01 250);
|
||||||
--color-stone-50: oklch(0.985 0.002 250);
|
--color-obsidian-900: oklch(0.155 0.012 250);
|
||||||
--color-stone-100: oklch(0.975 0.003 250);
|
--color-obsidian-850: oklch(0.205 0.014 250);
|
||||||
--color-stone-150: oklch(0.962 0.004 248);
|
--color-obsidian-800: oklch(0.255 0.014 250);
|
||||||
--color-stone-200: oklch(0.94 0.006 246);
|
--color-ivory-50: oklch(0.982 0.012 88);
|
||||||
--color-stone-300: oklch(0.90 0.008 244);
|
--color-stone-200: oklch(0.87 0.014 80);
|
||||||
/* Ink: dark slate, very low chroma — near-black with cool undertone */
|
--color-stone-400: oklch(0.69 0.014 80);
|
||||||
--color-ink-700: oklch(0.44 0.01 250);
|
--color-stone-500: oklch(0.6 0.014 80);
|
||||||
--color-ink-800: oklch(0.30 0.008 250);
|
--color-mint-300: oklch(0.86 0.09 170);
|
||||||
--color-ink-900: oklch(0.14 0.005 250);
|
--color-mint-500: oklch(0.72 0.105 170);
|
||||||
/* Brand: dry slate-blue — enterprise, not flashy */
|
--color-mint-700: oklch(0.56 0.095 170);
|
||||||
--color-brand-300: oklch(0.78 0.06 245);
|
--color-amber-300: oklch(0.84 0.11 72);
|
||||||
--color-brand-400: oklch(0.64 0.09 245);
|
--color-amber-500: oklch(0.72 0.13 68);
|
||||||
--color-brand-500: oklch(0.50 0.10 245);
|
--color-coral-400: oklch(0.7 0.16 30);
|
||||||
--color-brand-700: oklch(0.38 0.09 248);
|
--color-coral-500: oklch(0.64 0.18 28);
|
||||||
/* Mint → Steel: functional accent for success states */
|
--color-violet-300: oklch(0.77 0.1 305);
|
||||||
--color-mint-300: oklch(0.84 0.05 175);
|
--color-violet-500: oklch(0.62 0.13 300);
|
||||||
--color-mint-500: oklch(0.66 0.08 172);
|
|
||||||
--color-mint-700: oklch(0.48 0.08 170);
|
|
||||||
/* Amber: stays functional (warning states) */
|
|
||||||
--color-amber-300: oklch(0.88 0.045 73);
|
|
||||||
--color-amber-500: oklch(0.75 0.085 62);
|
|
||||||
--color-amber-700: oklch(0.56 0.09 54);
|
|
||||||
--color-red-500: oklch(0.62 0.16 28);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: dark;
|
||||||
|
|
||||||
--color-background: var(--color-stone-50);
|
--color-background: var(--color-obsidian-950);
|
||||||
--color-background-elevated: var(--color-stone-100);
|
--color-background-elevated: var(--color-obsidian-900);
|
||||||
--color-foreground: var(--color-ink-900);
|
--color-foreground: var(--color-ivory-50);
|
||||||
--color-muted: var(--color-stone-150);
|
--color-muted: var(--color-obsidian-850);
|
||||||
--color-muted-foreground: var(--color-ink-700);
|
--color-muted-foreground: var(--color-stone-400);
|
||||||
--color-card: rgba(255, 255, 255, 0.92);
|
--color-card: rgba(18, 24, 32, 0.92);
|
||||||
--color-card-foreground: var(--color-ink-900);
|
--color-card-foreground: var(--color-ivory-50);
|
||||||
--color-border: rgba(20, 20, 20, 0.08);
|
--color-border: rgba(255, 247, 225, 0.1);
|
||||||
--color-border-strong: rgba(20, 20, 20, 0.15);
|
--color-border-strong: rgba(255, 247, 225, 0.18);
|
||||||
--color-border-subtle: rgba(20, 20, 20, 0.05);
|
--color-border-subtle: rgba(255, 247, 225, 0.07);
|
||||||
--color-frame: rgba(20, 20, 20, 0.04);
|
--color-frame: rgba(255, 247, 225, 0.08);
|
||||||
--color-input: rgba(255, 255, 255, 0.94);
|
--color-input: rgba(18, 24, 32, 0.96);
|
||||||
--color-primary: var(--color-brand-500);
|
--color-primary: var(--color-mint-500);
|
||||||
--color-primary-foreground: #f8f9fb;
|
--color-primary-foreground: var(--color-obsidian-950);
|
||||||
--color-secondary: rgba(255, 255, 255, 0.82);
|
--color-secondary: rgba(255, 247, 225, 0.08);
|
||||||
--color-secondary-foreground: var(--color-ink-900);
|
--color-secondary-foreground: var(--color-ivory-50);
|
||||||
--color-accent: rgba(40, 60, 120, 0.06);
|
--color-accent: rgba(111, 229, 191, 0.12);
|
||||||
--color-accent-foreground: var(--color-brand-700);
|
--color-accent-foreground: var(--color-mint-300);
|
||||||
--color-success: var(--color-mint-700);
|
--color-success: var(--color-mint-500);
|
||||||
--color-warning: var(--color-amber-700);
|
--color-warning: var(--color-amber-500);
|
||||||
--color-destructive: var(--color-red-500);
|
--color-destructive: var(--color-coral-500);
|
||||||
--color-info: var(--color-brand-700);
|
--color-info: var(--color-violet-300);
|
||||||
|
|
||||||
--surface-page: rgba(255, 255, 255, 0.34);
|
--surface-page: rgba(7, 10, 15, 0.94);
|
||||||
--surface-shell: rgba(255, 255, 255, 0.78);
|
--surface-shell: rgba(13, 18, 25, 0.88);
|
||||||
--surface-shell-strong: rgba(255, 255, 255, 0.94);
|
--surface-shell-strong: rgba(17, 24, 32, 0.96);
|
||||||
--surface-card-soft: rgba(248, 249, 252, 0.82);
|
--surface-card-soft: rgba(20, 28, 36, 0.84);
|
||||||
--surface-muted: rgba(246, 248, 252, 0.88);
|
--surface-muted: rgba(255, 247, 225, 0.05);
|
||||||
--surface-muted-strong: rgba(250, 251, 254, 0.94);
|
--surface-muted-strong: rgba(255, 247, 225, 0.075);
|
||||||
--surface-accent: rgba(40, 60, 120, 0.04);
|
--surface-accent: rgba(111, 229, 191, 0.1);
|
||||||
--surface-accent-strong: rgba(246, 248, 254, 0.98);
|
--surface-accent-strong: rgba(111, 229, 191, 0.16);
|
||||||
--surface-trust: rgba(40, 60, 120, 0.03);
|
--surface-trust: rgba(175, 150, 255, 0.12);
|
||||||
--surface-section-tinted: rgba(246, 248, 252, 0.62);
|
--surface-section-tinted: rgba(15, 21, 29, 0.62);
|
||||||
--surface-section-warm: rgba(248, 249, 252, 0.52);
|
--surface-section-warm: rgba(255, 188, 92, 0.08);
|
||||||
|
|
||||||
--radius-sm: 1rem;
|
--radius-sm: 0.5rem;
|
||||||
--radius-md: 1.35rem;
|
--radius-md: 0.5rem;
|
||||||
--radius-lg: 1.75rem;
|
--radius-lg: 0.5rem;
|
||||||
--radius-panel: 2rem;
|
--radius-panel: 0.5rem;
|
||||||
--radius-pill: 999px;
|
--radius-pill: 999px;
|
||||||
|
|
||||||
--shadow-panel-strong: 0 28px 90px rgba(17, 36, 58, 0.14);
|
--shadow-panel-strong: 0 28px 90px rgba(0, 0, 0, 0.32);
|
||||||
--shadow-card: 0 20px 56px rgba(17, 36, 58, 0.1);
|
--shadow-card: 0 20px 54px rgba(0, 0, 0, 0.24);
|
||||||
--shadow-card-hover: 0 24px 64px rgba(17, 36, 58, 0.15);
|
--shadow-card-hover: 0 24px 64px rgba(0, 0, 0, 0.32);
|
||||||
--shadow-soft: 0 12px 36px rgba(17, 36, 58, 0.08);
|
--shadow-soft: 0 12px 32px rgba(0, 0, 0, 0.18);
|
||||||
--shadow-inline: 0 10px 22px rgba(17, 36, 58, 0.08);
|
--shadow-inline: 0 10px 24px rgba(0, 0, 0, 0.22);
|
||||||
|
|
||||||
--space-page-x: clamp(1.25rem, 2vw, 2.5rem);
|
--space-page-x: clamp(1rem, 2vw, 2.25rem);
|
||||||
--space-page-y: clamp(5rem, 8vw, 8rem);
|
--space-page-y: clamp(4rem, 7vw, 7rem);
|
||||||
--space-section-compact: clamp(3.5rem, 5vw, 5rem);
|
--space-section-compact: clamp(3rem, 5vw, 4.5rem);
|
||||||
--space-section: clamp(5rem, 8vw, 7rem);
|
--space-section: clamp(4.5rem, 7vw, 6.5rem);
|
||||||
--space-section-spacious: clamp(6rem, 9vw, 9rem);
|
--space-section-spacious: clamp(5.5rem, 8vw, 8rem);
|
||||||
--space-cluster-sm: 0.75rem;
|
--space-cluster-sm: 0.75rem;
|
||||||
--space-cluster: 1rem;
|
--space-cluster: 1rem;
|
||||||
--space-cluster-lg: 1.5rem;
|
--space-cluster-lg: 1.5rem;
|
||||||
--space-stack-sm: 0.75rem;
|
--space-stack-sm: 0.75rem;
|
||||||
--space-stack: 1.25rem;
|
--space-stack: 1.25rem;
|
||||||
--space-stack-lg: 1.75rem;
|
--space-stack-lg: 1.75rem;
|
||||||
--space-grid: 1.25rem;
|
--space-grid: 1rem;
|
||||||
--space-grid-lg: 1.75rem;
|
--space-grid-lg: 1.5rem;
|
||||||
|
|
||||||
--content-max-width: 76rem;
|
--content-max-width: 76rem;
|
||||||
--wide-max-width: 84rem;
|
--wide-max-width: 88rem;
|
||||||
--reading-max-width: 68rem;
|
--reading-max-width: 68rem;
|
||||||
|
|
||||||
--type-display-size: clamp(2.75rem, 5vw, 4.25rem);
|
--type-display-size: 4rem;
|
||||||
--type-page-size: clamp(2.25rem, 3.8vw, 3.25rem);
|
--type-page-size: 3rem;
|
||||||
--type-section-size: clamp(1.75rem, 2.8vw, 2.5rem);
|
--type-section-size: 2.25rem;
|
||||||
--type-card-size: clamp(1.25rem, 1.8vw, 1.6rem);
|
--type-card-size: 1.25rem;
|
||||||
--type-body-size: 1.05rem;
|
--type-body-size: 1.03rem;
|
||||||
--type-small-size: 0.94rem;
|
--type-small-size: 0.94rem;
|
||||||
--type-eyebrow-size: 0.74rem;
|
--type-eyebrow-size: 0.74rem;
|
||||||
--type-helper-size: 0.82rem;
|
--type-helper-size: 0.82rem;
|
||||||
--tracking-display: -0.04em;
|
--tracking-display: 0;
|
||||||
--tracking-tight: -0.025em;
|
--tracking-tight: 0;
|
||||||
--tracking-eyebrow: 0.18em;
|
--tracking-eyebrow: 0;
|
||||||
--line-display: 0.96;
|
--line-display: 1;
|
||||||
--line-heading: 1.04;
|
--line-heading: 1.06;
|
||||||
--line-body: 1.75;
|
--line-body: 1.7;
|
||||||
--line-tight: 1.45;
|
--line-tight: 1.45;
|
||||||
|
|
||||||
/* Legacy aliases used by existing primitives and content blocks. */
|
|
||||||
--color-copy: var(--color-muted-foreground);
|
--color-copy: var(--color-muted-foreground);
|
||||||
--color-line: var(--color-border);
|
--color-line: var(--color-border);
|
||||||
--color-panel: var(--surface-shell);
|
--color-panel: var(--surface-shell);
|
||||||
@ -124,4 +116,17 @@ :root {
|
|||||||
--color-signal: var(--color-success);
|
--color-signal: var(--color-success);
|
||||||
--color-warm: var(--color-warning);
|
--color-warm: var(--color-warning);
|
||||||
--shadow-panel: var(--shadow-panel-strong);
|
--shadow-panel: var(--shadow-panel-strong);
|
||||||
|
|
||||||
|
--color-ink-700: var(--color-stone-400);
|
||||||
|
--color-ink-800: var(--color-stone-200);
|
||||||
|
--color-ink-900: var(--color-foreground);
|
||||||
|
--color-stone-50: var(--color-background);
|
||||||
|
--color-stone-100: var(--color-background-elevated);
|
||||||
|
--color-stone-150: var(--surface-muted);
|
||||||
|
--color-stone-300: var(--color-stone-400);
|
||||||
|
--color-brand-300: var(--color-mint-300);
|
||||||
|
--color-brand-400: var(--color-mint-500);
|
||||||
|
--color-brand-500: var(--color-primary);
|
||||||
|
--color-brand-700: var(--color-mint-700);
|
||||||
|
--color-red-500: var(--color-destructive);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ export type ButtonVariant = 'primary' | 'secondary' | 'ghost';
|
|||||||
export type PageFamily = 'landing' | 'trust' | 'content';
|
export type PageFamily = 'landing' | 'trust' | 'content';
|
||||||
export type PageRole =
|
export type PageRole =
|
||||||
| 'home'
|
| 'home'
|
||||||
|
| 'platform'
|
||||||
| 'product'
|
| 'product'
|
||||||
| 'trust'
|
| 'trust'
|
||||||
| 'changelog'
|
| 'changelog'
|
||||||
@ -14,6 +15,7 @@ export type PageRole =
|
|||||||
| 'terms';
|
| 'terms';
|
||||||
export type SitePath =
|
export type SitePath =
|
||||||
| '/'
|
| '/'
|
||||||
|
| '/platform'
|
||||||
| '/product'
|
| '/product'
|
||||||
| '/trust'
|
| '/trust'
|
||||||
| '/changelog'
|
| '/changelog'
|
||||||
|
|||||||
@ -23,18 +23,25 @@ test('changelog publishes dated progress without displacing the contact path', a
|
|||||||
await expect(page.getByRole('main').getByRole('link', { name: 'Start the working session' }).first()).toBeVisible();
|
await expect(page.getByRole('main').getByRole('link', { name: 'Start the working session' }).first()).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('core IA keeps optional and deferred surfaces out of the published navigation contract', async ({ page }) => {
|
test('core IA publishes Tenantial homepage navigation without dead template routes', async ({ page }) => {
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
|
|
||||||
const header = page.getByRole('banner');
|
const header = page.getByRole('banner');
|
||||||
const footer = page.getByRole('contentinfo');
|
const footer = page.getByRole('contentinfo');
|
||||||
|
|
||||||
await expect(header.getByRole('link', { name: 'Resources' })).toHaveCount(0);
|
await expect(header.getByRole('link', { name: 'Platform', exact: true })).toHaveAttribute('href', '/platform');
|
||||||
await expect(header.getByRole('link', { name: 'Solutions' })).toHaveCount(0);
|
await expect(header.getByRole('link', { name: 'Solutions', exact: true })).toHaveAttribute('href', '/solutions');
|
||||||
await expect(header.getByRole('link', { name: 'Integrations' })).toHaveCount(0);
|
await expect(header.getByRole('link', { name: 'Resources', exact: true })).toHaveAttribute('href', '/changelog');
|
||||||
|
await expect(header.getByRole('link', { name: 'Pricing', exact: true })).toHaveAttribute('href', '/contact');
|
||||||
|
await expect(header.getByRole('link', { name: 'Company', exact: true })).toHaveAttribute('href', '/contact');
|
||||||
|
await expect(header.getByRole('link', { name: 'Book a demo', exact: true })).toHaveAttribute('href', '/contact');
|
||||||
|
await expect(header.locator('[data-nav-state="deferred"]').filter({ hasText: 'Sign in' }).first()).toBeVisible();
|
||||||
await expect(header.getByRole('link', { name: 'Security & Trust' })).toHaveCount(0);
|
await expect(header.getByRole('link', { name: 'Security & Trust' })).toHaveCount(0);
|
||||||
|
|
||||||
await expect(footer.getByRole('link', { name: 'Resources' })).toHaveCount(0);
|
for (const group of ['Platform', 'Solutions', 'Resources', 'Pricing', 'Company', 'Contact', 'Legal', 'Privacy', 'Security']) {
|
||||||
|
await expect(footer.getByText(group, { exact: true }).first()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
await expect(footer.getByRole('link', { name: 'Articles' })).toHaveCount(0);
|
await expect(footer.getByRole('link', { name: 'Articles' })).toHaveCount(0);
|
||||||
await expect(footer.getByRole('link', { name: 'Security & Trust' })).toHaveCount(0);
|
await expect(footer.getByRole('link', { name: 'Security & Trust' })).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -26,7 +26,7 @@ test('contact page qualifies the conversation and keeps legal links reachable',
|
|||||||
name: 'Structure the first conversation before anyone shares sensitive context.',
|
name: 'Structure the first conversation before anyone shares sensitive context.',
|
||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(page.getByRole('main').getByRole('link', { name: 'Email the TenantAtlas team' }).first()).toBeVisible();
|
await expect(page.getByRole('main').getByRole('link', { name: 'Email the Tenantial team' }).first()).toBeVisible();
|
||||||
await expect(page.getByRole('main').getByRole('link', { name: 'Review the trust posture' }).first()).toBeVisible();
|
await expect(page.getByRole('main').getByRole('link', { name: 'Review the trust posture' }).first()).toBeVisible();
|
||||||
await expect(page.getByRole('main').getByRole('link', { name: 'Privacy' }).first()).toBeVisible();
|
await expect(page.getByRole('main').getByRole('link', { name: 'Privacy' }).first()).toBeVisible();
|
||||||
await expect(page.getByRole('main').getByRole('link', { name: 'Terms' }).first()).toBeVisible();
|
await expect(page.getByRole('main').getByRole('link', { name: 'Terms' }).first()).toBeVisible();
|
||||||
@ -77,9 +77,9 @@ test.describe('mobile navigation', () => {
|
|||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await openMobileNavigation(page);
|
await openMobileNavigation(page);
|
||||||
await expect(page.locator('[data-mobile-nav]').first()).toBeVisible();
|
await expect(page.locator('[data-mobile-nav]').first()).toBeVisible();
|
||||||
await expect(page.getByRole('banner').getByRole('link', { name: /Contact/ }).first()).toBeVisible();
|
await expect(page.getByRole('banner').getByRole('link', { name: 'Book a demo' }).first()).toBeVisible();
|
||||||
await expect(page.getByRole('banner').getByRole('link', { name: 'Trust' }).first()).toBeVisible();
|
await expect(page.getByRole('banner').getByRole('link', { name: 'Platform' }).first()).toBeVisible();
|
||||||
await expect(page.getByRole('banner').getByRole('link', { name: 'Changelog' }).first()).toBeVisible();
|
await expect(page.getByRole('banner').getByRole('link', { name: 'Resources' }).first()).toBeVisible();
|
||||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Privacy' })).toBeVisible();
|
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Privacy' })).toBeVisible();
|
||||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Terms' })).toBeVisible();
|
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Terms' })).toBeVisible();
|
||||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Imprint' })).toBeVisible();
|
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Imprint' })).toBeVisible();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
expectCompatibilityRedirect,
|
||||||
expectCtaHierarchy,
|
expectCtaHierarchy,
|
||||||
expectDisclosureLayer,
|
expectDisclosureLayer,
|
||||||
expectFooterLinks,
|
expectFooterLinks,
|
||||||
@ -8,32 +9,103 @@ import {
|
|||||||
expectHomepageHeroOrder,
|
expectHomepageHeroOrder,
|
||||||
expectHomepageHeroRouteTargets,
|
expectHomepageHeroRouteTargets,
|
||||||
expectHomepageHeroStructure,
|
expectHomepageHeroStructure,
|
||||||
expectHomepageHeroTrustSignals,
|
|
||||||
expectHomepageHeroVisibleOnMobile,
|
expectHomepageHeroVisibleOnMobile,
|
||||||
expectHomepageSectionOrder,
|
expectHomepageSectionOrder,
|
||||||
expectMobileReadability,
|
expectMobileReadability,
|
||||||
expectNavigationVsCtaDifferentiation,
|
expectNavigationVsCtaDifferentiation,
|
||||||
|
expectNoBodyHorizontalOverflow,
|
||||||
expectOnwardRouteReachable,
|
expectOnwardRouteReachable,
|
||||||
expectPageFamily,
|
expectPageFamily,
|
||||||
expectProductNearVisual,
|
|
||||||
expectPrimaryNavigation,
|
expectPrimaryNavigation,
|
||||||
expectShell,
|
expectShell,
|
||||||
visitPage,
|
visitPage,
|
||||||
} from './smoke-helpers';
|
} from './smoke-helpers';
|
||||||
|
|
||||||
test('home uses the landing foundation to explain the product category with one clear action hierarchy', async ({
|
const forbiddenHomepageTerms = [
|
||||||
page,
|
'AstroDeck',
|
||||||
}) => {
|
'Open Source',
|
||||||
|
'MIT Licensed',
|
||||||
|
'TenantCTRL',
|
||||||
|
'TenantPilot',
|
||||||
|
'TenantAtlas',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const forbiddenPlatformTerms: Array<[string, RegExp]> = [
|
||||||
|
['AstroDeck', /AstroDeck/i],
|
||||||
|
['TemplateDeck', /TemplateDeck/i],
|
||||||
|
['Open Source', /Open Source/i],
|
||||||
|
['MIT', /\bMIT\b/i],
|
||||||
|
['TenantAtlas', /TenantAtlas/i],
|
||||||
|
['TenantPilot', /TenantPilot/i],
|
||||||
|
['TenantCTRL', /TenantCTRL/i],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const unsupportedPlatformClaims = [
|
||||||
|
/SOC 2/i,
|
||||||
|
/\bISO(?:\s?\d+)?\b/i,
|
||||||
|
/HIPAA/i,
|
||||||
|
/blanket GDPR/i,
|
||||||
|
/end-to-end encryption/i,
|
||||||
|
/guaranteed recovery/i,
|
||||||
|
/guaranteed compliance/i,
|
||||||
|
/zero drift/i,
|
||||||
|
/real-time everywhere/i,
|
||||||
|
/trusted by/i,
|
||||||
|
/customer logos/i,
|
||||||
|
/Microsoft certification/i,
|
||||||
|
/Microsoft partnership/i,
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
async function expectForbiddenHomepageResidueAbsent(page: import('@playwright/test').Page): Promise<void> {
|
||||||
|
const body = page.locator('body');
|
||||||
|
const metadata = await page.locator('head').evaluate((head) => {
|
||||||
|
const title = document.title;
|
||||||
|
const meta = Array.from(head.querySelectorAll('meta'))
|
||||||
|
.map((element) => element.getAttribute('content') ?? '')
|
||||||
|
.join(' ');
|
||||||
|
const canonical = head.querySelector('link[rel="canonical"]')?.getAttribute('href') ?? '';
|
||||||
|
|
||||||
|
return `${title} ${meta} ${canonical}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const term of forbiddenHomepageTerms) {
|
||||||
|
await expect(body).not.toContainText(new RegExp(term, 'i'));
|
||||||
|
expect(metadata, `Homepage metadata should not contain ${term}`).not.toMatch(new RegExp(term, 'i'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectPlatformResidueAbsent(page: import('@playwright/test').Page): Promise<void> {
|
||||||
|
const body = page.locator('body');
|
||||||
|
const metadata = await page.locator('head').evaluate((head) => {
|
||||||
|
const title = document.title;
|
||||||
|
const meta = Array.from(head.querySelectorAll('meta'))
|
||||||
|
.map((element) => element.getAttribute('content') ?? '')
|
||||||
|
.join(' ');
|
||||||
|
const canonical = head.querySelector('link[rel="canonical"]')?.getAttribute('href') ?? '';
|
||||||
|
|
||||||
|
return `${title} ${meta} ${canonical}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [term, pattern] of forbiddenPlatformTerms) {
|
||||||
|
await expect(body).not.toContainText(pattern);
|
||||||
|
expect(metadata, `Platform metadata should not contain ${term}`).not.toMatch(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('home first read positions Tenantial with one clear action hierarchy', async ({ page }) => {
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await expectShell(page, /TenantAtlas/);
|
await expectShell(page, 'Evidence-first governance for Microsoft tenants.');
|
||||||
await expectPageFamily(page, 'landing');
|
await expectPageFamily(page, 'landing');
|
||||||
await expectDisclosureLayer(page, '1');
|
await expectDisclosureLayer(page, '1');
|
||||||
await expectDisclosureLayer(page, '2');
|
await expectDisclosureLayer(page, '2');
|
||||||
await expectPrimaryNavigation(page);
|
await expectPrimaryNavigation(page);
|
||||||
await expectNavigationVsCtaDifferentiation(page);
|
await expectNavigationVsCtaDifferentiation(page);
|
||||||
await expectFooterLinks(page);
|
await expectFooterLinks(page);
|
||||||
await expectCtaHierarchy(page, 'Request a working session', 'See the product model');
|
await expectCtaHierarchy(page, 'Book a demo', 'Explore the platform');
|
||||||
await expect(page.getByRole('main').getByRole('link', { name: 'Request a working session' }).first()).toBeVisible();
|
await expect(page.getByRole('main').getByRole('link', { name: 'Book a demo' }).first()).toBeVisible();
|
||||||
|
await expect(page.getByRole('main').getByRole('link', { name: 'Explore the platform' }).first()).toBeVisible();
|
||||||
|
await expect(page.getByRole('heading', { level: 1 })).toHaveCount(1);
|
||||||
|
await expectForbiddenHomepageResidueAbsent(page);
|
||||||
|
|
||||||
const skipLink = page.getByRole('link', { name: 'Skip to content' });
|
const skipLink = page.getByRole('link', { name: 'Skip to content' });
|
||||||
|
|
||||||
@ -41,102 +113,95 @@ test('home uses the landing foundation to explain the product category with one
|
|||||||
await expect(skipLink).toBeFocused();
|
await expect(skipLink).toBeFocused();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('homepage hero explains the product with a product-near visual and outcome framing', async ({
|
test('homepage hero explains Tenantial, Microsoft tenant context, and platform CTA route', async ({ page }) => {
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await visitPage(page, '/');
|
|
||||||
await expectProductNearVisual(page);
|
|
||||||
await expect(page.locator('[data-section="outcome"]')).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('heading', { name: /calmer operations|governance pain|operational risk/i }).first(),
|
|
||||||
).toBeVisible();
|
|
||||||
await expectCtaHierarchy(page, 'Request a working session', 'See the product model');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('homepage maintains required section order: outcome before capability before trust before progress before cta', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await visitPage(page, '/');
|
|
||||||
await expectHomepageSectionOrder(page, ['outcome', 'capability', 'trust', 'progress', 'cta']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('homepage shows grouped capability clusters instead of a feature wall', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await visitPage(page, '/');
|
|
||||||
await expect(page.locator('[data-section="capability"]')).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('heading', { name: /product model|what TenantAtlas covers/i }),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('homepage shows explicit trust signals before the final CTA', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await visitPage(page, '/');
|
|
||||||
await expect(page.locator('[data-section="trust"]')).toBeVisible();
|
|
||||||
await expectOnwardRouteReachable(page, ['/trust']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('homepage hero makes the product category, text core, and one CTA pair explicit on first read', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await expectHomepageHeroStructure(page);
|
await expectHomepageHeroStructure(page);
|
||||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-eyebrow]')).toContainText(/microsoft tenant governance/i);
|
await expect(page.locator('[data-homepage-hero="true"] [data-hero-eyebrow]')).toContainText(
|
||||||
|
/governance that earns trust/i,
|
||||||
|
);
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('[data-homepage-hero="true"] [data-hero-heading]').getByRole('heading', {
|
page.locator('[data-homepage-hero="true"] [data-hero-heading]').getByRole('heading', {
|
||||||
level: 1,
|
level: 1,
|
||||||
name: /one operating record for change history, drift review, and restore planning/i,
|
name: 'Evidence-first governance for Microsoft tenants.',
|
||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-supporting-copy]')).toContainText(
|
await expect(page.locator('[data-homepage-hero="true"] [data-hero-supporting-copy]')).toContainText(
|
||||||
/security, endpoint, and platform teams use TenantAtlas to see what changed, preview restores, and move reviews forward/i,
|
/backup and restore with confidence\. detect drift before it becomes audit work\. preserve snapshot history/i,
|
||||||
);
|
);
|
||||||
await expectHomepageHeroCtaPair(page, /working session/i, /product model/i);
|
await expectHomepageHeroCtaPair(page, 'Book a demo', 'Explore the platform');
|
||||||
|
await expectHomepageHeroRouteTargets(page, ['/contact', '/platform']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('homepage hero keeps product-near proof and bounded trust cues inside the hero itself', async ({
|
test('homepage uses neutral trust statements and the six required feature pillars', async ({ page }) => {
|
||||||
|
await visitPage(page, '/');
|
||||||
|
await expect(page.locator('[data-section="trustbar"]')).toBeVisible();
|
||||||
|
await expect(page.locator('[data-section="trustbar"]')).toContainText(/Microsoft tenant focused/i);
|
||||||
|
await expect(page.locator('[data-section="trustbar"]')).toContainText(/Evidence-oriented workflows/i);
|
||||||
|
await expect(page.locator('[data-section="trustbar"]')).toContainText(/Designed for audit review/i);
|
||||||
|
await expect(page.locator('[data-section="trustbar"]')).toContainText(/Operator-led governance/i);
|
||||||
|
|
||||||
|
const pillars = page.locator('[data-section="feature-pillars"]');
|
||||||
|
await expect(pillars).toBeVisible();
|
||||||
|
|
||||||
|
for (const capability of ['Backup', 'Restore', 'Drift Detection', 'Evidence', 'Audit Trail', 'Governance Reviews']) {
|
||||||
|
await expect(pillars.getByRole('heading', { name: capability, exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(page.locator('body')).not.toContainText(/SOC 2|ISO\s?\d*|99\.9%|trusted by|customer logos/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('homepage dashboard preview is static, responsive, and explains status without color alone', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await expectProductNearVisual(page, /change history, restore preview, and a review queue/i);
|
|
||||||
await expectHomepageHeroTrustSignals(page);
|
const preview = page.locator('[data-dashboard-preview]').first();
|
||||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText(
|
|
||||||
/tenant-scoped boundaries/i,
|
await expect(preview).toBeVisible();
|
||||||
);
|
await expect(preview).toContainText('Static demo preview');
|
||||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText(
|
await expect(preview).toContainText('Demo values');
|
||||||
/reviewable change history/i,
|
await expect(preview).toContainText('92%');
|
||||||
);
|
await expect(preview).toContainText('14');
|
||||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText(
|
await expect(preview).toContainText('7');
|
||||||
/preview before restore/i,
|
await expect(preview).toContainText('1,248');
|
||||||
);
|
await expect(preview).toContainText('98%');
|
||||||
|
|
||||||
|
for (const panel of [
|
||||||
|
'Recent findings',
|
||||||
|
'Drift timeline',
|
||||||
|
'Backups and restores',
|
||||||
|
'Governance reviews',
|
||||||
|
'Evidence spotlight',
|
||||||
|
]) {
|
||||||
|
await expect(preview.getByText(panel, { exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const status of ['Healthy', 'Review required', 'Critical', 'Review-ready']) {
|
||||||
|
await expect(preview.getByText(status, { exact: true }).first()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(preview).not.toContainText(/real[- ]?time|streaming|live tenant feed|May 19|Updated 2m ago/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('homepage shows dated progress signals before the final CTA', async ({
|
test('homepage keeps the final CTA and launch-readiness sections in order', async ({ page }) => {
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await expect(page.locator('[data-section="progress"]')).toBeVisible();
|
await expectHomepageSectionOrder(page, ['hero', 'trustbar', 'feature-pillars', 'cta']);
|
||||||
await expectOnwardRouteReachable(page, ['/changelog']);
|
await expect(page.locator('[data-section="cta"]')).toContainText(
|
||||||
});
|
'Build tenant governance on evidence, not assumptions.',
|
||||||
|
);
|
||||||
test('homepage routes into Product, Trust, Changelog, and Contact', async ({
|
await expectOnwardRouteReachable(page, ['/platform', '/contact']);
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await visitPage(page, '/');
|
|
||||||
await expectOnwardRouteReachable(page, ['/product', '/trust', '/changelog', '/contact']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('homepage mobile', () => {
|
test.describe('homepage mobile', () => {
|
||||||
test.use({ viewport: { width: 390, height: 844 } });
|
test.use({ viewport: { width: 390, height: 844 } });
|
||||||
|
|
||||||
test('homepage remains readable on narrow screens', async ({ page }) => {
|
test('homepage remains readable on narrow screens without body overflow', async ({ page }) => {
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await expectMobileReadability(page);
|
await expectMobileReadability(page);
|
||||||
await expect(page.locator('[data-section="outcome"]')).toBeVisible();
|
await expectNoBodyHorizontalOverflow(page);
|
||||||
await expect(page.locator('[data-section="capability"]')).toBeVisible();
|
await expect(page.locator('[data-section="trustbar"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-section="trust"]')).toBeVisible();
|
await expect(page.locator('[data-section="feature-pillars"]')).toBeVisible();
|
||||||
|
await expect(page.locator('[data-dashboard-preview]').first()).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('homepage hero preserves meaning order and hero route intent on narrow screens', async ({ page }) => {
|
test('homepage hero preserves meaning order and hero route intent on narrow screens', async ({ page }) => {
|
||||||
@ -146,28 +211,181 @@ test.describe('homepage mobile', () => {
|
|||||||
'headline',
|
'headline',
|
||||||
'supporting-copy',
|
'supporting-copy',
|
||||||
'cta-pair',
|
'cta-pair',
|
||||||
'product-near-visual',
|
|
||||||
'trust-subclaims',
|
'trust-subclaims',
|
||||||
|
'product-near-visual',
|
||||||
]);
|
]);
|
||||||
await expectHomepageHeroVisibleOnMobile(page);
|
await expectHomepageHeroVisibleOnMobile(page);
|
||||||
await expectHomepageHeroRouteTargets(page, ['/contact', '/product']);
|
await expectHomepageHeroRouteTargets(page, ['/contact', '/platform']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('product keeps the connected operating model readable without collapsing into a feature list', async ({
|
test('platform hero explains Tenantial governance model', async ({ page }) => {
|
||||||
page,
|
await visitPage(page, '/platform');
|
||||||
}) => {
|
await expectShell(page, /govern Microsoft tenants through evidence/i);
|
||||||
await visitPage(page, '/product');
|
|
||||||
await expectShell(page, /operating model|restore posture|governance/i);
|
|
||||||
await expectPageFamily(page, 'landing');
|
await expectPageFamily(page, 'landing');
|
||||||
await expectDisclosureLayer(page, '1');
|
await expectDisclosureLayer(page, '1');
|
||||||
await expectDisclosureLayer(page, '2');
|
await expectDisclosureLayer(page, '2');
|
||||||
await expectPrimaryNavigation(page);
|
await expectPrimaryNavigation(page);
|
||||||
await expectNavigationVsCtaDifferentiation(page);
|
await expectNavigationVsCtaDifferentiation(page);
|
||||||
await expectFooterLinks(page);
|
await expectFooterLinks(page);
|
||||||
await expect(
|
|
||||||
page.getByRole('heading', { name: 'Explain what the product does before asking for buyer trust.' }),
|
const main = page.getByRole('main');
|
||||||
).toBeVisible();
|
|
||||||
await expectCtaHierarchy(page, 'Review the trust posture', 'Start the working session');
|
await expect(main.locator('[data-hero-eyebrow]').first()).toContainText(/TENANTIAL PLATFORM/i);
|
||||||
await expect(page.getByRole('main').getByRole('link', { name: 'Read the changelog' }).first()).toBeVisible();
|
await expect(page.getByRole('heading', { level: 1 })).toHaveCount(1);
|
||||||
|
await expect(page.getByRole('heading', { level: 1 })).toContainText(
|
||||||
|
/govern Microsoft tenants through evidence/i,
|
||||||
|
);
|
||||||
|
await expect(main.locator('[data-hero-supporting-copy]').first()).toContainText(/backup records/i);
|
||||||
|
await expect(main.locator('[data-hero-supporting-copy]').first()).toContainText(/configuration drift/i);
|
||||||
|
await expect(main.locator('[data-hero-supporting-copy]').first()).toContainText(/structured reviews/i);
|
||||||
|
await expectCtaHierarchy(page, 'Book a demo', 'See the governance loop');
|
||||||
|
await expect(main.locator('a[href="/contact"]').filter({ hasText: 'Book a demo' }).first()).toBeVisible();
|
||||||
|
await expect(main.locator('a[href="#governance-loop"]').filter({ hasText: 'See the governance loop' }).first()).toBeVisible();
|
||||||
|
await expect(main.locator('[data-platform-dashboard-preview]').first()).toBeVisible();
|
||||||
|
await expect(main.locator('[data-platform-dashboard-preview]').first()).toContainText(/Governance overview/i);
|
||||||
|
await expect(main.locator('[data-platform-dashboard-preview]').first()).toContainText(/Static demo preview/i);
|
||||||
|
await expect(main.locator('[data-platform-dashboard-preview]').first()).toContainText(/Evidence spotlight/i);
|
||||||
|
|
||||||
|
for (const signal of [
|
||||||
|
'Microsoft tenant focused',
|
||||||
|
'Evidence-oriented workflows',
|
||||||
|
'Reviewable decisions',
|
||||||
|
'Operator-led governance',
|
||||||
|
]) {
|
||||||
|
await expect(main.locator('[data-hero-trust-subclaims]').getByText(signal, { exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform metadata is Tenantial clean', async ({ page }) => {
|
||||||
|
await visitPage(page, '/platform');
|
||||||
|
|
||||||
|
await expect(page).toHaveTitle(/Tenantial Platform/i);
|
||||||
|
await expect(page).toHaveTitle(/Evidence-first governance/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /backup/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /restore/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /drift detection/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /findings/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /evidence/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /audit trails/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /exceptions/i);
|
||||||
|
await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', /reviews/i);
|
||||||
|
await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', /\/platform$/);
|
||||||
|
await expectPlatformResidueAbsent(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform governance flow explains reviewable evidence', async ({ page }) => {
|
||||||
|
await visitPage(page, '/platform');
|
||||||
|
|
||||||
|
const operatingModel = page.locator('[data-section="operating-model"]');
|
||||||
|
const governanceLoop = page.locator('[data-section="governance-loop"]');
|
||||||
|
|
||||||
|
await expect(operatingModel).toBeVisible();
|
||||||
|
|
||||||
|
await expect(operatingModel.locator('[data-operating-flow]')).toBeVisible();
|
||||||
|
|
||||||
|
for (const step of ['Snapshot', 'Drift', 'Finding', 'Review', 'Evidence', 'Audit trail']) {
|
||||||
|
await expect(operatingModel.getByText(step, { exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(governanceLoop).toBeVisible();
|
||||||
|
await expect(governanceLoop.locator('[data-governance-loop-diagram]')).toBeVisible();
|
||||||
|
|
||||||
|
for (const label of [
|
||||||
|
'Source of truth',
|
||||||
|
'Snapshot',
|
||||||
|
'Diff',
|
||||||
|
'Finding',
|
||||||
|
'Exception',
|
||||||
|
'Review',
|
||||||
|
'Evidence',
|
||||||
|
'Audit trail',
|
||||||
|
]) {
|
||||||
|
await expect(governanceLoop.getByText(label, { exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(governanceLoop).toContainText(/operator reviews/i);
|
||||||
|
await expect(governanceLoop).toContainText(/evidence/i);
|
||||||
|
await expect(governanceLoop).toContainText(/auditability/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform governance flow avoids automation guarantees', async ({ page }) => {
|
||||||
|
await visitPage(page, '/platform');
|
||||||
|
|
||||||
|
await expect(page.locator('body')).not.toContainText(
|
||||||
|
/automatic remediation|guaranteed recovery|live device actions|real-time tenant operations/i,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform capabilities cover governance primitives', async ({ page }) => {
|
||||||
|
await visitPage(page, '/platform');
|
||||||
|
|
||||||
|
const capabilities = page.locator('[data-section="platform-capabilities"]');
|
||||||
|
|
||||||
|
await expect(capabilities).toBeVisible();
|
||||||
|
|
||||||
|
await expect(capabilities.locator('[data-capability-system-grid]')).toBeVisible();
|
||||||
|
await expect(capabilities.locator('[data-capability-primary]')).toBeVisible();
|
||||||
|
await expect(capabilities.getByRole('heading', { name: 'Backup & Restore', exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
for (const capability of [
|
||||||
|
'Drift Detection',
|
||||||
|
'Findings',
|
||||||
|
'Evidence',
|
||||||
|
'Audit Trail',
|
||||||
|
'Exceptions',
|
||||||
|
'Governance Reviews',
|
||||||
|
]) {
|
||||||
|
await expect(capabilities.getByRole('heading', { name: capability, exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform boundaries keep claims conservative', async ({ page }) => {
|
||||||
|
await visitPage(page, '/platform');
|
||||||
|
|
||||||
|
const boundaries = page.locator('[data-section="platform-boundaries"]');
|
||||||
|
|
||||||
|
await expect(boundaries).toBeVisible();
|
||||||
|
await expect(boundaries).toContainText(/Built for governance of record/i);
|
||||||
|
await expect(boundaries).toContainText(/do not take actions on your behalf/i);
|
||||||
|
await expect(boundaries).toContainText(/do not manage devices/i);
|
||||||
|
await expect(boundaries).toContainText(/do not replace your ITSM/i);
|
||||||
|
await expect(boundaries).toContainText(/support reviewable decisions/i);
|
||||||
|
|
||||||
|
for (const claim of unsupportedPlatformClaims) {
|
||||||
|
await expect(page.locator('body')).not.toContainText(claim);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('platform mobile', () => {
|
||||||
|
test.use({ viewport: { width: 390, height: 844 } });
|
||||||
|
|
||||||
|
test('platform mobile layout stays readable without overflow', async ({ page }) => {
|
||||||
|
await visitPage(page, '/platform');
|
||||||
|
await expectMobileReadability(page);
|
||||||
|
await expectNoBodyHorizontalOverflow(page);
|
||||||
|
await expect(page.locator('[data-section="platform-capabilities"]').first()).toBeVisible();
|
||||||
|
await expect(page.locator('[data-section="platform-boundaries"]').first()).toBeVisible();
|
||||||
|
await expect(page.locator('[data-section="platform-capabilities"]').first()).toContainText(/Backup/i);
|
||||||
|
await expect(page.locator('[data-truth-layer-stack]').first()).toBeVisible();
|
||||||
|
await expect(page.locator('[data-governance-loop-diagram]').first()).toBeVisible();
|
||||||
|
await expect(page.locator('[data-section="platform-boundaries"]').first()).toContainText(/governance of record/i);
|
||||||
|
expect(await page.locator('[data-color-meaning="with-text"]').count()).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('primary navigation and footer point Platform links to platform', async ({ page }) => {
|
||||||
|
await visitPage(page, '/platform');
|
||||||
|
await expectPrimaryNavigation(page);
|
||||||
|
await expectFooterLinks(page);
|
||||||
|
await expect(page.getByRole('contentinfo')).toHaveAttribute('data-footer-intent', 'quiet');
|
||||||
|
await expect(page.locator('[data-section="platform-final-cta"]')).toBeVisible();
|
||||||
|
await expect(page.getByRole('contentinfo')).not.toContainText(/Build tenant governance on evidence/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('product redirects to platform', async ({ page }) => {
|
||||||
|
await expectCompatibilityRedirect(page, '/product', '/platform');
|
||||||
|
await expect(page.locator('body')).not.toContainText(
|
||||||
|
/AstroDeck|TemplateDeck|Open Source|MIT Licensed|TenantAtlas|TenantPilot|TenantCTRL/i,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,19 +1,34 @@
|
|||||||
import { expect, type Page } from '@playwright/test';
|
import { expect, type Page } from '@playwright/test';
|
||||||
|
|
||||||
export const coreRoutePaths = ['/', '/product', '/trust', '/changelog', '/contact', '/privacy', '/imprint'] as const;
|
export const coreRoutePaths = ['/', '/platform', '/trust', '/changelog', '/contact', '/privacy', '/imprint'] as const;
|
||||||
export const secondaryRoutePaths = ['/legal', '/terms', '/solutions', '/integrations'] as const;
|
export const secondaryRoutePaths = ['/legal', '/terms', '/solutions', '/integrations'] as const;
|
||||||
|
|
||||||
export const primaryNavigationLabels = ['Product', 'Trust', 'Changelog', 'Contact'] as const;
|
export const primaryNavigationLabels = ['Platform', 'Solutions', 'Resources', 'Pricing', 'Company'] as const;
|
||||||
export const hiddenPrimaryNavigationLabels = [
|
export const hiddenPrimaryNavigationLabels = ['Product', 'Security & Trust', 'Articles'] as const;
|
||||||
'Solutions',
|
|
||||||
'Integrations',
|
|
||||||
'Security & Trust',
|
|
||||||
'Resources',
|
|
||||||
'Articles',
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const footerLabels = ['Product', 'Changelog', 'Trust', 'Privacy', 'Imprint', 'Terms', 'Contact'] as const;
|
export const footerGroupLabels = [
|
||||||
export const hiddenFooterLabels = ['Resources', 'Articles', 'Security & Trust', 'Contact / Demo'] as const;
|
'Platform',
|
||||||
|
'Solutions',
|
||||||
|
'Resources',
|
||||||
|
'Pricing',
|
||||||
|
'Company',
|
||||||
|
'Contact',
|
||||||
|
'Legal',
|
||||||
|
'Privacy',
|
||||||
|
'Security',
|
||||||
|
] as const;
|
||||||
|
export const footerLabels = [
|
||||||
|
'Explore the platform',
|
||||||
|
'Solutions',
|
||||||
|
'Changelog',
|
||||||
|
'Book a demo',
|
||||||
|
'Contact',
|
||||||
|
'Legal',
|
||||||
|
'Imprint',
|
||||||
|
'Privacy',
|
||||||
|
'Trust',
|
||||||
|
] as const;
|
||||||
|
export const hiddenFooterLabels = ['Articles', 'Security & Trust', 'Contact / Demo'] as const;
|
||||||
|
|
||||||
export async function visitPage(page: Page, path: string): Promise<void> {
|
export async function visitPage(page: Page, path: string): Promise<void> {
|
||||||
await page.goto(path);
|
await page.goto(path);
|
||||||
@ -39,26 +54,56 @@ export async function expectPageFamily(page: Page, family: 'content' | 'landing'
|
|||||||
|
|
||||||
export async function expectPrimaryNavigation(page: Page): Promise<void> {
|
export async function expectPrimaryNavigation(page: Page): Promise<void> {
|
||||||
const header = page.getByRole('banner');
|
const header = page.getByRole('banner');
|
||||||
|
const expectedRoutes: Record<(typeof primaryNavigationLabels)[number], string> = {
|
||||||
|
Platform: '/platform',
|
||||||
|
Solutions: '/solutions',
|
||||||
|
Resources: '/changelog',
|
||||||
|
Pricing: '/contact',
|
||||||
|
Company: '/contact',
|
||||||
|
};
|
||||||
|
|
||||||
for (const label of primaryNavigationLabels) {
|
for (const label of primaryNavigationLabels) {
|
||||||
const link = header.getByRole('link', { name: label, exact: true }).first();
|
const link = header.getByRole('link', { name: label, exact: true }).first();
|
||||||
|
|
||||||
await expect(link).toBeVisible();
|
await expect(link).toBeVisible();
|
||||||
await expect(link).toHaveAttribute('data-nav-link');
|
await expect(link).toHaveAttribute('data-nav-link');
|
||||||
|
await expect(link).toHaveAttribute('href', expectedRoutes[label]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const label of hiddenPrimaryNavigationLabels) {
|
for (const label of hiddenPrimaryNavigationLabels) {
|
||||||
await expect(header.getByRole('link', { name: label, exact: true })).toHaveCount(0);
|
await expect(header.getByRole('link', { name: label, exact: true })).toHaveCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await expect(header.getByText('Sign in', { exact: true }).first()).toBeVisible();
|
||||||
|
await expect(header.locator('[data-nav-state="deferred"]').filter({ hasText: 'Sign in' }).first()).toBeVisible();
|
||||||
|
await expect(header.getByRole('link', { name: 'Book a demo', exact: true }).first()).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'/contact',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function expectFooterLinks(page: Page): Promise<void> {
|
export async function expectFooterLinks(page: Page): Promise<void> {
|
||||||
|
const footer = page.getByRole('contentinfo');
|
||||||
|
const expectedRoutes: Partial<Record<(typeof footerLabels)[number], string>> = {
|
||||||
|
'Explore the platform': '/platform',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const label of footerGroupLabels) {
|
||||||
|
await expect(footer.getByText(label, { exact: true }).first()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
for (const label of footerLabels) {
|
for (const label of footerLabels) {
|
||||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: label, exact: true })).toBeVisible();
|
const link = footer.getByRole('link', { name: label, exact: true }).first();
|
||||||
|
|
||||||
|
await expect(link).toBeVisible();
|
||||||
|
|
||||||
|
if (expectedRoutes[label]) {
|
||||||
|
await expect(link).toHaveAttribute('href', expectedRoutes[label]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const label of hiddenFooterLabels) {
|
for (const label of hiddenFooterLabels) {
|
||||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: label, exact: true })).toHaveCount(0);
|
await expect(footer.getByRole('link', { name: label, exact: true })).toHaveCount(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +310,7 @@ export async function expectNavigationVsCtaDifferentiation(page: Page): Promise<
|
|||||||
const header = page.getByRole('banner');
|
const header = page.getByRole('banner');
|
||||||
|
|
||||||
await expect(header.locator('[data-nav-link]').first()).toBeVisible();
|
await expect(header.locator('[data-nav-link]').first()).toBeVisible();
|
||||||
await expect(header.locator('[data-cta-weight="secondary"]').first()).toBeVisible();
|
await expect(header.locator('[data-header-cta]').first()).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function expectHomepageSectionOrder(page: Page, sections: string[]): Promise<void> {
|
export async function expectHomepageSectionOrder(page: Page, sections: string[]): Promise<void> {
|
||||||
@ -313,6 +358,17 @@ export async function expectMobileReadability(page: Page): Promise<void> {
|
|||||||
await expect(page.getByRole('contentinfo')).toBeVisible();
|
await expect(page.getByRole('contentinfo')).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectNoBodyHorizontalOverflow(page: Page): Promise<void> {
|
||||||
|
const overflow = await page.evaluate(() => {
|
||||||
|
const documentElement = document.documentElement;
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
return Math.max(documentElement.scrollWidth, body.scrollWidth) - documentElement.clientWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(overflow, 'Page should not create body-level horizontal overflow').toBeLessThanOrEqual(1);
|
||||||
|
}
|
||||||
|
|
||||||
export async function expectOnwardRouteReachable(page: Page, routes: string[]): Promise<void> {
|
export async function expectOnwardRouteReachable(page: Page, routes: string[]): Promise<void> {
|
||||||
const main = page.getByRole('main');
|
const main = page.getByRole('main');
|
||||||
|
|
||||||
|
|||||||
@ -13,13 +13,15 @@ test('representative pages route CTA, badge, surface, and input semantics throug
|
|||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await expectShell(page, /control surface/i);
|
await expectShell(page, 'Evidence-first governance for Microsoft tenants.');
|
||||||
await expectPageFamily(page, 'landing');
|
await expectPageFamily(page, 'landing');
|
||||||
await expectPrimaryNavigation(page);
|
await expectPrimaryNavigation(page);
|
||||||
await expectNavigationVsCtaDifferentiation(page);
|
await expectNavigationVsCtaDifferentiation(page);
|
||||||
await expectCtaHierarchy(page, 'Request a working session', /product model/i);
|
await expectCtaHierarchy(page, 'Book a demo', 'Explore the platform');
|
||||||
await expect(page.locator('[data-interaction="button"]').filter({ hasText: 'Request a working session' }).first()).toBeVisible();
|
await expect(page.locator('[data-interaction="button"]').filter({ hasText: 'Book a demo' }).first()).toBeVisible();
|
||||||
await expect(page.locator('[data-badge-tone]').first()).toBeVisible();
|
await expect(page.locator('[data-badge-tone]').first()).toBeVisible();
|
||||||
|
await expect(page.locator('[data-shell-surface="header"]').first()).toHaveAttribute('data-visual-tone', 'dark');
|
||||||
|
await expect(page.locator('[data-section="feature-pillars"] [data-surface="tenantial-pillar"]').first()).toBeVisible();
|
||||||
|
|
||||||
await visitPage(page, '/trust');
|
await visitPage(page, '/trust');
|
||||||
await expectShell(page, /trust posture|trust/i);
|
await expectShell(page, /trust posture|trust/i);
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
# Specification Quality Checklist: Tenantial Homepage Visual Rebuild
|
||||||
|
|
||||||
|
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||||
|
**Created**: 2026-05-17
|
||||||
|
**Feature**: [spec.md](../spec.md)
|
||||||
|
|
||||||
|
## Content Quality
|
||||||
|
|
||||||
|
- [x] No implementation details (languages, frameworks, APIs)
|
||||||
|
- [x] Focused on user value and business needs
|
||||||
|
- [x] Written for non-technical stakeholders
|
||||||
|
- [x] All mandatory sections completed
|
||||||
|
|
||||||
|
## Requirement Completeness
|
||||||
|
|
||||||
|
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||||
|
- [x] Requirements are testable and unambiguous
|
||||||
|
- [x] Success criteria are measurable
|
||||||
|
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||||
|
- [x] All acceptance scenarios are defined
|
||||||
|
- [x] Edge cases are identified
|
||||||
|
- [x] Scope is clearly bounded
|
||||||
|
- [x] Dependencies and assumptions identified
|
||||||
|
|
||||||
|
## Feature Readiness
|
||||||
|
|
||||||
|
- [x] All functional requirements have clear acceptance criteria
|
||||||
|
- [x] User scenarios cover primary flows
|
||||||
|
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||||
|
- [x] No implementation details leak into specification
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Validation iteration 1: Passed. The spec contains no clarification markers or template placeholders, keeps functional requirements focused on public homepage outcomes, and confines repo-specific validation language to the required testing impact section.
|
||||||
|
- Planning review correction: Approval class is exactly one class (`Core Enterprise`) per SPEC-GATE-001.
|
||||||
|
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`.
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: Tenantial Public Homepage Route Contract
|
||||||
|
version: "400.0.0"
|
||||||
|
summary: Static route expectations for the Tenantial homepage rebuild.
|
||||||
|
description: >
|
||||||
|
This contract documents public static website route behavior for Spec 400.
|
||||||
|
It does not introduce a backend API, authentication flow, database contract,
|
||||||
|
Microsoft Graph integration, or platform runtime dependency.
|
||||||
|
servers:
|
||||||
|
- url: http://127.0.0.1:4321
|
||||||
|
description: Local Astro website development server
|
||||||
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
summary: Render the Tenantial public homepage
|
||||||
|
operationId: renderTenantialHomepage
|
||||||
|
tags:
|
||||||
|
- public-website
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Static HTML homepage for Tenantial
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
examples:
|
||||||
|
homepage:
|
||||||
|
summary: Required first-read content
|
||||||
|
value: "<!doctype html><html><head><title>Tenantial - Evidence-first governance for Microsoft tenants</title></head><body>Evidence-first governance for Microsoft tenants.</body></html>"
|
||||||
|
x-route-kind: static-html
|
||||||
|
x-required-visible-content:
|
||||||
|
brand: Tenantial
|
||||||
|
headline: Evidence-first governance for Microsoft tenants.
|
||||||
|
primaryCta: Book a demo
|
||||||
|
secondaryCta: Explore the platform
|
||||||
|
requiredCapabilities:
|
||||||
|
- Backup
|
||||||
|
- Restore
|
||||||
|
- Drift Detection
|
||||||
|
- Evidence
|
||||||
|
- Audit Trail
|
||||||
|
- Governance Reviews
|
||||||
|
dashboardMetrics:
|
||||||
|
overallPosture: "92%"
|
||||||
|
findings: "14"
|
||||||
|
driftDetected: "7"
|
||||||
|
evidenceItems: "1,248"
|
||||||
|
backupStatus: "98%"
|
||||||
|
x-required-sections:
|
||||||
|
- Header
|
||||||
|
- Hero
|
||||||
|
- DashboardPreview
|
||||||
|
- TrustBar
|
||||||
|
- FeaturePillars
|
||||||
|
- CTASection
|
||||||
|
- Footer
|
||||||
|
x-forbidden-visible-or-metadata-content:
|
||||||
|
- AstroDeck
|
||||||
|
- Open Source
|
||||||
|
- MIT Licensed
|
||||||
|
- TenantCTRL
|
||||||
|
- TenantPilot
|
||||||
|
- TenantAtlas
|
||||||
|
x-trust-claim-boundary:
|
||||||
|
allowed:
|
||||||
|
- Microsoft tenant focused
|
||||||
|
- Evidence-oriented workflows
|
||||||
|
- Designed for audit review workflows
|
||||||
|
- Operator-led governance
|
||||||
|
forbidden-unless-verified:
|
||||||
|
- Real customer logos
|
||||||
|
- SOC 2 certification
|
||||||
|
- ISO certification
|
||||||
|
- 99.9% uptime
|
||||||
|
- Trusted by named real companies
|
||||||
|
x-accessibility-contract:
|
||||||
|
primaryHeadings: 1
|
||||||
|
focusVisible: true
|
||||||
|
keyboardReachableNavigation: true
|
||||||
|
nonColorOnlyStatus: true
|
||||||
|
mobileBodyHorizontalOverflow: false
|
||||||
|
/contact:
|
||||||
|
get:
|
||||||
|
summary: Intentional destination for Book a demo
|
||||||
|
operationId: renderContactDestination
|
||||||
|
tags:
|
||||||
|
- public-website
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Existing public contact route used as the demo destination
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-navigation-role: primary-homepage-cta-target
|
||||||
|
/product:
|
||||||
|
get:
|
||||||
|
summary: Intentional destination for Explore the platform
|
||||||
|
operationId: renderProductDestination
|
||||||
|
tags:
|
||||||
|
- public-website
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Existing public product route used as the platform exploration destination
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-navigation-role: secondary-homepage-cta-target
|
||||||
216
specs/400-tenantial-homepage-visual-rebuild/data-model.md
Normal file
216
specs/400-tenantial-homepage-visual-rebuild/data-model.md
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
# Data Model: Tenantial Homepage Visual Rebuild
|
||||||
|
|
||||||
|
This feature introduces no persisted database model, no runtime API schema, and no platform data contract. The models below describe static public website content that supports implementation and test planning.
|
||||||
|
|
||||||
|
## Entity: Homepage Message
|
||||||
|
|
||||||
|
**Purpose**: Defines the first-read Tenantial brand, product category, promise, and CTA hierarchy for `/`.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `brandName`: Must be `Tenantial`.
|
||||||
|
- `eyebrow`: Must communicate "GOVERNANCE THAT EARNS TRUST" or equivalent.
|
||||||
|
- `headline`: Must be "Evidence-first governance for Microsoft tenants."
|
||||||
|
- `supportingCopy`: Must mention backup, restore, drift detection, snapshot-backed audit context, evidence, and structured reviews.
|
||||||
|
- `primaryCta`: CTA with label `Book a demo` and an intentional destination.
|
||||||
|
- `secondaryCta`: CTA with label `Explore the platform` and an intentional destination.
|
||||||
|
- `trustBullets`: Up to three short trust cues.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Owns one primary CTA and one secondary CTA.
|
||||||
|
- Appears before Dashboard Preview and supporting homepage sections.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Exactly one primary page heading must be present.
|
||||||
|
- Public visible copy must not include old/template brand names.
|
||||||
|
- CTA hierarchy must remain primary `Book a demo`, secondary `Explore the platform`.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: Header Navigation Item
|
||||||
|
|
||||||
|
**Purpose**: Defines one visible public header label and its behavior.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `label`: One of Platform, Solutions, Resources, Pricing, Company, Sign in, Book a demo.
|
||||||
|
- `destination`: Existing route, known external URL, or no-link/de-emphasized text for unavailable sign-in.
|
||||||
|
- `priority`: Primary CTA, secondary utility, or navigation.
|
||||||
|
- `behavior`: Link, CTA, or inert/de-emphasized label.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Header owns multiple navigation items.
|
||||||
|
- CTA items point visitors toward Contact or Product routes.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Must not point to dead template routes.
|
||||||
|
- `Book a demo` must remain the primary visible action.
|
||||||
|
- `Sign in` must not imply implemented auth unless a real sign-in destination exists.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: Static Dashboard Preview
|
||||||
|
|
||||||
|
**Purpose**: Communicates product-near governance posture using static demo values.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `overallPosture`: Static demo value `92%`.
|
||||||
|
- `findings`: Static demo value `14`.
|
||||||
|
- `driftDetected`: Static demo value `7`.
|
||||||
|
- `evidenceItems`: Static demo value `1,248`.
|
||||||
|
- `backupStatus`: Static demo value `98%`.
|
||||||
|
- `panels`: Recent findings, drift timeline, backups and restores, reviews, evidence spotlight.
|
||||||
|
- `statusLabels`: Text labels that explain status meaning without relying on color only.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Contains Dashboard Metrics and Dashboard Panels.
|
||||||
|
- Appears with or immediately after the Hero.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Must not depend on backend/API/auth/tenant/platform data.
|
||||||
|
- Must not claim to be live or realtime.
|
||||||
|
- Must remain readable on desktop and usable on mobile without body-level horizontal overflow.
|
||||||
|
- Status meaning must be available through text, not only color.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: Dashboard Metric
|
||||||
|
|
||||||
|
**Purpose**: A single static value in the Dashboard Preview.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `label`: Human-readable metric name.
|
||||||
|
- `value`: Static demo value.
|
||||||
|
- `tone`: Healthy, warning, critical, evidence, or neutral.
|
||||||
|
- `description`: Short text explaining the metric.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Belongs to Static Dashboard Preview.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Tone must not be the only status signal.
|
||||||
|
- Value must be static and clearly demo-like.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: Trust Statement
|
||||||
|
|
||||||
|
**Purpose**: Provides credibility without fake proof.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `title`: Short credibility statement.
|
||||||
|
- `description`: One-sentence explanation.
|
||||||
|
- `claimType`: Product focus, workflow discipline, audit review workflow design, or operator governance.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Appears inside Trust Bar.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Must not use real customer logos unless verified.
|
||||||
|
- Must not claim SOC 2, ISO, 99.9% uptime, or named customer trust unless verified.
|
||||||
|
- Must not overstate security/compliance posture.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: Feature Pillar
|
||||||
|
|
||||||
|
**Purpose**: Describes one required Tenantial homepage capability.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `title`: Backup, Restore, Drift Detection, Evidence, Audit Trail, or Governance Reviews.
|
||||||
|
- `description`: Concise capability explanation.
|
||||||
|
- `iconLabel`: Meaningful accessible label or decorative status.
|
||||||
|
- `proofBoundary`: Copy note ensuring unsupported claims are avoided.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Feature Pillars section contains exactly the required six capability pillars unless implementation explicitly keeps four to six cards while preserving all required capabilities.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Required capability titles must be visible.
|
||||||
|
- Copy must be short and product-specific.
|
||||||
|
- Unsupported security/compliance promises are forbidden.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: CTA Section
|
||||||
|
|
||||||
|
**Purpose**: Repeats the homepage's primary next step near the bottom of the page.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `headline`: "Build tenant governance on evidence, not assumptions." or equivalent.
|
||||||
|
- `primaryCta`: `Book a demo`.
|
||||||
|
- `secondaryCta`: `Explore the platform`.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Mirrors Homepage Message CTA hierarchy.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- CTA labels and destinations must match actual behavior.
|
||||||
|
- CTA text must not clip on mobile.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: Footer Link Group
|
||||||
|
|
||||||
|
**Purpose**: Provides public footer navigation without template residue.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `groupTitle`: Platform, Solutions, Resources, Pricing, Company, Contact, Legal, Privacy, or Security.
|
||||||
|
- `items`: Intentional links or route aliases to existing public pages.
|
||||||
|
- `brandName`: Tenantial.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Footer owns multiple Footer Link Groups.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Footer must not contain AstroDeck/template content or old public brand names.
|
||||||
|
- Legal/privacy/security links must point to existing routes where available.
|
||||||
|
- No fake social proof.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
|
|
||||||
|
## Entity: SEO Metadata
|
||||||
|
|
||||||
|
**Purpose**: Defines public search/social metadata for the homepage.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `title`: `Tenantial - Evidence-first governance for Microsoft tenants`.
|
||||||
|
- `description`: Tenantial-specific summary of backup, restore, drift detection, audit trails, evidence, and structured reviews.
|
||||||
|
- `ogTitle`: Evidence-first governance for Microsoft tenants.
|
||||||
|
- `ogDescription`: Evidence-oriented governance positioning without unsupported certification, uptime, customer, or security guarantees.
|
||||||
|
- `canonicalPath`: `/`.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Applies to Homepage Message and public route `/`.
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
|
||||||
|
- Must not include AstroDeck/template titles.
|
||||||
|
- Must not include old public brand names.
|
||||||
|
- Must not include unverified customer, certification, or uptime claims.
|
||||||
|
|
||||||
|
**State Transitions**: None.
|
||||||
201
specs/400-tenantial-homepage-visual-rebuild/plan.md
Normal file
201
specs/400-tenantial-homepage-visual-rebuild/plan.md
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
# Implementation Plan: Tenantial Homepage Visual Rebuild
|
||||||
|
|
||||||
|
**Branch**: `400-tenantial-homepage-visual-rebuild` | **Date**: 2026-05-17 | **Spec**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md)
|
||||||
|
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md`
|
||||||
|
|
||||||
|
**Status**: Implementation complete in this branch. This plan now records the bounded Spec 400 implementation slice and its validation path.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Rebuild the public `/` homepage in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` so Tenantial is presented as a premium dark enterprise SaaS product for evidence-first Microsoft tenant governance. The implementation approach is a static Astro website slice: update homepage content, public shell/navigation/footer, website-local design tokens, a static product-near dashboard preview, SEO metadata, narrow public-page brand consistency, and Playwright smoke expectations without adding platform runtime, backend data, auth, database, Filament, Livewire, or Microsoft Graph coupling.
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
**Language/Version**: TypeScript 5.9, Astro 6 static components, HTML, CSS
|
||||||
|
**Primary Dependencies**: Astro 6.0.0, Tailwind CSS 4.2.2 through CSS-first `@theme` and `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, Playwright 1.59.1
|
||||||
|
**Storage**: N/A - static public website content only; no database or persisted runtime data
|
||||||
|
**Testing**: Playwright smoke tests under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke` plus Astro static build
|
||||||
|
**Validation Lanes**: browser
|
||||||
|
**Target Platform**: Static public website generated by Astro and served by the website deployment container
|
||||||
|
**Project Type**: Web frontend static site in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`
|
||||||
|
**Performance Goals**: Static homepage renders without heavy third-party scripts, body-level horizontal overflow, clipped CTA text, or live-data dependencies; desktop and mobile first-read content remains usable and readable
|
||||||
|
**Constraints**: No platform/auth/API/database/Graph coupling; no fake customer proof, certifications, or uptime claims; Tailwind v4 conventions only; use existing workspace script names and `WEBSITE_PORT` behavior; dark page must maintain accessible focus, contrast, and non-color-only status meaning; dark visual direction must use the Spec 400 Obsidian/Ivory/Mint baseline: near-black page background, ivory primary text, muted warm gray secondary text, mint primary accent, amber warning, coral critical, violet evidence/review accents, and WCAG-readable contrast for text and controls
|
||||||
|
**Scale/Scope**: One public route (`/`) plus shared public shell elements used by that route: header, footer, SEO metadata, website-local styles/tokens, static homepage sections, homepage smoke tests, and minimal brand/SEO cleanup on existing public pages reachable from the global shell
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Plan
|
||||||
|
|
||||||
|
- **Guardrail scope**: No operator-facing surface change.
|
||||||
|
- **Native vs custom classification summary**: N/A for Filament/admin surfaces. Public website components may be customized within `apps/website`.
|
||||||
|
- **Shared-family relevance**: None for operator-facing shared families.
|
||||||
|
- **State layers in scope**: Public website page and shell state only; no admin shell, detail state, URL-query state, tenant state, or operation state.
|
||||||
|
- **Audience modes in scope**: Public visitor, MSP buyer, enterprise IT buyer, security/compliance reviewer.
|
||||||
|
- **Decision/diagnostic/raw hierarchy plan**: Public marketing first-read content only; no operator diagnostics or support/raw evidence surfaces.
|
||||||
|
- **Raw/support gating plan**: N/A.
|
||||||
|
- **One-primary-action / duplicate-truth control**: Homepage keeps "Book a demo" as the primary CTA and "Explore the platform" as secondary; repeated CTA sections preserve this hierarchy.
|
||||||
|
- **Handling modes by drift class or surface**: N/A.
|
||||||
|
- **Repository-signal treatment**: Report-only for website smoke review; no guardrail exception.
|
||||||
|
- **Special surface test profiles**: N/A.
|
||||||
|
- **Required tests or manual smoke**: Homepage Playwright smoke coverage, metadata/template-residue assertions, mobile overflow check, desktop/mobile screenshot review.
|
||||||
|
- **Exception path and spread control**: None.
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage.
|
||||||
|
|
||||||
|
## Shared Pattern & System Fit
|
||||||
|
|
||||||
|
- **Cross-cutting feature marker**: No for operator-facing systems; yes only in the public website sense that header/footer/site metadata may be updated for Tenantial.
|
||||||
|
- **Systems touched**: Public website app under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`; homepage page, content, layout navigation/footer, styles, static visual component, existing public-page brand copy, and smoke tests.
|
||||||
|
- **Shared abstractions reused**: Existing website shell, layout, primitive/content/section components where they fit; existing Playwright smoke helper style.
|
||||||
|
- **New abstraction introduced? why?**: No runtime or domain abstraction. A page-local static dashboard preview component is acceptable as presentation, not shared product truth.
|
||||||
|
- **Why the existing abstraction was sufficient or insufficient**: Existing Astro/Tailwind/Playwright website structure is sufficient. Existing copy, light palette, TenantAtlas brand, and product visual direction are insufficient for the Tenantial dark enterprise homepage.
|
||||||
|
- **Bounded deviation / spread control**: Homepage-specific static preview and dark visual treatment stay in `apps/website`; no platform or admin design system changes. Existing internal workspace names such as `@tenantatlas/website` remain unchanged.
|
||||||
|
|
||||||
|
## OperationRun UX Impact
|
||||||
|
|
||||||
|
- **Touches OperationRun start/completion/link UX?**: No.
|
||||||
|
- **Central contract reused**: N/A.
|
||||||
|
- **Delegated UX behaviors**: N/A.
|
||||||
|
- **Surface-owned behavior kept local**: N/A.
|
||||||
|
- **Queued DB-notification policy**: N/A.
|
||||||
|
- **Terminal notification path**: N/A.
|
||||||
|
- **Exception path**: None.
|
||||||
|
|
||||||
|
## Provider Boundary & Portability Fit
|
||||||
|
|
||||||
|
- **Shared provider/platform boundary touched?**: No.
|
||||||
|
- **Provider-owned seams**: N/A.
|
||||||
|
- **Platform-core seams**: N/A.
|
||||||
|
- **Neutral platform terms / contracts preserved**: Existing runtime contracts remain unchanged.
|
||||||
|
- **Retained provider-specific semantics and why**: Marketing language may say "Microsoft tenants" because the public product positioning targets that environment. It must not change provider contracts, identifiers, compare semantics, governed-subject taxonomy, or runtime naming.
|
||||||
|
- **Bounded extraction or follow-up path**: None.
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
### Pre-Design Gate
|
||||||
|
|
||||||
|
PASS. No unresolved gate failures.
|
||||||
|
|
||||||
|
- **Spec Candidate Gate**: Passed in spec with exactly one approval class (`Core Enterprise`), approval score 11/12, and decision `approve`.
|
||||||
|
- **Inventory / snapshots / Graph / deterministic capabilities**: N/A. No platform inventory, snapshots, Graph calls, or capability resolver behavior.
|
||||||
|
- **Read/write separation**: PASS. Public static website change only; no write/change operation.
|
||||||
|
- **RBAC / workspace / tenant isolation**: N/A. Public homepage introduces no authorization, tenant route, workspace context, global search, or destructive action.
|
||||||
|
- **OperationRun / Ops-UX**: N/A. No queued, scheduled, remote, long-running, or operation-link behavior.
|
||||||
|
- **Data minimization**: PASS. Static demo preview data only; no secrets, tenant data, or live payloads.
|
||||||
|
- **Test governance**: PASS. Browser lane is explicit and scoped to homepage route/build behavior.
|
||||||
|
- **Proportionality / bloat**: PASS. No persisted entity, enum/status family, resolver, registry, interface, DTO layer, or cross-domain UI framework.
|
||||||
|
- **Shared pattern first**: PASS. Reuse website shell/primitives/test helpers where practical; no operator-facing shared path touched.
|
||||||
|
- **Provider boundary**: PASS. Microsoft tenant wording remains marketing positioning and does not enter platform-core contracts.
|
||||||
|
- **Filament / Livewire**: N/A. No Filament v5, Livewire v4, provider registration, global search, destructive action, or Filament asset behavior is touched.
|
||||||
|
|
||||||
|
### Post-Design Gate
|
||||||
|
|
||||||
|
PASS. Phase 0 and Phase 1 artifacts keep the feature static, bounded, and website-local.
|
||||||
|
|
||||||
|
- `research.md` resolves technical choices without adding framework or runtime coupling.
|
||||||
|
- `data-model.md` models static content only and explicitly avoids persisted data.
|
||||||
|
- `contracts/public-homepage.yaml` documents public route expectations and does not define a backend API.
|
||||||
|
- `quickstart.md` uses existing website build/test commands and does not introduce Sail, Laravel, Filament, database, or platform setup.
|
||||||
|
- No `NEEDS CLARIFICATION` markers remain.
|
||||||
|
|
||||||
|
## Test Governance Check
|
||||||
|
|
||||||
|
- **Test purpose / classification by changed surface**: Browser for public homepage route, content, responsive behavior, metadata, and visual smoke.
|
||||||
|
- **Affected validation lanes**: browser.
|
||||||
|
- **Why this lane mix is the narrowest sufficient proof**: The behavior is user-visible static website rendering. Browser smoke tests and build catch route, content, responsive, and metadata regressions without database or platform setup.
|
||||||
|
- **Narrowest proving command(s)**: `cd /Users/ahmeddarrazi/Documents/projects/wt-website && corepack pnpm build:website`; `cd /Users/ahmeddarrazi/Documents/projects/wt-website && WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`
|
||||||
|
- **Fixture / helper / factory / seed / context cost risks**: None. No DB, session, workspace, tenant, provider, member, or seed setup.
|
||||||
|
- **Expensive defaults or shared helper growth introduced?**: No. Any smoke helper additions must remain website-only.
|
||||||
|
- **Heavy-family additions, promotions, or visibility changes**: Browser coverage is deliberate and homepage-scoped; no heavy-governance family.
|
||||||
|
- **Surface-class relief / special coverage rule**: N/A.
|
||||||
|
- **Closing validation and reviewer handoff**: Reviewers should verify no platform setup appears in website tests, no unverified trust claims appear, no old brand/template residue remains on the homepage or globally reachable public shell, and mobile body overflow is absent.
|
||||||
|
- **Budget / baseline / trend follow-up**: None expected.
|
||||||
|
- **Review-stop questions**: Stop if smoke tests require platform state, if the dashboard preview depends on live data, if unsupported trust claims appear, if hidden horizontal overflow remains, or if navigation points to stale template routes.
|
||||||
|
- **Escalation path**: document-in-feature.
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage.
|
||||||
|
- **Why no dedicated follow-up spec is needed**: This is a contained homepage browser smoke update. A separate follow-up is only needed if the visual system expands into a cross-page public website framework.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/
|
||||||
|
├── plan.md
|
||||||
|
├── research.md
|
||||||
|
├── data-model.md
|
||||||
|
├── quickstart.md
|
||||||
|
├── contracts/
|
||||||
|
│ └── public-homepage.yaml
|
||||||
|
└── tasks.md # Created later by /speckit.tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
|
||||||
|
```text
|
||||||
|
/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/
|
||||||
|
├── src/
|
||||||
|
│ ├── pages/
|
||||||
|
│ │ └── index.astro
|
||||||
|
│ ├── content/pages/
|
||||||
|
│ │ └── home.ts
|
||||||
|
│ ├── lib/
|
||||||
|
│ │ ├── site.ts
|
||||||
|
│ │ └── seo.ts
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── layout/
|
||||||
|
│ │ │ ├── Navbar.astro
|
||||||
|
│ │ │ ├── PageShell.astro
|
||||||
|
│ │ │ └── Footer.astro
|
||||||
|
│ │ ├── content/
|
||||||
|
│ │ │ └── DashboardPreview.astro # expected new or renamed static preview
|
||||||
|
│ │ └── sections/
|
||||||
|
│ │ ├── FeaturePillars.astro # expected new or adapted homepage section
|
||||||
|
│ │ ├── TrustBar.astro # expected new or adapted homepage section
|
||||||
|
│ │ └── CTASection.astro
|
||||||
|
│ ├── styles/
|
||||||
|
│ │ ├── tokens.css
|
||||||
|
│ │ └── global.css
|
||||||
|
│ └── types/
|
||||||
|
│ └── site.ts
|
||||||
|
├── public/
|
||||||
|
│ └── favicon.svg
|
||||||
|
└── tests/smoke/
|
||||||
|
├── changelog-core-ia.spec.ts
|
||||||
|
├── home-product.spec.ts
|
||||||
|
├── smoke-helpers.ts
|
||||||
|
└── visual-foundation-guardrails.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure Decision**: Use the existing Astro website app and existing smoke-test family. The implementation should prefer adapting existing content/layout/primitives before adding new components. Any new dashboard or section component must remain website-local presentation and must not become a shared platform/admin abstraction.
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||||
|
|-----------|------------|-------------------------------------|
|
||||||
|
| None | N/A | N/A |
|
||||||
|
|
||||||
|
## Proportionality Review
|
||||||
|
|
||||||
|
- **Current operator problem**: Prospective buyers and stakeholders cannot currently understand Tenantial's Microsoft tenant governance value from the public homepage.
|
||||||
|
- **Existing structure is insufficient because**: Current homepage content, brand naming, light visual direction, and product visual do not match the premium dark Tenantial direction or required evidence-first story.
|
||||||
|
- **Narrowest correct implementation**: One static homepage slice plus the public shell/styling/smoke checks needed for that page.
|
||||||
|
- **Ownership cost created**: Website-local homepage content, static preview data, navigation mapping, public SEO metadata, and smoke assertions must be maintained.
|
||||||
|
- **Alternative intentionally rejected**: Full multi-page website rebuild or platform-coupled product data preview.
|
||||||
|
- **Release truth**: Current public website direction only.
|
||||||
|
|
||||||
|
## Phase 0: Research Output
|
||||||
|
|
||||||
|
Completed in [research.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/research.md). All planning unknowns are resolved.
|
||||||
|
|
||||||
|
## Phase 1: Design And Contracts Output
|
||||||
|
|
||||||
|
Completed artifacts:
|
||||||
|
|
||||||
|
- [data-model.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/data-model.md)
|
||||||
|
- [contracts/public-homepage.yaml](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml)
|
||||||
|
- [quickstart.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/quickstart.md)
|
||||||
|
|
||||||
|
Agent context update command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/ahmeddarrazi/Documents/projects/wt-website
|
||||||
|
.specify/scripts/bash/update-agent-context.sh codex
|
||||||
|
```
|
||||||
73
specs/400-tenantial-homepage-visual-rebuild/quickstart.md
Normal file
73
specs/400-tenantial-homepage-visual-rebuild/quickstart.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Quickstart: Tenantial Homepage Visual Rebuild
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Work from `/Users/ahmeddarrazi/Documents/projects/wt-website`.
|
||||||
|
- Use Node.js 20+ with Corepack and pnpm, matching the repo root `packageManager`.
|
||||||
|
- This feature does not require Sail, Laravel, Filament, a database, Microsoft Graph credentials, or platform auth.
|
||||||
|
|
||||||
|
## Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/ahmeddarrazi/Documents/projects/wt-website
|
||||||
|
corepack pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run The Website Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/ahmeddarrazi/Documents/projects/wt-website
|
||||||
|
WEBSITE_PORT=4321 corepack pnpm dev:website
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://127.0.0.1:4321/`.
|
||||||
|
|
||||||
|
## Build Validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/ahmeddarrazi/Documents/projects/wt-website
|
||||||
|
corepack pnpm build:website
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected result: Astro builds the static website without requiring platform services.
|
||||||
|
|
||||||
|
## Browser Smoke Validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/ahmeddarrazi/Documents/projects/wt-website
|
||||||
|
WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected proof:
|
||||||
|
|
||||||
|
- `/` renders the Tenantial homepage.
|
||||||
|
- The headline "Evidence-first governance for Microsoft tenants." is visible.
|
||||||
|
- `Book a demo` and `Explore the platform` are visible with the correct hierarchy.
|
||||||
|
- Static dashboard preview content is visible.
|
||||||
|
- Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews are visible.
|
||||||
|
- Old/template public copy is absent from visible homepage content and metadata.
|
||||||
|
- Existing public pages reachable from the global shell use Tenantial visible brand copy and SEO metadata.
|
||||||
|
- Mobile viewport has no body-level horizontal overflow.
|
||||||
|
|
||||||
|
## Manual Review
|
||||||
|
|
||||||
|
Use desktop and mobile browser review after implementation.
|
||||||
|
|
||||||
|
Review points:
|
||||||
|
|
||||||
|
- Tenantial is the only public brand visible on the homepage.
|
||||||
|
- No AstroDeck, Open Source, MIT Licensed, TenantCTRL, TenantPilot, or TenantAtlas homepage residue remains.
|
||||||
|
- No unverified customer logos, certification claims, uptime claims, or fake social proof appear.
|
||||||
|
- Dashboard preview labels use static/sample/demo wording and do not show future-dated timestamps.
|
||||||
|
- Header labels and CTA destinations are intentional.
|
||||||
|
- Dashboard preview is static and does not imply live/realtime data.
|
||||||
|
- Text does not clip, overlap, or create mobile body overflow.
|
||||||
|
- Focus states are visible and mobile navigation is keyboard usable.
|
||||||
|
|
||||||
|
## Out Of Scope For This Feature
|
||||||
|
|
||||||
|
- Do not add demo-booking backend behavior.
|
||||||
|
- Do not add sign-in/auth behavior.
|
||||||
|
- Do not add platform API calls.
|
||||||
|
- Do not add Laravel, Filament, Livewire, Microsoft Graph, database, queue, or OperationRun behavior.
|
||||||
|
- Do not rename root packages or existing workspace scripts.
|
||||||
94
specs/400-tenantial-homepage-visual-rebuild/research.md
Normal file
94
specs/400-tenantial-homepage-visual-rebuild/research.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Phase 0 Research: Tenantial Homepage Visual Rebuild
|
||||||
|
|
||||||
|
## Decision: Keep the implementation inside the existing Astro website app
|
||||||
|
|
||||||
|
**Decision**: Use `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` as the only runtime surface for this feature.
|
||||||
|
|
||||||
|
**Rationale**: The feature is a public static homepage rebuild. The repo already has a standalone Astro website app with static output, a public route structure, shared layout components, SEO helpers, Tailwind v4 styling, and Playwright smoke tests. This matches the feature without platform runtime setup.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Add platform/Laravel/Filament integration: rejected because the spec forbids platform coupling and this is public marketing content.
|
||||||
|
- Create a separate website app: rejected because an Astro website app already exists.
|
||||||
|
- Use a CMS/content collection strategy now: rejected because the spec explicitly excludes CMS and full content architecture.
|
||||||
|
|
||||||
|
## Decision: Update website-local brand metadata and public brand copy from TenantAtlas to Tenantial
|
||||||
|
|
||||||
|
**Decision**: Treat Tenantial as the public brand for the homepage and update homepage-visible copy, SEO metadata, header/footer labels, smoke expectations, and narrow brand/SEO copy on existing public pages reachable from the global shell accordingly.
|
||||||
|
|
||||||
|
**Rationale**: The current website content and `siteMetadata` still identified the public brand as TenantAtlas. The spec requires Tenantial to be the only visible public brand on the homepage and requires old/template brand residue to be absent. Because the updated header/footer route visitors to existing public pages, those pages also need minimal brand consistency cleanup so the website does not expose mixed public-brand SEO/copy.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Leave shared metadata as TenantAtlas and override only the homepage: rejected because header/footer/OG metadata would still leak the old public brand on the homepage.
|
||||||
|
- Rename root packages and internal workspace names: rejected because the spec requires internal package names and workspace scripts to remain unchanged.
|
||||||
|
- Redesign secondary public pages: rejected because Spec 400 is a homepage implementation slice; secondary pages receive only narrow brand/SEO consistency cleanup.
|
||||||
|
|
||||||
|
## Decision: Use Tailwind CSS v4 CSS-first tokens and existing global styles
|
||||||
|
|
||||||
|
**Decision**: Implement the premium dark visual direction by adapting `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/tokens.css` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css`, using Tailwind v4-compatible CSS-first `@theme` tokens and existing utility patterns.
|
||||||
|
|
||||||
|
**Rationale**: The app already imports Tailwind with `@import "tailwindcss"` and defines tokens with `@theme`. This matches project and Tailwind v4 conventions and avoids deprecated Tailwind v3 utilities.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Add `tailwind.config.js`: rejected because Tailwind v4 in this project is CSS-first.
|
||||||
|
- Add a one-off inline style block for all homepage visuals: rejected because homepage, header, footer, and CTA surfaces need consistent website-local tokens.
|
||||||
|
- Add a heavy visual library: rejected because the spec requires no heavy third-party scripts for static visuals.
|
||||||
|
|
||||||
|
## Decision: Build the dashboard preview as static semantic HTML/CSS, not a raster screenshot or live data
|
||||||
|
|
||||||
|
**Decision**: Implement a static product-near dashboard preview with fixed demo values and semantic labels.
|
||||||
|
|
||||||
|
**Rationale**: The spec requires the preview to be sharp, responsive, and clearly static. It must not imply live product data or depend on backend/API/auth/tenant data. HTML/CSS keeps the preview accessible, inspectable, responsive, and testable.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Raster screenshot: rejected because the spec asks for HTML/CSS/SVG and because text/status assertions would be weaker.
|
||||||
|
- Live dashboard data: rejected because the spec forbids backend/API/platform coupling.
|
||||||
|
- Interactive preview controls: rejected because they could imply functional product behavior that is not implemented.
|
||||||
|
|
||||||
|
## Decision: Map homepage navigation to existing intentional routes and de-emphasize sign-in
|
||||||
|
|
||||||
|
**Decision**: Use existing routes for visible CTAs where possible: `Book a demo` -> `/contact`, `Explore the platform` -> `/product`, `Platform` -> `/product`, and `Solutions` -> `/solutions`. `Resources`, `Pricing`, `Company`, and `Sign in` currently have no implemented same-name routes; they must be intentionally handled through existing routes, disabled/de-emphasized treatment, or omitted from functional link behavior instead of silently pointing at dead routes. `Sign in` must be text-only/de-emphasized or point only to a known valid platform URL if one is confirmed during implementation.
|
||||||
|
|
||||||
|
**Rationale**: The current website has `/product`, `/solutions`, `/contact`, `/trust`, `/changelog`, `/integrations`, and legal routes, but no `/pricing`, `/company`, `/resources`, or known sign-in route. `/resources` is currently gated off, and existing tests expect it to stay hidden until substantive material exists. The spec requires intentional links and forbids silent dead template routes or implying implemented auth.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Create secondary pages now: rejected because the spec excludes a full multi-page website build.
|
||||||
|
- Link to missing future routes: rejected because the spec requires intentional, non-dead routes.
|
||||||
|
- Hide all unavailable labels: rejected because the spec expects the desktop header to carry the named navigation labels.
|
||||||
|
|
||||||
|
## Decision: Use Playwright smoke coverage plus static build as validation
|
||||||
|
|
||||||
|
**Decision**: Validate with `corepack pnpm build:website` and the existing website Playwright smoke lane.
|
||||||
|
|
||||||
|
**Rationale**: This change is user-visible static website behavior. Browser smoke tests can verify route rendering, brand/copy, CTA hierarchy, forbidden copy, responsive overflow, metadata, and accessibility-relevant structure without database or platform setup.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Laravel/Pest tests: rejected because no Laravel/platform behavior is changed.
|
||||||
|
- Unit tests for every presentation component: rejected because this would over-test thin static presentation and add maintenance without proving user value.
|
||||||
|
- Visual regression infrastructure: deferred because this spec only requires browser screenshot review, not a permanent screenshot regression system.
|
||||||
|
|
||||||
|
## Decision: No backend API contract is introduced
|
||||||
|
|
||||||
|
**Decision**: The contract artifact documents the public static route expectations instead of defining a backend API.
|
||||||
|
|
||||||
|
**Rationale**: The homepage has user actions such as opening `/` and following links, but no data submission, backend mutation, authentication, or API response. A route contract gives reviewers a concrete acceptance surface while preserving the spec's no-backend boundary.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Generate REST endpoints for demo booking or sign-in: rejected because those flows are out of scope.
|
||||||
|
- Skip contracts entirely: rejected because the `/speckit.plan` workflow requires a contracts artifact.
|
||||||
|
|
||||||
|
## Decision: No unresolved clarifications remain
|
||||||
|
|
||||||
|
**Decision**: All open implementation choices have reasonable defaults from the spec.
|
||||||
|
|
||||||
|
**Rationale**: The feature has defaults for brand, CTA destinations, logo availability, customer proof, static preview content, and secondary pages. None require user clarification before planning.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Ask whether `/pricing`, `/company`, or `/resources` pages should be built: rejected because the spec's non-goals and assumptions exclude secondary pages for this slice.
|
||||||
260
specs/400-tenantial-homepage-visual-rebuild/spec.md
Normal file
260
specs/400-tenantial-homepage-visual-rebuild/spec.md
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# Feature Specification: Tenantial Homepage Visual Rebuild
|
||||||
|
|
||||||
|
**Feature Branch**: `400-tenantial-homepage-visual-rebuild`
|
||||||
|
**Created**: 2026-05-17
|
||||||
|
**Status**: Implemented (ready to merge)
|
||||||
|
**Input**: User description: "Rebuild the public website homepage for Tenantial to match a premium dark enterprise SaaS mockup direction, positioning Tenantial as evidence-first governance for Microsoft tenants."
|
||||||
|
|
||||||
|
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||||
|
|
||||||
|
- **Problem**: The public homepage still carries generic SaaS/template signals and does not clearly position Tenantial as a serious Microsoft tenant governance product.
|
||||||
|
- **Today's failure**: Visitors can leave without understanding that Tenantial focuses on backup, restore, drift detection, evidence, audit trails, and structured governance reviews for Microsoft tenants.
|
||||||
|
- **User-visible improvement**: The homepage immediately communicates Tenantial's evidence-first governance value, uses a distinct premium dark identity, and shows a product-near static dashboard preview without implying live data.
|
||||||
|
- **Smallest enterprise-capable version**: Rebuild only the public homepage and the shared public shell needed by that page: header, hero, static dashboard preview, trust bar, feature pillars, call to action, footer, SEO metadata, homepage smoke expectations, and minimal public-page brand consistency where globally reachable pages would otherwise mix Tenantial and TenantAtlas.
|
||||||
|
- **Explicit non-goals**: No platform UI, no Filament/admin changes, no authentication flow, no CMS, no backend data, no demo-booking backend, no database changes, no Microsoft Graph integration, and no full multi-page website build.
|
||||||
|
- **Permanent complexity imported**: Public homepage brand direction, static marketing content, a static product-preview concept, homepage smoke coverage, and brand/SEO cleanup expectations.
|
||||||
|
- **Why now**: The project needs one credible Tenantial homepage direction before expanding into platform, pricing, trust, resources, or company pages.
|
||||||
|
- **Why not local**: Page-local copy changes alone would leave brand hierarchy, navigation intent, trust boundaries, product-preview language, and template cleanup inconsistent.
|
||||||
|
- **Approval class**: Core Enterprise
|
||||||
|
- **Red flags triggered**: None. The work creates no persisted truth, no domain state, no runtime abstraction, and no cross-domain UI framework.
|
||||||
|
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12**
|
||||||
|
- **Decision**: approve
|
||||||
|
|
||||||
|
## Spec Scope Fields *(mandatory)*
|
||||||
|
|
||||||
|
- **Scope**: Public website homepage. This is outside workspace, tenant, and canonical admin scopes.
|
||||||
|
- **Primary Routes**: `/`
|
||||||
|
- **Data Ownership**: Static public website content and static marketing preview data only. No workspace-owned, tenant-owned, platform runtime, or persisted application data is introduced.
|
||||||
|
- **RBAC**: None. The homepage is public and introduces no authorization behavior.
|
||||||
|
|
||||||
|
## Relationship To Existing Website Specs
|
||||||
|
|
||||||
|
- Spec 223 remains the historical AstroDeck reset/rebuild frame.
|
||||||
|
- Spec 214 remains useful background for website visual discipline.
|
||||||
|
- Spec 215 remains useful for public information architecture principles.
|
||||||
|
- Spec 217 and Spec 218 remain background for homepage structure and hero discipline.
|
||||||
|
- Spec 400 is the concrete homepage implementation slice for the Tenantial mockup direction.
|
||||||
|
- Spec 400 owns the homepage implementation direction where the Tenantial mockup direction is more specific than the older AstroDeck-era homepage specs.
|
||||||
|
- Spec 400 permits website-local homepage presentation components for this slice only. It does not create a reusable public-site framework and does not broaden the AstroDeck reset into platform/admin code.
|
||||||
|
- No `specs/227-*` artifact is present in the current checkout. No phantom Spec 227 is created here; Spec 400 replaces the previously discussed homepage/visual-foundation path for the homepage only.
|
||||||
|
|
||||||
|
## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*
|
||||||
|
|
||||||
|
N/A - no shared operator interaction family touched. This feature changes a public marketing homepage, not admin notifications, operator status messaging, operation links, dashboard signals, alerts, report viewers, or other shared operator interaction contracts.
|
||||||
|
|
||||||
|
## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)*
|
||||||
|
|
||||||
|
N/A - no OperationRun start, completion, deduplication, resume, block, or link semantics touched.
|
||||||
|
|
||||||
|
## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)*
|
||||||
|
|
||||||
|
N/A - no shared provider/platform boundary touched. The homepage may describe Microsoft tenant governance at a marketing level, but it does not change platform-core contracts, provider seams, identity scope, compare strategy, governed-subject taxonomy, or runtime vocabulary.
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
|
||||||
|
|
||||||
|
N/A - no operator-facing surface change. This feature changes a public marketing surface only and does not affect Filament/admin pages, tenant-scoped operator workflows, shared operator components, action surfaces, or governance decision surfaces.
|
||||||
|
|
||||||
|
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||||
|
|
||||||
|
- **New source of truth?**: Yes, limited to public homepage brand/content direction. No product runtime or tenant-governance truth is introduced.
|
||||||
|
- **New persisted entity/table/artifact?**: No.
|
||||||
|
- **New abstraction?**: No runtime abstraction. Any page components are website-local presentation pieces for this homepage slice.
|
||||||
|
- **New enum/state/reason family?**: No.
|
||||||
|
- **New cross-domain UI framework/taxonomy?**: No.
|
||||||
|
- **Current operator problem**: Prospective buyers and stakeholders cannot yet infer Tenantial's Microsoft tenant governance value from the public homepage.
|
||||||
|
- **Existing structure is insufficient because**: The current website direction still contains template residue and does not provide a focused brand, trust, and evidence story for Tenantial.
|
||||||
|
- **Narrowest correct implementation**: One homepage slice with static content, static preview data, clear CTA hierarchy, trust boundaries, accessibility/responsive expectations, SEO cleanup, and smoke coverage.
|
||||||
|
- **Ownership cost**: Homepage copy, static preview content, public navigation labels, and brand/SEO expectations must be maintained as later website pages are added.
|
||||||
|
- **Alternative intentionally rejected**: Rebuilding the full website now. That would expand scope before the homepage direction is proven.
|
||||||
|
- **Release truth**: Current public website direction, not platform runtime truth or future product data truth.
|
||||||
|
|
||||||
|
### Compatibility posture
|
||||||
|
|
||||||
|
This feature assumes a pre-production environment.
|
||||||
|
|
||||||
|
Backward compatibility for old public template pages, old TenantAtlas/TenantPilot public copy, deleted demo routes, and template homepage content is out of scope unless explicitly required by a later spec. Public pages reachable from the updated header/footer may receive narrow brand/SEO copy cleanup so the website does not expose a mixed Tenantial/TenantAtlas public brand.
|
||||||
|
|
||||||
|
Internal package names, workspace scripts, and platform runtime contracts must not be renamed by this feature. In particular, the internal workspace package name `@tenantatlas/website` remains unchanged.
|
||||||
|
|
||||||
|
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||||
|
|
||||||
|
- **Test purpose / classification**: Browser.
|
||||||
|
- **Validation lane(s)**: browser.
|
||||||
|
- **Why this classification and these lanes are sufficient**: The feature changes a public static website route, visual hierarchy, responsive behavior, copy, and SEO metadata. Browser smoke checks and screenshot review are the narrowest honest proof.
|
||||||
|
- **New or expanded test families**: Homepage website smoke coverage only.
|
||||||
|
- **Fixture / helper cost impact**: None. No database, workspace, membership, provider, session, seed, or platform setup should be required.
|
||||||
|
- **Heavy-family visibility / justification**: Browser coverage is intentional and scoped to the homepage shell, visual behavior, and template-residue checks.
|
||||||
|
- **Special surface test profile**: N/A.
|
||||||
|
- **Standard-native relief or required special coverage**: Homepage smoke checks plus desktop/mobile visual review.
|
||||||
|
- **Reviewer handoff**: Reviewers must confirm that browser coverage remains website-scoped, no hidden platform setup is introduced, and the proof checks visible content, metadata cleanup, and mobile overflow.
|
||||||
|
- **Budget / baseline / trend impact**: None expected beyond a small homepage smoke suite.
|
||||||
|
- **Escalation needed**: document-in-feature.
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage.
|
||||||
|
- **Planned validation commands**: repository website build command; website browser smoke test command; `git diff --check`; desktop and mobile browser review when screenshots are intentionally captured.
|
||||||
|
|
||||||
|
## User Scenarios & Testing *(mandatory)*
|
||||||
|
|
||||||
|
### User Story 1 - Understand Tenantial Immediately (Priority: P1)
|
||||||
|
|
||||||
|
A first-time visitor opens the homepage and understands that Tenantial is an evidence-first governance platform for Microsoft tenants, not a generic SaaS template.
|
||||||
|
|
||||||
|
**Why this priority**: This is the minimum viable homepage outcome. If the first viewport does not establish the brand, category, and core promise, the rebuild fails.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by opening `/` and verifying that the first viewport shows the Tenantial brand, the evidence-first governance headline, Microsoft tenant context, and primary/secondary CTAs.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a first-time visitor, **When** they open `/`, **Then** they see Tenantial as the public brand and the headline "Evidence-first governance for Microsoft tenants."
|
||||||
|
2. **Given** a visitor scanning the hero, **When** they read the supporting copy, **Then** they can identify backup, restore, drift detection, snapshot-backed audit context, evidence, and structured reviews as the product focus.
|
||||||
|
3. **Given** a visitor looking for the next step, **When** they inspect the hero actions, **Then** "Book a demo" is the primary CTA and "Explore the platform" is secondary.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 2 - Evaluate Trust Without False Proof (Priority: P2)
|
||||||
|
|
||||||
|
An IT, MSP, security, or compliance stakeholder reviews the homepage and sees credible trust positioning without fake customer logos, unsupported certifications, or exaggerated availability/security claims.
|
||||||
|
|
||||||
|
**Why this priority**: Tenantial operates in a governance and evidence domain; credibility depends on restrained claims and clear proof boundaries.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by reviewing the trust bar, feature pillars, static preview, and footer for allowed claims and absence of unverified social proof.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** no verified customer logos are available, **When** the trust bar renders, **Then** it uses neutral credibility statements rather than named real customer logos.
|
||||||
|
2. **Given** no verified security certifications are provided, **When** the homepage renders, **Then** it does not claim SOC 2, ISO certification, uptime guarantees, or "trusted by" proof.
|
||||||
|
3. **Given** a stakeholder reviewing capabilities, **When** they reach the feature pillars, **Then** Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews are all present with concise descriptions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - Inspect A Product-Near Preview (Priority: P3)
|
||||||
|
|
||||||
|
A visitor sees a product-near dashboard preview that communicates posture, findings, drift, evidence, backup status, reviews, and recent activity while clearly remaining static marketing content.
|
||||||
|
|
||||||
|
**Why this priority**: The mockup direction depends on a strong preview surface, but false live-data implications would undermine trust.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by checking that the dashboard preview is visible, readable, responsive, and framed as static demo content.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** the homepage has loaded, **When** a visitor views the preview, **Then** they see static demo values for overall posture, findings, drift detected, evidence items, and backup status.
|
||||||
|
2. **Given** the preview contains status information, **When** colors are removed or unavailable, **Then** labels or text still communicate the status meaning.
|
||||||
|
3. **Given** a visitor is on mobile, **When** they reach the preview, **Then** the page remains usable without body-level horizontal overflow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 4 - Verify Public Launch Readiness (Priority: P4)
|
||||||
|
|
||||||
|
A website owner reviews the homepage before launch and can confirm that old template residue, wrong brand names, broken CTAs, and unsupported promises are absent.
|
||||||
|
|
||||||
|
**Why this priority**: The rebuild should establish a clean baseline before later public pages reuse the direction.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested through homepage smoke checks, metadata review, link review, and desktop/mobile visual review.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** the homepage is ready for review, **When** the visible copy and metadata are checked, **Then** AstroDeck/template positioning and old public brand names are absent.
|
||||||
|
2. **Given** a reviewer inspects navigation, **When** they follow homepage CTAs and nav links, **Then** links are intentional and do not silently point to dead template routes.
|
||||||
|
3. **Given** a reviewer checks desktop and mobile, **When** the page is visually reviewed, **Then** text does not clip, CTAs remain visible, and the header adapts to the viewport.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- If dedicated destination pages do not yet exist, homepage links must still be intentional and must not point to stale template routes.
|
||||||
|
- If no real logo asset is available, the homepage may use a simple Tenantial mark that does not imply a third-party endorsement.
|
||||||
|
- If no real customer proof is available, the trust bar must use neutral product-focus statements instead of fake logos or named companies.
|
||||||
|
- If a visitor uses a narrow mobile viewport, the hero, CTA text, header, and dashboard preview must not create body-level horizontal overflow.
|
||||||
|
- If motion reduction is preferred by the visitor, decorative movement must not be required to understand the page.
|
||||||
|
- If status colors are not distinguishable, status labels and text must still communicate meaning.
|
||||||
|
- If platform authentication is not implemented or no sign-in destination is known, homepage sign-in treatment must not imply a completed login flow.
|
||||||
|
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
**Constitution alignment (required):** This feature introduces no Microsoft Graph calls, no write/change behavior, no queued or scheduled work, no OperationRun, no AuditLog changes, no tenant isolation changes, and no platform runtime behavior.
|
||||||
|
|
||||||
|
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** This feature introduces no persisted product truth, no domain abstraction, no enum/status/reason family, and no cross-domain UI framework. The only new source of truth is the public homepage brand/content direction, bounded to the website.
|
||||||
|
|
||||||
|
**Constitution alignment (XCUT-001):** This feature does not touch shared operator interaction families. Public website navigation and marketing preview elements must remain website-local.
|
||||||
|
|
||||||
|
**Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** This feature does not change admin detail/status surfaces, audience modes, support/raw evidence disclosure, or operator next-action surfaces.
|
||||||
|
|
||||||
|
**Constitution alignment (PROV-001):** This feature does not change provider/platform seams. Microsoft tenant language is marketing positioning only and must not create runtime provider coupling.
|
||||||
|
|
||||||
|
**Constitution alignment (TEST-GOV-001):** Browser coverage is intentional, homepage-scoped, and must not introduce database, workspace, provider, membership, session, or platform defaults.
|
||||||
|
|
||||||
|
**Constitution alignment (OPS-UX / OPS-UX-START-001):** N/A - no OperationRun behavior is created, queued, completed, deduplicated, resumed, blocked, or linked.
|
||||||
|
|
||||||
|
**Constitution alignment (RBAC-UX):** N/A - no authorization plane, capability, membership, global search, mutation, or destructive action behavior changes.
|
||||||
|
|
||||||
|
**Constitution alignment (BADGE-001):** N/A - no admin status badge semantics change. Public marketing status labels in the static preview must be descriptive and not become a shared status taxonomy.
|
||||||
|
|
||||||
|
**Constitution alignment (UI-FIL-001):** N/A - no Filament, Blade admin, Livewire, Resource, RelationManager, or Page changes.
|
||||||
|
|
||||||
|
**Constitution alignment (UI-NAMING-001 / DECIDE-001 / UI surface rules):** N/A for operator-facing naming and action-surface contracts. Public homepage copy must still use calm, precise, user-facing language and avoid implementation-first labels.
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: `/` MUST render a Tenantial-specific public homepage.
|
||||||
|
- **FR-002**: The homepage MUST present Tenantial as an evidence-first governance platform for Microsoft tenants.
|
||||||
|
- **FR-003**: The homepage MUST use a premium dark enterprise visual direction with high-contrast text and restrained accent colors.
|
||||||
|
- **FR-004**: The homepage MUST show the headline "Evidence-first governance for Microsoft tenants."
|
||||||
|
- **FR-005**: The homepage MUST include supporting copy that communicates backup, restore, drift detection, snapshot-backed audit context, verifiable evidence, and structured governance reviews.
|
||||||
|
- **FR-006**: "Book a demo" MUST be visible as the primary CTA.
|
||||||
|
- **FR-007**: "Explore the platform" MUST be visible as the secondary CTA.
|
||||||
|
- **FR-008**: CTA and navigation destinations MUST be intentional and MUST NOT silently point to dead template routes.
|
||||||
|
- **FR-009**: If no implemented sign-in destination is known, "Sign in" MUST NOT be presented as a prominent functional promise.
|
||||||
|
- **FR-010**: The desktop header MUST include Tenantial branding and navigation labels for Platform, Solutions, Resources, Pricing, Company, Sign in, and Book a demo.
|
||||||
|
- **FR-011**: The homepage MUST include a static product-near dashboard preview.
|
||||||
|
- **FR-012**: The dashboard preview MUST be clearly static marketing/demo content and MUST NOT depend on backend, API, authentication, tenant, or platform data.
|
||||||
|
- **FR-013**: The dashboard preview MUST show static demo values for overall posture, findings, drift detected, evidence items, and backup status.
|
||||||
|
- **FR-014**: The dashboard preview MUST include supporting areas for recent findings, drift timeline, backups and restores, reviews, and evidence spotlight.
|
||||||
|
- **FR-015**: The homepage MUST include a trust bar using neutral credibility statements such as Microsoft tenant focus, evidence-oriented workflows, audit review workflow design, and operator-led governance.
|
||||||
|
- **FR-016**: The trust bar MUST NOT use real customer logos, certification claims, uptime claims, "trusted by" statements, or security/compliance promises unless independently verified.
|
||||||
|
- **FR-017**: The feature pillars MUST include Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews.
|
||||||
|
- **FR-018**: Feature pillar copy MUST remain concise, product-specific, and free of unsupported compliance or security promises.
|
||||||
|
- **FR-019**: The homepage MUST include a final CTA section with "Build tenant governance on evidence, not assumptions." or equivalent wording with the same meaning.
|
||||||
|
- **FR-020**: The homepage footer MUST include Tenantial branding and footer link groups for Platform, Solutions, Resources, Pricing, Company, Contact, Legal, Privacy, and Security.
|
||||||
|
- **FR-021**: Visible homepage copy MUST NOT contain AstroDeck, template demo positioning, Open Source, MIT Licensed, TenantCTRL, TenantPilot, or TenantAtlas as public brand names.
|
||||||
|
- **FR-022**: Homepage SEO metadata MUST be Tenantial-specific and MUST describe evidence-first governance for Microsoft tenants.
|
||||||
|
- **FR-023**: Homepage metadata MUST NOT contain AstroDeck/template titles, old public brand names, fake customer proof, or unverified certification claims.
|
||||||
|
- **FR-024**: The homepage MUST have exactly one primary page heading.
|
||||||
|
- **FR-025**: Keyboard users MUST be able to reach and identify interactive homepage controls, including navigation and CTAs.
|
||||||
|
- **FR-026**: Status information in the dashboard preview MUST NOT rely on color alone.
|
||||||
|
- **FR-027**: The homepage MUST provide sufficient contrast on the dark background for body copy, labels, links, and CTAs.
|
||||||
|
- **FR-028**: The homepage MUST remain usable on mobile, tablet, desktop, and wide desktop viewports.
|
||||||
|
- **FR-029**: The mobile homepage MUST NOT create body-level horizontal overflow.
|
||||||
|
- **FR-030**: The header MUST collapse or simplify on mobile without hiding essential navigation and CTA access from keyboard users.
|
||||||
|
- **FR-031**: Decorative icons or visual flourishes MUST NOT be required to understand the page content.
|
||||||
|
- **FR-032**: Decorative motion, if present, MUST be subtle and respect reduced-motion preferences.
|
||||||
|
- **FR-033**: The homepage MUST NOT introduce platform, auth, database, Microsoft Graph, Filament, Livewire, or runtime API coupling.
|
||||||
|
- **FR-034**: Existing root workspace script names and website port conventions MUST remain unchanged.
|
||||||
|
|
||||||
|
### Key Entities
|
||||||
|
|
||||||
|
- **Homepage Message**: The public brand, category, headline, supporting promise, and CTA hierarchy for the `/` route.
|
||||||
|
- **Static Dashboard Preview**: A product-near visual using fixed demo values to communicate governance posture, findings, drift, evidence, backup status, reviews, and recent activity.
|
||||||
|
- **Trust Statement**: A neutral credibility statement that supports confidence without implying verified customer proof, certifications, or availability guarantees.
|
||||||
|
- **Feature Pillar**: A concise capability card describing one of Tenantial's required homepage capabilities: Backup, Restore, Drift Detection, Evidence, Audit Trail, or Governance Reviews.
|
||||||
|
- **Footer Link Group**: A public website navigation group that points visitors toward product, company, contact, legal, privacy, and security information without template residue.
|
||||||
|
|
||||||
|
### Assumptions
|
||||||
|
|
||||||
|
- The public brand for this homepage is Tenantial.
|
||||||
|
- No verified customer logos, security certifications, or uptime claims are available for this slice.
|
||||||
|
- The "Book a demo" destination defaults to `/contact` unless a valid existing destination is confirmed before implementation.
|
||||||
|
- The "Explore the platform" destination defaults to `/product` until a dedicated platform route exists.
|
||||||
|
- Header route defaults: Platform -> `/product`, Solutions -> `/solutions`, Book a demo -> `/contact`, and Explore the platform -> `/product`. Resources, Pricing, Company, and Sign in must not link to missing same-name routes; they may be de-emphasized, mapped to an existing intentional route, or rendered non-prominently until real routes exist.
|
||||||
|
- No secondary public pages are built by this spec.
|
||||||
|
- The dashboard preview uses static demo content, not screenshots, live product data, or platform API data.
|
||||||
|
- Existing website font choices may be reused if they support the premium enterprise direction.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: In a stakeholder review, at least 8 of 10 reviewers can identify Tenantial as a Microsoft tenant governance platform within 5 seconds of viewing the first homepage viewport.
|
||||||
|
- **SC-002**: 100% of required homepage sections render on desktop and mobile review: header, hero, static dashboard preview, trust bar, feature pillars, CTA section, and footer.
|
||||||
|
- **SC-003**: Review of visible copy and homepage metadata finds 0 occurrences of AstroDeck, Open Source, MIT Licensed, TenantCTRL, TenantPilot, TenantAtlas, or other old/template public brand positioning.
|
||||||
|
- **SC-004**: Desktop and mobile review confirms 0 body-level horizontal overflow issues at narrow mobile, tablet, desktop, and wide desktop widths.
|
||||||
|
- **SC-005**: At least 90% of reviewers can identify Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews from the homepage without needing external documentation.
|
||||||
|
- **SC-006**: Trust review finds 0 unverified customer, certification, uptime, or security/compliance claims.
|
||||||
|
- **SC-007**: Accessibility review confirms the page has one primary heading, keyboard-visible controls, readable contrast on dark backgrounds, and non-color-only status meaning.
|
||||||
|
- **SC-008**: Homepage smoke validation confirms the route renders, required brand/capability copy is visible, required CTAs are visible, static preview content is present, and mobile overflow is absent.
|
||||||
257
specs/400-tenantial-homepage-visual-rebuild/tasks.md
Normal file
257
specs/400-tenantial-homepage-visual-rebuild/tasks.md
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
# Tasks: Tenantial Homepage Visual Rebuild
|
||||||
|
|
||||||
|
**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/`
|
||||||
|
**Prerequisites**: [plan.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/plan.md), [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md), [research.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/research.md), [data-model.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/data-model.md), [contracts/public-homepage.yaml](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml), [quickstart.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/quickstart.md)
|
||||||
|
|
||||||
|
**Tests**: Required. Spec 400 changes public website runtime behavior and requires browser smoke coverage. Test tasks use Playwright only; no Pest/Laravel/Filament tests are needed because no platform behavior changes.
|
||||||
|
|
||||||
|
**Organization**: Tasks are grouped by user story so each story can be implemented and verified as an independent increment after shared setup/foundation.
|
||||||
|
|
||||||
|
## Phase 1: Setup (Shared Infrastructure)
|
||||||
|
|
||||||
|
**Purpose**: Confirm the existing website structure, route inventory, test files, and styling baseline before editing.
|
||||||
|
|
||||||
|
- [X] T001 Review Spec 400 planning inputs in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/plan.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/data-model.md`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml`
|
||||||
|
- [X] T002 Verify current homepage assembly and content sources in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`
|
||||||
|
- [X] T003 [P] Verify current homepage smoke and IA assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/changelog-core-ia.spec.ts`
|
||||||
|
- [X] T004 [P] Verify Tailwind v4 CSS-first styling constraints in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/tokens.css`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/package.json`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational (Blocking Prerequisites)
|
||||||
|
|
||||||
|
**Purpose**: Establish shared Tenantial brand, dark shell, route intent, and smoke-test helpers used by all user stories.
|
||||||
|
|
||||||
|
**Critical**: No user story implementation should begin until this phase is complete.
|
||||||
|
|
||||||
|
- [X] T005 Update public site metadata, primary navigation seeds, footer group seeds, and route intent for Tenantial in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`
|
||||||
|
- [X] T006 Update homepage smoke helper constants and shared assertions for Tenantial labels, footer labels, forbidden old brands, metadata checks, and route targets in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
- [X] T007 Update global Tenantial homepage SEO defaults and old-brand cleanup assumptions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T008 [P] Replace the light TenantAtlas color foundation with website-local dark Tenantial design tokens using Tailwind v4 CSS-first `@theme` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/tokens.css`
|
||||||
|
- [X] T009 [P] Update body background, shell surfaces, focus-visible styling, reduced-motion behavior, and horizontal-overflow safeguards in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css`
|
||||||
|
- [X] T010 Update shared public shell styling and Tenantial brand presentation in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/PageShell.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Navbar.astro`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Footer.astro`
|
||||||
|
|
||||||
|
**Checkpoint**: Shared Tenantial brand shell, route intent, dark tokens, and test helpers are ready.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 - Understand Tenantial Immediately (Priority: P1) - MVP
|
||||||
|
|
||||||
|
**Goal**: A first-time visitor opens `/` and immediately understands Tenantial as an evidence-first governance platform for Microsoft tenants with clear primary and secondary CTAs.
|
||||||
|
|
||||||
|
**Independent Test**: Open `/` and verify Tenantial branding, the headline "Evidence-first governance for Microsoft tenants.", Microsoft tenant context, `Book a demo`, `Explore the platform`, and exactly one primary page heading.
|
||||||
|
|
||||||
|
### Tests for User Story 1
|
||||||
|
|
||||||
|
- [X] T011 [US1] Update homepage hero smoke assertions for Tenantial brand, headline, supporting copy, CTA hierarchy, one H1, and `/contact` plus `/product` route targets in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 1
|
||||||
|
|
||||||
|
- [X] T012 [US1] Replace homepage hero copy, trust bullets, CTA labels, CTA destinations, and SEO metadata with Tenantial first-read content in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T013 [US1] Adapt the homepage hero layout, heading scale, eyebrow treatment, CTA pair, and first-viewport composition in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/PageHero.astro`
|
||||||
|
- [X] T014 [US1] Update homepage section ordering so the hero and first-read CTA story appear before supporting sections in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro`
|
||||||
|
- [X] T015 [US1] Implement desktop and mobile header behavior for Platform -> `/product`, Solutions -> `/solutions`, Book a demo -> `/contact`, and explicit non-dead handling for Resources, Pricing, Company, and Sign in in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Navbar.astro`
|
||||||
|
- [X] T016 [US1] Run the User Story 1 smoke subset from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` and confirm it fails before implementation and passes after implementation
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 1 is independently testable as the homepage MVP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: User Story 2 - Evaluate Trust Without False Proof (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: An IT, MSP, security, or compliance stakeholder sees credible trust positioning, neutral claims, and the required capability pillars without fake proof.
|
||||||
|
|
||||||
|
**Independent Test**: Review `/` and confirm neutral trust statements, no unverified customer/certification/uptime claims, and visible Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews pillars.
|
||||||
|
|
||||||
|
### Tests for User Story 2
|
||||||
|
|
||||||
|
- [X] T017 [US2] Add homepage smoke assertions for TrustBar statements, required FeaturePillars, and absence of SOC 2, ISO, uptime, trusted-by, and real-customer-logo claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 2
|
||||||
|
|
||||||
|
- [X] T018 [P] [US2] Create the static trust bar section with neutral credibility statements in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/TrustBar.astro`
|
||||||
|
- [X] T019 [P] [US2] Create the six required feature pillar cards with accessible icon treatment in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/FeaturePillars.astro`
|
||||||
|
- [X] T020 [US2] Add TrustBar and FeaturePillars content data for Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T021 [US2] Wire TrustBar and FeaturePillars into the homepage after the hero section using a stable placeholder position that does not depend on the final DashboardPreview implementation in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro`
|
||||||
|
- [X] T022 [US2] Remove or replace old trust, logo-strip, and capability copy that conflicts with Tenantial's no-fake-proof boundary in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro`
|
||||||
|
- [X] T023 [US2] Run the User Story 2 smoke subset from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` and confirm trust/capability assertions pass
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 2 is independently testable after shared shell and homepage assembly are available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Story 3 - Inspect A Product-Near Preview (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: A visitor sees a static product-near dashboard preview with posture, findings, drift, evidence, backup status, reviews, and recent activity that remains responsive and clearly non-live.
|
||||||
|
|
||||||
|
**Independent Test**: Open `/`, inspect the preview, and verify static demo values `92%`, `14`, `7`, `1,248`, `98%`, supporting panels, text-based status meaning, no live/realtime claim, and no body-level horizontal overflow on mobile.
|
||||||
|
|
||||||
|
### Tests for User Story 3
|
||||||
|
|
||||||
|
- [X] T024 [US3] Add homepage smoke assertions for static dashboard metrics, supporting panels, non-live wording, and status text labels in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T025 [P] [US3] Add a reusable mobile body-overflow assertion for homepage smoke tests in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 3
|
||||||
|
|
||||||
|
- [X] T026 [US3] Create the static HTML/CSS dashboard preview with metrics, recent findings, drift timeline, backups and restores, reviews, and evidence spotlight in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/content/DashboardPreview.astro`
|
||||||
|
- [X] T027 [US3] Replace the old hero visual usage with DashboardPreview in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/PageHero.astro`
|
||||||
|
- [X] T028 [US3] Add dashboard-specific responsive layout, status label, and overflow containment styling in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css`
|
||||||
|
- [X] T029 [US3] Remove stale hero screenshot references from homepage content so the static dashboard preview is the only homepage product visual in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T030 [US3] Run the User Story 3 smoke subset from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` with desktop and 390px mobile viewport coverage
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 3 is independently testable as a static, responsive, non-live product preview.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: User Story 4 - Verify Public Launch Readiness (Priority: P4)
|
||||||
|
|
||||||
|
**Goal**: A website owner can verify that old template residue, wrong brand names, unsupported promises, broken CTAs, and mobile layout issues are absent before launch.
|
||||||
|
|
||||||
|
**Independent Test**: Run the homepage smoke suite and manually review desktop/mobile `/` for forbidden copy, metadata cleanup, intentional links, footer completeness, no clipping, and no body overflow.
|
||||||
|
|
||||||
|
### Tests for User Story 4
|
||||||
|
|
||||||
|
- [X] T031 [US4] Add forbidden visible-copy and metadata assertions for AstroDeck, Open Source, MIT Licensed, TenantCTRL, TenantPilot, and TenantAtlas in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T032 [P] [US4] Update core IA route and hidden-label assertions for Tenantial header/footer expectations in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/changelog-core-ia.spec.ts`
|
||||||
|
- [X] T033 [P] [US4] Update visual foundation guardrail smoke expectations for Tenantial brand, CTA text, and dark surface semantics in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 4
|
||||||
|
|
||||||
|
- [X] T034 [US4] Update final CTA copy and homepage CTA section wiring to "Build tenant governance on evidence, not assumptions." with Book a demo and Explore the platform in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/CTASection.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T035 [US4] Update footer copy, footer group labels, copyright text, and old-template cleanup in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Footer.astro`
|
||||||
|
- [X] T036 [P] [US4] Update the public Tenantial favicon or simple local mark in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/public/favicon.svg`
|
||||||
|
- [X] T037 [US4] Remove the unused old product-visual asset in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/public/images/hero-product-visual.svg` and replace old visual references in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T038 [US4] Run a forbidden-copy scrub across `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src` and update homepage-visible and globally reachable public brand residue found in the affected source files
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 4 is independently testable as launch-readiness cleanup and smoke validation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
**Purpose**: Validate the complete feature, record test governance, and ensure no scope drift into platform/admin behavior.
|
||||||
|
|
||||||
|
- [X] T039 Run `corepack pnpm build:website` from `/Users/ahmeddarrazi/Documents/projects/wt-website/package.json` and fix any build failures in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`
|
||||||
|
- [X] T040 Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/package.json` and fix any smoke failures in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke`
|
||||||
|
- [X] T041 Use browser review for mobile, tablet, desktop, and wide desktop `/` viewports, including keyboard focus order for header navigation, mobile menu, and CTAs, against `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/quickstart.md`
|
||||||
|
- [X] T042 [P] Verify no Laravel, Filament, Livewire, Microsoft Graph, database, auth, OperationRun, or platform coupling was introduced under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`
|
||||||
|
- [X] T043 [P] Verify Tailwind v4 compliance by checking no deprecated v3 utilities or `tailwind.config.js` assumptions were introduced under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components`
|
||||||
|
- [X] T044 Record final Smoke Coverage close-out notes and any residual browser-review caveats in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/tasks.md`
|
||||||
|
|
||||||
|
## Smoke Coverage Close-Out
|
||||||
|
|
||||||
|
- Build: `corepack pnpm build:website` passed on 2026-05-18. Astro emitted non-failing content-loader warnings for missing `src/content/articles/` and `src/content/resources/` directories.
|
||||||
|
- Browser smoke: `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` passed with 19/19 tests on 2026-05-18.
|
||||||
|
- Diff hygiene: `git diff --check` passed on 2026-05-18.
|
||||||
|
- Responsive/browser coverage: homepage smoke includes 390px mobile overflow checks, CTA hierarchy, metadata residue checks, static dashboard preview checks, header/mobile navigation, TrustBar, FeaturePillars, and footer reachability.
|
||||||
|
- Screenshots: no screenshot artifacts are committed for this branch. Earlier local-only screenshot paths were removed from this close-out so review remains repo-reproducible; use the quickstart manual review steps when fresh screenshots are needed.
|
||||||
|
- Scope/coupling: no Laravel, Filament, Livewire, database, auth, OperationRun, queue, or platform runtime behavior was introduced. Remaining `Microsoft Graph` and `database` search hits are pre-existing supporting-page/integration content, not new homepage coupling.
|
||||||
|
- Tailwind: no deprecated v3 utility patterns or `tailwind.config.js` assumptions were introduced in the checked website styles/components.
|
||||||
|
- Brand cleanup: existing public pages reachable from header/footer use Tenantial public brand copy while internal workspace names such as `@tenantatlas/website` remain unchanged.
|
||||||
|
- Spec 227: no `specs/227-*` artifact exists in the current checkout; no phantom spec was created.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & Execution Order
|
||||||
|
|
||||||
|
### Phase Dependencies
|
||||||
|
|
||||||
|
- **Phase 1 Setup**: No dependencies.
|
||||||
|
- **Phase 2 Foundational**: Depends on Phase 1 and blocks all user stories.
|
||||||
|
- **Phase 3 US1**: Depends on Phase 2 and is the MVP.
|
||||||
|
- **Phase 4 US2**: Depends on Phase 2; component creation and homepage wiring can proceed independently of US3 by using a stable post-hero section position.
|
||||||
|
- **Phase 5 US3**: Depends on Phase 2; dashboard component creation can begin after foundation, but final hero integration should account for US1 edits to `PageHero.astro`.
|
||||||
|
- **Phase 6 US4**: Depends on US1, US2, and US3 because it verifies final launch readiness across copy, metadata, navigation, footer, and visual behavior.
|
||||||
|
- **Phase 7 Polish**: Depends on all desired user stories being complete.
|
||||||
|
|
||||||
|
### User Story Dependencies
|
||||||
|
|
||||||
|
- **US1 (P1)**: Independent MVP after foundation.
|
||||||
|
- **US2 (P2)**: Independently testable after foundation; integrates with homepage assembly.
|
||||||
|
- **US3 (P3)**: Independently testable after foundation; integrates with hero/dashboard visual.
|
||||||
|
- **US4 (P4)**: Final readiness story; depends on the implemented homepage surface from US1-US3.
|
||||||
|
|
||||||
|
### Within Each User Story
|
||||||
|
|
||||||
|
- Write or update Playwright assertions first and confirm they fail before implementing the story.
|
||||||
|
- Update content/data before page wiring when both are needed.
|
||||||
|
- Create new components before importing them into `index.astro` or `PageHero.astro`.
|
||||||
|
- Run the story-specific smoke subset before proceeding to the next story.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallel Execution Examples
|
||||||
|
|
||||||
|
### User Story 1
|
||||||
|
|
||||||
|
```text
|
||||||
|
Sequential because US1 edits shared homepage and hero files:
|
||||||
|
T011 -> T012 -> T013 -> T014 -> T015 -> T016
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story 2
|
||||||
|
|
||||||
|
```text
|
||||||
|
Parallel component work after T017:
|
||||||
|
Task T018: Create TrustBar.astro
|
||||||
|
Task T019: Create FeaturePillars.astro
|
||||||
|
|
||||||
|
Then:
|
||||||
|
T020 -> T021 -> T022 -> T023
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story 3
|
||||||
|
|
||||||
|
```text
|
||||||
|
Parallel test helper and component work:
|
||||||
|
Task T025: Add mobile overflow helper
|
||||||
|
Task T026: Create DashboardPreview.astro
|
||||||
|
|
||||||
|
Then:
|
||||||
|
T027 -> T028 -> T029 -> T030
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story 4
|
||||||
|
|
||||||
|
```text
|
||||||
|
Parallel readiness assertions after US1-US3:
|
||||||
|
Task T032: Update changelog-core-ia.spec.ts
|
||||||
|
Task T033: Update visual-foundation-guardrails.spec.ts
|
||||||
|
Task T036: Update favicon.svg
|
||||||
|
|
||||||
|
Then:
|
||||||
|
T031 -> T034 -> T035 -> T037 -> T038
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### MVP First (User Story 1 Only)
|
||||||
|
|
||||||
|
1. Complete Phase 1 Setup.
|
||||||
|
2. Complete Phase 2 Foundational tasks.
|
||||||
|
3. Complete Phase 3 User Story 1.
|
||||||
|
4. Stop and validate `/` as a Tenantial first-read homepage MVP.
|
||||||
|
5. Continue only after the hero, brand, headline, CTA hierarchy, and basic navigation behavior pass.
|
||||||
|
|
||||||
|
### Incremental Delivery
|
||||||
|
|
||||||
|
1. **US1**: Tenantial first-read homepage and CTA hierarchy.
|
||||||
|
2. **US2**: Trust bar and feature pillars without false proof.
|
||||||
|
3. **US3**: Static product-near dashboard preview.
|
||||||
|
4. **US4**: Launch-readiness cleanup, metadata, footer, forbidden-copy checks, and mobile review.
|
||||||
|
|
||||||
|
### Parallel Team Strategy
|
||||||
|
|
||||||
|
1. Complete Setup and Foundation together.
|
||||||
|
2. Split US2 TrustBar and FeaturePillars work while US3 DashboardPreview work proceeds in a separate file.
|
||||||
|
3. Serialize edits to shared files: `index.astro`, `home.ts`, `PageHero.astro`, `global.css`, and smoke specs.
|
||||||
|
4. Reserve US4 for final cleanup after all homepage sections exist.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `[P]` tasks are safe to start in parallel because they touch different files or do not depend on incomplete same-file edits.
|
||||||
|
- Story labels map to the four user stories in [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md).
|
||||||
|
- Do not introduce platform, auth, database, Microsoft Graph, Laravel, Filament, Livewire, queue, or OperationRun behavior.
|
||||||
|
- Do not rename root packages or existing workspace scripts from `@tenantatlas/website` or the current pnpm script names.
|
||||||
38
specs/401-tenantial-platform-page/checklists/requirements.md
Normal file
38
specs/401-tenantial-platform-page/checklists/requirements.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Specification Quality Checklist: Tenantial Platform Page
|
||||||
|
|
||||||
|
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||||
|
**Created**: 2026-05-18
|
||||||
|
**Feature**: [spec.md](../spec.md)
|
||||||
|
|
||||||
|
## Content Quality
|
||||||
|
|
||||||
|
- [x] No implementation details (languages, frameworks, APIs)
|
||||||
|
- [x] Focused on user value and business needs
|
||||||
|
- [x] Written for non-technical stakeholders
|
||||||
|
- [x] All mandatory sections completed
|
||||||
|
|
||||||
|
## Requirement Completeness
|
||||||
|
|
||||||
|
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||||
|
- [x] Requirements are testable and unambiguous
|
||||||
|
- [x] Success criteria are measurable
|
||||||
|
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||||
|
- [x] All acceptance scenarios are defined
|
||||||
|
- [x] Edge cases are identified
|
||||||
|
- [x] Scope is clearly bounded
|
||||||
|
- [x] Dependencies and assumptions identified
|
||||||
|
|
||||||
|
## Feature Readiness
|
||||||
|
|
||||||
|
- [x] All functional requirements have clear acceptance criteria
|
||||||
|
- [x] User scenarios cover primary flows
|
||||||
|
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||||
|
- [x] No implementation details leak into specification
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Validation iteration 1 passed.
|
||||||
|
- No clarification markers remain.
|
||||||
|
- Route, repository boundary, and proof-command references are retained because the active Spec Kit and repository workflow require them; the feature requirements avoid HOW-level implementation choices such as component structure, framework internals, APIs, databases, or code design.
|
||||||
|
- Review outcome class: `acceptable-special-case`.
|
||||||
|
- Workflow outcome: `keep`.
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: Tenantial Platform Public Route Contract
|
||||||
|
version: 0.1.0
|
||||||
|
description: >
|
||||||
|
Static public website route contract for Spec 401. These are HTML page
|
||||||
|
routes, not backend application APIs.
|
||||||
|
servers:
|
||||||
|
- url: https://tenantial.example
|
||||||
|
paths:
|
||||||
|
/platform:
|
||||||
|
get:
|
||||||
|
summary: Render the canonical Tenantial Platform page
|
||||||
|
operationId: getTenantialPlatformPage
|
||||||
|
tags:
|
||||||
|
- Public Website
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Canonical Platform page renders successfully.
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
examples:
|
||||||
|
requiredContent:
|
||||||
|
summary: Required visible content signals
|
||||||
|
value: >
|
||||||
|
TENANTIAL PLATFORM. Govern Microsoft tenants through
|
||||||
|
evidence, drift context, and reviewable decisions.
|
||||||
|
x-route-contract:
|
||||||
|
canonicalPath: /platform
|
||||||
|
sitemap: true
|
||||||
|
requiredVisibleText:
|
||||||
|
- TENANTIAL PLATFORM
|
||||||
|
- Govern Microsoft tenants through evidence
|
||||||
|
- Backup
|
||||||
|
- Restore
|
||||||
|
- Drift Detection
|
||||||
|
- Findings
|
||||||
|
- Evidence
|
||||||
|
- Audit Trail
|
||||||
|
- Exceptions
|
||||||
|
- Governance Reviews
|
||||||
|
- Built for governance of record
|
||||||
|
forbiddenVisibleText:
|
||||||
|
- AstroDeck
|
||||||
|
- TemplateDeck
|
||||||
|
- Open Source
|
||||||
|
- MIT
|
||||||
|
- TenantAtlas
|
||||||
|
- TenantPilot
|
||||||
|
- TenantCTRL
|
||||||
|
requiredMetadata:
|
||||||
|
titleIncludes:
|
||||||
|
- Tenantial Platform
|
||||||
|
- Evidence-first governance
|
||||||
|
descriptionIncludes:
|
||||||
|
- backup
|
||||||
|
- restore
|
||||||
|
- drift detection
|
||||||
|
- findings
|
||||||
|
- evidence
|
||||||
|
- audit trails
|
||||||
|
- exceptions
|
||||||
|
- reviews
|
||||||
|
requiredAccessibility:
|
||||||
|
onePrimaryHeading: true
|
||||||
|
keyboardAccessibleCtas: true
|
||||||
|
noColorOnlyMeaning: true
|
||||||
|
noBodyHorizontalOverflow: true
|
||||||
|
/product:
|
||||||
|
get:
|
||||||
|
summary: Redirect product route compatibility to the canonical Platform page
|
||||||
|
operationId: getProductCompatibilityRoute
|
||||||
|
tags:
|
||||||
|
- Public Website
|
||||||
|
responses:
|
||||||
|
"301":
|
||||||
|
description: Compatibility route redirects permanently to /platform.
|
||||||
|
"302":
|
||||||
|
description: Compatibility route redirects temporarily to /platform if the current static build cannot emit a permanent redirect.
|
||||||
|
x-route-contract:
|
||||||
|
canonicalPath: /platform
|
||||||
|
primaryNavigationTarget: false
|
||||||
|
allowedBehaviors:
|
||||||
|
- redirectToPlatform
|
||||||
|
forbiddenBehavior:
|
||||||
|
- renderEquivalentPlatformContentWithCanonicalPlatformMetadata
|
||||||
|
- exposeStaleProductCopy
|
||||||
|
- exposeConflictingCanonicalMetadata
|
||||||
|
- remainPrimaryPlatformNavigationTarget
|
||||||
|
smokeCoverageRequired: true
|
||||||
268
specs/401-tenantial-platform-page/data-model.md
Normal file
268
specs/401-tenantial-platform-page/data-model.md
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
# Data Model: Tenantial Platform Page
|
||||||
|
|
||||||
|
This feature uses static public website content only. The "entities" below are content models and route contracts, not database tables or persisted application records.
|
||||||
|
|
||||||
|
## Platform Page
|
||||||
|
|
||||||
|
**Purpose**: Canonical public route that explains Tenantial's evidence-first governance platform.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `path`: `/platform`
|
||||||
|
- `canonicalPath`: `/platform`
|
||||||
|
- `title`: Tenantial Platform page title
|
||||||
|
- `description`: SEO description covering backup, restore, drift detection, findings, evidence, audit trails, exceptions, and reviews
|
||||||
|
- `hero`: Platform Hero content
|
||||||
|
- `operatingModelSteps`: ordered list of Operating Model Steps
|
||||||
|
- `capabilities`: list of Capability items
|
||||||
|
- `governanceLoop`: Governance Loop content
|
||||||
|
- `truthLayers`: optional list of Truth Layer items
|
||||||
|
- `operatorWorkflows`: optional list of Operator Workflow items
|
||||||
|
- `platformBoundary`: Platform Boundary content
|
||||||
|
- `finalCta`: CTA content
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- `path` must be `/platform`.
|
||||||
|
- Page must have exactly one primary heading.
|
||||||
|
- Metadata must use Tenantial branding and must not contain stale public brand/template terms.
|
||||||
|
- Page must not depend on tenant data, customer data, platform APIs, auth state, Microsoft Graph, or backend fetches.
|
||||||
|
- Page must include required sections from the spec: hero, operating model overview, capability grid, governance loop, platform boundaries, final CTA, and footer.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Links from homepage secondary CTA.
|
||||||
|
- Links from header Platform navigation.
|
||||||
|
- Links from footer Platform navigation.
|
||||||
|
- May be reached from `/product` only through redirect compatibility.
|
||||||
|
|
||||||
|
**State transitions**: N/A - static route content.
|
||||||
|
|
||||||
|
## Platform Hero
|
||||||
|
|
||||||
|
**Purpose**: First viewport explanation of what Tenantial is and why the visitor is on this page.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `eyebrow`: "TENANTIAL PLATFORM" or equivalent
|
||||||
|
- `headline`: product-specific governance headline
|
||||||
|
- `description`: backup, drift, findings, evidence, audit trail, and reviews in one operating loop
|
||||||
|
- `primaryCta`: "Book a demo" linking to the current intentional conversion route
|
||||||
|
- `secondaryCta`: "See the governance loop" linking to the relevant page section
|
||||||
|
- `visual`: large static dashboard/product preview
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- Must be understandable in the first viewport.
|
||||||
|
- Must use a split layout where the product preview is the dominant visual anchor.
|
||||||
|
- Must not duplicate homepage hero copy exactly.
|
||||||
|
- Must not imply live tenant data.
|
||||||
|
- Must keep "Book a demo" primary.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- References Governance Loop via secondary CTA.
|
||||||
|
- Uses Platform Page metadata and shell.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
|
|
||||||
|
## Operating Model Step
|
||||||
|
|
||||||
|
**Purpose**: Explain the operator-led governance sequence.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `label`: step name
|
||||||
|
- `description`: plain-language explanation
|
||||||
|
- `order`: numeric display order
|
||||||
|
|
||||||
|
**Required ordered values**:
|
||||||
|
|
||||||
|
1. Snapshot
|
||||||
|
2. Drift
|
||||||
|
3. Finding
|
||||||
|
4. Review
|
||||||
|
5. Evidence
|
||||||
|
6. Audit trail
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- Steps must render as a compact connected sequence on desktop.
|
||||||
|
- Steps must stack or simplify on mobile.
|
||||||
|
- Copy must avoid implying automatic remediation.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Supports Platform Hero and Governance Loop.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
|
|
||||||
|
## Capability
|
||||||
|
|
||||||
|
**Purpose**: Describe a core Tenantial platform capability in a grouped section with one larger primary block and smaller supporting cards.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `title`: capability name
|
||||||
|
- `description`: concise public explanation
|
||||||
|
- `icon`: optional decorative icon identifier
|
||||||
|
|
||||||
|
**Required values**:
|
||||||
|
|
||||||
|
- Backup & Restore
|
||||||
|
- Drift Detection
|
||||||
|
- Findings
|
||||||
|
- Evidence
|
||||||
|
- Audit Trail
|
||||||
|
- Exceptions
|
||||||
|
- Governance Reviews
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- One primary Backup & Restore block and 6 supporting capability cards should render.
|
||||||
|
- Descriptions must not claim guaranteed compliance, guaranteed recovery, full automation, zero drift, or real-time coverage.
|
||||||
|
- Icons are decorative unless explicitly labelled.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Belongs to Platform Page capability grid.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
|
|
||||||
|
## Governance Loop
|
||||||
|
|
||||||
|
**Purpose**: Show how configuration change becomes reviewable evidence.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `headline`: section heading
|
||||||
|
- `description`: public explanation of why context and evidence matter
|
||||||
|
- `steps`: ordered loop labels
|
||||||
|
|
||||||
|
**Required loop labels**:
|
||||||
|
|
||||||
|
- Source of truth
|
||||||
|
- Snapshot
|
||||||
|
- Diff
|
||||||
|
- Finding
|
||||||
|
- Exception
|
||||||
|
- Review
|
||||||
|
- Evidence
|
||||||
|
- Audit trail
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- Must be shown as a connected visual diagram, not only a text grid.
|
||||||
|
- Must make operator review explicit.
|
||||||
|
- Must make evidence preservation explicit.
|
||||||
|
- Must make auditability explicit.
|
||||||
|
- Must not imply live remediation or device actions.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Referenced by Platform Hero secondary CTA.
|
||||||
|
- Connects Operating Model Steps and Capability items.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
|
|
||||||
|
## Truth Layer
|
||||||
|
|
||||||
|
**Purpose**: Explain that different forms of product truth are not collapsed into one misleading status.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `title`: layer name
|
||||||
|
- `description`: plain-language explanation
|
||||||
|
|
||||||
|
**Expected values**:
|
||||||
|
|
||||||
|
- Execution Truth
|
||||||
|
- Artifact Truth
|
||||||
|
- Backup Truth
|
||||||
|
- Recovery Evidence
|
||||||
|
- Operator Next Action
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- Must be understandable to IT leaders and operators.
|
||||||
|
- Must avoid claiming perfect knowledge of all external systems.
|
||||||
|
- Must not create a runtime status taxonomy.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Optional section on Platform Page.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
|
|
||||||
|
## Operator Workflow
|
||||||
|
|
||||||
|
**Purpose**: Help target audiences recognize how they would use the platform.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `audience`: target audience label
|
||||||
|
- `description`: short workflow statement
|
||||||
|
|
||||||
|
**Expected audiences**:
|
||||||
|
|
||||||
|
- MSP Operator
|
||||||
|
- Enterprise IT
|
||||||
|
- Security Reviewer
|
||||||
|
- Compliance / Audit Stakeholder
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- Copy must be short and conservative.
|
||||||
|
- Must not include fake logos, named customer claims, or unverified proof.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Optional section on Platform Page.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
|
|
||||||
|
## Platform Boundary
|
||||||
|
|
||||||
|
**Purpose**: Prevent misunderstanding of Tenantial's role.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `headline`: boundary statement
|
||||||
|
- `description`: what Tenantial is designed for and what it is not replacing
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- Must clarify governance of record and recovery context.
|
||||||
|
- Must state that Tenantial does not take actions on the customer's behalf, does not manage devices, does not replace ITSM/SIEM/ticketing/Microsoft admin centers, and supports reviewable decisions.
|
||||||
|
- Must remain sales-friendly and not hostile.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Required section on Platform Page.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
|
|
||||||
|
## Product Compatibility Route
|
||||||
|
|
||||||
|
**Purpose**: Keep existing `/product` route reachable without exposing stale or conflicting public content.
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
|
||||||
|
- `path`: `/product`
|
||||||
|
- `canonicalPath`: `/platform`
|
||||||
|
- `behavior`: redirect to `/platform`
|
||||||
|
- `sitemap`: excluded when treated as compatibility
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
|
||||||
|
- Must not expose inconsistent or stale public product copy.
|
||||||
|
- Must not render equivalent page content at `/product`.
|
||||||
|
- Must not remain the primary Platform navigation target.
|
||||||
|
- Must be covered by smoke tests if retained.
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
|
||||||
|
- Compatibility route for historical links.
|
||||||
|
- Canonical route is Platform Page.
|
||||||
|
|
||||||
|
**State transitions**: N/A.
|
||||||
193
specs/401-tenantial-platform-page/plan.md
Normal file
193
specs/401-tenantial-platform-page/plan.md
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
# Implementation Plan: Tenantial Platform Page
|
||||||
|
|
||||||
|
**Branch**: `feat/401-tenantial-platform-page` | **Date**: 2026-05-18 | **Spec**: [/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/spec.md)
|
||||||
|
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/spec.md`
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Create a canonical public Tenantial Platform page at `/platform` inside the standalone Astro website. The implementation should reuse the Spec 400 public visual direction and existing website shell/content primitives, move homepage/header/footer Platform links to `/platform`, and treat the current `/product` page as redirect-only compatibility to the canonical Platform page. The follow-up visual refactor tightens the page around the supplied mockup target: split hero with large static dashboard preview, compact connected Operating Model, central Governance Loop diagram, asymmetric Capabilities, visual Truth Layers, warmer Operator Workflows, clear Boundaries band, focused CTA, and quiet footer. The feature remains static website content with route smoke coverage; it introduces no Laravel platform runtime behavior, no backend API, no database changes, no authentication, and no Microsoft Graph integration.
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
**Language/Version**: TypeScript 5.9.3 and Astro 6.0.0 in `apps/website`; Node >=20.0.0 via workspace package metadata.
|
||||||
|
**Primary Dependencies**: Astro, Tailwind CSS v4.2.2 through `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, Playwright smoke tests.
|
||||||
|
**Storage**: N/A - static website content only; no database, CMS, API, customer data, tenant data, or runtime persistence.
|
||||||
|
**Testing**: Playwright smoke tests in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke`; website build through root `build:website`.
|
||||||
|
**Validation Lanes**: Browser lane plus static build and whitespace diff check.
|
||||||
|
**Target Platform**: Static public Astro website under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`.
|
||||||
|
**Project Type**: Monorepo with standalone website app and separate Laravel platform app; this feature is website-only.
|
||||||
|
**Performance Goals**: Static page remains mostly HTML/CSS, uses no backend fetches, no platform API calls, no tracking scripts, and no unapproved large raster screenshots.
|
||||||
|
**Constraints**: Preserve root pnpm script names, `WEBSITE_PORT` convention, internal package name, public Tenantial brand, Spec 400 visual system, mobile readability, keyboard access, and no body-level horizontal overflow.
|
||||||
|
**Scale/Scope**: One canonical public route (`/platform`), one redirect-only compatibility route for `/product`, shared navigation/footer link updates, route metadata, focused smoke assertions, and website-only visual composition updates for `/platform`.
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Plan
|
||||||
|
|
||||||
|
- **Guardrail scope**: No operator-facing admin surface change; public website marketing surface only.
|
||||||
|
- **Native vs custom classification summary**: N/A for Filament/admin nativity. Website work should reuse existing Astro website primitives and section components where they fit.
|
||||||
|
- **Shared-family relevance**: None for shared operator interaction families.
|
||||||
|
- **State layers in scope**: Public website shell, page route, static page content, route metadata.
|
||||||
|
- **Audience modes in scope**: Public buyer/stakeholder audience only; no customer/read-only, operator/MSP admin mode, or support/platform mode.
|
||||||
|
- **Decision/diagnostic/raw hierarchy plan**: Public product explanation first; no diagnostics or raw evidence surfaces.
|
||||||
|
- **Raw/support gating plan**: N/A - no raw/support evidence is shown.
|
||||||
|
- **One-primary-action / duplicate-truth control**: "Book a demo" remains the dominant conversion CTA; "See the governance loop" is a subordinate in-page learning action.
|
||||||
|
- **Handling modes by drift class or surface**: N/A.
|
||||||
|
- **Repository-signal treatment**: Report-only for public website route/content smoke; no admin guardrail signals.
|
||||||
|
- **Special surface test profiles**: N/A.
|
||||||
|
- **Required tests or manual smoke**: Website smoke tests for `/platform`, homepage/header/footer route intent, `/product` compatibility, forbidden residue, visual structure signals, quiet footer behavior, and mobile overflow.
|
||||||
|
- **Exception path and spread control**: None.
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage.
|
||||||
|
|
||||||
|
## Shared Pattern & System Fit
|
||||||
|
|
||||||
|
- **Cross-cutting feature marker**: No for operator shared interaction classes.
|
||||||
|
- **Systems touched**: Public website shell, public navigation, public footer, page definitions, static content, and smoke tests.
|
||||||
|
- **Shared abstractions reused**: Existing website content modules in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages`, shared shell in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout`, and page/section primitives in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components`.
|
||||||
|
- **New abstraction introduced? why?**: None planned. Page-local arrays and existing typed content shapes are sufficient.
|
||||||
|
- **Why the existing abstraction was sufficient or insufficient**: Existing PageShell, navigation, content modules, SEO helpers, primitives, and smoke helpers already support static public pages.
|
||||||
|
- **Bounded deviation / spread control**: If a new visual section is needed for the governance loop, keep it page-local or a narrow website section component; do not create a generic governance UI framework.
|
||||||
|
|
||||||
|
## OperationRun UX Impact
|
||||||
|
|
||||||
|
- **Touches OperationRun start/completion/link UX?**: No.
|
||||||
|
- **Central contract reused**: N/A.
|
||||||
|
- **Delegated UX behaviors**: N/A.
|
||||||
|
- **Surface-owned behavior kept local**: N/A.
|
||||||
|
- **Queued DB-notification policy**: N/A.
|
||||||
|
- **Terminal notification path**: N/A.
|
||||||
|
- **Exception path**: None.
|
||||||
|
|
||||||
|
## Provider Boundary & Portability Fit
|
||||||
|
|
||||||
|
- **Shared provider/platform boundary touched?**: No.
|
||||||
|
- **Provider-owned seams**: N/A.
|
||||||
|
- **Platform-core seams**: N/A.
|
||||||
|
- **Neutral platform terms / contracts preserved**: Runtime provider/platform contracts are untouched.
|
||||||
|
- **Retained provider-specific semantics and why**: Public copy may refer to Microsoft tenant environments because the product positioning requires it; this does not change runtime provider abstractions.
|
||||||
|
- **Bounded extraction or follow-up path**: None.
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
*GATE: Passed before Phase 0 research and re-checked after Phase 1 design, with explicit repository-wide PHP/Pest/Pint close-out commands documented for finalization.*
|
||||||
|
|
||||||
|
- Inventory-first: N/A - no Inventory, snapshot, backup, or tenant runtime state changes.
|
||||||
|
- Read/write separation: Pass - public website content only; no write/change behavior.
|
||||||
|
- Graph contract path: Pass - no Microsoft Graph calls or contract registry changes.
|
||||||
|
- Deterministic capabilities: N/A - no capability derivation changes.
|
||||||
|
- RBAC-UX and workspace/tenant isolation: Pass - no `/admin`, `/system`, tenant, workspace, global search, policy, capability, or authorization changes.
|
||||||
|
- Destructive confirmations: N/A - no destructive actions.
|
||||||
|
- Run observability and OperationRun UX: Pass - no queued, scheduled, long-running, remote, or operational action.
|
||||||
|
- Automation: N/A - no queued or scheduled operations.
|
||||||
|
- Data minimization: Pass - no customer, tenant, Microsoft API, tracking, or runtime data is introduced.
|
||||||
|
- Test governance: Pass - Browser classification and smoke coverage are explicit, website-scoped, and avoid platform fixtures. Repository-wide Pest and Pint gates are covered as close-out commands, not as additional feature proof.
|
||||||
|
- Proportionality: Pass - one public route/content slice; no persisted truth, domain state, abstraction, enum, status, resolver, registry, or taxonomy.
|
||||||
|
- No premature abstraction / few layers: Pass - reuse existing website content/shell components; keep any new page sections narrow.
|
||||||
|
- Persisted truth / behavioral state: Pass - no persistence or behavioral state.
|
||||||
|
- UI semantics / badge semantics: Pass - no admin status semantics; public labels stay descriptive.
|
||||||
|
- Shared pattern first: Pass - no shared operator interaction family touched; website shell patterns are reused.
|
||||||
|
- Provider boundary: Pass - public Microsoft tenant wording does not touch platform-core seams.
|
||||||
|
- Filament-native UI and Filament Action Surface Contract: N/A - no Filament, Blade admin, Livewire, Resource, RelationManager, or Page changes.
|
||||||
|
- Decision-first / audience-aware operator disclosure: N/A - no operator-facing admin detail/status surface.
|
||||||
|
- UI review workflow: Pass - plan keeps the spec's `no operator-facing surface change` classification.
|
||||||
|
|
||||||
|
### Post-Design Constitution Check
|
||||||
|
|
||||||
|
Phase 0 and Phase 1 design artifacts preserve the same decisions: static website-only scope, no backend/API/runtime coupling, no new persistence, no new abstractions, and browser smoke validation as the feature proof. Repository-wide Pest and Pint close-out commands are explicit, so no unresolved gate violations remain.
|
||||||
|
|
||||||
|
## Test Governance Check
|
||||||
|
|
||||||
|
- **Test purpose / classification by changed surface**: Browser for public website route, navigation, metadata, copy safety, and responsive behavior.
|
||||||
|
- **Affected validation lanes**: browser.
|
||||||
|
- **Why this lane mix is the narrowest sufficient proof**: The feature is a static public page. Playwright route/content checks plus build validation prove user-visible route behavior without Laravel, database, workspace, tenant, provider, or session setup.
|
||||||
|
- **Narrowest proving command(s)**: `corepack pnpm build:website`; `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`; `git diff --check`; desktop and mobile browser screenshot review.
|
||||||
|
- **Repository-wide constitution close-out**: Run `cd /Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform && ./vendor/bin/sail artisan test --compact tests/Deprecation/IsPlatformSuperadminDeprecationTest.php` and `cd /Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform && ./vendor/bin/sail bin pint --dirty` before finalizing. These commands satisfy the repository-wide gate without adding platform fixtures or treating Laravel behavior as the feature proof.
|
||||||
|
- **Fixture / helper / factory / seed / context cost risks**: None.
|
||||||
|
- **Expensive defaults or shared helper growth introduced?**: No.
|
||||||
|
- **Heavy-family additions, promotions, or visibility changes**: None beyond existing website smoke family.
|
||||||
|
- **Surface-class relief / special coverage rule**: N/A.
|
||||||
|
- **Closing validation and reviewer handoff**: Reviewers should confirm smoke coverage remains website-scoped and asserts `/platform`, homepage CTA route, header/footer route, `/product` compatibility, old public-brand residue absence, claim safety, and mobile overflow.
|
||||||
|
- **Budget / baseline / trend follow-up**: None expected.
|
||||||
|
- **Review-stop questions**: Stop if tests require platform services, hidden fixtures, backend auth, database, broad browser families, or unbounded visual regression scope.
|
||||||
|
- **Escalation path**: document-in-feature.
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage.
|
||||||
|
- **Why no dedicated follow-up spec is needed**: The smoke expansion is feature-local and does not create recurring test-lane cost or new infrastructure.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/
|
||||||
|
|-- plan.md
|
||||||
|
|-- research.md
|
||||||
|
|-- data-model.md
|
||||||
|
|-- quickstart.md
|
||||||
|
|-- contracts/
|
||||||
|
| `-- public-routes.openapi.yaml
|
||||||
|
|-- checklists/
|
||||||
|
| `-- requirements.md
|
||||||
|
`-- tasks.md # created later by /speckit.tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
|
||||||
|
```text
|
||||||
|
/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/
|
||||||
|
|-- package.json
|
||||||
|
|-- src/
|
||||||
|
| |-- components/
|
||||||
|
| | |-- content/
|
||||||
|
| | |-- layout/
|
||||||
|
| | |-- primitives/
|
||||||
|
| | `-- sections/
|
||||||
|
| |-- content/pages/
|
||||||
|
| | |-- home.ts
|
||||||
|
| | |-- platform.ts # static page content and visual-section copy
|
||||||
|
| | `-- product.ts # planned stale-content retirement path
|
||||||
|
| |-- lib/
|
||||||
|
| | |-- seo.ts
|
||||||
|
| | `-- site.ts
|
||||||
|
| |-- pages/
|
||||||
|
| | |-- index.astro
|
||||||
|
| | |-- platform.astro # canonical route and mockup-aligned visual composition
|
||||||
|
| | `-- product.astro # planned redirect compatibility route
|
||||||
|
| |-- styles/
|
||||||
|
| `-- types/
|
||||||
|
`-- tests/smoke/
|
||||||
|
|-- home-product.spec.ts # planned update or split
|
||||||
|
`-- smoke-helpers.ts # planned route expectations update
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure Decision**: Use the existing standalone Astro website structure under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`. Keep content in typed page content modules, render through existing layout and section primitives, and extend existing smoke tests instead of adding a second website architecture.
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
No constitution violations or BLOAT-001-triggering runtime complexity are introduced.
|
||||||
|
|
||||||
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||||
|
|-----------|------------|-------------------------------------|
|
||||||
|
| None | N/A | N/A |
|
||||||
|
|
||||||
|
## Proportionality Review
|
||||||
|
|
||||||
|
- **Current operator problem**: Public buyers and stakeholders cannot yet understand Tenantial's platform operating model from a dedicated page after the homepage.
|
||||||
|
- **Existing structure is insufficient because**: The homepage introduces the promise, while existing `/product` copy is not the canonical `/platform` journey and still leaves route confusion.
|
||||||
|
- **Narrowest correct implementation**: One canonical static `/platform` page, route/navigation alignment, `/product` redirect compatibility, metadata, and focused smoke checks.
|
||||||
|
- **Ownership cost created**: One additional public page, static content maintenance, route compatibility expectations, and a small smoke-test expansion.
|
||||||
|
- **Alternative intentionally rejected**: Build pricing, trust center, CMS, i18n, live screenshots, backend integration, or platform runtime hooks in this slice.
|
||||||
|
- **Release truth**: Current public website truth.
|
||||||
|
|
||||||
|
## Phase 0 Research
|
||||||
|
|
||||||
|
See [/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/research.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/research.md).
|
||||||
|
|
||||||
|
## Phase 1 Design
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
- [/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/data-model.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/data-model.md)
|
||||||
|
- [/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/contracts/public-routes.openapi.yaml](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/contracts/public-routes.openapi.yaml)
|
||||||
|
- [/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/quickstart.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/quickstart.md)
|
||||||
|
|
||||||
|
## Phase 2 Planning Boundary
|
||||||
|
|
||||||
|
This `/speckit.plan` run stops after Phase 2 planning artifacts. Implementation tasks are intentionally left for `/speckit.tasks`.
|
||||||
90
specs/401-tenantial-platform-page/quickstart.md
Normal file
90
specs/401-tenantial-platform-page/quickstart.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# Quickstart: Tenantial Platform Page
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Work from `/Users/ahmeddarrazi/Documents/projects/wt-website`.
|
||||||
|
- Use branch `feat/401-tenantial-platform-page`.
|
||||||
|
- Keep scope limited to `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` and this feature spec directory.
|
||||||
|
- Do not modify `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` for this feature.
|
||||||
|
|
||||||
|
## Implementation Outline
|
||||||
|
|
||||||
|
1. Verify the current website route structure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find apps/website/src/pages -maxdepth 1 -type f | sort
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add the canonical Platform route:
|
||||||
|
|
||||||
|
- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`
|
||||||
|
|
||||||
|
3. Update website route and navigation definitions:
|
||||||
|
|
||||||
|
- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`
|
||||||
|
- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/types/site.ts`
|
||||||
|
|
||||||
|
4. Update homepage Platform CTA destinations:
|
||||||
|
|
||||||
|
- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
|
||||||
|
5. Handle `/product` compatibility:
|
||||||
|
|
||||||
|
- Redirect `/product` to `/platform`.
|
||||||
|
- Retire or stop importing stale `/product` page content so the route cannot render conflicting public copy.
|
||||||
|
|
||||||
|
6. Add or update smoke coverage:
|
||||||
|
|
||||||
|
- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
|
||||||
|
## Validation Commands
|
||||||
|
|
||||||
|
Run from `/Users/ahmeddarrazi/Documents/projects/wt-website`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
corepack pnpm build:website
|
||||||
|
WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Also review desktop and mobile screenshots for the mockup-aligned visual structure: split hero with dashboard preview, compact Operating Model flow, connected Governance Loop diagram, asymmetric Capabilities, Truth Layers visual, warmer Operator Workflows, Boundaries band, focused CTA, and quiet footer.
|
||||||
|
|
||||||
|
Before finalizing, also run the repository-wide constitution Quality Gates without treating Laravel behavior as the feature proof:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform && ./vendor/bin/sail artisan test --compact tests/Deprecation/IsPlatformSuperadminDeprecationTest.php
|
||||||
|
cd /Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform && ./vendor/bin/sail bin pint --dirty
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Review Checklist
|
||||||
|
|
||||||
|
- `/platform` renders the Tenantial Platform page.
|
||||||
|
- First viewport explains evidence-first governance for Microsoft tenants and includes a large static dashboard/product preview.
|
||||||
|
- Operating Model renders as a compact connected flow: Snapshot, Drift, Finding, Review, Evidence, Audit trail.
|
||||||
|
- Capability section uses a larger Backup & Restore block plus Drift Detection, Findings, Evidence, Audit Trail, Exceptions, and Governance Reviews.
|
||||||
|
- Governance loop renders as a connected diagram and makes operator review, evidence preservation, and auditability explicit.
|
||||||
|
- Truth Layers render as a layer-stack or equivalent visual model.
|
||||||
|
- Operator Workflow cards are compact and visually warmer than the main control-room sections.
|
||||||
|
- Platform boundaries state that Tenantial is not a helpdesk action tool, live device action tool, SIEM, or Microsoft admin center replacement.
|
||||||
|
- Final CTA and footer do not create two equally heavy closing CTA zones.
|
||||||
|
- Homepage "Explore the platform" reaches `/platform`.
|
||||||
|
- Header and footer Platform links reach `/platform`.
|
||||||
|
- `/product`, if retained, redirects to `/platform` and does not expose stale or conflicting public copy.
|
||||||
|
- Page metadata uses Tenantial branding and canonical `/platform`.
|
||||||
|
- No visible or metadata occurrences of AstroDeck, TemplateDeck, Open Source, MIT, TenantAtlas, TenantPilot, or TenantCTRL.
|
||||||
|
- No unsupported claims: SOC 2, ISO, HIPAA, blanket GDPR, guaranteed recovery, guaranteed compliance, zero drift, real-time everywhere, named customers, Microsoft certification, or Microsoft partnership.
|
||||||
|
- Mobile, tablet, desktop, and wide desktop review show no body-level horizontal overflow.
|
||||||
|
- Links and CTAs are keyboard-accessible and have visible focus states.
|
||||||
|
|
||||||
|
## Out-of-Scope Checks
|
||||||
|
|
||||||
|
Stop and return to planning if implementation appears to require:
|
||||||
|
|
||||||
|
- Laravel, Filament, Livewire, or Blade admin changes.
|
||||||
|
- Database migrations, persisted entities, queues, workers, or schedules.
|
||||||
|
- Microsoft Graph calls or platform API calls.
|
||||||
|
- Authentication, sessions, account creation, or demo-booking backend logic.
|
||||||
|
- Third-party tracking scripts or external embeds.
|
||||||
|
- Real customer screenshots, tenant identifiers, customer logos, certifications, or partnership claims.
|
||||||
100
specs/401-tenantial-platform-page/research.md
Normal file
100
specs/401-tenantial-platform-page/research.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Phase 0 Research: Tenantial Platform Page
|
||||||
|
|
||||||
|
## Decision: Keep the feature inside the standalone Astro website
|
||||||
|
|
||||||
|
**Rationale**: The active spec is public website-only. The repository already has a standalone Astro app at `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` with typed content modules, reusable layout components, Tailwind v4 styling, SEO helpers, and Playwright smoke tests. Keeping the work there preserves the boundary from the Laravel platform app.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Change `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform`: rejected because the spec excludes Laravel, Filament, Livewire, auth, APIs, database, queues, and runtime tenant data.
|
||||||
|
- Add a separate website package or docs system: rejected because the existing Astro website already supports the needed static route and public shell.
|
||||||
|
|
||||||
|
## Decision: Make `/platform` the canonical route
|
||||||
|
|
||||||
|
**Rationale**: The feature is explicitly a Tenantial Platform page and Spec 400 created an "Explore the platform" journey. A canonical `/platform` route is clearer for buyers than retaining `/product` as the primary destination.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Keep `/product` as primary: rejected because the spec asks for `/platform` and the current homepage CTA should become a clean platform journey.
|
||||||
|
- Remove `/product` immediately with no compatibility: rejected because the route already exists and current tests/navigation may still reference it during the transition.
|
||||||
|
|
||||||
|
## Decision: Treat `/product` as redirect-only compatibility
|
||||||
|
|
||||||
|
**Rationale**: The current repository has `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/product.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/product.ts`. The narrowest safe path is to keep `/product` reachable while preventing stale or conflicting copy by redirecting it to `/platform`. This avoids maintaining two renderable public product pages or duplicate metadata during the transition.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Leave `/product` unchanged: rejected because it would create route confusion and keep homepage/platform semantics split.
|
||||||
|
- Delete `/product` in the same slice: rejected because it can break existing public links and smoke tests without adding product value.
|
||||||
|
- Render equivalent Platform content at `/product`: rejected because it keeps a second renderable product page and makes metadata/canonical drift more likely than redirect-only compatibility.
|
||||||
|
|
||||||
|
## Decision: Store Platform page content in a typed static content module
|
||||||
|
|
||||||
|
**Rationale**: Existing public pages use `apps/website/src/content/pages/*.ts` to hold SEO and page content, then render through `apps/website/src/pages/*.astro`. A new `platform.ts` content module matches the current pattern and keeps static copy centralized without adding a CMS or new data source.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Put all copy inline in `platform.astro`: rejected because it would diverge from current website content conventions.
|
||||||
|
- Add a CMS or content collection for the Platform page: rejected because the spec excludes CMS complexity.
|
||||||
|
|
||||||
|
## Decision: Reuse existing website shell, primitives, and section components first
|
||||||
|
|
||||||
|
**Rationale**: Existing components already cover PageShell, Navbar, Footer, PageHero, FeatureGrid, CTASection, Section, Container, Grid, Card, Badge, and CTA primitives. Reusing them keeps Spec 401 aligned with Spec 400 and avoids a parallel public-site component language.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Create a full new Platform page component system: rejected as unnecessary abstraction for one route.
|
||||||
|
- Use a large raster screenshot as the primary visual: rejected because no approved Tenantial product screenshot exists and the spec prohibits fake live data.
|
||||||
|
|
||||||
|
## Decision: Use static diagrams and structured panels for the platform model
|
||||||
|
|
||||||
|
**Rationale**: The page needs to explain operating model, governance loop, truth layers, and boundaries without implying live tenant data. Static HTML/CSS/SVG-like structure or existing panel/card components can communicate the model accessibly and keep performance simple.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Use real tenant screenshots: rejected because no approved customer or tenant environment assets are available.
|
||||||
|
- Use heavy animation or diagram libraries: rejected because the spec requires mostly static content and no unnecessary client-side JavaScript.
|
||||||
|
|
||||||
|
## Decision: Update site route definitions and navigation metadata
|
||||||
|
|
||||||
|
**Rationale**: `apps/website/src/lib/site.ts` owns primary navigation, footer navigation, route definitions, canonical routes, and sitemap inclusion. Adding `/platform` there keeps header/footer/sitemap/canonical behavior consistent with current site conventions.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Hardcode `/platform` only in page files: rejected because header/footer and smoke helpers would remain inconsistent.
|
||||||
|
- Add a second route registry: rejected as needless parallel state.
|
||||||
|
|
||||||
|
## Decision: Keep "Book a demo" pointed at the existing contact route
|
||||||
|
|
||||||
|
**Rationale**: The current public conversion path is `/contact`, and the spec excludes a demo-booking backend. Reusing `/contact` keeps CTA behavior intentional and avoids fake form behavior.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Create `/demo` or `/pricing`: rejected because those pages/flows are out of scope.
|
||||||
|
- Use a dead placeholder link: rejected because the spec requires intentional, working CTA destinations.
|
||||||
|
|
||||||
|
## Decision: Expand website smoke coverage and explicitly close repository-wide gates
|
||||||
|
|
||||||
|
**Rationale**: The feature changes a public static route, navigation, metadata, copy safety, and responsive behavior. Existing Playwright smoke tests are the correct feature proof surface. No new Laravel/Pest/database/Filament product test should be introduced because no platform behavior changes. Repository-wide close-out still runs a targeted existing Pest hygiene check plus Pint dirty formatting before finalization.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Add platform feature tests: rejected because they would introduce unrelated setup and false confidence for a website-only route.
|
||||||
|
- Add broad visual regression infrastructure: rejected because this feature only needs focused route/content/mobile smoke coverage.
|
||||||
|
|
||||||
|
## Decision: Preserve conservative public claim boundaries
|
||||||
|
|
||||||
|
**Rationale**: Tenantial's public trust depends on avoiding unsupported claims. The page should use language such as evidence-oriented, reviewable decisions, recovery context, operator confirmation, and traceable records while avoiding certifications, guarantees, named customers, Microsoft endorsement, and real-time claims unless approved evidence exists.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Use stronger enterprise proof language for sales impact: rejected because unsupported claims create trust and compliance risk.
|
||||||
|
- Omit boundaries entirely: rejected because the page must clarify that Tenantial is not a helpdesk, SIEM, device action tool, or Microsoft admin center replacement.
|
||||||
|
|
||||||
|
## Decision: No unresolved clarifications remain
|
||||||
|
|
||||||
|
**Rationale**: The current repository answers the planning unknowns: Astro website exists, `/product` exists, `/platform` does not yet exist, navigation points to `/product`, smoke tests exist, and `/contact` is the current conversion path.
|
||||||
|
|
||||||
|
**Alternatives considered**:
|
||||||
|
|
||||||
|
- Ask for route/CTA clarification: rejected because the spec provides reasonable defaults and current repository state supports a safe path.
|
||||||
280
specs/401-tenantial-platform-page/spec.md
Normal file
280
specs/401-tenantial-platform-page/spec.md
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
# Feature Specification: Tenantial Platform Page
|
||||||
|
|
||||||
|
**Feature Branch**: `feat/401-tenantial-platform-page`
|
||||||
|
**Created**: 2026-05-18
|
||||||
|
**Status**: Draft
|
||||||
|
**Input**: User description: "Spec 401 - Tenantial Platform Page. Create a dedicated public `/platform` page for Tenantial that explains the evidence-first governance operating model, core capabilities, governance loop, safe product boundaries, navigation consistency, Tenantial metadata, accessibility, responsiveness, conservative claims, and website-only runtime scope."
|
||||||
|
|
||||||
|
**Visual Refactor Update**: 2026-05-18 follow-up request: refactor `/platform` so it closely follows the provided Tenantial mockup as a target for composition, visual hierarchy, density, product expression, dashboard-preview hero, connected governance diagrams, asymmetric capability grouping, truth-layer visual, warmer operator workflow zone, clear boundaries band, focused CTA, and quiet footer.
|
||||||
|
|
||||||
|
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||||
|
|
||||||
|
- **Problem**: After the homepage rebuild, visitors have a stronger first impression but no dedicated page that explains what Tenantial governs, how the governance loop works, and how backup, restore, drift, findings, evidence, audit trails, exceptions, and reviews connect.
|
||||||
|
- **Today's failure**: A buyer can mistake Tenantial for a generic backup tool, helpdesk product, device action tool, or template site because the public website does not yet explain the product model beyond the homepage promise.
|
||||||
|
- **User-visible improvement**: The Platform page gives IT, MSP, security, and audit stakeholders a structured explanation of Tenantial's evidence-first governance model and clear next steps without unsupported security, compliance, customer, or runtime-data claims.
|
||||||
|
- **Smallest enterprise-capable version**: Add one canonical public Platform page, align public navigation and homepage CTA links to it, preserve or resolve `/product` compatibility if needed, provide Tenantial-specific metadata, include conservative product boundaries, and add website smoke coverage for the route and key claims.
|
||||||
|
- **Explicit non-goals**: No platform runtime changes, no admin UI, no Filament or Livewire changes, no authentication, no backend APIs, no Microsoft Graph calls, no real tenant data, no CMS, no i18n, no pricing page, no trust center, no demo-booking backend, and no customer logos or unsupported certifications.
|
||||||
|
- **Permanent complexity imported**: One public website page narrative, route and navigation expectations, static content sections, website-only smoke expectations, and ongoing responsibility to keep public Tenantial copy aligned with Spec 400.
|
||||||
|
- **Why now**: Spec 400 makes "Explore the platform" a natural next step; without a real Platform page, the primary homepage journey is incomplete and the public product story remains underexplained.
|
||||||
|
- **Why not local**: A homepage-only copy patch cannot answer product-model questions or resolve `/platform` versus `/product` navigation consistency across the public site.
|
||||||
|
- **Approval class**: Core Enterprise
|
||||||
|
- **Red flags triggered**: None. The feature introduces no persisted truth, domain state, runtime abstraction, provider contract, or cross-domain UI framework.
|
||||||
|
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12**
|
||||||
|
- **Decision**: approve
|
||||||
|
|
||||||
|
## Spec Scope Fields *(mandatory)*
|
||||||
|
|
||||||
|
- **Scope**: Public website. This is outside workspace, tenant, and canonical admin scopes.
|
||||||
|
- **Primary Routes**: `/platform`; existing `/product` route must be retained only as redirect compatibility to `/platform` if present.
|
||||||
|
- **Data Ownership**: Static public website content only. No workspace-owned, tenant-owned, platform runtime, or persisted application data is introduced.
|
||||||
|
- **RBAC**: None. The Platform page is public and introduces no authorization behavior.
|
||||||
|
|
||||||
|
## Relationship To Existing Specs
|
||||||
|
|
||||||
|
- Spec 400 establishes the Tenantial homepage direction, dark premium visual language, public brand copy, and the homepage "Explore the platform" journey.
|
||||||
|
- Spec 401 extends that direction into the first dedicated public product explanation page.
|
||||||
|
- Spec 223 remains historical website rebuild context if still present, but Spec 401 is the Tenantial-specific Platform page slice.
|
||||||
|
- If no local Spec 227 artifact exists, this spec does not create one. Any homepage/platform visual foundation overlap is treated as refined by Specs 400 and 401.
|
||||||
|
|
||||||
|
## Visual Refactor Target
|
||||||
|
|
||||||
|
- `/platform` should read as a product-marketing page with governance-control-room density, not as a documentation page or a repeated stack of equal dark cards.
|
||||||
|
- The first viewport must use a split layout: concise positioning and CTAs on the left, a large static dashboard/product preview on the right.
|
||||||
|
- The Operating Model must render as a compact connected step chain: Snapshot, Drift, Finding, Review, Evidence, Audit trail.
|
||||||
|
- The Governance Loop must be a central visual diagram connecting Source of truth, Snapshot, Diff, Finding, Exception, Review, Evidence, and Audit trail.
|
||||||
|
- Capabilities must be visually grouped with a larger Backup & Restore primary block and secondary capability cards for Drift Detection, Findings, Evidence, Audit Trail, Exceptions, and Governance Reviews.
|
||||||
|
- Truth Layers must use a layer-stack or equivalent visual model for Execution truth, Artifact truth, Backup truth, Recovery evidence, and Operator next action.
|
||||||
|
- Operator Workflows should be compact, persona-oriented, and visually warmer than the control-room sections.
|
||||||
|
- Platform Boundaries must read as a trust/honesty band with short boundary statements rather than a defensive list.
|
||||||
|
- Final CTA and footer must avoid two equally heavy closing sections; the footer should be calmer after the focused CTA.
|
||||||
|
- The refactor remains website-only and must not change `apps/platform`, workspace contracts, packages, dependencies, or lockfiles.
|
||||||
|
|
||||||
|
## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*
|
||||||
|
|
||||||
|
N/A - no shared operator interaction family touched. This feature changes public marketing navigation and content, not admin notifications, operator status messaging, operation links, dashboard signals, alerts, report viewers, or shared operator interaction contracts.
|
||||||
|
|
||||||
|
## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)*
|
||||||
|
|
||||||
|
N/A - no OperationRun start, completion, deduplication, resume, block, or link semantics touched.
|
||||||
|
|
||||||
|
## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)*
|
||||||
|
|
||||||
|
N/A - no shared provider/platform boundary touched. The page may describe Microsoft tenant governance at a public product level, but it does not change provider contracts, platform-core vocabulary, identity scope, compare strategy, governed-subject taxonomy, or runtime provider behavior.
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
|
||||||
|
|
||||||
|
N/A - no operator-facing admin surface change. This feature changes a public marketing page only and does not affect Filament/admin pages, tenant-scoped operator workflows, shared operator components, action surfaces, or governance decision surfaces.
|
||||||
|
|
||||||
|
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||||
|
|
||||||
|
- **New source of truth?**: Yes, limited to public Platform page messaging and public route intent. No product runtime or tenant-governance truth is introduced.
|
||||||
|
- **New persisted entity/table/artifact?**: No.
|
||||||
|
- **New abstraction?**: No runtime abstraction. Any page sections are website-local presentation content for this public page.
|
||||||
|
- **New enum/state/reason family?**: No.
|
||||||
|
- **New cross-domain UI framework/taxonomy?**: No.
|
||||||
|
- **Current operator problem**: Prospective buyers and stakeholders cannot yet understand Tenantial's governance operating model from a dedicated public page.
|
||||||
|
- **Existing structure is insufficient because**: The homepage introduces the promise but cannot carry a full explanation of the platform model, capability relationships, route expectations, boundaries, and proof-safe positioning.
|
||||||
|
- **Narrowest correct implementation**: One static public Platform page with aligned navigation, route compatibility expectations, conservative copy, accessibility/responsive requirements, Tenantial metadata, and route smoke coverage.
|
||||||
|
- **Ownership cost**: Platform page copy, static section content, navigation labels, metadata, and smoke assertions must be maintained as later website pages are added.
|
||||||
|
- **Alternative intentionally rejected**: Building pricing, trust, solutions, resources, i18n, CMS, or live product-data integration in the same slice. Those would broaden scope before the core Platform explanation is stable.
|
||||||
|
- **Release truth**: Current public website truth, not platform runtime truth or future live data truth.
|
||||||
|
|
||||||
|
### Compatibility posture
|
||||||
|
|
||||||
|
This feature assumes a pre-production environment.
|
||||||
|
|
||||||
|
Backward compatibility for stale public `/product` copy is not preserved as a product requirement. If `/product` already exists, it must remain only as a redirect compatibility path to `/platform`. No public route may expose stale AstroDeck, TemplateDeck, TenantAtlas, TenantPilot, or TenantCTRL copy.
|
||||||
|
|
||||||
|
Internal package names, workspace scripts, and platform runtime contracts must not be renamed by this feature. In particular, an internal website package name may remain unchanged even if it includes a historical repository name.
|
||||||
|
|
||||||
|
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||||
|
|
||||||
|
- **Test purpose / classification**: Browser.
|
||||||
|
- **Validation lane(s)**: browser.
|
||||||
|
- **Why this classification and these lanes are sufficient**: The feature changes a public static website route, navigation, content, metadata, responsive behavior, and copy safety. Website build and smoke checks are the narrowest honest proof.
|
||||||
|
- **New or expanded test families**: Platform route website smoke coverage only.
|
||||||
|
- **Fixture / helper cost impact**: None. No database, workspace, membership, provider, session, seed, or platform setup should be required.
|
||||||
|
- **Heavy-family visibility / justification**: Browser coverage is intentional and scoped to the public Platform route plus route compatibility where needed.
|
||||||
|
- **Special surface test profile**: N/A.
|
||||||
|
- **Standard-native relief or required special coverage**: Website smoke checks plus desktop/mobile visual review.
|
||||||
|
- **Reviewer handoff**: Reviewers must confirm browser coverage remains website-scoped, no hidden platform setup is introduced, and the proof checks visible content, metadata cleanup, navigation, and mobile overflow.
|
||||||
|
- **Budget / baseline / trend impact**: None expected beyond a small Platform route smoke addition.
|
||||||
|
- **Escalation needed**: document-in-feature.
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage.
|
||||||
|
- **Planned validation commands**: `corepack pnpm build:website`; `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`; `git diff --check`.
|
||||||
|
|
||||||
|
## User Scenarios & Testing *(mandatory)*
|
||||||
|
|
||||||
|
### User Story 1 - Understand The Platform Model (Priority: P1)
|
||||||
|
|
||||||
|
A first-time buyer or stakeholder opens the Platform page and understands that Tenantial is an evidence-first governance platform for Microsoft tenant environments, not only a backup tool or generic SaaS site.
|
||||||
|
|
||||||
|
**Why this priority**: This is the minimum viable Platform page outcome. If the page does not explain the product model, the homepage CTA journey remains incomplete.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by opening `/platform` and verifying that the first viewport shows Tenantial Platform positioning, a clear governance headline, Microsoft tenant context, and a product-oriented explanation.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a visitor follows "Explore the platform" from the homepage, **When** `/platform` opens, **Then** they see Tenantial Platform branding and a headline about governing Microsoft tenants through evidence, drift context, and reviewable decisions.
|
||||||
|
2. **Given** a visitor reads the first viewport, **When** they scan the hero, **Then** they can identify backup records, configuration drift, findings, evidence, audit trails, and structured reviews as connected parts of the platform model, supported by a large static dashboard/product preview.
|
||||||
|
3. **Given** a visitor is deciding what to do next, **When** they inspect the page actions, **Then** "Book a demo" is available as the primary CTA and "See the governance loop" guides them deeper into the page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 2 - See How Governance Work Flows (Priority: P2)
|
||||||
|
|
||||||
|
An IT, MSP, security, or compliance stakeholder reviews the operating model and understands how raw tenant change becomes reviewable governance evidence.
|
||||||
|
|
||||||
|
**Why this priority**: Tenantial's public promise depends on explaining the governance loop, not just listing features.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by reviewing the operating model and governance loop sections for the required steps and conservative operator-led language.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a stakeholder reaches the operating model section, **When** they review the workflow, **Then** they see a compact connected sequence covering Snapshot, Drift, Finding, Review, Evidence, and Audit trail.
|
||||||
|
2. **Given** the governance loop is visible, **When** the visitor scans the section, **Then** they understand through a connected diagram that governance is about preserving context and reviewable decisions, not only knowing current configuration state.
|
||||||
|
3. **Given** the page describes findings and restore context, **When** a visitor reads the copy, **Then** it does not imply automatic remediation, guaranteed recovery, or device action workflows.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - Evaluate Capabilities And Boundaries (Priority: P3)
|
||||||
|
|
||||||
|
A stakeholder evaluates the page and can distinguish Tenantial's core capabilities from out-of-scope helpdesk, SIEM, device action, or Microsoft admin center replacement promises.
|
||||||
|
|
||||||
|
**Why this priority**: Product trust depends on explaining what the platform does and where its boundaries are.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by reviewing capability, truth-layer, workflow, and boundary sections for required concepts and absence of unsupported claims.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a visitor reads the capability section, **When** they scan the grouped capability zone, **Then** they see Backup & Restore as the primary block plus Drift Detection, Findings, Evidence, Audit Trail, Exceptions, and Governance Reviews as supporting capabilities.
|
||||||
|
2. **Given** a visitor reads the platform boundaries section, **When** they compare Tenantial to other tools, **Then** they understand it is built for governance of record and recovery context, not helpdesk actions or live device operations.
|
||||||
|
3. **Given** a security or audit stakeholder reviews the page, **When** they inspect claims, **Then** the page contains no unsupported certifications, guarantees, customer logos, Microsoft endorsement, uptime promises, or blanket compliance statements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 4 - Navigate A Brand-Clean Public Site (Priority: P4)
|
||||||
|
|
||||||
|
A website owner reviews the public site journey and confirms that navigation, metadata, and compatibility routing support the Platform page without stale brand or template residue.
|
||||||
|
|
||||||
|
**Why this priority**: The Platform page should become the canonical next step after the homepage without introducing route confusion.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested through link review, metadata review, visible-copy review, and website smoke coverage for `/platform` plus redirect behavior from `/product` if `/product` remains.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** the homepage is available, **When** a visitor activates "Explore the platform", **Then** they reach `/platform`.
|
||||||
|
2. **Given** the global header or footer includes Platform, **When** a visitor follows that link, **Then** it reaches the canonical Platform page.
|
||||||
|
3. **Given** `/product` exists before implementation, **When** route compatibility is reviewed, **Then** it redirects to `/platform` and exposes no stale public copy or conflicting metadata.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- If `/product` already exists, it must redirect to `/platform` and must not remain a renderable primary public product route with inconsistent or stale content.
|
||||||
|
- If no dedicated demo-booking flow exists, "Book a demo" must point to an existing intentional contact path or use another explicitly documented non-dead destination.
|
||||||
|
- If no approved product screenshot exists, the page may use static diagrams, structured panels, or product-process visuals rather than fake live screenshots.
|
||||||
|
- If no customer logos, certifications, partnership proof, or compliance evidence are available, the page must use conservative product language instead of social proof.
|
||||||
|
- If a visitor uses a narrow mobile viewport, diagrams and card grids must simplify or stack without body-level horizontal overflow.
|
||||||
|
- If icons or visual diagrams are unavailable to assistive technology, adjacent text must communicate the same meaning.
|
||||||
|
- If status or capability meaning is shown with accent colors, the same meaning must be available through text labels.
|
||||||
|
- If Spec 400 components are not available in the current branch, the page must still follow the same visual direction rather than inventing a separate brand style.
|
||||||
|
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
**Constitution alignment (required):** This feature introduces no Microsoft Graph calls, no write/change behavior, no queued or scheduled work, no OperationRun, no AuditLog changes, no tenant isolation changes, and no platform runtime behavior.
|
||||||
|
|
||||||
|
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** This feature introduces no persisted product truth, no domain abstraction, no enum/status/reason family, and no cross-domain UI framework. The only new source of truth is public Platform page messaging and route intent, bounded to the website.
|
||||||
|
|
||||||
|
**Constitution alignment (XCUT-001):** This feature does not touch shared operator interaction families. Public website navigation, CTAs, static diagrams, and marketing visuals must remain website-local.
|
||||||
|
|
||||||
|
**Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** This feature does not change admin detail/status surfaces, audience modes, support/raw evidence disclosure, or operator next-action surfaces.
|
||||||
|
|
||||||
|
**Constitution alignment (PROV-001):** This feature does not change provider/platform seams. Microsoft tenant language is public positioning only and must not create runtime provider coupling.
|
||||||
|
|
||||||
|
**Constitution alignment (TEST-GOV-001):** Browser coverage is intentional, Platform-route-scoped, and must not introduce database, workspace, provider, membership, session, or platform defaults.
|
||||||
|
|
||||||
|
**Constitution alignment (OPS-UX / OPS-UX-START-001):** N/A - no OperationRun behavior is created, queued, completed, deduplicated, resumed, blocked, or linked.
|
||||||
|
|
||||||
|
**Constitution alignment (RBAC-UX):** N/A - no authorization plane, capability, membership, global search, mutation, or destructive action behavior changes.
|
||||||
|
|
||||||
|
**Constitution alignment (BADGE-001):** N/A - no admin status badge semantics change. Public marketing labels must remain descriptive and not become a shared status taxonomy.
|
||||||
|
|
||||||
|
**Constitution alignment (UI-FIL-001):** N/A - no Filament, Blade admin, Livewire, Resource, RelationManager, or Page changes.
|
||||||
|
|
||||||
|
**Constitution alignment (UI-NAMING-001 / DECIDE-001 / UI surface rules):** N/A for operator-facing naming and action-surface contracts. Public Platform copy must still use precise user-facing language and avoid implementation-first labels.
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: The public website MUST provide a canonical Platform page at `/platform`.
|
||||||
|
- **FR-002**: The Platform page MUST present Tenantial as an evidence-first governance platform for Microsoft tenant environments.
|
||||||
|
- **FR-003**: The first viewport MUST include the eyebrow "TENANTIAL PLATFORM" or equivalent wording with the same meaning.
|
||||||
|
- **FR-004**: The first viewport MUST include a headline that communicates governing Microsoft tenants through evidence, drift context, and reviewable decisions.
|
||||||
|
- **FR-005**: The first viewport supporting copy MUST connect backup records, configuration drift, findings, evidence, audit trails, and structured reviews into one governance operating loop.
|
||||||
|
- **FR-006**: The first viewport MUST provide "Book a demo" as the primary CTA.
|
||||||
|
- **FR-007**: The first viewport MUST provide a secondary CTA that navigates to the governance loop section or equivalent in-page explanation.
|
||||||
|
- **FR-008**: The Platform page MUST include a large static dashboard/product preview in the hero that is clearly public marketing content and not live tenant data.
|
||||||
|
- **FR-009**: The Platform page MUST include a compact connected operating model overview showing the sequence: Snapshot, Drift, Finding, Review, Evidence, and Audit trail.
|
||||||
|
- **FR-010**: The Platform page MUST include a visually grouped capability section with Backup & Restore as the primary block plus Drift Detection, Findings, Evidence, Audit Trail, Exceptions, and Governance Reviews as supporting capabilities.
|
||||||
|
- **FR-011**: Capability descriptions MUST be concise, product-specific, and free of claims that imply guaranteed remediation, guaranteed compliance, or full automation.
|
||||||
|
- **FR-012**: The Platform page MUST include a governance loop section with a connected visual diagram explaining how source-of-truth context, snapshots, diffs, findings, exceptions, reviews, evidence, and audit trails connect.
|
||||||
|
- **FR-013**: The governance loop MUST make operator review and evidence preservation explicit.
|
||||||
|
- **FR-014**: The Platform page SHOULD include a truth-layers section with a visual layer-stack or equivalent model explaining execution truth, artifact truth, backup truth, recovery evidence, and operator next action in plain language.
|
||||||
|
- **FR-015**: The Platform page SHOULD include compact operator workflow examples for MSP operators, enterprise IT, security reviewers, and compliance or audit stakeholders, with a warmer visual treatment than the main control-room sections.
|
||||||
|
- **FR-016**: The Platform page MUST include a platform boundaries section stating that Tenantial is built for governance of record and recovery context, does not take actions on the customer's behalf, does not manage devices, does not replace ITSM/SIEM/ticketing/Microsoft admin centers, and supports reviewable decisions.
|
||||||
|
- **FR-017**: The Platform page MUST include a focused final CTA that invites visitors to see how evidence-first governance fits their Microsoft tenant operations, followed by a calmer footer.
|
||||||
|
- **FR-018**: "Book a demo" CTA destinations MUST be intentional and MUST NOT point to missing or stale template routes.
|
||||||
|
- **FR-019**: Homepage "Explore the platform" navigation MUST point to `/platform`.
|
||||||
|
- **FR-020**: Header and footer Platform navigation MUST point to `/platform`.
|
||||||
|
- **FR-021**: If `/product` exists, it MUST redirect to `/platform` and MUST NOT render stale, conflicting, or primary public product content.
|
||||||
|
- **FR-022**: No public route involved in this feature MAY expose stale AstroDeck, TemplateDeck, Open Source, MIT, TenantAtlas, TenantPilot, or TenantCTRL public copy.
|
||||||
|
- **FR-023**: The Platform page MUST use public Tenantial branding and MUST NOT rename internal package or workspace conventions.
|
||||||
|
- **FR-024**: The Platform page title MUST communicate "Tenantial Platform" and evidence-first governance for Microsoft tenants.
|
||||||
|
- **FR-025**: The Platform page description metadata MUST mention backup, restore, drift detection, findings, evidence, audit trails, exceptions, and reviews for Microsoft tenant governance.
|
||||||
|
- **FR-026**: Metadata MUST NOT contain AstroDeck, TemplateDeck, old public brand names, unsupported compliance claims, or unsupported security claims.
|
||||||
|
- **FR-027**: The Platform page MUST NOT claim SOC 2 certification, ISO certification, HIPAA compliance, blanket GDPR compliance, end-to-end encryption, guaranteed recovery, guaranteed compliance, zero drift, universal real-time coverage, named customer trust, Microsoft certification, or Microsoft partnership unless independently approved evidence exists.
|
||||||
|
- **FR-028**: The Platform page MUST use the Spec 400 public visual direction and the provided mockup target: dark premium background, warm light text, muted secondary text, restrained borders, mint/teal accents, strong product surfaces, subtle grid/wave/glow elements, enterprise calm, and higher visual density than the initial card-heavy implementation.
|
||||||
|
- **FR-029**: The Platform page MUST avoid generic bright startup gradients, cybersecurity cliche visuals, fake customer logos, and unapproved customer environment screenshots.
|
||||||
|
- **FR-030**: The Platform page MUST have exactly one primary `h1`.
|
||||||
|
- **FR-031**: Page sections MUST use a meaningful heading hierarchy and semantic structure.
|
||||||
|
- **FR-032**: Links and CTAs MUST be keyboard-accessible and have visible focus states.
|
||||||
|
- **FR-033**: Diagrams and icons MUST have text equivalents or adjacent explanatory text.
|
||||||
|
- **FR-034**: No meaning MAY be conveyed by color alone.
|
||||||
|
- **FR-035**: Text and controls MUST remain readable on mobile, tablet, desktop, and wide desktop viewports.
|
||||||
|
- **FR-036**: The page MUST NOT create body-level horizontal overflow on narrow mobile viewports.
|
||||||
|
- **FR-037**: The page MUST remain mostly static and MUST NOT introduce third-party tracking scripts, backend fetches, platform API calls, auth/session coupling, or live tenant-data dependencies.
|
||||||
|
- **FR-038**: Existing root workspace scripts, website port conventions, and platform runtime contracts MUST remain unchanged.
|
||||||
|
|
||||||
|
### Key Entities
|
||||||
|
|
||||||
|
- **Platform Page Message**: The public brand, product category, governance promise, supporting explanation, and CTA hierarchy for `/platform`.
|
||||||
|
- **Operating Model Step**: A public explanation step in the governance flow, such as snapshot, drift detection, finding creation, review context, evidence attachment, decision, or audit trail.
|
||||||
|
- **Capability**: A concise public description of a Tenantial capability: Backup, Restore, Drift Detection, Findings, Evidence, Audit Trail, Exceptions, or Governance Reviews.
|
||||||
|
- **Governance Loop**: A visual and textual explanation of how configuration state, drift context, operator decisions, evidence, and auditability connect.
|
||||||
|
- **Truth Layer**: A plain-language distinction between execution truth, artifact truth, backup truth, recovery evidence, and operator next action.
|
||||||
|
- **Operator Workflow**: A short public persona workflow for MSP operators, enterprise IT, security reviewers, or compliance and audit stakeholders.
|
||||||
|
- **Platform Boundary**: A product expectation statement that clarifies what Tenantial is not intended to replace or claim.
|
||||||
|
|
||||||
|
### Assumptions
|
||||||
|
|
||||||
|
- The public brand for this page is Tenantial.
|
||||||
|
- `/platform` is the canonical public route for this feature.
|
||||||
|
- `/product` currently exists in the website and is treated as redirect-only compatibility to `/platform` rather than the primary public route after this feature.
|
||||||
|
- "Book a demo" defaults to the existing contact destination unless a dedicated demo route is already present when implementation starts.
|
||||||
|
- No approved customer logos, certifications, Microsoft partnership proof, compliance guarantees, uptime guarantees, real tenant screenshots, or live product telemetry are available for this slice.
|
||||||
|
- Static diagrams or structured panels are acceptable substitutes for screenshots when no approved Tenantial product asset exists.
|
||||||
|
- Spec 400 is the visual and copy baseline for public Tenantial pages.
|
||||||
|
- This feature is website-only and does not change the Laravel platform, Filament admin, Livewire behavior, Microsoft Graph integration, database schema, queues, workers, sessions, or authentication.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: In stakeholder review, at least 8 of 10 reviewers can identify Tenantial as an evidence-first Microsoft tenant governance platform within 5 seconds of viewing the Platform page first viewport.
|
||||||
|
- **SC-002**: 100% of required Platform page sections render on desktop and mobile review: hero, operating model overview, capability grid, governance loop, platform boundaries, final CTA, and footer.
|
||||||
|
- **SC-003**: At least 90% of reviewers can explain how backup records, drift, findings, evidence, audit trails, exceptions, and reviews connect after reading the page.
|
||||||
|
- **SC-004**: Visible copy and metadata review finds 0 occurrences of AstroDeck, TemplateDeck, Open Source, MIT, TenantAtlas, TenantPilot, TenantCTRL, or other stale public brand/template positioning.
|
||||||
|
- **SC-005**: Claims review finds 0 unsupported customer, certification, partnership, uptime, recovery, security, or blanket compliance claims.
|
||||||
|
- **SC-006**: Navigation review confirms the homepage "Explore the platform" CTA, header Platform link, and footer Platform link all reach `/platform`.
|
||||||
|
- **SC-007**: If `/product` remains available, route review confirms it redirects to `/platform` and does not expose inconsistent public content or conflicting metadata.
|
||||||
|
- **SC-008**: Desktop and mobile review confirms 0 body-level horizontal overflow issues at narrow mobile, tablet, desktop, and wide desktop widths.
|
||||||
|
- **SC-009**: Accessibility review confirms one primary heading, meaningful section headings, keyboard-accessible CTAs, visible focus states, readable contrast, and no color-only meaning.
|
||||||
|
- **SC-010**: Website smoke validation confirms `/platform` renders, required brand/capability/boundary copy is visible, old template/public brand residue is absent, and route compatibility behavior is covered if `/product` remains.
|
||||||
255
specs/401-tenantial-platform-page/tasks.md
Normal file
255
specs/401-tenantial-platform-page/tasks.md
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
# Tasks: Tenantial Platform Page
|
||||||
|
|
||||||
|
**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/`
|
||||||
|
**Prerequisites**: `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/plan.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/spec.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/research.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/data-model.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/contracts/public-routes.openapi.yaml`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/quickstart.md`
|
||||||
|
|
||||||
|
**Tests**: Required. Spec 401 changes the public website runtime route and test surface; use focused Playwright smoke coverage in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke`.
|
||||||
|
|
||||||
|
**Scope Boundary**: Work is limited to `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` plus this spec directory. Do not modify `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform`.
|
||||||
|
|
||||||
|
**Constitution Gate**: Finalization includes the repository-wide targeted Pest and Pint close-out commands documented in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/plan.md` without treating Laravel behavior as the feature proof.
|
||||||
|
|
||||||
|
## Phase 1: Setup (Shared Infrastructure)
|
||||||
|
|
||||||
|
**Purpose**: Confirm website-only implementation boundaries and the existing route/content patterns before changing code.
|
||||||
|
|
||||||
|
- [X] T001 Review existing public page patterns in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/product.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/product.ts`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T002 [P] Confirm no dependency or script changes are needed in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/package.json` and `/Users/ahmeddarrazi/Documents/projects/wt-website/package.json`
|
||||||
|
- [X] T003 [P] Confirm Tailwind v4 styling conventions and existing surface classes in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/tokens.css`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/PageHero.astro`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational (Blocking Prerequisites)
|
||||||
|
|
||||||
|
**Purpose**: Add route-level foundation needed before user-story implementation can compile and be tested.
|
||||||
|
|
||||||
|
**Critical**: No user-story implementation should start until `/platform` exists in route metadata and typed site definitions, and T007 confirms the repository-wide quality-gate commands are explicit.
|
||||||
|
|
||||||
|
- [X] T004 Add `/platform` to `SitePath`, `PageRole`, route unions, and related public website route typing in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/types/site.ts`
|
||||||
|
- [X] T005 Add `/platform` page definition, canonical route membership, sitemap inclusion, and `/product` redirect compatibility classification in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`
|
||||||
|
- [X] T006 Update smoke helper route constants to recognize `/platform` and preserve `/product` redirect compatibility expectations in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
- [X] T007 Confirm the repository-wide targeted Pest and Pint close-out commands are explicit in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/plan.md`
|
||||||
|
|
||||||
|
**Checkpoint**: Website route metadata is ready; user story implementation can proceed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 - Understand The Platform Model (Priority: P1) MVP
|
||||||
|
|
||||||
|
**Goal**: A first-time buyer or stakeholder opens `/platform` and understands Tenantial as an evidence-first governance platform for Microsoft tenant environments.
|
||||||
|
|
||||||
|
**Independent Test**: Open `/platform` and verify the first viewport shows Tenantial Platform positioning, Microsoft tenant context, a governance headline, static product-oriented visual, "Book a demo", and "See the governance loop".
|
||||||
|
|
||||||
|
### Tests for User Story 1
|
||||||
|
|
||||||
|
- [X] T008 [US1] Add failing `/platform` hero smoke test titled "platform hero explains Tenantial governance model" for shell, one `h1`, "TENANTIAL PLATFORM", governance headline, primary CTA, secondary CTA, and static visual in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T009 [US1] Add failing `/platform` metadata smoke test titled "platform metadata is Tenantial clean" for title, description, canonical URL, and absence of stale public-brand terms in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 1
|
||||||
|
|
||||||
|
- [X] T010 [US1] Create Platform page static hero, SEO, CTA, and first-viewport visual content exports in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`
|
||||||
|
- [X] T011 [US1] Create canonical Platform page route using `PageShell`, existing section primitives, hero content, and static visual markup in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T012 [US1] Ensure `/platform` uses Tenantial-specific metadata, canonical `/platform`, exactly one primary heading, keyboard-accessible CTAs, and no live tenant-data language in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T013 [US1] Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke --grep "platform hero|platform metadata"` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
|
||||||
|
**Checkpoint**: `/platform` is usable as the MVP public Platform page with a clear first-viewport product model.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: User Story 2 - See How Governance Work Flows (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: A stakeholder understands how raw tenant change becomes reviewable governance evidence.
|
||||||
|
|
||||||
|
**Independent Test**: Review the operating model and governance loop sections and verify required steps, operator-led language, evidence preservation, and no automatic remediation or guaranteed recovery claims.
|
||||||
|
|
||||||
|
### Tests for User Story 2
|
||||||
|
|
||||||
|
- [X] T014 [US2] Add failing `/platform` smoke test titled "platform governance flow explains reviewable evidence" for operating model steps, governance loop labels, operator review wording, evidence preservation wording, and auditability wording in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T015 [US2] Add failing `/platform` smoke test titled "platform governance flow avoids automation guarantees" that does not imply automatic remediation, guaranteed recovery, live device actions, or real-time tenant operations in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 2
|
||||||
|
|
||||||
|
- [X] T016 [US2] Add ordered operating model and governance loop content arrays to `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`
|
||||||
|
- [X] T017 [US2] Render the operating model overview with the required sequence in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T018 [US2] Render the governance loop section with accessible text equivalents and mobile-stacking behavior in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T019 [US2] Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke --grep "platform governance flow"` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
|
||||||
|
**Checkpoint**: The Platform page explains the governance flow independently of later capability and navigation cleanup work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Story 3 - Evaluate Capabilities And Boundaries (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: A stakeholder can evaluate Tenantial's core capabilities and distinguish them from helpdesk, SIEM, device-action, or Microsoft admin center replacement promises.
|
||||||
|
|
||||||
|
**Independent Test**: Review `/platform` for all eight capabilities, truth-layer or workflow explanation, boundary language, and absence of unsupported customer, certification, partnership, uptime, recovery, security, or compliance claims.
|
||||||
|
|
||||||
|
### Tests for User Story 3
|
||||||
|
|
||||||
|
- [X] T020 [US3] Add failing `/platform` smoke test titled "platform capabilities cover governance primitives" for Backup, Restore, Drift Detection, Findings, Evidence, Audit Trail, Exceptions, and Governance Reviews in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T021 [US3] Add failing `/platform` smoke test titled "platform boundaries keep claims conservative" for platform boundary copy and unsupported-claim absence on `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T022 [US3] Add failing `/platform` mobile smoke test titled "platform mobile layout stays readable without overflow" for readable capability cards, boundary content, no color-only meaning, and no body-level horizontal overflow in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 3
|
||||||
|
|
||||||
|
- [X] T023 [US3] Add capability, truth-layer, operator-workflow, platform-boundary, and final CTA content exports to `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`
|
||||||
|
- [X] T024 [US3] Render the capability grid using existing website primitives and lucide-compatible icon names in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T025 [US3] Render truth-layer and operator-workflow sections with concise public copy in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T026 [US3] Render platform boundary and final CTA sections with conservative claim language and working `/contact` demo CTA in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T027 [US3] Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke --grep "platform capabilities|platform boundaries|platform mobile"` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
|
||||||
|
**Checkpoint**: Stakeholders can evaluate the platform capabilities and boundaries without unsupported claims.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: User Story 4 - Navigate A Brand-Clean Public Site (Priority: P4)
|
||||||
|
|
||||||
|
**Goal**: Website owners can confirm that homepage, header, footer, metadata, and `/product` compatibility all support `/platform` without stale brand or template residue.
|
||||||
|
|
||||||
|
**Independent Test**: Follow homepage "Explore the platform", header Platform, footer Platform, and `/product`; verify they resolve to canonical `/platform` behavior, with `/product` redirecting to `/platform`.
|
||||||
|
|
||||||
|
### Tests for User Story 4
|
||||||
|
|
||||||
|
- [X] T028 [US4] Update homepage hero route-target smoke assertions from `/product` to `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T029 [US4] Update primary navigation, footer navigation, and onward-route smoke expectations for Platform links to `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
- [X] T030 [US4] Add `/product` compatibility smoke test titled "product redirects to platform" for redirect-to-`/platform` behavior and absence of stale public copy in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 4
|
||||||
|
|
||||||
|
- [X] T031 [US4] Update homepage "Explore the platform" CTA and final CTA destinations to `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T032 [US4] Update primary navigation and footer Platform link destinations to `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`
|
||||||
|
- [X] T033 [US4] Convert `/product` to a redirect-only compatibility route pointing to `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/product.astro`
|
||||||
|
- [X] T034 [US4] Retire stale product content usage so `/product` no longer imports or renders conflicting public copy from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/product.ts`
|
||||||
|
- [X] T035 [US4] Update sitemap and canonical route behavior for `/platform` and `/product` redirect compatibility in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`
|
||||||
|
- [X] T036 [US4] Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke --grep "homepage.*platform|primary navigation|footer|product redirects"` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
|
||||||
|
**Checkpoint**: The public site journey consistently routes Platform intent to `/platform`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
**Purpose**: Validate quality, accessibility, claim safety, and repo boundaries after all user stories are complete.
|
||||||
|
|
||||||
|
- [X] T037 [P] Run `corepack pnpm build:website` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
- [X] T038 [P] Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
- [X] T039 [P] Run `git diff --check` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
- [X] T040 Review `/platform` at mobile, tablet, desktop, and wide desktop sizes for no body-level horizontal overflow in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T041 Review visible copy and metadata for forbidden public-brand residue and unsupported claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`
|
||||||
|
- [X] T042 Confirm no files under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` were changed for this website-only feature
|
||||||
|
- [X] T043 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform && ./vendor/bin/sail bin pint --dirty` from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform`
|
||||||
|
- [X] T044 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform && ./vendor/bin/sail artisan test --compact tests/Deprecation/IsPlatformSuperadminDeprecationTest.php` from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform`
|
||||||
|
- [X] T045 Confirm no Playwright helper change introduces platform, database, auth, provider, session, workspace, or tenant setup in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
- [X] T046 Confirm no unrelated Laravel, Filament, or Livewire product test family is introduced solely for Spec 401 website smoke proof in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke`
|
||||||
|
- [X] T047 Confirm final validation commands include `corepack pnpm build:website`, `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`, `git diff --check`, and the constitution close-out commands from T043/T044 in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/tasks.md`
|
||||||
|
- [X] T048 Review static website files for no third-party tracking scripts, backend fetches, platform API calls, auth/session coupling, or live tenant-data dependencies in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Mockup-Aligned Visual Refactor Follow-Up
|
||||||
|
|
||||||
|
**Purpose**: Apply the follow-up visual refactor request so `/platform` is much closer to the supplied mockup target rather than the initial card-heavy implementation.
|
||||||
|
|
||||||
|
- [X] T049 Update Spec 401 artifacts for the follow-up visual target in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/spec.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/plan.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/data-model.md`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/401-tenantial-platform-page/quickstart.md`
|
||||||
|
- [X] T050 Replace `/platform` static content with mockup-aligned hero trust signals, six-step operating model, governance loop diagram copy, asymmetric capability content, truth layers, workflows, boundaries, and focused CTA in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`
|
||||||
|
- [X] T051 Refactor `/platform` page composition into split dashboard hero, compact Operating Model flow, central Governance Loop diagram, asymmetric Capabilities, Truth Layers visual, warmer Operator Workflows, Boundaries band, and focused CTA in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro`
|
||||||
|
- [X] T052 Add quiet footer behavior for `/platform` so the final CTA and footer are not two equally heavy closing sections in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Footer.astro`
|
||||||
|
- [X] T053 Update website smoke coverage for the new visual structure, grouped capabilities, truth-layer visual, boundaries wording, and quiet footer in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T054 Run `corepack pnpm build:website` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
- [X] T055 Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
- [X] T056 Run `git diff --check` from `/Users/ahmeddarrazi/Documents/projects/wt-website`
|
||||||
|
- [X] T057 Review desktop and mobile `/platform` screenshots for mockup proximity, no body-level horizontal overflow, and no incoherent text overlap
|
||||||
|
- [X] T058 Confirm the visual refactor remains limited to `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` and Spec 401 artifacts, with no `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & Execution Order
|
||||||
|
|
||||||
|
### Phase Dependencies
|
||||||
|
|
||||||
|
- Phase 1 must complete before Phase 2.
|
||||||
|
- Phase 2 must complete before any user story implementation.
|
||||||
|
- US1 is the MVP and should complete before US2, US3, and US4 because it creates the canonical `/platform` route.
|
||||||
|
- US2 can proceed after US1 because it expands the same page with operating model and governance loop sections.
|
||||||
|
- US3 can proceed after US1 and can run in parallel with US2 if file-level coordination is handled for `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/platform.ts`.
|
||||||
|
- US4 should complete after US1 because homepage/header/footer links need a real canonical route target.
|
||||||
|
- Phase 7 runs after all selected user stories.
|
||||||
|
|
||||||
|
### User Story Dependency Graph
|
||||||
|
|
||||||
|
```text
|
||||||
|
Setup -> Foundation -> US1 (MVP)
|
||||||
|
|-> US2
|
||||||
|
|-> US3
|
||||||
|
`-> US4
|
||||||
|
US2 + US3 + US4 -> Polish
|
||||||
|
```
|
||||||
|
|
||||||
|
### Story Completion Order
|
||||||
|
|
||||||
|
1. US1 - Understand The Platform Model
|
||||||
|
2. US2 - See How Governance Work Flows
|
||||||
|
3. US3 - Evaluate Capabilities And Boundaries
|
||||||
|
4. US4 - Navigate A Brand-Clean Public Site
|
||||||
|
|
||||||
|
## Parallel Execution Examples
|
||||||
|
|
||||||
|
### Setup / Foundation
|
||||||
|
|
||||||
|
```text
|
||||||
|
T002 can run alongside T003 after T001 starts.
|
||||||
|
T004 and T005 should be coordinated because both affect route typing and route definitions.
|
||||||
|
T006 can run after T004/T005 interfaces are known.
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story 1
|
||||||
|
|
||||||
|
```text
|
||||||
|
T008 and T009 both edit /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts, so coordinate them in one test-edit pass.
|
||||||
|
T010 and T011 should be sequential because the page route imports the content module.
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story 2
|
||||||
|
|
||||||
|
```text
|
||||||
|
T014 and T015 can be drafted together in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts before implementation.
|
||||||
|
T016 should complete before T017 and T018 render the new content.
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story 3
|
||||||
|
|
||||||
|
```text
|
||||||
|
T020, T021, and T022 can be drafted in one test-edit pass.
|
||||||
|
T023 can proceed before T024, T025, and T026 render the new sections.
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story 4
|
||||||
|
|
||||||
|
```text
|
||||||
|
T028 and T030 both edit /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts and should be coordinated.
|
||||||
|
T031 can run in parallel with T032 because they edit different files.
|
||||||
|
T033 and T034 should be coordinated because both define /product compatibility behavior.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Final Validation
|
||||||
|
|
||||||
|
```text
|
||||||
|
T037, T038, and T039 are independent validation commands and can run in parallel if local resources allow.
|
||||||
|
T040, T041, T042, T045, T046, and T048 can run after validation commands finish.
|
||||||
|
T043 and T044 close the repository-wide constitution gates and should finish before T047.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### MVP First
|
||||||
|
|
||||||
|
Deliver US1 first. This creates the canonical `/platform` page with Tenantial Platform metadata, first-viewport positioning, static product-oriented visual, and primary/secondary CTA hierarchy. At that point the feature has a usable public destination even before deeper sections and compatibility cleanup land.
|
||||||
|
|
||||||
|
### Incremental Delivery
|
||||||
|
|
||||||
|
1. Complete Setup and Foundational phases.
|
||||||
|
2. Complete US1 and verify `/platform` independently.
|
||||||
|
3. Add US2 to explain the operating model and governance loop.
|
||||||
|
4. Add US3 to cover capabilities, truth layers, workflows, boundaries, and claim safety.
|
||||||
|
5. Add US4 to align homepage/header/footer navigation and redirect `/product` to `/platform`.
|
||||||
|
6. Run Phase 7 validation and record smoke coverage close-out.
|
||||||
Loading…
Reference in New Issue
Block a user