feat: rebuild Tenantial homepage visuals
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m24s
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m24s
This commit is contained in:
parent
55338a88c6
commit
2190322711
@ -942,6 +942,7 @@ ## Active Technologies
|
||||
- PHP 8.4 (Laravel 12) + Filament v5 + Livewire v4
|
||||
- PostgreSQL (Sail)
|
||||
- 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)
|
||||
|
||||
## Recent Changes
|
||||
- 066-rbac-ui-enforcement-helper-v2-session-1769732329: Planned UiEnforcement v2 (spec + plan + design artifacts)
|
||||
|
||||
@ -4,7 +4,7 @@ import tailwindcss from '@tailwindcss/vite';
|
||||
import icon from 'astro-icon';
|
||||
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({
|
||||
integrations: [icon()],
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
||||
<rect width="64" height="64" rx="18" fill="#17120F" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
|
||||
<rect width="96" height="96" rx="22" fill="#111516" />
|
||||
<rect x="14" y="14" width="20" height="20" rx="5.5" fill="#FFF7E1" />
|
||||
<path
|
||||
d="M17 18H47V25H36V46H28V25H17V18Z"
|
||||
fill="#FFF7F1"
|
||||
/>
|
||||
<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"
|
||||
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="#FFF7E1"
|
||||
/>
|
||||
<circle cx="78" cy="72" r="12" fill="#FFF7E1" />
|
||||
</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 |
1092
apps/website/src/components/content/DashboardPreview.astro
Normal file
1092
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 />
|
||||
</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>
|
||||
@ -17,10 +17,10 @@ const footerNavigationGroups = await getFooterNavigationGroups();
|
||||
<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">
|
||||
<div class="space-y-5">
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
<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-[0.98] text-[var(--color-ink-900)] sm:text-4xl">
|
||||
<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)]">
|
||||
@ -33,7 +33,7 @@ const footerNavigationGroups = await getFooterNavigationGroups();
|
||||
{
|
||||
footerNavigationGroups.map((group) => (
|
||||
<div>
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-ink-900)]">
|
||||
<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)]">
|
||||
@ -52,9 +52,9 @@ const footerNavigationGroups = await getFooterNavigationGroups();
|
||||
</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">
|
||||
<p class="m-0">© {currentYear} {siteMetadata.siteName}. Core public route foundation.</p>
|
||||
<p class="m-0">© {currentYear} {siteMetadata.siteName}. Evidence-first governance for Microsoft tenants.</p>
|
||||
<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>
|
||||
</div>
|
||||
</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 { getHeaderCta, getPrimaryNavigation, isActiveNavigationPath, siteMetadata } from '@/lib/site';
|
||||
|
||||
@ -12,39 +13,31 @@ const headerCta = getHeaderCta(currentPath);
|
||||
const primaryNavigation = await getPrimaryNavigation();
|
||||
---
|
||||
|
||||
<header class="sticky top-0 z-30 px-[var(--space-page-x)] pt-4 sm:pt-6">
|
||||
<Container width="wide">
|
||||
<header class="relative z-30 px-[var(--space-page-x)] pt-5 sm:pt-7">
|
||||
<Container width="wide" class="!max-w-[96rem]">
|
||||
<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-visual-tone="dark"
|
||||
>
|
||||
<a href="/" class="flex min-w-0 items-center gap-3 no-underline">
|
||||
<span
|
||||
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)]"
|
||||
>
|
||||
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 href="/" class="group flex min-w-0 no-underline" aria-label="Tenantial home">
|
||||
<TenantialLogo
|
||||
label={siteMetadata.siteName}
|
||||
imageClass="h-[2.75rem] w-auto sm:h-12"
|
||||
/>
|
||||
</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) => (
|
||||
<a
|
||||
href={item.href}
|
||||
aria-current={isActiveNavigationPath(currentPath, item.href) ? 'page' : undefined}
|
||||
class:list={[
|
||||
'rounded-full px-4 py-2 text-sm font-medium transition',
|
||||
'text-sm font-medium transition',
|
||||
isActiveNavigationPath(currentPath, item.href)
|
||||
? 'bg-[var(--color-accent)] text-[var(--color-accent-foreground)]'
|
||||
: 'text-[var(--color-ink-800)] hover:bg-white/70',
|
||||
? 'text-[var(--color-foreground)]'
|
||||
: 'text-[var(--color-stone-200)] hover:text-[var(--color-primary)]',
|
||||
]}
|
||||
data-nav-link="primary"
|
||||
>
|
||||
@ -54,14 +47,23 @@ const primaryNavigation = await getPrimaryNavigation();
|
||||
}
|
||||
</nav>
|
||||
|
||||
<div class="hidden lg:block">
|
||||
<SecondaryCTA cta={headerCta} size="sm" />
|
||||
<div class="hidden items-center gap-5 lg:flex">
|
||||
<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>
|
||||
|
||||
<details class="relative lg:hidden" data-mobile-nav>
|
||||
<summary
|
||||
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
|
||||
>
|
||||
<span class="sr-only">Open navigation menu</span>
|
||||
@ -84,7 +86,7 @@ const primaryNavigation = await getPrimaryNavigation();
|
||||
'rounded-[1rem] px-4 py-3 text-sm',
|
||||
isActiveNavigationPath(currentPath, item.href)
|
||||
? '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"
|
||||
>
|
||||
@ -97,8 +99,14 @@ const primaryNavigation = await getPrimaryNavigation();
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<div class="mt-2 rounded-[1rem] bg-[rgba(47,111,183,0.08)] p-3">
|
||||
<SecondaryCTA cta={headerCta} size="sm" showHelper />
|
||||
<span
|
||||
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>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@ -37,10 +37,15 @@ const pageDefinition = getPageDefinition(currentPath);
|
||||
data-surface-group={pageDefinition.surfaceGroup}
|
||||
data-journey-stage={pageDefinition.journeyStage}
|
||||
>
|
||||
<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%)]"
|
||||
>
|
||||
</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>
|
||||
{
|
||||
pageDefinition.pageRole === 'home' && (
|
||||
<>
|
||||
<div class="home-ambient-wash" aria-hidden="true"></div>
|
||||
<div class="home-dot-field" aria-hidden="true"></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Navbar currentPath={currentPath} />
|
||||
<main id="content" class="foundation-main pb-20 sm:pb-24">
|
||||
<slot />
|
||||
|
||||
@ -34,10 +34,10 @@ const sizeClasses = {
|
||||
|
||||
const variantClasses = {
|
||||
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:
|
||||
'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]',
|
||||
ghost: 'border-transparent bg-transparent text-[var(--color-ink-800)] hover:bg-white/70',
|
||||
'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-muted-foreground)] hover:bg-[var(--surface-muted)] hover:text-[var(--color-foreground)]',
|
||||
};
|
||||
|
||||
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 Cluster from '@/components/primitives/Cluster.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import DashboardPreview from '@/components/content/DashboardPreview.astro';
|
||||
import Headline from '@/components/content/Headline.astro';
|
||||
import HeroDashboard from '@/components/content/HeroDashboard.astro';
|
||||
import Lead from '@/components/content/Lead.astro';
|
||||
import Metric from '@/components/content/Metric.astro';
|
||||
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
||||
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import type { HeroContent, MetricItem } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
@ -23,11 +24,13 @@ const isHomepageHero = Astro.url.pathname === '/';
|
||||
const heroHeadlineSize = isHomepageHero ? 'page' : 'display';
|
||||
const heroLeadSize = isHomepageHero ? 'body' : 'lead';
|
||||
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
|
||||
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-surface={isHomepageHero ? 'homepage' : 'page'}
|
||||
@ -35,12 +38,12 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
||||
data-hero-primary-anchor={isHomepageHero && heroPrimaryAnchor === 'composition' ? 'composition' : undefined}
|
||||
data-section={isHomepageHero ? 'hero' : undefined}
|
||||
>
|
||||
<Container width="wide">
|
||||
<Container width="wide" class={isHomepageHero ? '!max-w-[96rem]' : ''}>
|
||||
{isHomepageHero ? (
|
||||
<div class="space-y-6 sm:space-y-8 lg: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="motion-rise flex flex-col gap-5 sm:gap-6 lg:max-w-[40rem]" data-hero-panel="text">
|
||||
<div class="space-y-5" data-hero-anchor-group>
|
||||
<div class="space-y-8 sm:space-y-10" data-disclosure-layer="1" data-hero-layout>
|
||||
<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-7 pt-1 sm:gap-8 xl:max-w-[39rem] xl:pt-14" data-hero-panel="text">
|
||||
<div class="space-y-6" data-hero-anchor-group>
|
||||
<div data-hero-text-core>
|
||||
<div data-hero-eyebrow data-hero-segment="eyebrow">
|
||||
<Badge>{hero.eyebrow}</Badge>
|
||||
@ -54,7 +57,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
||||
<Headline
|
||||
as="h1"
|
||||
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}
|
||||
</Headline>
|
||||
@ -64,7 +67,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
||||
data-hero-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}
|
||||
</Lead>
|
||||
</div>
|
||||
@ -72,7 +75,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
||||
</div>
|
||||
{(hero.primaryCta || hero.secondaryCta) && (
|
||||
<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" />
|
||||
{hero.secondaryCta && (
|
||||
<SecondaryCTA cta={hero.secondaryCta} />
|
||||
@ -81,9 +84,37 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
||||
</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
|
||||
class="motion-rise lg:pt-1"
|
||||
class="motion-rise min-w-0 xl:-mr-4"
|
||||
style="animation-delay: 120ms;"
|
||||
data-hero-panel="dashboard"
|
||||
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-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">
|
||||
{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>
|
||||
<DashboardPreview />
|
||||
</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>
|
||||
) : (
|
||||
<!-- 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';
|
||||
|
||||
export const changelogSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Changelog',
|
||||
title: 'Tenantial | Changelog',
|
||||
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',
|
||||
};
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const contactSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Contact',
|
||||
title: 'Tenantial | Contact',
|
||||
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',
|
||||
};
|
||||
|
||||
@ -13,8 +13,8 @@ export const contactHero: HeroContent = {
|
||||
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.',
|
||||
primaryCta: {
|
||||
href: 'mailto:hello@tenantatlas.example?subject=TenantAtlas%20working%20session',
|
||||
label: 'Email the TenantAtlas team',
|
||||
href: 'mailto:hello@tenantial.example?subject=Tenantial%20working%20session',
|
||||
label: 'Email the Tenantial team',
|
||||
variant: 'primary',
|
||||
},
|
||||
secondaryCta: {
|
||||
@ -50,7 +50,7 @@ export const contactPrompts = [
|
||||
|
||||
export const contactPreview = {
|
||||
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',
|
||||
};
|
||||
|
||||
|
||||
@ -1,171 +1,112 @@
|
||||
import type {
|
||||
CapabilityClusterContent,
|
||||
CtaLink,
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
IntegrationEntry,
|
||||
OutcomeSectionContent,
|
||||
PageSeo,
|
||||
ProgressTeaserContent,
|
||||
TrustSignalGroupContent,
|
||||
TrustPrincipleContent,
|
||||
} from '@/types/site';
|
||||
|
||||
export const homeSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Governance of record for Microsoft tenant operations',
|
||||
title: 'Tenantial - Evidence-first governance for Microsoft tenants',
|
||||
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: '/',
|
||||
};
|
||||
|
||||
export const homeHero: HeroContent = {
|
||||
eyebrow: 'Microsoft tenant governance',
|
||||
title: 'TenantAtlas gives Microsoft tenant teams one operating record for change history, drift review, and restore planning.',
|
||||
eyebrow: 'Governance that earns trust',
|
||||
title: 'Evidence-first governance for Microsoft tenants.',
|
||||
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: {
|
||||
href: '/contact',
|
||||
label: 'Request a working session',
|
||||
label: 'Book a demo',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/product',
|
||||
label: 'See the product model',
|
||||
label: 'Explore the platform',
|
||||
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: [
|
||||
'Tenant-scoped boundaries',
|
||||
'Reviewable change history',
|
||||
'Preview before restore',
|
||||
'Built for Microsoft 365 and Azure AD',
|
||||
'Snapshot-first history with review context.',
|
||||
'Designed for audit-ready workflows.',
|
||||
],
|
||||
};
|
||||
|
||||
export const homeOutcome: OutcomeSectionContent = {
|
||||
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[] = [
|
||||
export const homeTrustBar: TrustPrincipleContent[] = [
|
||||
{
|
||||
title: 'Backup & Version History',
|
||||
description: 'Immutable snapshots of tenant configuration state with full version lineage.',
|
||||
capabilities: ['Automated backup', 'Immutable snapshots', 'Version comparison', 'Change timeline'],
|
||||
href: '/product',
|
||||
meta: 'Core safety net',
|
||||
title: 'Microsoft tenant focused',
|
||||
description: 'Built around the governance surfaces Microsoft tenant teams already need to inspect.',
|
||||
},
|
||||
{
|
||||
title: 'Restore & Recovery',
|
||||
description: 'Preview-first restore with selective scope and explicit confirmation.',
|
||||
capabilities: ['Dry-run preview', 'Selective restore', 'Rollback safety', 'Conflict detection'],
|
||||
href: '/product',
|
||||
meta: 'Safer change operations',
|
||||
title: 'Evidence-oriented workflows',
|
||||
description: 'Backup, drift, restore, and review context stay connected to the decision trail.',
|
||||
},
|
||||
{
|
||||
title: 'Inventory & Drift Visibility',
|
||||
description: 'Connected view of what exists, what drifted, and what needs attention.',
|
||||
capabilities: ['Policy inventory', 'Drift detection', 'Assignment visibility', 'Cross-tenant comparison'],
|
||||
href: '/product',
|
||||
meta: 'Operational clarity',
|
||||
title: 'Designed for audit review',
|
||||
description: 'Claims stay grounded in reviewable evidence rather than unverified certification language.',
|
||||
},
|
||||
{
|
||||
title: 'Governance & Evidence',
|
||||
description: 'Audit-ready evidence, findings, and review workflows for tenant operations.',
|
||||
capabilities: ['Audit trails', 'Evidence linkage', 'Exception tracking', 'Review workflows'],
|
||||
href: '/product',
|
||||
meta: 'Accountability and review',
|
||||
title: 'Operator-led governance',
|
||||
description: 'High-risk actions stay framed by preview, confirmation, and explicit accountability.',
|
||||
},
|
||||
];
|
||||
|
||||
export const homeTrustSignals: TrustSignalGroupContent = {
|
||||
title: 'Trust is a first-read concern, not a footnote.',
|
||||
description:
|
||||
'Tenant isolation, access boundaries, and operating discipline are product rules, not marketing language. Every public claim routes back to one bounded trust surface.',
|
||||
supportRoute: '/trust',
|
||||
signals: [
|
||||
{
|
||||
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',
|
||||
export const homeFeaturePillars: FeatureItemContent[] = [
|
||||
{
|
||||
title: 'Backup',
|
||||
description: 'Preserve full Microsoft tenant configuration snapshots so recovery starts from known evidence.',
|
||||
icon: 'archive',
|
||||
meta: 'Snapshot record',
|
||||
},
|
||||
};
|
||||
{
|
||||
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 = {
|
||||
eyebrow: 'Next step',
|
||||
title: 'Move from first read into a working session.',
|
||||
title: 'Build tenant governance on evidence, not assumptions.',
|
||||
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: {
|
||||
href: '/contact',
|
||||
label: 'Request a working session',
|
||||
label: 'Book a demo',
|
||||
} as CtaLink,
|
||||
secondary: {
|
||||
href: '/product',
|
||||
label: 'See the product model',
|
||||
label: 'Explore the platform',
|
||||
variant: 'secondary',
|
||||
} 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';
|
||||
|
||||
export const imprintSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Imprint',
|
||||
title: 'Tenantial | Imprint',
|
||||
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',
|
||||
};
|
||||
|
||||
export const imprintHero: HeroContent = {
|
||||
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:
|
||||
'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: {
|
||||
@ -32,7 +32,7 @@ export const imprintSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Current publication status',
|
||||
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.',
|
||||
],
|
||||
bullets: [
|
||||
|
||||
@ -6,9 +6,9 @@ import type {
|
||||
} from '@/types/site';
|
||||
|
||||
export const integrationsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Integrations',
|
||||
title: 'Tenantial | Integrations',
|
||||
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',
|
||||
};
|
||||
|
||||
@ -16,7 +16,7 @@ export const integrationsHero: HeroContent = {
|
||||
eyebrow: 'Retained supporting page',
|
||||
title: 'Keep ecosystem fit detail visible without pretending it is the first thing every buyer needs.',
|
||||
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: {
|
||||
href: '/contact',
|
||||
label: 'Plan the working session',
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const legalSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Legal',
|
||||
title: 'Tenantial | Legal',
|
||||
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',
|
||||
};
|
||||
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const privacySeo: PageSeo = {
|
||||
title: 'TenantAtlas | Privacy',
|
||||
title: 'Tenantial | Privacy',
|
||||
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',
|
||||
};
|
||||
|
||||
export const privacyHero: HeroContent = {
|
||||
eyebrow: 'Privacy',
|
||||
title: 'Public-site privacy overview for TenantAtlas inquiries.',
|
||||
title: 'Public-site privacy overview for Tenantial inquiries.',
|
||||
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.',
|
||||
primaryCta: {
|
||||
@ -32,7 +32,7 @@ export const privacySections: LegalSection[] = [
|
||||
{
|
||||
title: 'Scope',
|
||||
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.',
|
||||
],
|
||||
},
|
||||
|
||||
@ -7,9 +7,9 @@ import type {
|
||||
} from '@/types/site';
|
||||
|
||||
export const productSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Product',
|
||||
title: 'Tenantial | Product',
|
||||
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',
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ export const productHero: HeroContent = {
|
||||
eyebrow: 'Product model',
|
||||
title: 'Explain the product as one operating model before asking a buyer to trust the route map around it.',
|
||||
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: {
|
||||
href: '/trust',
|
||||
label: 'Review the trust posture',
|
||||
|
||||
@ -6,9 +6,9 @@ import type {
|
||||
} from '@/types/site';
|
||||
|
||||
export const securityTrustSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Security & Trust',
|
||||
title: 'Tenantial | Security & Trust',
|
||||
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',
|
||||
};
|
||||
|
||||
|
||||
@ -6,9 +6,9 @@ import type {
|
||||
} from '@/types/site';
|
||||
|
||||
export const solutionsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Solutions',
|
||||
title: 'Tenantial | Solutions',
|
||||
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',
|
||||
};
|
||||
|
||||
@ -79,7 +79,7 @@ export const solutionsSignals: FeatureItemContent[] = [
|
||||
eyebrow: 'Where it lands',
|
||||
title: 'The product belongs in the operating layer, not just the reporting layer.',
|
||||
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',
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const termsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Terms',
|
||||
title: 'Tenantial | Terms',
|
||||
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',
|
||||
};
|
||||
|
||||
export const termsHero: HeroContent = {
|
||||
eyebrow: 'Website terms',
|
||||
title: 'Website terms for the public TenantAtlas surface.',
|
||||
title: 'Website terms for the public Tenantial surface.',
|
||||
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.',
|
||||
primaryCta: {
|
||||
@ -32,7 +32,7 @@ export const termsSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Informational website use',
|
||||
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.',
|
||||
],
|
||||
},
|
||||
|
||||
@ -6,9 +6,9 @@ import type {
|
||||
} from '@/types/site';
|
||||
|
||||
export const trustSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Trust',
|
||||
title: 'Tenantial | Trust',
|
||||
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',
|
||||
};
|
||||
|
||||
|
||||
@ -16,11 +16,11 @@ import type {
|
||||
} from '@/types/site';
|
||||
|
||||
export const siteMetadata: SiteMetadata = {
|
||||
siteName: 'TenantAtlas',
|
||||
siteTagline: 'Governance of record for Microsoft tenant operations.',
|
||||
siteName: 'Tenantial',
|
||||
siteTagline: 'Evidence-first governance for Microsoft tenants.',
|
||||
siteDescription:
|
||||
'TenantAtlas helps MSP and enterprise teams keep Microsoft tenant change history observable, reviewable, and safer to operate.',
|
||||
siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example',
|
||||
'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://tenantial.example',
|
||||
};
|
||||
|
||||
export const visualFoundationContract: VisualFoundationContract = {
|
||||
@ -68,17 +68,17 @@ interface FooterGroupSeed {
|
||||
|
||||
export const contactCta: CtaLink = {
|
||||
href: '/contact',
|
||||
label: 'Request a working session',
|
||||
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
|
||||
label: 'Book a demo',
|
||||
helper: 'Bring tenant governance questions, restore concerns, or audit-readiness goals.',
|
||||
variant: 'primary',
|
||||
};
|
||||
|
||||
const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
||||
landing: {
|
||||
eyebrow: 'Keep the next move obvious',
|
||||
title: 'Product, trust, progress, and contact should stay connected.',
|
||||
eyebrow: 'Next step',
|
||||
title: 'Build tenant governance on evidence, not assumptions.',
|
||||
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',
|
||||
primaryCta: contactCta,
|
||||
},
|
||||
@ -90,7 +90,7 @@ const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
||||
intent: 'guidance',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Discuss trust requirements',
|
||||
label: 'Book a demo',
|
||||
helper: 'Bring current review, legal, or rollout questions into one working conversation.',
|
||||
variant: 'primary',
|
||||
},
|
||||
@ -103,7 +103,7 @@ const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
||||
intent: 'legal',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Continue the evaluation path',
|
||||
label: 'Book a demo',
|
||||
helper: 'Move from the reading surface back into a product or trust conversation.',
|
||||
variant: 'primary',
|
||||
},
|
||||
@ -113,19 +113,19 @@ const footerLeadByFamily: Record<PageFamily, FooterLead> = {
|
||||
const headerCtaByFamily: Record<PageFamily, CtaLink> = {
|
||||
landing: {
|
||||
href: '/contact',
|
||||
label: 'Request a working session',
|
||||
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
|
||||
label: 'Book a demo',
|
||||
helper: 'Discuss the tenant governance surface with the Tenantial team.',
|
||||
variant: 'secondary',
|
||||
},
|
||||
trust: {
|
||||
href: '/contact',
|
||||
label: 'Discuss trust requirements',
|
||||
label: 'Book a demo',
|
||||
helper: 'Route trust, legal, or rollout questions into one conversation.',
|
||||
variant: 'secondary',
|
||||
},
|
||||
content: {
|
||||
href: '/contact',
|
||||
label: 'Start the contact path',
|
||||
label: 'Book a demo',
|
||||
helper: 'Turn the reading path into a concrete next step.',
|
||||
variant: 'secondary',
|
||||
},
|
||||
@ -157,38 +157,55 @@ export async function getSurfaceAvailability(): Promise<SurfaceAvailability> {
|
||||
}
|
||||
|
||||
const primaryNavigationSeeds: CollectionGatedNavigationItem[] = [
|
||||
{ href: '/product', label: 'Product', description: 'See how the product connects backup, restore, drift, and evidence.' },
|
||||
{ href: '/trust', label: 'Trust', description: 'Review the operating posture and bounded public claims.' },
|
||||
{ href: '/changelog', label: 'Changelog', description: 'Inspect dated product progress instead of placeholder content.' },
|
||||
{ href: '/resources', label: 'Resources', description: 'Optional deeper content when substantive material exists.', collection: 'resources' },
|
||||
{ href: '/contact', label: 'Contact', description: 'Move into a working session with one clear next step.' },
|
||||
{ href: '/product', label: 'Platform', description: 'Explore the evidence-first governance surface.' },
|
||||
{ href: '/solutions', label: 'Solutions', description: 'Review MSP and enterprise governance fit.' },
|
||||
{ href: '/changelog', label: 'Resources', description: 'Use the changelog as the current public resource baseline.' },
|
||||
{ href: '/contact', label: 'Pricing', description: 'Pricing is handled through a scoped demo conversation.' },
|
||||
{ href: '/contact', label: 'Company', description: 'Contact is the current company and team introduction path.' },
|
||||
];
|
||||
|
||||
const footerNavigationGroupSeeds: FooterGroupSeed[] = [
|
||||
{
|
||||
title: 'Product',
|
||||
items: [
|
||||
{ href: '/product', label: 'Product' },
|
||||
{ href: '/changelog', label: 'Changelog' },
|
||||
],
|
||||
title: 'Platform',
|
||||
items: [{ href: '/product', label: 'Explore the platform' }],
|
||||
},
|
||||
{
|
||||
title: 'Trust & Legal',
|
||||
items: [
|
||||
{ href: '/trust', label: 'Trust' },
|
||||
{ href: '/privacy', label: 'Privacy' },
|
||||
{ href: '/imprint', label: 'Imprint' },
|
||||
{ href: '/terms', label: 'Terms' },
|
||||
],
|
||||
title: 'Solutions',
|
||||
items: [{ href: '/solutions', label: 'Solutions' }],
|
||||
},
|
||||
{
|
||||
title: 'Resources',
|
||||
items: [{ href: '/changelog', label: 'Changelog' }],
|
||||
},
|
||||
{
|
||||
title: 'Pricing',
|
||||
items: [{ href: '/contact', label: 'Book a demo' }],
|
||||
},
|
||||
{
|
||||
title: 'Company',
|
||||
items: [{ href: '/contact', label: 'Contact' }],
|
||||
},
|
||||
{
|
||||
title: 'Contact',
|
||||
items: [{ href: '/contact', label: 'Contact' }],
|
||||
},
|
||||
{
|
||||
title: 'Content',
|
||||
collection: 'resources',
|
||||
items: [{ href: '/resources', label: 'Resources', collection: 'resources' }],
|
||||
title: 'Legal',
|
||||
items: [
|
||||
{ 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' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1,87 +1,24 @@
|
||||
---
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import CapabilityGrid from '@/components/sections/CapabilityGrid.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import LogoStrip from '@/components/sections/LogoStrip.astro';
|
||||
import OutcomeSection from '@/components/sections/OutcomeSection.astro';
|
||||
import FeaturePillars from '@/components/sections/FeaturePillars.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import ProgressTeaser from '@/components/sections/ProgressTeaser.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 TrustBar from '@/components/sections/TrustBar.astro';
|
||||
import {
|
||||
homeCapabilities,
|
||||
homeCtaSection,
|
||||
homeEcosystem,
|
||||
homeFeaturePillars,
|
||||
homeHero,
|
||||
homeOutcome,
|
||||
homeProgressTeaser,
|
||||
homeSeo,
|
||||
homeTrustSignals,
|
||||
homeTrustBar,
|
||||
} 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}>
|
||||
<PageHero hero={homeHero} />
|
||||
|
||||
<LogoStrip
|
||||
eyebrow="Ecosystem fit"
|
||||
title="Built around the Microsoft tenant reality buyers already need to govern."
|
||||
items={homeEcosystem}
|
||||
/>
|
||||
<TrustBar statements={homeTrustBar} />
|
||||
|
||||
<OutcomeSection
|
||||
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>
|
||||
)}
|
||||
<FeaturePillars items={homeFeaturePillars} />
|
||||
|
||||
<div data-section="cta">
|
||||
<CTASection
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
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."
|
||||
calloutDescription="Tenantial 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
|
||||
|
||||
@ -2,18 +2,19 @@
|
||||
@import "./tokens.css";
|
||||
|
||||
html {
|
||||
overflow-x: clip;
|
||||
background: var(--color-background);
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
overflow-x: clip;
|
||||
margin: 0;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-foreground);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 255, 255, 0.88), transparent 30%),
|
||||
linear-gradient(180deg, var(--color-background) 0%, var(--color-background-elevated) 48%, var(--color-muted) 100%);
|
||||
linear-gradient(180deg, #050607 0%, #050607 58%, #07090d 100%);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@ -29,7 +30,8 @@ a:not([data-button-variant]) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
img {
|
||||
img,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
@ -39,12 +41,12 @@ code {
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: rgba(40, 60, 120, 0.12);
|
||||
background: rgba(111, 229, 191, 0.28);
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
: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;
|
||||
}
|
||||
|
||||
@ -59,41 +61,85 @@ .foundation-page {
|
||||
}
|
||||
|
||||
.foundation-page::before {
|
||||
position: absolute;
|
||||
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%);
|
||||
content: none;
|
||||
}
|
||||
|
||||
.foundation-page::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.home-ambient-wash,
|
||||
.home-dot-field {
|
||||
position: absolute;
|
||||
inset: clamp(0.7rem, 1vw, 1.2rem);
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
content: "";
|
||||
border: 1px solid var(--color-frame);
|
||||
border-radius: calc(var(--radius-panel) + 0.45rem);
|
||||
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 {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
@ -101,9 +147,9 @@ .skip-link {
|
||||
z-index: 40;
|
||||
transform: translateY(-200%);
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--color-foreground);
|
||||
background: var(--color-primary);
|
||||
padding: 0.75rem 1rem;
|
||||
color: white;
|
||||
color: var(--color-primary-foreground);
|
||||
text-decoration: none;
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
@ -120,36 +166,29 @@ .site-shell {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.shell-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));
|
||||
box-shadow: var(--shadow-panel-strong);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.surface-card {
|
||||
.surface-card,
|
||||
.surface-card-muted,
|
||||
.surface-card-accent {
|
||||
border: 1px solid var(--color-border);
|
||||
background: linear-gradient(180deg, var(--color-card), var(--surface-card-soft));
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
.surface-card-muted {
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
background: linear-gradient(180deg, var(--surface-muted-strong), var(--surface-muted));
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.surface-card-accent {
|
||||
border: 1px solid var(--color-border-strong);
|
||||
background: linear-gradient(180deg, var(--surface-accent-strong), var(--surface-accent));
|
||||
box-shadow: var(--shadow-soft);
|
||||
border-color: var(--color-border-strong);
|
||||
background: linear-gradient(180deg, rgba(111, 229, 191, 0.12), rgba(20, 28, 36, 0.9));
|
||||
}
|
||||
|
||||
.section-density-compact {
|
||||
@ -164,22 +203,32 @@ .section-density-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);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.52), rgba(243, 247, 251, 0.4));
|
||||
border-radius: calc(var(--radius-lg) + 0.15rem);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.section-shell-muted,
|
||||
.section-tinted {
|
||||
background: var(--surface-section-tinted);
|
||||
}
|
||||
|
||||
.section-shell-emphasis {
|
||||
border: 1px solid var(--color-border-strong);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.56), rgba(40, 60, 120, 0.03));
|
||||
border-radius: calc(var(--radius-lg) + 0.15rem);
|
||||
border-color: var(--color-border-strong);
|
||||
background: rgba(111, 229, 191, 0.08);
|
||||
}
|
||||
|
||||
.section-warm {
|
||||
background: var(--surface-section-warm);
|
||||
}
|
||||
|
||||
.text-link {
|
||||
color: var(--color-foreground);
|
||||
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;
|
||||
transition:
|
||||
color 140ms ease,
|
||||
@ -188,7 +237,7 @@ .text-link {
|
||||
|
||||
.text-link:hover {
|
||||
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 {
|
||||
@ -212,18 +261,18 @@ .legal-prose li + li {
|
||||
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;
|
||||
}
|
||||
|
||||
.foundation-page [data-disclosure-layer='1'] {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.foundation-page [data-disclosure-layer='2'] {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.foundation-page [data-disclosure-layer='2'],
|
||||
.foundation-page [data-disclosure-layer='3'] {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@ -243,26 +292,21 @@ @keyframes rise-in {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Section tint bands ── */
|
||||
.section-tinted {
|
||||
background: var(--surface-section-tinted);
|
||||
border-radius: calc(var(--radius-lg) + 0.15rem);
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::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 {
|
||||
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);
|
||||
}
|
||||
|
||||
/* ── Card hover lift ── */
|
||||
.surface-card,
|
||||
.surface-card-muted,
|
||||
.surface-card-accent {
|
||||
@ -276,7 +320,6 @@ .card-hoverable:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* ── Callout accent bar ── */
|
||||
.callout-bar {
|
||||
position: relative;
|
||||
padding-left: 1.25rem;
|
||||
@ -285,8 +328,8 @@ .callout-bar {
|
||||
.callout-bar::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
border-radius: 3px;
|
||||
background: var(--color-primary);
|
||||
@ -301,25 +344,19 @@ .callout-bar[data-bar-tone="warm"]::before {
|
||||
background: var(--color-warning);
|
||||
}
|
||||
|
||||
/* ── Accent text highlight ── */
|
||||
.text-accent-word {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ── Feature icon circle ── */
|
||||
.feature-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border: 1px solid rgba(111, 229, 191, 0.24);
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--surface-accent), var(--surface-accent-strong));
|
||||
background: var(--surface-accent);
|
||||
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-mono: "IBM Plex Mono", "SFMono-Regular", monospace;
|
||||
|
||||
/* Cool slate neutrals — shadcn / Apex direction */
|
||||
--color-stone-50: oklch(0.985 0.002 250);
|
||||
--color-stone-100: oklch(0.975 0.003 250);
|
||||
--color-stone-150: oklch(0.962 0.004 248);
|
||||
--color-stone-200: oklch(0.94 0.006 246);
|
||||
--color-stone-300: oklch(0.90 0.008 244);
|
||||
/* Ink: dark slate, very low chroma — near-black with cool undertone */
|
||||
--color-ink-700: oklch(0.44 0.01 250);
|
||||
--color-ink-800: oklch(0.30 0.008 250);
|
||||
--color-ink-900: oklch(0.14 0.005 250);
|
||||
/* Brand: dry slate-blue — enterprise, not flashy */
|
||||
--color-brand-300: oklch(0.78 0.06 245);
|
||||
--color-brand-400: oklch(0.64 0.09 245);
|
||||
--color-brand-500: oklch(0.50 0.10 245);
|
||||
--color-brand-700: oklch(0.38 0.09 248);
|
||||
/* Mint → Steel: functional accent for success states */
|
||||
--color-mint-300: oklch(0.84 0.05 175);
|
||||
--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);
|
||||
--color-obsidian-950: oklch(0.115 0.01 250);
|
||||
--color-obsidian-900: oklch(0.155 0.012 250);
|
||||
--color-obsidian-850: oklch(0.205 0.014 250);
|
||||
--color-obsidian-800: oklch(0.255 0.014 250);
|
||||
--color-ivory-50: oklch(0.982 0.012 88);
|
||||
--color-stone-200: oklch(0.87 0.014 80);
|
||||
--color-stone-400: oklch(0.69 0.014 80);
|
||||
--color-stone-500: oklch(0.6 0.014 80);
|
||||
--color-mint-300: oklch(0.86 0.09 170);
|
||||
--color-mint-500: oklch(0.72 0.105 170);
|
||||
--color-mint-700: oklch(0.56 0.095 170);
|
||||
--color-amber-300: oklch(0.84 0.11 72);
|
||||
--color-amber-500: oklch(0.72 0.13 68);
|
||||
--color-coral-400: oklch(0.7 0.16 30);
|
||||
--color-coral-500: oklch(0.64 0.18 28);
|
||||
--color-violet-300: oklch(0.77 0.1 305);
|
||||
--color-violet-500: oklch(0.62 0.13 300);
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
color-scheme: dark;
|
||||
|
||||
--color-background: var(--color-stone-50);
|
||||
--color-background-elevated: var(--color-stone-100);
|
||||
--color-foreground: var(--color-ink-900);
|
||||
--color-muted: var(--color-stone-150);
|
||||
--color-muted-foreground: var(--color-ink-700);
|
||||
--color-card: rgba(255, 255, 255, 0.92);
|
||||
--color-card-foreground: var(--color-ink-900);
|
||||
--color-border: rgba(20, 20, 20, 0.08);
|
||||
--color-border-strong: rgba(20, 20, 20, 0.15);
|
||||
--color-border-subtle: rgba(20, 20, 20, 0.05);
|
||||
--color-frame: rgba(20, 20, 20, 0.04);
|
||||
--color-input: rgba(255, 255, 255, 0.94);
|
||||
--color-primary: var(--color-brand-500);
|
||||
--color-primary-foreground: #f8f9fb;
|
||||
--color-secondary: rgba(255, 255, 255, 0.82);
|
||||
--color-secondary-foreground: var(--color-ink-900);
|
||||
--color-accent: rgba(40, 60, 120, 0.06);
|
||||
--color-accent-foreground: var(--color-brand-700);
|
||||
--color-success: var(--color-mint-700);
|
||||
--color-warning: var(--color-amber-700);
|
||||
--color-destructive: var(--color-red-500);
|
||||
--color-info: var(--color-brand-700);
|
||||
--color-background: var(--color-obsidian-950);
|
||||
--color-background-elevated: var(--color-obsidian-900);
|
||||
--color-foreground: var(--color-ivory-50);
|
||||
--color-muted: var(--color-obsidian-850);
|
||||
--color-muted-foreground: var(--color-stone-400);
|
||||
--color-card: rgba(18, 24, 32, 0.92);
|
||||
--color-card-foreground: var(--color-ivory-50);
|
||||
--color-border: rgba(255, 247, 225, 0.1);
|
||||
--color-border-strong: rgba(255, 247, 225, 0.18);
|
||||
--color-border-subtle: rgba(255, 247, 225, 0.07);
|
||||
--color-frame: rgba(255, 247, 225, 0.08);
|
||||
--color-input: rgba(18, 24, 32, 0.96);
|
||||
--color-primary: var(--color-mint-500);
|
||||
--color-primary-foreground: var(--color-obsidian-950);
|
||||
--color-secondary: rgba(255, 247, 225, 0.08);
|
||||
--color-secondary-foreground: var(--color-ivory-50);
|
||||
--color-accent: rgba(111, 229, 191, 0.12);
|
||||
--color-accent-foreground: var(--color-mint-300);
|
||||
--color-success: var(--color-mint-500);
|
||||
--color-warning: var(--color-amber-500);
|
||||
--color-destructive: var(--color-coral-500);
|
||||
--color-info: var(--color-violet-300);
|
||||
|
||||
--surface-page: rgba(255, 255, 255, 0.34);
|
||||
--surface-shell: rgba(255, 255, 255, 0.78);
|
||||
--surface-shell-strong: rgba(255, 255, 255, 0.94);
|
||||
--surface-card-soft: rgba(248, 249, 252, 0.82);
|
||||
--surface-muted: rgba(246, 248, 252, 0.88);
|
||||
--surface-muted-strong: rgba(250, 251, 254, 0.94);
|
||||
--surface-accent: rgba(40, 60, 120, 0.04);
|
||||
--surface-accent-strong: rgba(246, 248, 254, 0.98);
|
||||
--surface-trust: rgba(40, 60, 120, 0.03);
|
||||
--surface-section-tinted: rgba(246, 248, 252, 0.62);
|
||||
--surface-section-warm: rgba(248, 249, 252, 0.52);
|
||||
--surface-page: rgba(7, 10, 15, 0.94);
|
||||
--surface-shell: rgba(13, 18, 25, 0.88);
|
||||
--surface-shell-strong: rgba(17, 24, 32, 0.96);
|
||||
--surface-card-soft: rgba(20, 28, 36, 0.84);
|
||||
--surface-muted: rgba(255, 247, 225, 0.05);
|
||||
--surface-muted-strong: rgba(255, 247, 225, 0.075);
|
||||
--surface-accent: rgba(111, 229, 191, 0.1);
|
||||
--surface-accent-strong: rgba(111, 229, 191, 0.16);
|
||||
--surface-trust: rgba(175, 150, 255, 0.12);
|
||||
--surface-section-tinted: rgba(15, 21, 29, 0.62);
|
||||
--surface-section-warm: rgba(255, 188, 92, 0.08);
|
||||
|
||||
--radius-sm: 1rem;
|
||||
--radius-md: 1.35rem;
|
||||
--radius-lg: 1.75rem;
|
||||
--radius-panel: 2rem;
|
||||
--radius-sm: 0.5rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-panel: 0.5rem;
|
||||
--radius-pill: 999px;
|
||||
|
||||
--shadow-panel-strong: 0 28px 90px rgba(17, 36, 58, 0.14);
|
||||
--shadow-card: 0 20px 56px rgba(17, 36, 58, 0.1);
|
||||
--shadow-card-hover: 0 24px 64px rgba(17, 36, 58, 0.15);
|
||||
--shadow-soft: 0 12px 36px rgba(17, 36, 58, 0.08);
|
||||
--shadow-inline: 0 10px 22px rgba(17, 36, 58, 0.08);
|
||||
--shadow-panel-strong: 0 28px 90px rgba(0, 0, 0, 0.32);
|
||||
--shadow-card: 0 20px 54px rgba(0, 0, 0, 0.24);
|
||||
--shadow-card-hover: 0 24px 64px rgba(0, 0, 0, 0.32);
|
||||
--shadow-soft: 0 12px 32px rgba(0, 0, 0, 0.18);
|
||||
--shadow-inline: 0 10px 24px rgba(0, 0, 0, 0.22);
|
||||
|
||||
--space-page-x: clamp(1.25rem, 2vw, 2.5rem);
|
||||
--space-page-y: clamp(5rem, 8vw, 8rem);
|
||||
--space-section-compact: clamp(3.5rem, 5vw, 5rem);
|
||||
--space-section: clamp(5rem, 8vw, 7rem);
|
||||
--space-section-spacious: clamp(6rem, 9vw, 9rem);
|
||||
--space-page-x: clamp(1rem, 2vw, 2.25rem);
|
||||
--space-page-y: clamp(4rem, 7vw, 7rem);
|
||||
--space-section-compact: clamp(3rem, 5vw, 4.5rem);
|
||||
--space-section: clamp(4.5rem, 7vw, 6.5rem);
|
||||
--space-section-spacious: clamp(5.5rem, 8vw, 8rem);
|
||||
--space-cluster-sm: 0.75rem;
|
||||
--space-cluster: 1rem;
|
||||
--space-cluster-lg: 1.5rem;
|
||||
--space-stack-sm: 0.75rem;
|
||||
--space-stack: 1.25rem;
|
||||
--space-stack-lg: 1.75rem;
|
||||
--space-grid: 1.25rem;
|
||||
--space-grid-lg: 1.75rem;
|
||||
--space-grid: 1rem;
|
||||
--space-grid-lg: 1.5rem;
|
||||
|
||||
--content-max-width: 76rem;
|
||||
--wide-max-width: 84rem;
|
||||
--wide-max-width: 88rem;
|
||||
--reading-max-width: 68rem;
|
||||
|
||||
--type-display-size: clamp(2.75rem, 5vw, 4.25rem);
|
||||
--type-page-size: clamp(2.25rem, 3.8vw, 3.25rem);
|
||||
--type-section-size: clamp(1.75rem, 2.8vw, 2.5rem);
|
||||
--type-card-size: clamp(1.25rem, 1.8vw, 1.6rem);
|
||||
--type-body-size: 1.05rem;
|
||||
--type-display-size: 4rem;
|
||||
--type-page-size: 3rem;
|
||||
--type-section-size: 2.25rem;
|
||||
--type-card-size: 1.25rem;
|
||||
--type-body-size: 1.03rem;
|
||||
--type-small-size: 0.94rem;
|
||||
--type-eyebrow-size: 0.74rem;
|
||||
--type-helper-size: 0.82rem;
|
||||
--tracking-display: -0.04em;
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-eyebrow: 0.18em;
|
||||
--line-display: 0.96;
|
||||
--line-heading: 1.04;
|
||||
--line-body: 1.75;
|
||||
--tracking-display: 0;
|
||||
--tracking-tight: 0;
|
||||
--tracking-eyebrow: 0;
|
||||
--line-display: 1;
|
||||
--line-heading: 1.06;
|
||||
--line-body: 1.7;
|
||||
--line-tight: 1.45;
|
||||
|
||||
/* Legacy aliases used by existing primitives and content blocks. */
|
||||
--color-copy: var(--color-muted-foreground);
|
||||
--color-line: var(--color-border);
|
||||
--color-panel: var(--surface-shell);
|
||||
@ -124,4 +116,17 @@ :root {
|
||||
--color-signal: var(--color-success);
|
||||
--color-warm: var(--color-warning);
|
||||
--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);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
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, '/');
|
||||
|
||||
const header = page.getByRole('banner');
|
||||
const footer = page.getByRole('contentinfo');
|
||||
|
||||
await expect(header.getByRole('link', { name: 'Resources' })).toHaveCount(0);
|
||||
await expect(header.getByRole('link', { name: 'Solutions' })).toHaveCount(0);
|
||||
await expect(header.getByRole('link', { name: 'Integrations' })).toHaveCount(0);
|
||||
await expect(header.getByRole('link', { name: 'Platform', exact: true })).toHaveAttribute('href', '/product');
|
||||
await expect(header.getByRole('link', { name: 'Solutions', exact: true })).toHaveAttribute('href', '/solutions');
|
||||
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(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: '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.',
|
||||
}),
|
||||
).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: 'Privacy' }).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 openMobileNavigation(page);
|
||||
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: 'Trust' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('banner').getByRole('link', { name: 'Changelog' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('banner').getByRole('link', { name: 'Book a demo' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('banner').getByRole('link', { name: 'Platform' }).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: 'Terms' })).toBeVisible();
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Imprint' })).toBeVisible();
|
||||
|
||||
@ -8,32 +8,59 @@ import {
|
||||
expectHomepageHeroOrder,
|
||||
expectHomepageHeroRouteTargets,
|
||||
expectHomepageHeroStructure,
|
||||
expectHomepageHeroTrustSignals,
|
||||
expectHomepageHeroVisibleOnMobile,
|
||||
expectHomepageSectionOrder,
|
||||
expectMobileReadability,
|
||||
expectNavigationVsCtaDifferentiation,
|
||||
expectNoBodyHorizontalOverflow,
|
||||
expectOnwardRouteReachable,
|
||||
expectPageFamily,
|
||||
expectProductNearVisual,
|
||||
expectPrimaryNavigation,
|
||||
expectShell,
|
||||
visitPage,
|
||||
} from './smoke-helpers';
|
||||
|
||||
test('home uses the landing foundation to explain the product category with one clear action hierarchy', async ({
|
||||
page,
|
||||
}) => {
|
||||
const forbiddenHomepageTerms = [
|
||||
'AstroDeck',
|
||||
'Open Source',
|
||||
'MIT Licensed',
|
||||
'TenantCTRL',
|
||||
'TenantPilot',
|
||||
'TenantAtlas',
|
||||
] 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'));
|
||||
}
|
||||
}
|
||||
|
||||
test('home first read positions Tenantial with one clear action hierarchy', async ({ page }) => {
|
||||
await visitPage(page, '/');
|
||||
await expectShell(page, /TenantAtlas/);
|
||||
await expectShell(page, 'Evidence-first governance for Microsoft tenants.');
|
||||
await expectPageFamily(page, 'landing');
|
||||
await expectDisclosureLayer(page, '1');
|
||||
await expectDisclosureLayer(page, '2');
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectNavigationVsCtaDifferentiation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expectCtaHierarchy(page, 'Request a working session', 'See the product model');
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Request a working session' }).first()).toBeVisible();
|
||||
await expectCtaHierarchy(page, 'Book a demo', 'Explore the platform');
|
||||
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' });
|
||||
|
||||
@ -41,102 +68,95 @@ test('home uses the landing foundation to explain the product category with one
|
||||
await expect(skipLink).toBeFocused();
|
||||
});
|
||||
|
||||
test('homepage hero explains the product with a product-near visual and outcome framing', async ({
|
||||
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,
|
||||
}) => {
|
||||
test('homepage hero explains Tenantial, Microsoft tenant context, and required CTA routes', async ({ page }) => {
|
||||
await visitPage(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(
|
||||
page.locator('[data-homepage-hero="true"] [data-hero-heading]').getByRole('heading', {
|
||||
level: 1,
|
||||
name: /one operating record for change history, drift review, and restore planning/i,
|
||||
name: 'Evidence-first governance for Microsoft tenants.',
|
||||
}),
|
||||
).toBeVisible();
|
||||
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', '/product']);
|
||||
});
|
||||
|
||||
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,
|
||||
}) => {
|
||||
await visitPage(page, '/');
|
||||
await expectProductNearVisual(page, /change history, restore preview, and a review queue/i);
|
||||
await expectHomepageHeroTrustSignals(page);
|
||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText(
|
||||
/tenant-scoped boundaries/i,
|
||||
);
|
||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText(
|
||||
/reviewable change history/i,
|
||||
);
|
||||
await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText(
|
||||
/preview before restore/i,
|
||||
);
|
||||
|
||||
const preview = page.locator('[data-dashboard-preview]').first();
|
||||
|
||||
await expect(preview).toBeVisible();
|
||||
await expect(preview).toContainText('Static demo preview');
|
||||
await expect(preview).toContainText('Demo values');
|
||||
await expect(preview).toContainText('92%');
|
||||
await expect(preview).toContainText('14');
|
||||
await expect(preview).toContainText('7');
|
||||
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 ({
|
||||
page,
|
||||
}) => {
|
||||
test('homepage keeps the final CTA and launch-readiness sections in order', async ({ page }) => {
|
||||
await visitPage(page, '/');
|
||||
await expect(page.locator('[data-section="progress"]')).toBeVisible();
|
||||
await expectOnwardRouteReachable(page, ['/changelog']);
|
||||
});
|
||||
|
||||
test('homepage routes into Product, Trust, Changelog, and Contact', async ({
|
||||
page,
|
||||
}) => {
|
||||
await visitPage(page, '/');
|
||||
await expectOnwardRouteReachable(page, ['/product', '/trust', '/changelog', '/contact']);
|
||||
await expectHomepageSectionOrder(page, ['hero', 'trustbar', 'feature-pillars', 'cta']);
|
||||
await expect(page.locator('[data-section="cta"]')).toContainText(
|
||||
'Build tenant governance on evidence, not assumptions.',
|
||||
);
|
||||
await expectOnwardRouteReachable(page, ['/product', '/contact']);
|
||||
});
|
||||
|
||||
test.describe('homepage mobile', () => {
|
||||
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 expectMobileReadability(page);
|
||||
await expect(page.locator('[data-section="outcome"]')).toBeVisible();
|
||||
await expect(page.locator('[data-section="capability"]')).toBeVisible();
|
||||
await expect(page.locator('[data-section="trust"]')).toBeVisible();
|
||||
await expectNoBodyHorizontalOverflow(page);
|
||||
await expect(page.locator('[data-section="trustbar"]')).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 }) => {
|
||||
@ -146,8 +166,8 @@ test.describe('homepage mobile', () => {
|
||||
'headline',
|
||||
'supporting-copy',
|
||||
'cta-pair',
|
||||
'product-near-visual',
|
||||
'trust-subclaims',
|
||||
'product-near-visual',
|
||||
]);
|
||||
await expectHomepageHeroVisibleOnMobile(page);
|
||||
await expectHomepageHeroRouteTargets(page, ['/contact', '/product']);
|
||||
|
||||
@ -3,17 +3,32 @@ import { expect, type Page } from '@playwright/test';
|
||||
export const coreRoutePaths = ['/', '/product', '/trust', '/changelog', '/contact', '/privacy', '/imprint'] as const;
|
||||
export const secondaryRoutePaths = ['/legal', '/terms', '/solutions', '/integrations'] as const;
|
||||
|
||||
export const primaryNavigationLabels = ['Product', 'Trust', 'Changelog', 'Contact'] as const;
|
||||
export const hiddenPrimaryNavigationLabels = [
|
||||
'Solutions',
|
||||
'Integrations',
|
||||
'Security & Trust',
|
||||
'Resources',
|
||||
'Articles',
|
||||
] as const;
|
||||
export const primaryNavigationLabels = ['Platform', 'Solutions', 'Resources', 'Pricing', 'Company'] as const;
|
||||
export const hiddenPrimaryNavigationLabels = ['Product', 'Security & Trust', 'Articles'] as const;
|
||||
|
||||
export const footerLabels = ['Product', 'Changelog', 'Trust', 'Privacy', 'Imprint', 'Terms', 'Contact'] as const;
|
||||
export const hiddenFooterLabels = ['Resources', 'Articles', 'Security & Trust', 'Contact / Demo'] as const;
|
||||
export const footerGroupLabels = [
|
||||
'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> {
|
||||
await page.goto(path);
|
||||
@ -39,26 +54,47 @@ export async function expectPageFamily(page: Page, family: 'content' | 'landing'
|
||||
|
||||
export async function expectPrimaryNavigation(page: Page): Promise<void> {
|
||||
const header = page.getByRole('banner');
|
||||
const expectedRoutes: Record<(typeof primaryNavigationLabels)[number], string> = {
|
||||
Platform: '/product',
|
||||
Solutions: '/solutions',
|
||||
Resources: '/changelog',
|
||||
Pricing: '/contact',
|
||||
Company: '/contact',
|
||||
};
|
||||
|
||||
for (const label of primaryNavigationLabels) {
|
||||
const link = header.getByRole('link', { name: label, exact: true }).first();
|
||||
|
||||
await expect(link).toBeVisible();
|
||||
await expect(link).toHaveAttribute('data-nav-link');
|
||||
await expect(link).toHaveAttribute('href', expectedRoutes[label]);
|
||||
}
|
||||
|
||||
for (const label of hiddenPrimaryNavigationLabels) {
|
||||
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> {
|
||||
const footer = page.getByRole('contentinfo');
|
||||
|
||||
for (const label of footerGroupLabels) {
|
||||
await expect(footer.getByText(label, { exact: true }).first()).toBeVisible();
|
||||
}
|
||||
|
||||
for (const label of footerLabels) {
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: label, exact: true })).toBeVisible();
|
||||
await expect(footer.getByRole('link', { name: label, exact: true }).first()).toBeVisible();
|
||||
}
|
||||
|
||||
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 +301,7 @@ export async function expectNavigationVsCtaDifferentiation(page: Page): Promise<
|
||||
const header = page.getByRole('banner');
|
||||
|
||||
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> {
|
||||
@ -313,6 +349,17 @@ export async function expectMobileReadability(page: Page): Promise<void> {
|
||||
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> {
|
||||
const main = page.getByRole('main');
|
||||
|
||||
|
||||
@ -13,13 +13,15 @@ test('representative pages route CTA, badge, surface, and input semantics throug
|
||||
page,
|
||||
}) => {
|
||||
await visitPage(page, '/');
|
||||
await expectShell(page, /control surface/i);
|
||||
await expectShell(page, 'Evidence-first governance for Microsoft tenants.');
|
||||
await expectPageFamily(page, 'landing');
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectNavigationVsCtaDifferentiation(page);
|
||||
await expectCtaHierarchy(page, 'Request a working session', /product model/i);
|
||||
await expect(page.locator('[data-interaction="button"]').filter({ hasText: 'Request a working session' }).first()).toBeVisible();
|
||||
await expectCtaHierarchy(page, 'Book a demo', 'Explore the platform');
|
||||
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-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 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.
|
||||
Loading…
Reference in New Issue
Block a user