Agent: commit workspace changes (217-homepage-hero-session-1776809852) (#259)
Some checks failed
Main Confidence / confidence (push) Failing after 50s
Some checks failed
Main Confidence / confidence (push) Failing after 50s
Automated commit by agent: commits workspace changes for feature 217-homepage-hero. Please review and merge into `dev`. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #259
This commit is contained in:
parent
81bb5f42c7
commit
cebd5ee1b0
10
.github/agents/copilot-instructions.md
vendored
10
.github/agents/copilot-instructions.md
vendored
@ -218,8 +218,10 @@ ## Active Technologies
|
|||||||
- Static filesystem pages, content modules, and Astro content collections under `apps/website/src` and `apps/website/public`; no database (215-website-core-pages)
|
- Static filesystem pages, content modules, and Astro content collections under `apps/website/src` and `apps/website/public`; no database (215-website-core-pages)
|
||||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament Resources/Pages/Actions, Livewire 4, Pest 4, `ProviderOperationStartGate`, `ProviderOperationRegistry`, `ProviderConnectionResolver`, `OperationRunService`, `ProviderNextStepsRegistry`, `ReasonPresenter`, `OperationUxPresenter`, `OperationRunLinks` (216-provider-dispatch-gate)
|
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament Resources/Pages/Actions, Livewire 4, Pest 4, `ProviderOperationStartGate`, `ProviderOperationRegistry`, `ProviderConnectionResolver`, `OperationRunService`, `ProviderNextStepsRegistry`, `ReasonPresenter`, `OperationUxPresenter`, `OperationRunLinks` (216-provider-dispatch-gate)
|
||||||
- PostgreSQL via existing `operation_runs`, `provider_connections`, `managed_tenant_onboarding_sessions`, `restore_runs`, and tenant-owned runtime records; no new tables planned (216-provider-dispatch-gate)
|
- PostgreSQL via existing `operation_runs`, `provider_connections`, `managed_tenant_onboarding_sessions`, `restore_runs`, and tenant-owned runtime records; no new tables planned (216-provider-dispatch-gate)
|
||||||
- Astro 6.0.0 templates + TypeScript 5.9.x + Astro 6, Tailwind CSS v4, local Astro layout/section primitives, Astro content collections, Playwright browser smoke tests (216-homepage-structure)
|
- Astro 6.0.0 templates + TypeScript 5.9.x + Astro 6, Tailwind CSS v4, local Astro layout/section primitives, Astro content collections, Playwright browser smoke tests (217-homepage-structure)
|
||||||
- Static filesystem content, Astro content collections, and assets under `apps/website/src` and `apps/website/public`; no database (216-homepage-structure)
|
- Static filesystem content, Astro content collections, and assets under `apps/website/src` and `apps/website/public`; no database (217-homepage-structure)
|
||||||
|
- Astro 6.0.0 templates + TypeScript 5.9.x + Astro 6, Tailwind CSS v4, existing Astro content modules and section primitives, Playwright browser smoke tests (218-homepage-hero)
|
||||||
|
- Static filesystem content and assets under `apps/website/src` and `apps/website/public`; no database (218-homepage-hero)
|
||||||
- PHP 8.4.15 / Laravel 12 + Filament v5, Livewire v4.0+, Pest v4, Tailwind CSS v4 (219-finding-ownership-semantics)
|
- PHP 8.4.15 / Laravel 12 + Filament v5, Livewire v4.0+, Pest v4, Tailwind CSS v4 (219-finding-ownership-semantics)
|
||||||
- PostgreSQL via Sail; existing `findings.owner_user_id`, `findings.assignee_user_id`, and `finding_exceptions.owner_user_id` fields; no schema changes planned (219-finding-ownership-semantics)
|
- PostgreSQL via Sail; existing `findings.owner_user_id`, `findings.assignee_user_id`, and `finding_exceptions.owner_user_id` fields; no schema changes planned (219-finding-ownership-semantics)
|
||||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament v5, Livewire v4, Pest v4, Laravel Sail, `TenantlessOperationRunViewer`, `OperationRunResource`, `ArtifactTruthPresenter`, `OperatorExplanationBuilder`, `ReasonPresenter`, `OperationUxPresenter`, `SummaryCountsNormalizer`, and the existing enterprise-detail builders (220-governance-run-summaries)
|
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament v5, Livewire v4, Pest v4, Laravel Sail, `TenantlessOperationRunViewer`, `OperationRunResource`, `ArtifactTruthPresenter`, `OperatorExplanationBuilder`, `ReasonPresenter`, `OperationUxPresenter`, `SummaryCountsNormalizer`, and the existing enterprise-detail builders (220-governance-run-summaries)
|
||||||
@ -263,6 +265,10 @@ ## Recent Changes
|
|||||||
- 221-findings-operator-inbox: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament admin panel pages, `Finding`, `FindingResource`, `WorkspaceOverviewBuilder`, `WorkspaceContext`, `WorkspaceCapabilityResolver`, `CapabilityResolver`, `CanonicalAdminTenantFilterState`, and `CanonicalNavigationContext`
|
- 221-findings-operator-inbox: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament admin panel pages, `Finding`, `FindingResource`, `WorkspaceOverviewBuilder`, `WorkspaceContext`, `WorkspaceCapabilityResolver`, `CapabilityResolver`, `CanonicalAdminTenantFilterState`, and `CanonicalNavigationContext`
|
||||||
- 220-governance-run-summaries: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament v5, Livewire v4, Pest v4, Laravel Sail, `TenantlessOperationRunViewer`, `OperationRunResource`, `ArtifactTruthPresenter`, `OperatorExplanationBuilder`, `ReasonPresenter`, `OperationUxPresenter`, `SummaryCountsNormalizer`, and the existing enterprise-detail builders
|
- 220-governance-run-summaries: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament v5, Livewire v4, Pest v4, Laravel Sail, `TenantlessOperationRunViewer`, `OperationRunResource`, `ArtifactTruthPresenter`, `OperatorExplanationBuilder`, `ReasonPresenter`, `OperationUxPresenter`, `SummaryCountsNormalizer`, and the existing enterprise-detail builders
|
||||||
- 219-finding-ownership-semantics: Added PHP 8.4.15 / Laravel 12 + Filament v5, Livewire v4.0+, Pest v4, Tailwind CSS v4
|
- 219-finding-ownership-semantics: Added PHP 8.4.15 / Laravel 12 + Filament v5, Livewire v4.0+, Pest v4, Tailwind CSS v4
|
||||||
|
- 218-homepage-hero: Added Astro 6.0.0 templates + TypeScript 5.9.x + Astro 6, Tailwind CSS v4, existing Astro content modules and section primitives, Playwright browser smoke tests
|
||||||
|
- 217-homepage-structure: Added Astro 6.0.0 templates + TypeScript 5.9.x + Astro 6, Tailwind CSS v4, local Astro layout/section primitives, Astro content collections, Playwright browser smoke tests
|
||||||
|
- 216-provider-dispatch-gate: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament Resources/Pages/Actions, Livewire 4, Pest 4, `ProviderOperationStartGate`, `ProviderOperationRegistry`, `ProviderConnectionResolver`, `OperationRunService`, `ProviderNextStepsRegistry`, `ReasonPresenter`, `OperationUxPresenter`, `OperationRunLinks`
|
||||||
|
- 215-website-core-pages: Added Astro 6.0.0 templates + TypeScript 5.9 strict + Astro 6, Tailwind CSS v4 via `@tailwindcss/vite`, Astro content collections, local Astro layout/primitive/content helpers, Playwright smoke tests
|
||||||
<!-- MANUAL ADDITIONS START -->
|
<!-- MANUAL ADDITIONS START -->
|
||||||
|
|
||||||
### Pre-production compatibility check
|
### Pre-production compatibility check
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
import icon from 'astro-icon';
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example';
|
const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
integrations: [icon()],
|
||||||
output: 'static',
|
output: 'static',
|
||||||
site: publicSiteUrl,
|
site: publicSiteUrl,
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
@ -14,7 +14,9 @@
|
|||||||
"test:smoke": "playwright test"
|
"test:smoke": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^6.0.0"
|
"@iconify-json/lucide": "^1.2.102",
|
||||||
|
"astro": "^6.0.0",
|
||||||
|
"astro-icon": "^1.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.59.1",
|
"@playwright/test": "^1.59.1",
|
||||||
|
|||||||
@ -1,81 +1,70 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="500" viewBox="0 0 800 500">
|
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="500" viewBox="0 0 800 500" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
<linearGradient id="bg" x1="60" y1="40" x2="740" y2="460" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0%" style="stop-color:#f8fafc;stop-opacity:1" />
|
<stop offset="0" stop-color="#F8FAFC" />
|
||||||
<stop offset="100%" style="stop-color:#e2e8f0;stop-opacity:1" />
|
<stop offset="1" stop-color="#E2E8F0" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="header" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="topbar" x1="0" y1="0" x2="800" y2="0" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0%" style="stop-color:#2f6fb7;stop-opacity:1" />
|
<stop offset="0" stop-color="#1D4ED8" />
|
||||||
<stop offset="100%" style="stop-color:#8eaed1;stop-opacity:1" />
|
<stop offset="1" stop-color="#4F46E5" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="800" height="500" rx="16" fill="url(#bg)" stroke="#e2e8f0" stroke-width="2"/>
|
<rect x="8" y="8" width="784" height="484" rx="20" fill="url(#bg)" stroke="#D7E2EE" stroke-width="2" />
|
||||||
<!-- Header bar -->
|
<rect x="8" y="8" width="784" height="52" rx="20" fill="url(#topbar)" />
|
||||||
<rect x="0" y="0" width="800" height="48" rx="16" fill="url(#header)"/>
|
<rect x="8" y="30" width="784" height="30" fill="url(#topbar)" />
|
||||||
<rect x="0" y="16" width="800" height="32" fill="url(#header)"/>
|
<text x="32" y="40" font-family="system-ui" font-size="15" font-weight="700" fill="#FFFFFF">TenantAtlas</text>
|
||||||
<text x="24" y="30" font-family="system-ui" font-size="14" fill="white" font-weight="600">TenantAtlas</text>
|
<circle cx="742" cy="34" r="6" fill="#FFFFFF" fill-opacity="0.7" />
|
||||||
<!-- Sidebar -->
|
<circle cx="762" cy="34" r="6" fill="#FFFFFF" fill-opacity="0.45" />
|
||||||
<rect x="0" y="48" width="180" height="452" fill="#f1f5f9"/>
|
<rect x="24" y="76" width="168" height="396" rx="18" fill="#F3F6FB" stroke="#D7E2EE" />
|
||||||
<rect x="16" y="68" width="148" height="32" rx="8" fill="#2f6fb7" opacity="0.12"/>
|
<text x="44" y="108" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">WORKSPACE</text>
|
||||||
<text x="28" y="89" font-family="system-ui" font-size="12" fill="#2f6fb7" font-weight="600">Inventory</text>
|
<rect x="36" y="128" width="144" height="34" rx="10" fill="#DBEAFE" />
|
||||||
<rect x="16" y="112" width="148" height="28" rx="8" fill="transparent"/>
|
<text x="52" y="149" font-family="system-ui" font-size="12" font-weight="700" fill="#1D4ED8">Change history</text>
|
||||||
<text x="28" y="130" font-family="system-ui" font-size="12" fill="#64748b">Backup History</text>
|
<text x="52" y="194" font-family="system-ui" font-size="12" fill="#475569">Restore preview</text>
|
||||||
<rect x="16" y="148" width="148" height="28" rx="8" fill="transparent"/>
|
<text x="52" y="230" font-family="system-ui" font-size="12" fill="#475569">Review queue</text>
|
||||||
<text x="28" y="166" font-family="system-ui" font-size="12" fill="#64748b">Restore</text>
|
<text x="52" y="266" font-family="system-ui" font-size="12" fill="#475569">Evidence</text>
|
||||||
<rect x="16" y="184" width="148" height="28" rx="8" fill="transparent"/>
|
<text x="52" y="302" font-family="system-ui" font-size="12" fill="#475569">Assignments</text>
|
||||||
<text x="28" y="202" font-family="system-ui" font-size="12" fill="#64748b">Drift Detection</text>
|
<rect x="36" y="344" width="144" height="96" rx="14" fill="#FFFFFF" stroke="#D7E2EE" />
|
||||||
<rect x="16" y="220" width="148" height="28" rx="8" fill="transparent"/>
|
<text x="52" y="370" font-family="system-ui" font-size="11" font-weight="700" fill="#334155">Current tenant</text>
|
||||||
<text x="28" y="238" font-family="system-ui" font-size="12" fill="#64748b">Governance</text>
|
<text x="52" y="394" font-family="system-ui" font-size="12" fill="#0F172A">Northwind Services</text>
|
||||||
<!-- Main content area -->
|
<text x="52" y="418" font-family="system-ui" font-size="11" fill="#64748B">Inventory linked to reviewable history</text>
|
||||||
<rect x="196" y="64" width="588" height="420" rx="12" fill="white" stroke="#e2e8f0"/>
|
<rect x="212" y="76" width="564" height="396" rx="18" fill="#FFFFFF" stroke="#D7E2EE" />
|
||||||
<!-- Stats row -->
|
<text x="236" y="108" font-family="system-ui" font-size="12" font-weight="700" fill="#334155">Recent tenant changes</text>
|
||||||
<rect x="212" y="80" width="130" height="64" rx="8" fill="#f8fafc" stroke="#e2e8f0"/>
|
<rect x="236" y="124" width="90" height="24" rx="12" fill="#EFF6FF" />
|
||||||
<text x="228" y="104" font-family="system-ui" font-size="22" fill="#1e293b" font-weight="700">247</text>
|
<text x="252" y="140" font-family="system-ui" font-size="10" font-weight="700" fill="#2563EB">Policies</text>
|
||||||
<text x="228" y="130" font-family="system-ui" font-size="11" fill="#64748b">Policies tracked</text>
|
<rect x="336" y="124" width="82" height="24" rx="12" fill="#F8FAFC" stroke="#D7E2EE" />
|
||||||
<rect x="358" y="80" width="130" height="64" rx="8" fill="#f8fafc" stroke="#e2e8f0"/>
|
<text x="356" y="140" font-family="system-ui" font-size="10" font-weight="700" fill="#475569">Drift</text>
|
||||||
<text x="374" y="104" font-family="system-ui" font-size="22" fill="#16a34a" font-weight="700">98.4%</text>
|
<rect x="428" y="124" width="108" height="24" rx="12" fill="#F8FAFC" stroke="#D7E2EE" />
|
||||||
<text x="374" y="130" font-family="system-ui" font-size="11" fill="#64748b">Compliance rate</text>
|
<text x="448" y="140" font-family="system-ui" font-size="10" font-weight="700" fill="#475569">Assignments</text>
|
||||||
<rect x="504" y="80" width="130" height="64" rx="8" fill="#f8fafc" stroke="#e2e8f0"/>
|
<rect x="236" y="164" width="320" height="236" rx="16" fill="#F8FAFC" stroke="#D7E2EE" />
|
||||||
<text x="520" y="104" font-family="system-ui" font-size="22" fill="#2f6fb7" font-weight="700">12</text>
|
<text x="256" y="192" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">CHANGE RECORD</text>
|
||||||
<text x="520" y="130" font-family="system-ui" font-size="11" fill="#64748b">Versions stored</text>
|
<line x1="256" y1="210" x2="536" y2="210" stroke="#E2E8F0" />
|
||||||
<rect x="650" y="80" width="118" height="64" rx="8" fill="#f8fafc" 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="666" y="104" font-family="system-ui" font-size="22" fill="#f59e0b" font-weight="700">3</text>
|
<text x="256" y="256" font-family="system-ui" font-size="11" fill="#475569">Version diff prepared for review</text>
|
||||||
<text x="666" y="130" font-family="system-ui" font-size="11" fill="#64748b">Drift alerts</text>
|
<rect x="454" y="222" width="78" height="22" rx="11" fill="#DCFCE7" />
|
||||||
<!-- Table header -->
|
<text x="469" y="237" font-family="system-ui" font-size="10" font-weight="700" fill="#15803D">Ready</text>
|
||||||
<rect x="212" y="160" width="556" height="36" rx="0" fill="#f8fafc"/>
|
<line x1="256" y1="274" x2="536" y2="274" stroke="#E2E8F0" />
|
||||||
<text x="228" y="183" font-family="system-ui" font-size="11" fill="#64748b" font-weight="600">POLICY NAME</text>
|
<text x="256" y="300" font-family="system-ui" font-size="13" font-weight="700" fill="#0F172A">Conditional Access MFA</text>
|
||||||
<text x="440" y="183" font-family="system-ui" font-size="11" fill="#64748b" font-weight="600">TYPE</text>
|
<text x="256" y="320" font-family="system-ui" font-size="11" fill="#475569">Assignment drift surfaced before rollout</text>
|
||||||
<text x="560" y="183" font-family="system-ui" font-size="11" fill="#64748b" font-weight="600">STATUS</text>
|
<rect x="438" y="286" width="94" height="22" rx="11" fill="#FEF3C7" />
|
||||||
<text x="670" y="183" font-family="system-ui" font-size="11" fill="#64748b" font-weight="600">LAST BACKUP</text>
|
<text x="452" y="301" font-family="system-ui" font-size="10" font-weight="700" fill="#B45309">Needs review</text>
|
||||||
<!-- Table rows -->
|
<line x1="256" y1="338" x2="536" y2="338" stroke="#E2E8F0" />
|
||||||
<line x1="212" y1="196" x2="768" y2="196" stroke="#e2e8f0"/>
|
<text x="256" y="364" font-family="system-ui" font-size="13" font-weight="700" fill="#0F172A">BitLocker policy</text>
|
||||||
<text x="228" y="220" font-family="system-ui" font-size="12" fill="#1e293b">Windows Compliance Baseline</text>
|
<text x="256" y="384" font-family="system-ui" font-size="11" fill="#475569">Restore candidate linked to prior snapshot</text>
|
||||||
<text x="440" y="220" font-family="system-ui" font-size="12" fill="#64748b">Compliance</text>
|
<rect x="420" y="350" width="112" height="22" rx="11" fill="#DBEAFE" />
|
||||||
<rect x="560" y="208" width="56" height="20" rx="10" fill="#dcfce7"/>
|
<text x="436" y="365" font-family="system-ui" font-size="10" font-weight="700" fill="#1D4ED8">Snapshot linked</text>
|
||||||
<text x="572" y="222" font-family="system-ui" font-size="10" fill="#16a34a" font-weight="500">Synced</text>
|
<rect x="576" y="164" width="176" height="112" rx="16" fill="#F8FAFC" stroke="#D7E2EE" />
|
||||||
<text x="670" y="220" font-family="system-ui" font-size="12" fill="#64748b">2 min ago</text>
|
<text x="596" y="192" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">RESTORE PREVIEW</text>
|
||||||
<line x1="212" y1="236" x2="768" y2="236" stroke="#f1f5f9"/>
|
<text x="596" y="220" font-family="system-ui" font-size="12" fill="#0F172A">Scope validated</text>
|
||||||
<text x="228" y="260" font-family="system-ui" font-size="12" fill="#1e293b">BitLocker Encryption Policy</text>
|
<text x="596" y="242" font-family="system-ui" font-size="12" fill="#0F172A">Assignments included</text>
|
||||||
<text x="440" y="260" font-family="system-ui" font-size="12" fill="#64748b">Config</text>
|
<text x="596" y="264" font-family="system-ui" font-size="12" fill="#0F172A">Confirmation required</text>
|
||||||
<rect x="560" y="248" width="56" height="20" rx="10" fill="#dcfce7"/>
|
<circle cx="580" cy="217" r="4" fill="#16A34A" />
|
||||||
<text x="572" y="262" font-family="system-ui" font-size="10" fill="#16a34a" font-weight="500">Synced</text>
|
<circle cx="580" cy="239" r="4" fill="#16A34A" />
|
||||||
<text x="670" y="260" font-family="system-ui" font-size="12" fill="#64748b">15 min ago</text>
|
<circle cx="580" cy="261" r="4" fill="#F59E0B" />
|
||||||
<line x1="212" y1="276" x2="768" y2="276" stroke="#f1f5f9"/>
|
<rect x="576" y="292" width="176" height="108" rx="16" fill="#F8FAFC" stroke="#D7E2EE" />
|
||||||
<text x="228" y="300" font-family="system-ui" font-size="12" fill="#1e293b">Conditional Access – MFA</text>
|
<text x="596" y="320" font-family="system-ui" font-size="11" font-weight="700" fill="#64748B">REVIEW QUEUE</text>
|
||||||
<text x="440" y="300" font-family="system-ui" font-size="12" fill="#64748b">Access</text>
|
<text x="596" y="346" font-family="system-ui" font-size="12" fill="#0F172A">Conditional Access drift</text>
|
||||||
<rect x="560" y="288" width="48" height="20" rx="10" fill="#fef3c7"/>
|
<text x="596" y="368" font-family="system-ui" font-size="12" fill="#0F172A">Restore plan awaiting approval</text>
|
||||||
<text x="569" y="302" font-family="system-ui" font-size="10" fill="#d97706" font-weight="500">Drift</text>
|
<text x="596" y="390" font-family="system-ui" font-size="12" fill="#0F172A">Evidence attached to change record</text>
|
||||||
<text x="670" y="300" font-family="system-ui" font-size="12" fill="#64748b">1 hr ago</text>
|
<rect x="236" y="420" width="516" height="28" rx="14" fill="#EEF2FF" />
|
||||||
<line x1="212" y1="316" x2="768" y2="316" stroke="#f1f5f9"/>
|
<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>
|
||||||
<text x="228" y="340" font-family="system-ui" font-size="12" fill="#1e293b">Autopilot Deployment Profile</text>
|
|
||||||
<text x="440" y="340" font-family="system-ui" font-size="12" fill="#64748b">Enrollment</text>
|
|
||||||
<rect x="560" y="328" width="56" height="20" rx="10" fill="#dcfce7"/>
|
|
||||||
<text x="572" y="342" font-family="system-ui" font-size="10" fill="#16a34a" font-weight="500">Synced</text>
|
|
||||||
<text x="670" y="340" font-family="system-ui" font-size="12" fill="#64748b">30 min ago</text>
|
|
||||||
<line x1="212" y1="356" x2="768" y2="356" stroke="#f1f5f9"/>
|
|
||||||
<text x="228" y="380" font-family="system-ui" font-size="12" fill="#1e293b">App Protection – iOS Managed</text>
|
|
||||||
<text x="440" y="380" font-family="system-ui" font-size="12" fill="#64748b">Protection</text>
|
|
||||||
<rect x="560" y="368" width="56" height="20" rx="10" fill="#dcfce7"/>
|
|
||||||
<text x="572" y="382" font-family="system-ui" font-size="10" fill="#16a34a" font-weight="500">Synced</text>
|
|
||||||
<text x="670" y="380" font-family="system-ui" font-size="12" fill="#64748b">45 min ago</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.1 KiB |
@ -11,9 +11,11 @@ interface Props {
|
|||||||
|
|
||||||
const { content } = Astro.props;
|
const { content } = Astro.props;
|
||||||
const variant = content.tone === 'accent' ? 'accent' : content.tone === 'subtle' ? 'subtle' : 'default';
|
const variant = content.tone === 'accent' ? 'accent' : content.tone === 'subtle' ? 'subtle' : 'default';
|
||||||
|
const barTone = content.tone === 'accent' ? undefined : content.tone === 'subtle' ? 'trust' : undefined;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Card variant={variant}>
|
<Card variant={variant} hoverable>
|
||||||
|
<div class="callout-bar" data-bar-tone={barTone}>
|
||||||
{content.eyebrow && <Eyebrow>{content.eyebrow}</Eyebrow>}
|
{content.eyebrow && <Eyebrow>{content.eyebrow}</Eyebrow>}
|
||||||
<Headline as="h3" size="card" class="mt-4">
|
<Headline as="h3" size="card" class="mt-4">
|
||||||
{content.title}
|
{content.title}
|
||||||
@ -21,4 +23,5 @@ const variant = content.tone === 'accent' ? 'accent' : content.tone === 'subtle'
|
|||||||
<Lead class="mt-3" size="body">
|
<Lead class="mt-3" size="body">
|
||||||
{content.description}
|
{content.description}
|
||||||
</Lead>
|
</Lead>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
import Card from '@/components/primitives/Card.astro';
|
import Card from '@/components/primitives/Card.astro';
|
||||||
import Eyebrow from '@/components/content/Eyebrow.astro';
|
import Eyebrow from '@/components/content/Eyebrow.astro';
|
||||||
import Headline from '@/components/content/Headline.astro';
|
import Headline from '@/components/content/Headline.astro';
|
||||||
@ -10,18 +11,49 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { item } = Astro.props;
|
const { item } = Astro.props;
|
||||||
|
|
||||||
|
const lucideMap: Record<string, string> = {
|
||||||
|
'shield': 'lucide:shield',
|
||||||
|
'database': 'lucide:database',
|
||||||
|
'refresh': 'lucide:refresh-cw',
|
||||||
|
'eye': 'lucide:eye',
|
||||||
|
'file-check': 'lucide:file-check',
|
||||||
|
'layers': 'lucide:layers',
|
||||||
|
'search': 'lucide:search',
|
||||||
|
'lock': 'lucide:lock',
|
||||||
|
'zap': 'lucide:zap',
|
||||||
|
'clipboard': 'lucide:clipboard-list',
|
||||||
|
'git-branch': 'lucide:git-branch',
|
||||||
|
'bar-chart': 'lucide:bar-chart-3',
|
||||||
|
'activity': 'lucide:activity',
|
||||||
|
'settings': 'lucide:settings',
|
||||||
|
'globe': 'lucide:globe',
|
||||||
|
'users': 'lucide:users',
|
||||||
|
'check-circle': 'lucide:check-circle',
|
||||||
|
'archive': 'lucide:archive',
|
||||||
|
'trending-up': 'lucide:trending-up',
|
||||||
|
'cpu': 'lucide:cpu',
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconName = item.icon ? lucideMap[item.icon] : undefined;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Card class="h-full">
|
<Card class="h-full" hoverable>
|
||||||
|
<div class="space-y-3">
|
||||||
|
{iconName && (
|
||||||
|
<div class="feature-icon">
|
||||||
|
<Icon name={iconName} size={20} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{item.eyebrow && <Eyebrow>{item.eyebrow}</Eyebrow>}
|
{item.eyebrow && <Eyebrow>{item.eyebrow}</Eyebrow>}
|
||||||
<Headline as="h3" size="card" class="mt-4">
|
<Headline as="h3" size="card">
|
||||||
{item.title}
|
{item.title}
|
||||||
</Headline>
|
</Headline>
|
||||||
<Lead class="mt-3" size="body">
|
<Lead size="body">
|
||||||
{item.description}
|
{item.description}
|
||||||
</Lead>
|
</Lead>
|
||||||
{(item.meta || item.href) && (
|
{(item.meta || item.href) && (
|
||||||
<div class="mt-5 flex flex-wrap items-center gap-3 text-sm">
|
<div class="mt-2 flex flex-wrap items-center gap-3 text-sm">
|
||||||
{item.meta && <span class="text-[var(--color-brand)]">{item.meta}</span>}
|
{item.meta && <span class="text-[var(--color-brand)]">{item.meta}</span>}
|
||||||
{item.href && (
|
{item.href && (
|
||||||
<a class="text-link font-semibold" href={item.href}>
|
<a class="text-link font-semibold" href={item.href}>
|
||||||
@ -30,4 +62,5 @@ const { item } = Astro.props;
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -9,14 +9,14 @@ const { as = 'h2', class: className = '', size = 'section' } = Astro.props;
|
|||||||
const Tag = as;
|
const Tag = as;
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
display:
|
display:
|
||||||
'font-[var(--font-display)] text-[length:var(--type-display-size)] leading-[var(--line-display)] tracking-[var(--tracking-display)]',
|
'font-[var(--font-display)] font-bold text-[length:var(--type-display-size)] leading-[var(--line-display)] tracking-[var(--tracking-display)]',
|
||||||
page: 'font-[var(--font-display)] text-[length:var(--type-page-size)] leading-[var(--line-heading)] tracking-[var(--tracking-tight)]',
|
page: 'font-[var(--font-display)] font-semibold text-[length:var(--type-page-size)] leading-[var(--line-heading)] tracking-[var(--tracking-tight)]',
|
||||||
section:
|
section:
|
||||||
'font-[var(--font-display)] text-[length:var(--type-section-size)] leading-[var(--line-heading)] tracking-[var(--tracking-tight)]',
|
'font-[var(--font-display)] font-semibold text-[length:var(--type-section-size)] leading-[var(--line-heading)] tracking-[var(--tracking-tight)]',
|
||||||
card: 'font-semibold text-[length:var(--type-card-size)] leading-[1.12] tracking-[var(--tracking-tight)]',
|
card: 'font-medium text-[length:var(--type-card-size)] leading-[1.18] tracking-[var(--tracking-tight)]',
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<Tag class:list={['m-0 text-[var(--color-ink-900)]', sizeClasses[size], className]}>
|
<Tag class:list={['m-0 text-[var(--color-ink-900)] [&>.accent]:text-[var(--color-primary)]', sizeClasses[size], className]}>
|
||||||
<slot />
|
<slot />
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|||||||
467
apps/website/src/components/content/HeroDashboard.astro
Normal file
467
apps/website/src/components/content/HeroDashboard.astro
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
<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>
|
||||||
@ -11,7 +11,8 @@ interface Props {
|
|||||||
const { item } = Astro.props;
|
const { item } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Card class="h-full">
|
<Card class="h-full" hoverable>
|
||||||
|
<div class="callout-bar" data-bar-tone="trust">
|
||||||
<Headline as="h3" size="card">
|
<Headline as="h3" size="card">
|
||||||
{item.title}
|
{item.title}
|
||||||
</Headline>
|
</Headline>
|
||||||
@ -19,4 +20,5 @@ const { item } = Astro.props;
|
|||||||
{item.description}
|
{item.description}
|
||||||
</Lead>
|
</Lead>
|
||||||
{item.note && <Lead class="mt-4 text-[var(--color-brand)]" size="small">{item.note}</Lead>}
|
{item.note && <Lead class="mt-4 text-[var(--color-brand)]" size="small">{item.note}</Lead>}
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -24,19 +24,19 @@ const {
|
|||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
const baseClass =
|
const baseClass =
|
||||||
'inline-flex items-center justify-center rounded-[var(--radius-pill)] border font-semibold tracking-[var(--tracking-tight)] transition duration-150';
|
'inline-flex items-center justify-center rounded-[var(--radius-pill)] border font-semibold tracking-[var(--tracking-tight)] transition-all duration-200 cursor-pointer';
|
||||||
|
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
sm: 'min-h-10 px-4 text-sm',
|
sm: 'min-h-10 px-5 text-sm',
|
||||||
md: 'min-h-11 px-5 text-sm sm:text-[0.95rem]',
|
md: 'min-h-12 px-6 text-[0.95rem]',
|
||||||
lg: 'min-h-12 px-6 text-[0.97rem]',
|
lg: 'min-h-14 px-8 text-base',
|
||||||
};
|
};
|
||||||
|
|
||||||
const variantClasses = {
|
const variantClasses = {
|
||||||
primary:
|
primary:
|
||||||
'border-transparent bg-[var(--color-primary)] text-[var(--color-primary-foreground)] shadow-[var(--shadow-inline)] hover:bg-[var(--color-brand-700)]',
|
'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]',
|
||||||
secondary:
|
secondary:
|
||||||
'border-[color:var(--color-border)] bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)] hover:border-[var(--color-border-strong)] hover:bg-white',
|
'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',
|
ghost: 'border-transparent bg-transparent text-[var(--color-ink-800)] hover:bg-white/70',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,12 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
as?: keyof HTMLElementTagNameMap;
|
as?: keyof HTMLElementTagNameMap;
|
||||||
class?: string;
|
class?: string;
|
||||||
|
hoverable?: boolean;
|
||||||
variant?: 'accent' | 'default' | 'subtle';
|
variant?: 'accent' | 'default' | 'subtle';
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { as = 'article', class: className = '', variant = 'default', ...rest } = Astro.props;
|
const { as = 'article', class: className = '', hoverable = false, variant = 'default', ...rest } = Astro.props;
|
||||||
|
|
||||||
const variantClasses = {
|
const variantClasses = {
|
||||||
default: 'surface-card',
|
default: 'surface-card',
|
||||||
@ -18,7 +19,7 @@ const Tag = as;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Tag
|
<Tag
|
||||||
class:list={['rounded-[1.65rem] p-6 sm:p-7', variantClasses[variant], className]}
|
class:list={['rounded-[1.65rem] p-6 sm:p-7', variantClasses[variant], hoverable && 'card-hoverable', className]}
|
||||||
data-surface={variant}
|
data-surface={variant}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ interface Props {
|
|||||||
density?: 'base' | 'compact' | 'spacious';
|
density?: 'base' | 'compact' | 'spacious';
|
||||||
id?: string;
|
id?: string;
|
||||||
layer?: '1' | '2' | '3';
|
layer?: '1' | '2' | '3';
|
||||||
tone?: 'default' | 'emphasis' | 'muted';
|
tone?: 'default' | 'emphasis' | 'muted' | 'tinted' | 'warm';
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +28,8 @@ const toneClasses = {
|
|||||||
default: '',
|
default: '',
|
||||||
muted: 'section-shell-muted px-3 sm:px-4',
|
muted: 'section-shell-muted px-3 sm:px-4',
|
||||||
emphasis: 'section-shell-emphasis px-3 sm:px-4',
|
emphasis: 'section-shell-emphasis px-3 sm:px-4',
|
||||||
|
tinted: 'section-tinted px-3 sm:px-4',
|
||||||
|
warm: 'section-warm px-3 sm:px-4',
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ interface Props {
|
|||||||
description?: string;
|
description?: string;
|
||||||
eyebrow?: string;
|
eyebrow?: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
titleHtml?: string;
|
||||||
width?: 'default' | 'measure' | 'wide';
|
width?: 'default' | 'measure' | 'wide';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ const {
|
|||||||
description,
|
description,
|
||||||
eyebrow,
|
eyebrow,
|
||||||
title,
|
title,
|
||||||
|
titleHtml,
|
||||||
width = 'default',
|
width = 'default',
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
const widthClasses = {
|
const widthClasses = {
|
||||||
@ -29,6 +31,6 @@ const widthClasses = {
|
|||||||
|
|
||||||
<div class:list={[widthClasses[width], align === 'center' ? 'mx-auto text-center' : '', className]}>
|
<div class:list={[widthClasses[width], align === 'center' ? 'mx-auto text-center' : '', className]}>
|
||||||
{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
|
{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
|
||||||
<Headline>{title}</Headline>
|
{titleHtml ? <Headline><Fragment set:html={titleHtml} /></Headline> : <Headline>{title}</Headline>}
|
||||||
{description && <Lead class="mt-4">{description}</Lead>}
|
{description && <Lead class="mt-4">{description}</Lead>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,18 +14,19 @@ interface Props {
|
|||||||
eyebrow?: string;
|
eyebrow?: string;
|
||||||
items: CapabilityClusterContent[];
|
items: CapabilityClusterContent[];
|
||||||
title: string;
|
title: string;
|
||||||
|
titleHtml?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { description, eyebrow, items, title } = Astro.props;
|
const { description, eyebrow, items, title, titleHtml } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Section layer="2" data-section="capability">
|
<Section layer="2" tone="tinted" data-section="capability">
|
||||||
<Container width="wide">
|
<Container width="wide">
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
<SectionHeader eyebrow={eyebrow} title={title} titleHtml={titleHtml} description={description} />
|
||||||
<Grid cols="2" gap="lg">
|
<Grid cols="2" gap="lg">
|
||||||
{items.map((cluster) => (
|
{items.map((cluster) => (
|
||||||
<Card class="h-full">
|
<Card class="h-full" hoverable>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between gap-3">
|
||||||
<Headline as="h3" size="card">
|
<Headline as="h3" size="card">
|
||||||
|
|||||||
@ -11,15 +11,17 @@ interface Props {
|
|||||||
eyebrow?: string;
|
eyebrow?: string;
|
||||||
items: FeatureItemContent[];
|
items: FeatureItemContent[];
|
||||||
title: string;
|
title: string;
|
||||||
|
titleHtml?: string;
|
||||||
|
tone?: 'default' | 'tinted';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { description, eyebrow, items, title } = Astro.props;
|
const { description, eyebrow, items, title, titleHtml, tone = 'default' } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Section layer="2">
|
<Section layer="2" tone={tone === 'tinted' ? 'tinted' : 'default'}>
|
||||||
<Container width="wide">
|
<Container width="wide">
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
<SectionHeader eyebrow={eyebrow} title={title} titleHtml={titleHtml} description={description} />
|
||||||
<Grid cols="3">
|
<Grid cols="3">
|
||||||
{items.map((item) => <FeatureItem item={item} />)}
|
{items.map((item) => <FeatureItem item={item} />)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -10,9 +10,10 @@ import type { OutcomeSectionContent } from '@/types/site';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
content: OutcomeSectionContent;
|
content: OutcomeSectionContent;
|
||||||
|
titleHtml?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { content } = Astro.props;
|
const { content, titleHtml } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Section layer="2" data-section="outcome">
|
<Section layer="2" data-section="outcome">
|
||||||
@ -21,11 +22,12 @@ const { content } = Astro.props;
|
|||||||
<SectionHeader
|
<SectionHeader
|
||||||
eyebrow="Why it matters"
|
eyebrow="Why it matters"
|
||||||
title={content.title}
|
title={content.title}
|
||||||
|
titleHtml={titleHtml}
|
||||||
description={content.description}
|
description={content.description}
|
||||||
/>
|
/>
|
||||||
<Grid cols="3">
|
<Grid cols="3">
|
||||||
{content.outcomes.map((outcome) => (
|
{content.outcomes.map((outcome) => (
|
||||||
<Card class="h-full">
|
<Card class="h-full" hoverable>
|
||||||
<Headline as="h3" size="card">
|
<Headline as="h3" size="card">
|
||||||
{outcome.title}
|
{outcome.title}
|
||||||
</Headline>
|
</Headline>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Card from '@/components/primitives/Card.astro';
|
|||||||
import Cluster from '@/components/primitives/Cluster.astro';
|
import Cluster from '@/components/primitives/Cluster.astro';
|
||||||
import Container from '@/components/primitives/Container.astro';
|
import Container from '@/components/primitives/Container.astro';
|
||||||
import Headline from '@/components/content/Headline.astro';
|
import Headline from '@/components/content/Headline.astro';
|
||||||
|
import HeroDashboard from '@/components/content/HeroDashboard.astro';
|
||||||
import Lead from '@/components/content/Lead.astro';
|
import Lead from '@/components/content/Lead.astro';
|
||||||
import Metric from '@/components/content/Metric.astro';
|
import Metric from '@/components/content/Metric.astro';
|
||||||
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
||||||
@ -18,74 +19,205 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { calloutDescription, calloutTitle, hero, metrics = [] } = Astro.props;
|
const { calloutDescription, calloutTitle, hero, metrics = [] } = Astro.props;
|
||||||
|
const isHomepageHero = Astro.url.pathname === '/';
|
||||||
|
const heroHeadlineSize = isHomepageHero ? 'page' : 'display';
|
||||||
|
const heroLeadSize = isHomepageHero ? 'body' : 'lead';
|
||||||
|
const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="pt-8 sm:pt-10 lg:pt-14">
|
<section
|
||||||
|
class:list={[
|
||||||
|
isHomepageHero ? 'hero-gradient pt-2 sm:pt-8 lg:pt-14' : 'pt-8 sm:pt-10 lg:pt-14',
|
||||||
|
]}
|
||||||
|
data-hero-root
|
||||||
|
data-hero-surface={isHomepageHero ? 'homepage' : 'page'}
|
||||||
|
data-homepage-hero={isHomepageHero ? 'true' : undefined}
|
||||||
|
data-hero-primary-anchor={isHomepageHero && heroPrimaryAnchor === 'composition' ? 'composition' : undefined}
|
||||||
|
data-section={isHomepageHero ? 'hero' : undefined}
|
||||||
|
>
|
||||||
<Container width="wide">
|
<Container width="wide">
|
||||||
<div class="grid gap-6 lg:grid-cols-[1.35fr,0.85fr]" data-disclosure-layer="1">
|
{isHomepageHero ? (
|
||||||
<Card class="motion-rise overflow-hidden">
|
<div class="space-y-6 sm:space-y-8 lg:space-y-10" data-disclosure-layer="1" data-hero-layout>
|
||||||
<div class="space-y-6">
|
<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 data-hero-text-core>
|
||||||
|
<div data-hero-eyebrow data-hero-segment="eyebrow">
|
||||||
<Badge>{hero.eyebrow}</Badge>
|
<Badge>{hero.eyebrow}</Badge>
|
||||||
<div class="space-y-4">
|
</div>
|
||||||
<Headline as="h1" size="display" class="max-w-4xl">
|
<div class="mt-3 space-y-4 sm:mt-5 sm:space-y-5">
|
||||||
{hero.title}
|
<div
|
||||||
|
data-hero-heading
|
||||||
|
data-hero-primary-anchor={heroPrimaryAnchor === 'headline' ? 'headline' : undefined}
|
||||||
|
data-hero-segment="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]"
|
||||||
|
>
|
||||||
|
{hero.titleHtml ? <Fragment set:html={hero.titleHtml} /> : hero.title}
|
||||||
</Headline>
|
</Headline>
|
||||||
<Lead class="max-w-3xl" size="lead">
|
</div>
|
||||||
|
<div
|
||||||
|
data-hero-copy-role="supporting"
|
||||||
|
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]">
|
||||||
{hero.description}
|
{hero.description}
|
||||||
</Lead>
|
</Lead>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{(hero.primaryCta || hero.secondaryCta) && (
|
{(hero.primaryCta || hero.secondaryCta) && (
|
||||||
<Cluster data-cta-cluster gap="md">
|
<div data-hero-cta-pair data-hero-segment="cta-pair">
|
||||||
|
<Cluster data-cta-cluster gap="sm" class="items-center sm:gap-[var(--space-cluster)]">
|
||||||
|
<PrimaryCTA cta={hero.primaryCta} size="lg" />
|
||||||
|
{hero.secondaryCta && (
|
||||||
|
<SecondaryCTA cta={hero.secondaryCta} />
|
||||||
|
)}
|
||||||
|
</Cluster>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="motion-rise lg:pt-1"
|
||||||
|
style="animation-delay: 120ms;"
|
||||||
|
data-hero-panel="dashboard"
|
||||||
|
data-hero-primary-anchor={heroPrimaryAnchor === 'product-visual' ? 'product-visual' : undefined}
|
||||||
|
data-hero-visual
|
||||||
|
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>
|
||||||
|
</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 -->
|
||||||
|
<div
|
||||||
|
class="grid gap-5 sm:gap-6 lg:grid-cols-[minmax(0,1.08fr)_minmax(20rem,0.92fr)] lg:items-start"
|
||||||
|
data-disclosure-layer="1"
|
||||||
|
data-hero-layout
|
||||||
|
>
|
||||||
|
<Card class="motion-rise overflow-hidden" data-hero-panel="text">
|
||||||
|
<div class="space-y-4 sm:space-y-6">
|
||||||
|
<div data-hero-text-core>
|
||||||
|
<div data-hero-eyebrow data-hero-segment="eyebrow">
|
||||||
|
<Badge>{hero.eyebrow}</Badge>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 space-y-4 sm:mt-4 sm:space-y-5">
|
||||||
|
<div data-hero-heading data-hero-segment="headline">
|
||||||
|
<Headline
|
||||||
|
as="h1"
|
||||||
|
size={heroHeadlineSize}
|
||||||
|
class="max-w-3xl text-balance"
|
||||||
|
>
|
||||||
|
{hero.titleHtml ? <Fragment set:html={hero.titleHtml} /> : hero.title}
|
||||||
|
</Headline>
|
||||||
|
</div>
|
||||||
|
<div data-hero-copy-role="supporting" data-hero-supporting-copy data-hero-segment="supporting-copy">
|
||||||
|
<Lead class="max-w-2xl" size={heroLeadSize}>
|
||||||
|
{hero.description}
|
||||||
|
</Lead>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{(hero.primaryCta || hero.secondaryCta) && (
|
||||||
|
<div data-hero-cta-pair data-hero-segment="cta-pair">
|
||||||
|
<Cluster data-cta-cluster gap="sm" class="sm:gap-[var(--space-cluster)]">
|
||||||
<PrimaryCTA cta={hero.primaryCta} />
|
<PrimaryCTA cta={hero.primaryCta} />
|
||||||
{hero.secondaryCta && (
|
{hero.secondaryCta && (
|
||||||
<SecondaryCTA cta={hero.secondaryCta} />
|
<SecondaryCTA cta={hero.secondaryCta} />
|
||||||
)}
|
)}
|
||||||
</Cluster>
|
</Cluster>
|
||||||
)}
|
</div>
|
||||||
{hero.trustSubclaims && hero.trustSubclaims.length > 0 && (
|
|
||||||
<ul class="grid gap-3 p-0 sm:grid-cols-3">
|
|
||||||
{
|
|
||||||
hero.trustSubclaims.map((claim) => (
|
|
||||||
<li class="list-none rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/70 px-4 py-3 text-sm font-medium text-[var(--color-ink-800)]">
|
|
||||||
{claim}
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
)}
|
)}
|
||||||
{hero.highlights && hero.highlights.length > 0 && !hero.trustSubclaims?.length && (
|
{hero.highlights && hero.highlights.length > 0 && !hero.trustSubclaims?.length && (
|
||||||
<ul class="grid gap-3 p-0 sm:grid-cols-3">
|
<ul class="grid gap-3 p-0 sm:grid-cols-3">
|
||||||
{
|
{hero.highlights.map((highlight) => (
|
||||||
hero.highlights.map((highlight) => (
|
|
||||||
<li class="list-none rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/70 px-4 py-3 text-sm font-medium text-[var(--color-ink-800)]">
|
<li class="list-none rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/70 px-4 py-3 text-sm font-medium text-[var(--color-ink-800)]">
|
||||||
{highlight}
|
{highlight}
|
||||||
</li>
|
</li>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div class="grid gap-5">
|
<div class="grid gap-4 sm:gap-5" data-hero-panel="supporting">
|
||||||
{hero.productVisual && (
|
{hero.productVisual && (
|
||||||
<Card variant="accent" class="motion-rise overflow-hidden" data-hero-visual>
|
<Card
|
||||||
|
variant="accent"
|
||||||
|
class="motion-rise overflow-hidden"
|
||||||
|
data-hero-segment="product-near-visual"
|
||||||
|
data-hero-visual
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={hero.productVisual.src}
|
src={hero.productVisual.src}
|
||||||
alt={hero.productVisual.alt}
|
alt={hero.productVisual.alt}
|
||||||
class="w-full rounded-[var(--radius-lg)] object-cover"
|
class="max-h-[22rem] w-full rounded-[var(--radius-lg)] object-cover object-top"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{hero.trustSubclaims && hero.trustSubclaims.length > 0 && (
|
||||||
|
<Card variant="subtle" class="motion-rise" data-hero-segment="trust-subclaims">
|
||||||
|
<div class="space-y-3" data-hero-trust-signals>
|
||||||
|
<p class="m-0 text-[0.72rem] font-semibold uppercase tracking-[var(--tracking-eyebrow)] text-[var(--color-copy)]">
|
||||||
|
Early trust
|
||||||
|
</p>
|
||||||
|
<ul class="grid gap-2.5 p-0">
|
||||||
|
{hero.trustSubclaims.map((claim) => (
|
||||||
|
<li class="list-none rounded-[1rem] border border-[color:var(--color-line)] bg-white/82 px-4 py-3 text-sm font-medium text-[var(--color-ink-800)]">
|
||||||
|
{claim}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{!hero.productVisual && (calloutTitle || calloutDescription) && (
|
{!hero.productVisual && (calloutTitle || calloutDescription) && (
|
||||||
<Card variant="accent" class="motion-rise">
|
<Card variant="accent" class="motion-rise">
|
||||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.15em] text-[var(--color-brand)]">
|
<p class="m-0 text-sm font-semibold uppercase tracking-[0.15em] text-[var(--color-brand)]">
|
||||||
Trust-first launch surface
|
Trust-first launch surface
|
||||||
</p>
|
</p>
|
||||||
{calloutTitle && (
|
{calloutTitle && (
|
||||||
<h2 class="mt-4 font-[var(--font-display)] text-3xl leading-tight text-[var(--color-ink-900)]">
|
<h2 class="mt-4 font-[var(--font-display)] text-3xl font-bold leading-tight text-[var(--color-ink-900)]">
|
||||||
{calloutTitle}
|
{calloutTitle}
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
@ -102,5 +234,6 @@ const { calloutDescription, calloutTitle, hero, metrics = [] } = Astro.props;
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -11,15 +11,16 @@ interface Props {
|
|||||||
eyebrow?: string;
|
eyebrow?: string;
|
||||||
items: TrustPrincipleContent[];
|
items: TrustPrincipleContent[];
|
||||||
title: string;
|
title: string;
|
||||||
|
titleHtml?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { description, eyebrow, items, title } = Astro.props;
|
const { description, eyebrow, items, title, titleHtml } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Section layer="2">
|
<Section layer="2" tone="warm">
|
||||||
<Container width="wide">
|
<Container width="wide">
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
<SectionHeader eyebrow={eyebrow} title={title} titleHtml={titleHtml} description={description} />
|
||||||
<Grid cols="3">
|
<Grid cols="3">
|
||||||
{items.map((item) => <TrustPrincipleCard item={item} />)}
|
{items.map((item) => <TrustPrincipleCard item={item} />)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -17,10 +17,10 @@ export const homeSeo: PageSeo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const homeHero: HeroContent = {
|
export const homeHero: HeroContent = {
|
||||||
eyebrow: 'Governance of record',
|
eyebrow: 'Microsoft tenant governance',
|
||||||
title: 'TenantAtlas keeps Microsoft tenant change history, restore posture, and review context inside one operating record.',
|
title: 'TenantAtlas gives Microsoft tenant teams one operating record for change history, drift review, and restore planning.',
|
||||||
description:
|
description:
|
||||||
'MSP and enterprise teams use TenantAtlas to understand what changed, what drifted, what can be restored, and what needs review — without turning governance into disconnected screens.',
|
'Security, endpoint, and platform teams use TenantAtlas to see what changed, preview restores, and move reviews forward without stitching governance across exports and memory.',
|
||||||
primaryCta: {
|
primaryCta: {
|
||||||
href: '/contact',
|
href: '/contact',
|
||||||
label: 'Request a working session',
|
label: 'Request a working session',
|
||||||
@ -32,12 +32,12 @@ export const homeHero: HeroContent = {
|
|||||||
},
|
},
|
||||||
productVisual: {
|
productVisual: {
|
||||||
src: '/images/hero-product-visual.svg',
|
src: '/images/hero-product-visual.svg',
|
||||||
alt: 'TenantAtlas governance dashboard showing tenant change history and restore posture',
|
alt: 'TenantAtlas screen showing change history, restore preview, and a review queue for Microsoft tenant policies',
|
||||||
},
|
},
|
||||||
trustSubclaims: [
|
trustSubclaims: [
|
||||||
'Tenant-isolated by design',
|
'Tenant-scoped boundaries',
|
||||||
'Immutable change history',
|
'Reviewable change history',
|
||||||
'Bounded public claims',
|
'Preview before restore',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -50,36 +50,42 @@ export const productMetrics: MetricItem[] = [
|
|||||||
export const productModelBlocks: FeatureItemContent[] = [
|
export const productModelBlocks: FeatureItemContent[] = [
|
||||||
{
|
{
|
||||||
eyebrow: 'Inventory and drift',
|
eyebrow: 'Inventory and drift',
|
||||||
|
icon: 'search',
|
||||||
title: 'Current-state inventory and drift visibility establish what the tenant actually looks like now.',
|
title: 'Current-state inventory and drift visibility establish what the tenant actually looks like now.',
|
||||||
description:
|
description:
|
||||||
'The product starts with last-observed tenant state so teams can compare real configuration truth instead of relying on partial memory or exported spreadsheets.',
|
'The product starts with last-observed tenant state so teams can compare real configuration truth instead of relying on partial memory or exported spreadsheets.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
eyebrow: 'Backup and versioning',
|
eyebrow: 'Backup and versioning',
|
||||||
|
icon: 'database',
|
||||||
title: 'Snapshots and versions preserve immutable history without replacing present-tense truth.',
|
title: 'Snapshots and versions preserve immutable history without replacing present-tense truth.',
|
||||||
description:
|
description:
|
||||||
'Backups and versions are explicit artifacts tied to tenant context, operators, and timing so the history remains reproducible and queryable.',
|
'Backups and versions are explicit artifacts tied to tenant context, operators, and timing so the history remains reproducible and queryable.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
eyebrow: 'Restore safety',
|
eyebrow: 'Restore safety',
|
||||||
|
icon: 'refresh',
|
||||||
title: 'Restore is handled as a governed operation, not as a blind push.',
|
title: 'Restore is handled as a governed operation, not as a blind push.',
|
||||||
description:
|
description:
|
||||||
'Preview, validation, selective scope, and confirmation reduce the risk of turning a recovery step into a new incident.',
|
'Preview, validation, selective scope, and confirmation reduce the risk of turning a recovery step into a new incident.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
eyebrow: 'Audit and review',
|
eyebrow: 'Audit and review',
|
||||||
|
icon: 'eye',
|
||||||
title: 'Differences become reviewable signals instead of noisy raw deltas.',
|
title: 'Differences become reviewable signals instead of noisy raw deltas.',
|
||||||
description:
|
description:
|
||||||
'Human-readable summaries and structured differences help operators and reviewers decide what changed, who needs to know, and what deserves follow-up.',
|
'Human-readable summaries and structured differences help operators and reviewers decide what changed, who needs to know, and what deserves follow-up.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
eyebrow: 'Findings and evidence',
|
eyebrow: 'Findings and evidence',
|
||||||
|
icon: 'file-check',
|
||||||
title: 'Findings, exceptions, and evidence stay anchored to operational truth.',
|
title: 'Findings, exceptions, and evidence stay anchored to operational truth.',
|
||||||
description:
|
description:
|
||||||
'Governance discussions stay attached to the real object, version, and review context instead of drifting into separate manual trackers.',
|
'Governance discussions stay attached to the real object, version, and review context instead of drifting into separate manual trackers.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
eyebrow: 'Baselines and governance',
|
eyebrow: 'Baselines and governance',
|
||||||
|
icon: 'shield',
|
||||||
title: 'Baselines, reviews, and operator safety belong to the same workflow.',
|
title: 'Baselines, reviews, and operator safety belong to the same workflow.',
|
||||||
description:
|
description:
|
||||||
'The product is built so teams can explain actions afterward, not just execute them quickly in the moment.',
|
'The product is built so teams can explain actions afterward, not just execute them quickly in the moment.',
|
||||||
|
|||||||
@ -41,6 +41,12 @@ const openGraphDescription = Astro.props.openGraphDescription ?? description;
|
|||||||
<meta name="twitter:description" content={openGraphDescription} />
|
<meta name="twitter:description" content={openGraphDescription} />
|
||||||
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -38,11 +38,15 @@ const progressContent = {
|
|||||||
items={homeEcosystem}
|
items={homeEcosystem}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<OutcomeSection content={homeOutcome} />
|
<OutcomeSection
|
||||||
|
content={homeOutcome}
|
||||||
|
titleHtml='Teams that <span class="accent">understand their tenant</span> make better decisions.'
|
||||||
|
/>
|
||||||
|
|
||||||
<CapabilityGrid
|
<CapabilityGrid
|
||||||
eyebrow="What TenantAtlas covers"
|
eyebrow="What TenantAtlas covers"
|
||||||
title="A connected product model, not a feature wall."
|
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."
|
description="TenantAtlas groups backup, restore, inventory, drift, and governance into connected clusters instead of listing isolated features."
|
||||||
items={homeCapabilities}
|
items={homeCapabilities}
|
||||||
/>
|
/>
|
||||||
@ -51,6 +55,7 @@ const progressContent = {
|
|||||||
<TrustGrid
|
<TrustGrid
|
||||||
eyebrow="Trust posture"
|
eyebrow="Trust posture"
|
||||||
title={homeTrustSignals.title}
|
title={homeTrustSignals.title}
|
||||||
|
titleHtml='Trust is a <span class="accent">first-read concern</span>, not a footnote.'
|
||||||
description={homeTrustSignals.description}
|
description={homeTrustSignals.description}
|
||||||
items={homeTrustSignals.signals}
|
items={homeTrustSignals.signals}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -28,8 +28,10 @@ import {
|
|||||||
<FeatureGrid
|
<FeatureGrid
|
||||||
eyebrow="Connected governance model"
|
eyebrow="Connected governance model"
|
||||||
title="Explain what the product does before asking for buyer trust."
|
title="Explain what the product does before asking for buyer trust."
|
||||||
|
titleHtml='Explain what the product does before asking for <span class="accent">buyer trust</span>.'
|
||||||
description="This page should explain how the pieces fit together so visitors do not mistake the product for a loose collection of backup, reporting, and restore features."
|
description="This page should explain how the pieces fit together so visitors do not mistake the product for a loose collection of backup, reporting, and restore features."
|
||||||
items={productModelBlocks}
|
items={productModelBlocks}
|
||||||
|
tone="tinted"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Section tone="muted" density="base" layer="3">
|
<Section tone="muted" density="base" layer="3">
|
||||||
|
|||||||
@ -12,9 +12,11 @@ body {
|
|||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top left, rgba(255, 255, 255, 0.92), transparent 30%),
|
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, var(--color-background) 0%, var(--color-background-elevated) 48%, var(--color-muted) 100%);
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
@ -23,7 +25,7 @@ body {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a:not([data-button-variant]) {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,12 +39,12 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background: rgba(47, 111, 183, 0.18);
|
background: rgba(40, 60, 120, 0.12);
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
:where(a, button, input, textarea, summary):focus-visible {
|
:where(a, button, input, textarea, summary):focus-visible {
|
||||||
outline: 3px solid rgba(47, 111, 183, 0.32);
|
outline: 3px solid rgba(40, 60, 120, 0.22);
|
||||||
outline-offset: 4px;
|
outline-offset: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,16 +71,16 @@ .foundation-page::before {
|
|||||||
.foundation-page[data-shell-tone='brand']::before {
|
.foundation-page[data-shell-tone='brand']::before {
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 8% 0%, rgba(255, 255, 255, 0.82), transparent 30%),
|
radial-gradient(circle at 8% 0%, rgba(255, 255, 255, 0.82), transparent 30%),
|
||||||
radial-gradient(circle at 86% 0%, rgba(47, 111, 183, 0.18), transparent 28%),
|
radial-gradient(circle at 86% 0%, rgba(40, 60, 120, 0.04), transparent 28%),
|
||||||
radial-gradient(circle at 72% 22%, rgba(59, 139, 120, 0.12), transparent 26%),
|
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%);
|
linear-gradient(180deg, rgba(255, 255, 255, 0.62), transparent 18%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.foundation-page[data-shell-tone='trust']::before {
|
.foundation-page[data-shell-tone='trust']::before {
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 10% 0%, rgba(255, 255, 255, 0.8), transparent 28%),
|
radial-gradient(circle at 10% 0%, rgba(255, 255, 255, 0.8), transparent 28%),
|
||||||
radial-gradient(circle at 82% 8%, rgba(59, 139, 120, 0.16), transparent 30%),
|
radial-gradient(circle at 82% 8%, rgba(40, 60, 120, 0.04), transparent 30%),
|
||||||
radial-gradient(circle at 74% 30%, rgba(175, 109, 67, 0.08), transparent 26%),
|
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%);
|
linear-gradient(180deg, rgba(255, 255, 255, 0.62), transparent 18%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +172,7 @@ .section-shell-muted {
|
|||||||
|
|
||||||
.section-shell-emphasis {
|
.section-shell-emphasis {
|
||||||
border: 1px solid var(--color-border-strong);
|
border: 1px solid var(--color-border-strong);
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.56), rgba(47, 111, 183, 0.05));
|
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-radius: calc(var(--radius-lg) + 0.15rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +188,7 @@ .text-link {
|
|||||||
|
|
||||||
.text-link:hover {
|
.text-link:hover {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-decoration-color: rgba(47, 111, 183, 0.35);
|
text-decoration-color: rgba(40, 60, 120, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legal-prose p {
|
.legal-prose p {
|
||||||
@ -240,3 +242,84 @@ @keyframes rise-in {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Section tint bands ── */
|
||||||
|
.section-tinted {
|
||||||
|
background: var(--surface-section-tinted);
|
||||||
|
border-radius: calc(var(--radius-lg) + 0.15rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
transition:
|
||||||
|
box-shadow 220ms ease,
|
||||||
|
transform 220ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hoverable:hover {
|
||||||
|
box-shadow: var(--shadow-card-hover);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Callout accent bar ── */
|
||||||
|
.callout-bar {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-bar::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--color-primary);
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-bar[data-bar-tone="trust"]::before {
|
||||||
|
background: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.75rem;
|
||||||
|
height: 2.75rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, var(--surface-accent), var(--surface-accent-strong));
|
||||||
|
color: var(--color-primary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon svg {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,23 +1,28 @@
|
|||||||
@theme {
|
@theme {
|
||||||
--font-sans: "Avenir Next", "Segoe UI", sans-serif;
|
--font-sans: "Inter", "Avenir Next", "Segoe UI", sans-serif;
|
||||||
--font-display: "Iowan Old Style", "Palatino Linotype", serif;
|
--font-display: "Inter", "Avenir Next", "Segoe UI", sans-serif;
|
||||||
--font-mono: "IBM Plex Mono", "SFMono-Regular", monospace;
|
--font-mono: "IBM Plex Mono", "SFMono-Regular", monospace;
|
||||||
|
|
||||||
--color-stone-50: oklch(0.986 0.008 86);
|
/* Cool slate neutrals — shadcn / Apex direction */
|
||||||
--color-stone-100: oklch(0.974 0.012 84);
|
--color-stone-50: oklch(0.985 0.002 250);
|
||||||
--color-stone-150: oklch(0.958 0.016 82);
|
--color-stone-100: oklch(0.975 0.003 250);
|
||||||
--color-stone-200: oklch(0.936 0.018 79);
|
--color-stone-150: oklch(0.962 0.004 248);
|
||||||
--color-stone-300: oklch(0.896 0.025 76);
|
--color-stone-200: oklch(0.94 0.006 246);
|
||||||
--color-ink-700: oklch(0.4 0.04 244);
|
--color-stone-300: oklch(0.90 0.008 244);
|
||||||
--color-ink-800: oklch(0.31 0.04 248);
|
/* Ink: dark slate, very low chroma — near-black with cool undertone */
|
||||||
--color-ink-900: oklch(0.23 0.038 252);
|
--color-ink-700: oklch(0.44 0.01 250);
|
||||||
--color-brand-300: oklch(0.84 0.05 228);
|
--color-ink-800: oklch(0.30 0.008 250);
|
||||||
--color-brand-400: oklch(0.76 0.09 214);
|
--color-ink-900: oklch(0.14 0.005 250);
|
||||||
--color-brand-500: oklch(0.67 0.12 226);
|
/* Brand: dry slate-blue — enterprise, not flashy */
|
||||||
--color-brand-700: oklch(0.48 0.1 232);
|
--color-brand-300: oklch(0.78 0.06 245);
|
||||||
--color-mint-300: oklch(0.85 0.05 182);
|
--color-brand-400: oklch(0.64 0.09 245);
|
||||||
--color-mint-500: oklch(0.72 0.07 186);
|
--color-brand-500: oklch(0.50 0.10 245);
|
||||||
--color-mint-700: oklch(0.54 0.07 184);
|
--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-300: oklch(0.88 0.045 73);
|
||||||
--color-amber-500: oklch(0.75 0.085 62);
|
--color-amber-500: oklch(0.75 0.085 62);
|
||||||
--color-amber-700: oklch(0.56 0.09 54);
|
--color-amber-700: oklch(0.56 0.09 54);
|
||||||
@ -32,18 +37,18 @@ :root {
|
|||||||
--color-foreground: var(--color-ink-900);
|
--color-foreground: var(--color-ink-900);
|
||||||
--color-muted: var(--color-stone-150);
|
--color-muted: var(--color-stone-150);
|
||||||
--color-muted-foreground: var(--color-ink-700);
|
--color-muted-foreground: var(--color-ink-700);
|
||||||
--color-card: rgba(255, 255, 255, 0.9);
|
--color-card: rgba(255, 255, 255, 0.92);
|
||||||
--color-card-foreground: var(--color-ink-900);
|
--color-card-foreground: var(--color-ink-900);
|
||||||
--color-border: rgba(17, 36, 58, 0.12);
|
--color-border: rgba(20, 20, 20, 0.08);
|
||||||
--color-border-strong: rgba(47, 111, 183, 0.22);
|
--color-border-strong: rgba(20, 20, 20, 0.15);
|
||||||
--color-border-subtle: rgba(17, 36, 58, 0.07);
|
--color-border-subtle: rgba(20, 20, 20, 0.05);
|
||||||
--color-frame: rgba(17, 36, 58, 0.06);
|
--color-frame: rgba(20, 20, 20, 0.04);
|
||||||
--color-input: rgba(255, 255, 255, 0.94);
|
--color-input: rgba(255, 255, 255, 0.94);
|
||||||
--color-primary: var(--color-brand-500);
|
--color-primary: var(--color-brand-500);
|
||||||
--color-primary-foreground: #f9fbff;
|
--color-primary-foreground: #f8f9fb;
|
||||||
--color-secondary: rgba(255, 255, 255, 0.82);
|
--color-secondary: rgba(255, 255, 255, 0.82);
|
||||||
--color-secondary-foreground: var(--color-ink-900);
|
--color-secondary-foreground: var(--color-ink-900);
|
||||||
--color-accent: rgba(47, 111, 183, 0.1);
|
--color-accent: rgba(40, 60, 120, 0.06);
|
||||||
--color-accent-foreground: var(--color-brand-700);
|
--color-accent-foreground: var(--color-brand-700);
|
||||||
--color-success: var(--color-mint-700);
|
--color-success: var(--color-mint-700);
|
||||||
--color-warning: var(--color-amber-700);
|
--color-warning: var(--color-amber-700);
|
||||||
@ -53,12 +58,14 @@ :root {
|
|||||||
--surface-page: rgba(255, 255, 255, 0.34);
|
--surface-page: rgba(255, 255, 255, 0.34);
|
||||||
--surface-shell: rgba(255, 255, 255, 0.78);
|
--surface-shell: rgba(255, 255, 255, 0.78);
|
||||||
--surface-shell-strong: rgba(255, 255, 255, 0.94);
|
--surface-shell-strong: rgba(255, 255, 255, 0.94);
|
||||||
--surface-card-soft: rgba(246, 248, 251, 0.82);
|
--surface-card-soft: rgba(248, 249, 252, 0.82);
|
||||||
--surface-muted: rgba(243, 247, 251, 0.88);
|
--surface-muted: rgba(246, 248, 252, 0.88);
|
||||||
--surface-muted-strong: rgba(247, 249, 252, 0.94);
|
--surface-muted-strong: rgba(250, 251, 254, 0.94);
|
||||||
--surface-accent: rgba(47, 111, 183, 0.1);
|
--surface-accent: rgba(40, 60, 120, 0.04);
|
||||||
--surface-accent-strong: rgba(241, 246, 253, 0.98);
|
--surface-accent-strong: rgba(246, 248, 254, 0.98);
|
||||||
--surface-trust: rgba(59, 139, 120, 0.09);
|
--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);
|
||||||
|
|
||||||
--radius-sm: 1rem;
|
--radius-sm: 1rem;
|
||||||
--radius-md: 1.35rem;
|
--radius-md: 1.35rem;
|
||||||
@ -68,14 +75,15 @@ :root {
|
|||||||
|
|
||||||
--shadow-panel-strong: 0 28px 90px rgba(17, 36, 58, 0.14);
|
--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: 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-soft: 0 12px 36px rgba(17, 36, 58, 0.08);
|
||||||
--shadow-inline: 0 10px 22px rgba(17, 36, 58, 0.08);
|
--shadow-inline: 0 10px 22px rgba(17, 36, 58, 0.08);
|
||||||
|
|
||||||
--space-page-x: clamp(1.25rem, 2vw, 2.5rem);
|
--space-page-x: clamp(1.25rem, 2vw, 2.5rem);
|
||||||
--space-page-y: clamp(4rem, 6vw, 6rem);
|
--space-page-y: clamp(5rem, 8vw, 8rem);
|
||||||
--space-section-compact: clamp(3rem, 4vw, 4.25rem);
|
--space-section-compact: clamp(3.5rem, 5vw, 5rem);
|
||||||
--space-section: clamp(4rem, 6vw, 5.75rem);
|
--space-section: clamp(5rem, 8vw, 7rem);
|
||||||
--space-section-spacious: clamp(5rem, 7vw, 7rem);
|
--space-section-spacious: clamp(6rem, 9vw, 9rem);
|
||||||
--space-cluster-sm: 0.75rem;
|
--space-cluster-sm: 0.75rem;
|
||||||
--space-cluster: 1rem;
|
--space-cluster: 1rem;
|
||||||
--space-cluster-lg: 1.5rem;
|
--space-cluster-lg: 1.5rem;
|
||||||
@ -89,19 +97,19 @@ :root {
|
|||||||
--wide-max-width: 84rem;
|
--wide-max-width: 84rem;
|
||||||
--reading-max-width: 68rem;
|
--reading-max-width: 68rem;
|
||||||
|
|
||||||
--type-display-size: clamp(3.3rem, 6vw, 5rem);
|
--type-display-size: clamp(2.75rem, 5vw, 4.25rem);
|
||||||
--type-page-size: clamp(2.75rem, 4.2vw, 3.75rem);
|
--type-page-size: clamp(2.25rem, 3.8vw, 3.25rem);
|
||||||
--type-section-size: clamp(2rem, 3.5vw, 3rem);
|
--type-section-size: clamp(1.75rem, 2.8vw, 2.5rem);
|
||||||
--type-card-size: clamp(1.35rem, 2vw, 1.85rem);
|
--type-card-size: clamp(1.25rem, 1.8vw, 1.6rem);
|
||||||
--type-body-size: 1.02rem;
|
--type-body-size: 1.05rem;
|
||||||
--type-small-size: 0.94rem;
|
--type-small-size: 0.94rem;
|
||||||
--type-eyebrow-size: 0.74rem;
|
--type-eyebrow-size: 0.74rem;
|
||||||
--type-helper-size: 0.82rem;
|
--type-helper-size: 0.82rem;
|
||||||
--tracking-display: -0.055em;
|
--tracking-display: -0.04em;
|
||||||
--tracking-tight: -0.03em;
|
--tracking-tight: -0.025em;
|
||||||
--tracking-eyebrow: 0.18em;
|
--tracking-eyebrow: 0.18em;
|
||||||
--line-display: 0.95;
|
--line-display: 0.96;
|
||||||
--line-heading: 1.02;
|
--line-heading: 1.04;
|
||||||
--line-body: 1.75;
|
--line-body: 1.75;
|
||||||
--line-tight: 1.45;
|
--line-tight: 1.45;
|
||||||
|
|
||||||
|
|||||||
@ -98,11 +98,14 @@ export interface HeroContent {
|
|||||||
description: string;
|
description: string;
|
||||||
eyebrow: string;
|
eyebrow: string;
|
||||||
highlights?: string[];
|
highlights?: string[];
|
||||||
|
primaryAnchor?: HeroPrimaryAnchor;
|
||||||
primaryCta: CtaLink;
|
primaryCta: CtaLink;
|
||||||
productVisual?: HeroVisualContent;
|
productVisual?: HeroVisualContent;
|
||||||
secondaryCta?: CtaLink;
|
secondaryCta?: CtaLink;
|
||||||
title: string;
|
title: string;
|
||||||
|
titleHtml?: string;
|
||||||
trustSubclaims?: string[];
|
trustSubclaims?: string[];
|
||||||
|
visualFocus?: HeroVisualFocusContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetricItem {
|
export interface MetricItem {
|
||||||
@ -115,6 +118,7 @@ export interface FeatureItemContent {
|
|||||||
description: string;
|
description: string;
|
||||||
eyebrow?: string;
|
eyebrow?: string;
|
||||||
href?: string;
|
href?: string;
|
||||||
|
icon?: string;
|
||||||
meta?: string;
|
meta?: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
@ -163,6 +167,14 @@ export interface HeroVisualContent {
|
|||||||
src: string;
|
src: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HeroPrimaryAnchor = 'headline' | 'product-visual' | 'composition';
|
||||||
|
|
||||||
|
export interface HeroVisualFocusContent {
|
||||||
|
eyebrow: string;
|
||||||
|
points: string[];
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface OutcomeItemContent {
|
export interface OutcomeItemContent {
|
||||||
description: string;
|
description: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@ -4,6 +4,12 @@ import {
|
|||||||
expectCtaHierarchy,
|
expectCtaHierarchy,
|
||||||
expectDisclosureLayer,
|
expectDisclosureLayer,
|
||||||
expectFooterLinks,
|
expectFooterLinks,
|
||||||
|
expectHomepageHeroCtaPair,
|
||||||
|
expectHomepageHeroOrder,
|
||||||
|
expectHomepageHeroRouteTargets,
|
||||||
|
expectHomepageHeroStructure,
|
||||||
|
expectHomepageHeroTrustSignals,
|
||||||
|
expectHomepageHeroVisibleOnMobile,
|
||||||
expectHomepageSectionOrder,
|
expectHomepageSectionOrder,
|
||||||
expectMobileReadability,
|
expectMobileReadability,
|
||||||
expectNavigationVsCtaDifferentiation,
|
expectNavigationVsCtaDifferentiation,
|
||||||
@ -72,6 +78,41 @@ test('homepage shows explicit trust signals before the final CTA', async ({
|
|||||||
await expectOnwardRouteReachable(page, ['/trust']);
|
await expectOnwardRouteReachable(page, ['/trust']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('homepage hero makes the product category, text core, and one CTA pair explicit on first read', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await visitPage(page, '/');
|
||||||
|
await 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-heading]').getByRole('heading', {
|
||||||
|
level: 1,
|
||||||
|
name: /one operating record for change history, drift review, and restore planning/i,
|
||||||
|
}),
|
||||||
|
).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,
|
||||||
|
);
|
||||||
|
await expectHomepageHeroCtaPair(page, /working session/i, /product model/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('homepage hero keeps product-near proof and bounded trust cues inside the hero itself', 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('homepage shows dated progress signals before the final CTA', async ({
|
test('homepage shows dated progress signals before the final CTA', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -97,6 +138,20 @@ test.describe('homepage mobile', () => {
|
|||||||
await expect(page.locator('[data-section="capability"]')).toBeVisible();
|
await expect(page.locator('[data-section="capability"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-section="trust"]')).toBeVisible();
|
await expect(page.locator('[data-section="trust"]')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('homepage hero preserves meaning order and hero route intent on narrow screens', async ({ page }) => {
|
||||||
|
await visitPage(page, '/');
|
||||||
|
await expectHomepageHeroOrder(page, [
|
||||||
|
'eyebrow',
|
||||||
|
'headline',
|
||||||
|
'supporting-copy',
|
||||||
|
'cta-pair',
|
||||||
|
'product-near-visual',
|
||||||
|
'trust-subclaims',
|
||||||
|
]);
|
||||||
|
await expectHomepageHeroVisibleOnMobile(page);
|
||||||
|
await expectHomepageHeroRouteTargets(page, ['/contact', '/product']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('product keeps the connected operating model readable without collapsing into a feature list', async ({
|
test('product keeps the connected operating model readable without collapsing into a feature list', async ({
|
||||||
|
|||||||
@ -87,6 +87,180 @@ export async function expectCtaHierarchy(
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroStructure(page: Page): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
|
||||||
|
await expect(hero).toBeVisible();
|
||||||
|
await expect(hero.locator('[data-hero-text-core]').first()).toBeVisible();
|
||||||
|
await expect(hero.locator('[data-hero-eyebrow]').first()).toBeVisible();
|
||||||
|
await expect(hero.locator('[data-hero-heading]').getByRole('heading', { level: 1 })).toBeVisible();
|
||||||
|
await expect(hero.locator('[data-hero-supporting-copy]').first()).toBeVisible();
|
||||||
|
await expect(hero.locator('[data-hero-cta-pair]').first()).toBeVisible();
|
||||||
|
await expect(hero.locator('[data-cta-slot="primary"]')).toHaveCount(1);
|
||||||
|
await expect(hero.locator('[data-cta-slot="secondary"]')).toHaveCount(1);
|
||||||
|
await expect(hero.locator('[data-hero-visual]').first()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroCtaPair(
|
||||||
|
page: Page,
|
||||||
|
primaryLabel: string | RegExp,
|
||||||
|
secondaryLabel: string | RegExp,
|
||||||
|
): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
|
||||||
|
await expect(hero.locator('[data-cta-weight="primary"]').filter({ hasText: primaryLabel }).first()).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
hero.locator('[data-cta-weight="secondary"]').filter({ hasText: secondaryLabel }).first(),
|
||||||
|
).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroPrimaryAnchor(
|
||||||
|
page: Page,
|
||||||
|
anchor: 'headline' | 'product-visual' | 'composition',
|
||||||
|
): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
|
||||||
|
await expect(hero.locator(`[data-hero-primary-anchor="${anchor}"]`).first()).toBeVisible();
|
||||||
|
await expect(hero.locator('[data-hero-primary-anchor]')).toHaveCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroSupportingCopySubordination(page: Page): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
const heading = hero.locator('[data-hero-heading]').getByRole('heading', { level: 1 }).first();
|
||||||
|
const supportingCopy = hero.locator('[data-hero-supporting-copy] p').first();
|
||||||
|
|
||||||
|
await expect(hero.locator('[data-hero-supporting-copy]').first()).toHaveAttribute('data-hero-copy-role', 'supporting');
|
||||||
|
|
||||||
|
const [headingFontSize, supportingCopyFontSize] = await Promise.all([
|
||||||
|
heading.evaluate((element) => Number.parseFloat(window.getComputedStyle(element).fontSize)),
|
||||||
|
supportingCopy.evaluate((element) => Number.parseFloat(window.getComputedStyle(element).fontSize)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(headingFontSize, 'Hero heading should remain larger than supporting copy').toBeGreaterThan(
|
||||||
|
supportingCopyFontSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroAnchorCtaAlignment(page: Page): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
const anchorGroup = hero.locator('[data-hero-anchor-group]').first();
|
||||||
|
|
||||||
|
await expect(anchorGroup).toBeVisible();
|
||||||
|
await expect(anchorGroup.locator('[data-hero-heading]').first()).toBeVisible();
|
||||||
|
await expect(anchorGroup.locator('[data-hero-cta-pair]').first()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroOrder(
|
||||||
|
page: Page,
|
||||||
|
segments: Array<'eyebrow' | 'headline' | 'supporting-copy' | 'cta-pair' | 'product-near-visual' | 'trust-subclaims'>,
|
||||||
|
): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
const actual = await hero.locator('[data-hero-segment]').evaluateAll((elements) =>
|
||||||
|
elements
|
||||||
|
.map((element) => element.getAttribute('data-hero-segment'))
|
||||||
|
.filter(Boolean),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
expect(actual.indexOf(segments[i]), `Hero segment "${segments[i]}" should exist`).toBeGreaterThanOrEqual(0);
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
expect(
|
||||||
|
actual.indexOf(segments[i]),
|
||||||
|
`Hero segment "${segments[i]}" should appear after "${segments[i - 1]}"`,
|
||||||
|
).toBeGreaterThan(actual.indexOf(segments[i - 1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroTrustSignals(page: Page): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
const trustSignals = hero.locator('[data-hero-trust-signals] li');
|
||||||
|
const count = await trustSignals.count();
|
||||||
|
|
||||||
|
await expect(hero.locator('[data-hero-trust-signals]').first()).toBeVisible();
|
||||||
|
expect(count).toBeGreaterThan(0);
|
||||||
|
expect(count).toBeLessThanOrEqual(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroVisualSemantics(
|
||||||
|
page: Page,
|
||||||
|
terms: Array<string | RegExp>,
|
||||||
|
): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
const visual = hero.locator('[data-hero-visual]').first();
|
||||||
|
|
||||||
|
await expect(visual).toBeVisible();
|
||||||
|
await expect(visual).toHaveAttribute('data-hero-visual-style', 'governance-surface');
|
||||||
|
|
||||||
|
for (const term of terms) {
|
||||||
|
await expect(visual).toContainText(term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroSplitLayout(page: Page): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
const textPanel = hero.locator('[data-hero-panel="text"]').first();
|
||||||
|
const visualPanel = hero.locator('[data-hero-panel="dashboard"]').first();
|
||||||
|
|
||||||
|
await expect(textPanel).toBeVisible();
|
||||||
|
await expect(visualPanel).toBeVisible();
|
||||||
|
|
||||||
|
const [textBox, visualBox] = await Promise.all([textPanel.boundingBox(), visualPanel.boundingBox()]);
|
||||||
|
|
||||||
|
expect(textBox, 'Hero text panel should have a bounding box').not.toBeNull();
|
||||||
|
expect(visualBox, 'Hero visual panel should have a bounding box').not.toBeNull();
|
||||||
|
|
||||||
|
if (!textBox || !visualBox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
textBox.x + textBox.width,
|
||||||
|
'Desktop hero text should end before the visual surface begins so both read as one split composition',
|
||||||
|
).toBeLessThanOrEqual(visualBox.x + 48);
|
||||||
|
expect(
|
||||||
|
Math.abs(textBox.y - visualBox.y),
|
||||||
|
'Desktop hero text and visual should share a horizontal composition instead of stacking far apart',
|
||||||
|
).toBeLessThan(140);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectLocatorInInitialViewport(page: Page, selector: string, label: string): Promise<void> {
|
||||||
|
const locator = page.locator(selector).first();
|
||||||
|
const box = await locator.boundingBox();
|
||||||
|
const viewport = page.viewportSize();
|
||||||
|
|
||||||
|
await expect(locator).toBeVisible();
|
||||||
|
|
||||||
|
expect(box, `${label} should have a bounding box`).not.toBeNull();
|
||||||
|
expect(viewport, 'Viewport should be available').not.toBeNull();
|
||||||
|
|
||||||
|
if (!box || !viewport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(box.y, `${label} should start within the initial viewport`).toBeLessThan(viewport.height);
|
||||||
|
expect(box.y + Math.min(box.height, 32), `${label} should remain on screen at first paint`).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroVisibleOnMobile(page: Page): Promise<void> {
|
||||||
|
await expectLocatorInInitialViewport(
|
||||||
|
page,
|
||||||
|
'[data-homepage-hero="true"] [data-hero-primary-anchor]',
|
||||||
|
'Hero primary anchor',
|
||||||
|
);
|
||||||
|
await expectLocatorInInitialViewport(page, '[data-homepage-hero="true"] [data-hero-cta-pair]', 'Hero CTA pair');
|
||||||
|
await expect(page.locator('[data-homepage-hero="true"] [data-hero-visual]').first()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHomepageHeroRouteTargets(page: Page, routes: string[]): Promise<void> {
|
||||||
|
const hero = page.locator('[data-homepage-hero="true"]').first();
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
await expect(hero.locator(`a[href="${route}"]`).first(), `Route "${route}" should be reachable from hero`).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function expectNavigationVsCtaDifferentiation(page: Page): Promise<void> {
|
export async function expectNavigationVsCtaDifferentiation(page: Page): Promise<void> {
|
||||||
const header = page.getByRole('banner');
|
const header = page.getByRole('banner');
|
||||||
|
|
||||||
@ -120,10 +294,15 @@ export async function expectHomepageSectionOrder(page: Page, sections: string[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function expectProductNearVisual(page: Page): Promise<void> {
|
export async function expectProductNearVisual(page: Page, alt?: string | RegExp): Promise<void> {
|
||||||
const main = page.getByRole('main');
|
const main = page.getByRole('main');
|
||||||
|
const visual = main.locator('[data-hero-visual] img, [data-hero-visual]').first();
|
||||||
|
|
||||||
await expect(main.locator('[data-hero-visual] img, [data-hero-visual]').first()).toBeVisible();
|
await expect(visual).toBeVisible();
|
||||||
|
|
||||||
|
if (alt) {
|
||||||
|
await expect(main.getByRole('img', { name: alt }).first()).toBeVisible();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function expectMobileReadability(page: Page): Promise<void> {
|
export async function expectMobileReadability(page: Page): Promise<void> {
|
||||||
|
|||||||
@ -13,11 +13,11 @@ test('representative pages route CTA, badge, surface, and input semantics throug
|
|||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await visitPage(page, '/');
|
await visitPage(page, '/');
|
||||||
await expectShell(page, /TenantAtlas/);
|
await expectShell(page, /control surface/i);
|
||||||
await expectPageFamily(page, 'landing');
|
await expectPageFamily(page, 'landing');
|
||||||
await expectPrimaryNavigation(page);
|
await expectPrimaryNavigation(page);
|
||||||
await expectNavigationVsCtaDifferentiation(page);
|
await expectNavigationVsCtaDifferentiation(page);
|
||||||
await expectCtaHierarchy(page, 'Request a working session', 'See the product model');
|
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 expect(page.locator('[data-interaction="button"]').filter({ hasText: 'Request a working session' }).first()).toBeVisible();
|
||||||
await expect(page.locator('[data-badge-tone]').first()).toBeVisible();
|
await expect(page.locator('[data-badge-tone]').first()).toBeVisible();
|
||||||
|
|
||||||
|
|||||||
423
pnpm-lock.yaml
423
pnpm-lock.yaml
@ -43,9 +43,15 @@ importers:
|
|||||||
|
|
||||||
apps/website:
|
apps/website:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@iconify-json/lucide':
|
||||||
|
specifier: ^1.2.102
|
||||||
|
version: 1.2.102
|
||||||
astro:
|
astro:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3)
|
version: 6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3)
|
||||||
|
astro-icon:
|
||||||
|
specifier: ^1.1.5
|
||||||
|
version: 1.1.5
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.59.1
|
specifier: ^1.59.1
|
||||||
@ -65,6 +71,12 @@ importers:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
'@antfu/install-pkg@1.1.0':
|
||||||
|
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
|
||||||
|
|
||||||
|
'@antfu/utils@8.1.1':
|
||||||
|
resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==}
|
||||||
|
|
||||||
'@astrojs/compiler@3.0.1':
|
'@astrojs/compiler@3.0.1':
|
||||||
resolution: {integrity: sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA==}
|
resolution: {integrity: sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA==}
|
||||||
|
|
||||||
@ -567,6 +579,18 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@iconify-json/lucide@1.2.102':
|
||||||
|
resolution: {integrity: sha512-Dm3EEqu5NrmzyDMB2U1+8yroEj2/dB9V4KlH0m/szwwF/ofSf0cPaGTZqkd1aExXjCor+vU53ttRMCGuXf+/cg==}
|
||||||
|
|
||||||
|
'@iconify/tools@4.2.0':
|
||||||
|
resolution: {integrity: sha512-WRxPva/ipxYkqZd1+CkEAQmd86dQmrwH0vwK89gmp2Kh2WyyVw57XbPng0NehP3x4V1LzLsXUneP1uMfTMZmUA==}
|
||||||
|
|
||||||
|
'@iconify/types@2.0.0':
|
||||||
|
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||||
|
|
||||||
|
'@iconify/utils@2.3.0':
|
||||||
|
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
|
||||||
|
|
||||||
'@img/colour@1.1.0':
|
'@img/colour@1.1.0':
|
||||||
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
|
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -720,6 +744,10 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
|
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@jridgewell/gen-mapping@0.3.13':
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||||
|
|
||||||
@ -1040,9 +1068,17 @@ packages:
|
|||||||
'@types/unist@3.0.3':
|
'@types/unist@3.0.3':
|
||||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||||
|
|
||||||
|
'@types/yauzl@2.10.3':
|
||||||
|
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
|
acorn@8.16.0:
|
||||||
|
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
ansi-regex@5.0.1:
|
ansi-regex@5.0.1:
|
||||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -1065,6 +1101,9 @@ packages:
|
|||||||
array-iterate@2.0.1:
|
array-iterate@2.0.1:
|
||||||
resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==}
|
resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==}
|
||||||
|
|
||||||
|
astro-icon@1.1.5:
|
||||||
|
resolution: {integrity: sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw==}
|
||||||
|
|
||||||
astro@6.1.4:
|
astro@6.1.4:
|
||||||
resolution: {integrity: sha512-SRy1bONuCHkGWhI5JiWCQKVDVbeaXOikjAVZs/Nz+lvUvubtdLoZfnacmuZHQ9RL2IOkU54M8/qZYm9ypJDKrg==}
|
resolution: {integrity: sha512-SRy1bONuCHkGWhI5JiWCQKVDVbeaXOikjAVZs/Nz+lvUvubtdLoZfnacmuZHQ9RL2IOkU54M8/qZYm9ypJDKrg==}
|
||||||
engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'}
|
engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'}
|
||||||
@ -1086,6 +1125,9 @@ packages:
|
|||||||
boolbase@1.0.0:
|
boolbase@1.0.0:
|
||||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||||
|
|
||||||
|
buffer-crc32@0.2.13:
|
||||||
|
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||||
|
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
@ -1109,10 +1151,21 @@ packages:
|
|||||||
character-entities@2.0.2:
|
character-entities@2.0.2:
|
||||||
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
||||||
|
|
||||||
|
cheerio-select@2.1.0:
|
||||||
|
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||||
|
|
||||||
|
cheerio@1.2.0:
|
||||||
|
resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==}
|
||||||
|
engines: {node: '>=20.18.1'}
|
||||||
|
|
||||||
chokidar@5.0.0:
|
chokidar@5.0.0:
|
||||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||||
engines: {node: '>= 20.19.0'}
|
engines: {node: '>= 20.19.0'}
|
||||||
|
|
||||||
|
chownr@3.0.0:
|
||||||
|
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
ci-info@4.4.0:
|
ci-info@4.4.0:
|
||||||
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
|
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -1143,6 +1196,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
|
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
commander@7.2.0:
|
||||||
|
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
common-ancestor-path@2.0.0:
|
common-ancestor-path@2.0.0:
|
||||||
resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==}
|
resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
@ -1152,6 +1209,12 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
confbox@0.1.8:
|
||||||
|
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||||
|
|
||||||
|
confbox@0.2.4:
|
||||||
|
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
|
||||||
|
|
||||||
cookie-es@1.2.3:
|
cookie-es@1.2.3:
|
||||||
resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==}
|
resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==}
|
||||||
|
|
||||||
@ -1169,6 +1232,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
|
resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
|
||||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
|
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
|
||||||
|
|
||||||
|
css-tree@2.3.1:
|
||||||
|
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
|
||||||
|
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||||
|
|
||||||
css-tree@3.2.1:
|
css-tree@3.2.1:
|
||||||
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
|
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
|
||||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||||
@ -1344,6 +1411,12 @@ packages:
|
|||||||
emoji-regex@8.0.0:
|
emoji-regex@8.0.0:
|
||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
|
|
||||||
|
encoding-sniffer@0.2.1:
|
||||||
|
resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||||
|
|
||||||
enhanced-resolve@5.20.1:
|
enhanced-resolve@5.20.1:
|
||||||
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
|
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@ -1356,6 +1429,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
|
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
|
||||||
engines: {node: '>=0.12'}
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
|
entities@7.0.1:
|
||||||
|
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
es-define-property@1.0.1:
|
es-define-property@1.0.1:
|
||||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -1404,9 +1481,17 @@ packages:
|
|||||||
eventemitter3@5.0.4:
|
eventemitter3@5.0.4:
|
||||||
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
|
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
|
||||||
|
|
||||||
|
exsolve@1.0.8:
|
||||||
|
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
|
||||||
|
|
||||||
extend@3.0.2:
|
extend@3.0.2:
|
||||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||||
|
|
||||||
|
extract-zip@2.0.1:
|
||||||
|
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
|
||||||
|
engines: {node: '>= 10.17.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
fast-string-truncated-width@1.2.1:
|
fast-string-truncated-width@1.2.1:
|
||||||
resolution: {integrity: sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==}
|
resolution: {integrity: sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==}
|
||||||
|
|
||||||
@ -1416,6 +1501,9 @@ packages:
|
|||||||
fast-wrap-ansi@0.1.6:
|
fast-wrap-ansi@0.1.6:
|
||||||
resolution: {integrity: sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==}
|
resolution: {integrity: sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==}
|
||||||
|
|
||||||
|
fd-slicer@1.1.0:
|
||||||
|
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||||
|
|
||||||
fdir@6.5.0:
|
fdir@6.5.0:
|
||||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@ -1474,12 +1562,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-stream@5.2.0:
|
||||||
|
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
get-tsconfig@4.13.7:
|
get-tsconfig@4.13.7:
|
||||||
resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
|
resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
|
||||||
|
|
||||||
github-slugger@2.0.0:
|
github-slugger@2.0.0:
|
||||||
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
||||||
|
|
||||||
|
globals@15.15.0:
|
||||||
|
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
gopd@1.2.0:
|
gopd@1.2.0:
|
||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -1542,9 +1638,16 @@ packages:
|
|||||||
html-void-elements@3.0.0:
|
html-void-elements@3.0.0:
|
||||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||||
|
|
||||||
|
htmlparser2@10.1.0:
|
||||||
|
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
|
||||||
|
|
||||||
http-cache-semantics@4.2.0:
|
http-cache-semantics@4.2.0:
|
||||||
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
|
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
iron-webcrypto@1.2.1:
|
iron-webcrypto@1.2.1:
|
||||||
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
|
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
|
||||||
|
|
||||||
@ -1578,6 +1681,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
kolorist@1.8.0:
|
||||||
|
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||||
|
|
||||||
laravel-vite-plugin@2.1.0:
|
laravel-vite-plugin@2.1.0:
|
||||||
resolution: {integrity: sha512-z+ck2BSV6KWtYcoIzk9Y5+p4NEjqM+Y4i8/H+VZRLq0OgNjW2DqyADquwYu5j8qRvaXwzNmfCWl1KrMlV1zpsg==}
|
resolution: {integrity: sha512-z+ck2BSV6KWtYcoIzk9Y5+p4NEjqM+Y4i8/H+VZRLq0OgNjW2DqyADquwYu5j8qRvaXwzNmfCWl1KrMlV1zpsg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@ -1659,6 +1765,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
|
|
||||||
|
local-pkg@1.1.2:
|
||||||
|
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
longest-streak@3.1.0:
|
longest-streak@3.1.0:
|
||||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
||||||
|
|
||||||
@ -1721,6 +1831,9 @@ packages:
|
|||||||
mdn-data@2.0.28:
|
mdn-data@2.0.28:
|
||||||
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
|
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
|
||||||
|
|
||||||
|
mdn-data@2.0.30:
|
||||||
|
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||||
|
|
||||||
mdn-data@2.27.1:
|
mdn-data@2.27.1:
|
||||||
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
|
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
|
||||||
|
|
||||||
@ -1816,6 +1929,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
minipass@7.1.3:
|
||||||
|
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
minizlib@3.1.0:
|
||||||
|
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
|
mlly@1.8.2:
|
||||||
|
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||||
|
|
||||||
mrmime@2.0.1:
|
mrmime@2.0.1:
|
||||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -1857,6 +1981,9 @@ packages:
|
|||||||
ohash@2.0.11:
|
ohash@2.0.11:
|
||||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
oniguruma-parser@0.12.1:
|
oniguruma-parser@0.12.1:
|
||||||
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
|
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
|
||||||
|
|
||||||
@ -1881,9 +2008,21 @@ packages:
|
|||||||
parse-latin@7.0.0:
|
parse-latin@7.0.0:
|
||||||
resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==}
|
resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==}
|
||||||
|
|
||||||
|
parse5-htmlparser2-tree-adapter@7.1.0:
|
||||||
|
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
|
||||||
|
|
||||||
|
parse5-parser-stream@7.1.2:
|
||||||
|
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
|
||||||
|
|
||||||
parse5@7.3.0:
|
parse5@7.3.0:
|
||||||
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
|
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
|
||||||
|
|
||||||
|
pathe@2.0.3:
|
||||||
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
|
pend@1.2.0:
|
||||||
|
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||||
|
|
||||||
pg-cloudflare@1.3.0:
|
pg-cloudflare@1.3.0:
|
||||||
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
|
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
|
||||||
|
|
||||||
@ -1932,6 +2071,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
pkg-types@1.3.1:
|
||||||
|
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||||
|
|
||||||
|
pkg-types@2.3.0:
|
||||||
|
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||||
|
|
||||||
playwright-core@1.59.1:
|
playwright-core@1.59.1:
|
||||||
resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==}
|
resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -1973,6 +2118,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||||
|
|
||||||
|
quansync@0.2.11:
|
||||||
|
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||||
|
|
||||||
radix3@1.1.2:
|
radix3@1.1.2:
|
||||||
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
|
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
|
||||||
|
|
||||||
@ -2044,6 +2195,9 @@ packages:
|
|||||||
rxjs@7.8.2:
|
rxjs@7.8.2:
|
||||||
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2:
|
||||||
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
sax@1.6.0:
|
sax@1.6.0:
|
||||||
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
|
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
|
||||||
engines: {node: '>=11.0.0'}
|
engines: {node: '>=11.0.0'}
|
||||||
@ -2109,6 +2263,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
svgo@3.3.3:
|
||||||
|
resolution: {integrity: sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
svgo@4.0.1:
|
svgo@4.0.1:
|
||||||
resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==}
|
resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@ -2121,6 +2280,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
|
resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
tar@7.5.13:
|
||||||
|
resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
tiny-inflate@1.0.3:
|
tiny-inflate@1.0.3:
|
||||||
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
|
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
|
||||||
|
|
||||||
@ -2181,6 +2344,10 @@ packages:
|
|||||||
undici-types@7.16.0:
|
undici-types@7.16.0:
|
||||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
|
undici@7.25.0:
|
||||||
|
resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==}
|
||||||
|
engines: {node: '>=20.18.1'}
|
||||||
|
|
||||||
unified@11.0.5:
|
unified@11.0.5:
|
||||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||||
|
|
||||||
@ -2339,6 +2506,15 @@ packages:
|
|||||||
web-namespaces@2.0.1:
|
web-namespaces@2.0.1:
|
||||||
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
||||||
|
|
||||||
|
whatwg-encoding@3.1.1:
|
||||||
|
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
|
||||||
|
|
||||||
|
whatwg-mimetype@4.0.0:
|
||||||
|
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
which-pm-runs@1.1.0:
|
which-pm-runs@1.1.0:
|
||||||
resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
|
resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -2347,6 +2523,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
wrappy@1.0.2:
|
||||||
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
xtend@4.0.2:
|
xtend@4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
@ -2358,6 +2537,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
yallist@5.0.0:
|
||||||
|
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
yargs-parser@21.1.1:
|
yargs-parser@21.1.1:
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -2370,6 +2553,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yauzl@2.10.0:
|
||||||
|
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||||
|
|
||||||
yocto-queue@1.2.2:
|
yocto-queue@1.2.2:
|
||||||
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
|
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
|
||||||
engines: {node: '>=12.20'}
|
engines: {node: '>=12.20'}
|
||||||
@ -2382,6 +2568,13 @@ packages:
|
|||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
|
'@antfu/install-pkg@1.1.0':
|
||||||
|
dependencies:
|
||||||
|
package-manager-detector: 1.6.0
|
||||||
|
tinyexec: 1.1.1
|
||||||
|
|
||||||
|
'@antfu/utils@8.1.1': {}
|
||||||
|
|
||||||
'@astrojs/compiler@3.0.1': {}
|
'@astrojs/compiler@3.0.1': {}
|
||||||
|
|
||||||
'@astrojs/internal-helpers@0.8.0':
|
'@astrojs/internal-helpers@0.8.0':
|
||||||
@ -2698,6 +2891,39 @@ snapshots:
|
|||||||
'@esbuild/win32-x64@0.27.7':
|
'@esbuild/win32-x64@0.27.7':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@iconify-json/lucide@1.2.102':
|
||||||
|
dependencies:
|
||||||
|
'@iconify/types': 2.0.0
|
||||||
|
|
||||||
|
'@iconify/tools@4.2.0':
|
||||||
|
dependencies:
|
||||||
|
'@iconify/types': 2.0.0
|
||||||
|
'@iconify/utils': 2.3.0
|
||||||
|
cheerio: 1.2.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
extract-zip: 2.0.1
|
||||||
|
local-pkg: 1.1.2
|
||||||
|
pathe: 2.0.3
|
||||||
|
svgo: 3.3.3
|
||||||
|
tar: 7.5.13
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@iconify/types@2.0.0': {}
|
||||||
|
|
||||||
|
'@iconify/utils@2.3.0':
|
||||||
|
dependencies:
|
||||||
|
'@antfu/install-pkg': 1.1.0
|
||||||
|
'@antfu/utils': 8.1.1
|
||||||
|
'@iconify/types': 2.0.0
|
||||||
|
debug: 4.4.3
|
||||||
|
globals: 15.15.0
|
||||||
|
kolorist: 1.8.0
|
||||||
|
local-pkg: 1.1.2
|
||||||
|
mlly: 1.8.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@img/colour@1.1.0':
|
'@img/colour@1.1.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -2795,6 +3021,10 @@ snapshots:
|
|||||||
'@img/sharp-win32-x64@0.34.5':
|
'@img/sharp-win32-x64@0.34.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
|
dependencies:
|
||||||
|
minipass: 7.1.3
|
||||||
|
|
||||||
'@jridgewell/gen-mapping@0.3.13':
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
@ -3037,8 +3267,15 @@ snapshots:
|
|||||||
|
|
||||||
'@types/unist@3.0.3': {}
|
'@types/unist@3.0.3': {}
|
||||||
|
|
||||||
|
'@types/yauzl@2.10.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.12.2
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
|
acorn@8.16.0: {}
|
||||||
|
|
||||||
ansi-regex@5.0.1: {}
|
ansi-regex@5.0.1: {}
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
ansi-styles@4.3.0:
|
||||||
@ -3056,6 +3293,14 @@ snapshots:
|
|||||||
|
|
||||||
array-iterate@2.0.1: {}
|
array-iterate@2.0.1: {}
|
||||||
|
|
||||||
|
astro-icon@1.1.5:
|
||||||
|
dependencies:
|
||||||
|
'@iconify/tools': 4.2.0
|
||||||
|
'@iconify/types': 2.0.0
|
||||||
|
'@iconify/utils': 2.3.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
astro@6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3):
|
astro@6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 3.0.1
|
'@astrojs/compiler': 3.0.1
|
||||||
@ -3166,6 +3411,8 @@ snapshots:
|
|||||||
|
|
||||||
boolbase@1.0.0: {}
|
boolbase@1.0.0: {}
|
||||||
|
|
||||||
|
buffer-crc32@0.2.13: {}
|
||||||
|
|
||||||
buffer-from@1.1.2: {}
|
buffer-from@1.1.2: {}
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
@ -3186,10 +3433,35 @@ snapshots:
|
|||||||
|
|
||||||
character-entities@2.0.2: {}
|
character-entities@2.0.2: {}
|
||||||
|
|
||||||
|
cheerio-select@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
boolbase: 1.0.0
|
||||||
|
css-select: 5.2.2
|
||||||
|
css-what: 6.2.2
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.2.2
|
||||||
|
|
||||||
|
cheerio@1.2.0:
|
||||||
|
dependencies:
|
||||||
|
cheerio-select: 2.1.0
|
||||||
|
dom-serializer: 2.0.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.2.2
|
||||||
|
encoding-sniffer: 0.2.1
|
||||||
|
htmlparser2: 10.1.0
|
||||||
|
parse5: 7.3.0
|
||||||
|
parse5-htmlparser2-tree-adapter: 7.1.0
|
||||||
|
parse5-parser-stream: 7.1.2
|
||||||
|
undici: 7.25.0
|
||||||
|
whatwg-mimetype: 4.0.0
|
||||||
|
|
||||||
chokidar@5.0.0:
|
chokidar@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 5.0.0
|
readdirp: 5.0.0
|
||||||
|
|
||||||
|
chownr@3.0.0: {}
|
||||||
|
|
||||||
ci-info@4.4.0: {}
|
ci-info@4.4.0: {}
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
@ -3214,6 +3486,8 @@ snapshots:
|
|||||||
|
|
||||||
commander@11.1.0: {}
|
commander@11.1.0: {}
|
||||||
|
|
||||||
|
commander@7.2.0: {}
|
||||||
|
|
||||||
common-ancestor-path@2.0.0: {}
|
common-ancestor-path@2.0.0: {}
|
||||||
|
|
||||||
concurrently@9.2.1:
|
concurrently@9.2.1:
|
||||||
@ -3225,6 +3499,10 @@ snapshots:
|
|||||||
tree-kill: 1.2.2
|
tree-kill: 1.2.2
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
|
|
||||||
|
confbox@0.1.8: {}
|
||||||
|
|
||||||
|
confbox@0.2.4: {}
|
||||||
|
|
||||||
cookie-es@1.2.3: {}
|
cookie-es@1.2.3: {}
|
||||||
|
|
||||||
cookie@1.1.1: {}
|
cookie@1.1.1: {}
|
||||||
@ -3246,6 +3524,11 @@ snapshots:
|
|||||||
mdn-data: 2.0.28
|
mdn-data: 2.0.28
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
css-tree@2.3.1:
|
||||||
|
dependencies:
|
||||||
|
mdn-data: 2.0.30
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
css-tree@3.2.1:
|
css-tree@3.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
mdn-data: 2.27.1
|
mdn-data: 2.27.1
|
||||||
@ -3324,6 +3607,15 @@ snapshots:
|
|||||||
|
|
||||||
emoji-regex@8.0.0: {}
|
emoji-regex@8.0.0: {}
|
||||||
|
|
||||||
|
encoding-sniffer@0.2.1:
|
||||||
|
dependencies:
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
whatwg-encoding: 3.1.1
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
dependencies:
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
enhanced-resolve@5.20.1:
|
enhanced-resolve@5.20.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@ -3333,6 +3625,8 @@ snapshots:
|
|||||||
|
|
||||||
entities@6.0.1: {}
|
entities@6.0.1: {}
|
||||||
|
|
||||||
|
entities@7.0.1: {}
|
||||||
|
|
||||||
es-define-property@1.0.1: {}
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
es-errors@1.3.0: {}
|
es-errors@1.3.0: {}
|
||||||
@ -3441,8 +3735,20 @@ snapshots:
|
|||||||
|
|
||||||
eventemitter3@5.0.4: {}
|
eventemitter3@5.0.4: {}
|
||||||
|
|
||||||
|
exsolve@1.0.8: {}
|
||||||
|
|
||||||
extend@3.0.2: {}
|
extend@3.0.2: {}
|
||||||
|
|
||||||
|
extract-zip@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
get-stream: 5.2.0
|
||||||
|
yauzl: 2.10.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/yauzl': 2.10.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
fast-string-truncated-width@1.2.1: {}
|
fast-string-truncated-width@1.2.1: {}
|
||||||
|
|
||||||
fast-string-width@1.1.0:
|
fast-string-width@1.1.0:
|
||||||
@ -3453,6 +3759,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-string-width: 1.1.0
|
fast-string-width: 1.1.0
|
||||||
|
|
||||||
|
fd-slicer@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
pend: 1.2.0
|
||||||
|
|
||||||
fdir@6.5.0(picomatch@4.0.4):
|
fdir@6.5.0(picomatch@4.0.4):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
@ -3505,12 +3815,18 @@ snapshots:
|
|||||||
dunder-proto: 1.0.1
|
dunder-proto: 1.0.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
get-stream@5.2.0:
|
||||||
|
dependencies:
|
||||||
|
pump: 3.0.4
|
||||||
|
|
||||||
get-tsconfig@4.13.7:
|
get-tsconfig@4.13.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
github-slugger@2.0.0: {}
|
github-slugger@2.0.0: {}
|
||||||
|
|
||||||
|
globals@15.15.0: {}
|
||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
@ -3630,8 +3946,19 @@ snapshots:
|
|||||||
|
|
||||||
html-void-elements@3.0.0: {}
|
html-void-elements@3.0.0: {}
|
||||||
|
|
||||||
|
htmlparser2@10.1.0:
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.2.2
|
||||||
|
entities: 7.0.1
|
||||||
|
|
||||||
http-cache-semantics@4.2.0: {}
|
http-cache-semantics@4.2.0: {}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
iron-webcrypto@1.2.1: {}
|
iron-webcrypto@1.2.1: {}
|
||||||
|
|
||||||
is-docker@3.0.0: {}
|
is-docker@3.0.0: {}
|
||||||
@ -3654,6 +3981,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
||||||
|
kolorist@1.8.0: {}
|
||||||
|
|
||||||
laravel-vite-plugin@2.1.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
laravel-vite-plugin@2.1.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
@ -3709,6 +4038,12 @@ snapshots:
|
|||||||
lightningcss-win32-arm64-msvc: 1.32.0
|
lightningcss-win32-arm64-msvc: 1.32.0
|
||||||
lightningcss-win32-x64-msvc: 1.32.0
|
lightningcss-win32-x64-msvc: 1.32.0
|
||||||
|
|
||||||
|
local-pkg@1.1.2:
|
||||||
|
dependencies:
|
||||||
|
mlly: 1.8.2
|
||||||
|
pkg-types: 2.3.0
|
||||||
|
quansync: 0.2.11
|
||||||
|
|
||||||
longest-streak@3.1.0: {}
|
longest-streak@3.1.0: {}
|
||||||
|
|
||||||
lru-cache@11.3.2: {}
|
lru-cache@11.3.2: {}
|
||||||
@ -3849,6 +4184,8 @@ snapshots:
|
|||||||
|
|
||||||
mdn-data@2.0.28: {}
|
mdn-data@2.0.28: {}
|
||||||
|
|
||||||
|
mdn-data@2.0.30: {}
|
||||||
|
|
||||||
mdn-data@2.27.1: {}
|
mdn-data@2.27.1: {}
|
||||||
|
|
||||||
micromark-core-commonmark@2.0.3:
|
micromark-core-commonmark@2.0.3:
|
||||||
@ -4048,6 +4385,19 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.52.0
|
mime-db: 1.52.0
|
||||||
|
|
||||||
|
minipass@7.1.3: {}
|
||||||
|
|
||||||
|
minizlib@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
minipass: 7.1.3
|
||||||
|
|
||||||
|
mlly@1.8.2:
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.16.0
|
||||||
|
pathe: 2.0.3
|
||||||
|
pkg-types: 1.3.1
|
||||||
|
ufo: 1.6.3
|
||||||
|
|
||||||
mrmime@2.0.1: {}
|
mrmime@2.0.1: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
@ -4080,6 +4430,10 @@ snapshots:
|
|||||||
|
|
||||||
ohash@2.0.11: {}
|
ohash@2.0.11: {}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
wrappy: 1.0.2
|
||||||
|
|
||||||
oniguruma-parser@0.12.1: {}
|
oniguruma-parser@0.12.1: {}
|
||||||
|
|
||||||
oniguruma-to-es@4.3.5:
|
oniguruma-to-es@4.3.5:
|
||||||
@ -4110,10 +4464,23 @@ snapshots:
|
|||||||
unist-util-visit-children: 3.0.0
|
unist-util-visit-children: 3.0.0
|
||||||
vfile: 6.0.3
|
vfile: 6.0.3
|
||||||
|
|
||||||
|
parse5-htmlparser2-tree-adapter@7.1.0:
|
||||||
|
dependencies:
|
||||||
|
domhandler: 5.0.3
|
||||||
|
parse5: 7.3.0
|
||||||
|
|
||||||
|
parse5-parser-stream@7.1.2:
|
||||||
|
dependencies:
|
||||||
|
parse5: 7.3.0
|
||||||
|
|
||||||
parse5@7.3.0:
|
parse5@7.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
entities: 6.0.1
|
entities: 6.0.1
|
||||||
|
|
||||||
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
|
pend@1.2.0: {}
|
||||||
|
|
||||||
pg-cloudflare@1.3.0:
|
pg-cloudflare@1.3.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -4157,6 +4524,18 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@4.0.4: {}
|
picomatch@4.0.4: {}
|
||||||
|
|
||||||
|
pkg-types@1.3.1:
|
||||||
|
dependencies:
|
||||||
|
confbox: 0.1.8
|
||||||
|
mlly: 1.8.2
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
pkg-types@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
confbox: 0.2.4
|
||||||
|
exsolve: 1.0.8
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
playwright-core@1.59.1: {}
|
playwright-core@1.59.1: {}
|
||||||
|
|
||||||
playwright@1.59.1:
|
playwright@1.59.1:
|
||||||
@ -4187,6 +4566,13 @@ snapshots:
|
|||||||
|
|
||||||
proxy-from-env@2.1.0: {}
|
proxy-from-env@2.1.0: {}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
dependencies:
|
||||||
|
end-of-stream: 1.4.5
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
|
quansync@0.2.11: {}
|
||||||
|
|
||||||
radix3@1.1.2: {}
|
radix3@1.1.2: {}
|
||||||
|
|
||||||
readdirp@5.0.0: {}
|
readdirp@5.0.0: {}
|
||||||
@ -4331,6 +4717,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
sax@1.6.0: {}
|
sax@1.6.0: {}
|
||||||
|
|
||||||
semver@7.7.4: {}
|
semver@7.7.4: {}
|
||||||
@ -4420,6 +4808,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
|
|
||||||
|
svgo@3.3.3:
|
||||||
|
dependencies:
|
||||||
|
commander: 7.2.0
|
||||||
|
css-select: 5.2.2
|
||||||
|
css-tree: 2.3.1
|
||||||
|
css-what: 6.2.2
|
||||||
|
csso: 5.0.5
|
||||||
|
picocolors: 1.1.1
|
||||||
|
sax: 1.6.0
|
||||||
|
|
||||||
svgo@4.0.1:
|
svgo@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
commander: 11.1.0
|
commander: 11.1.0
|
||||||
@ -4434,6 +4832,14 @@ snapshots:
|
|||||||
|
|
||||||
tapable@2.3.2: {}
|
tapable@2.3.2: {}
|
||||||
|
|
||||||
|
tar@7.5.13:
|
||||||
|
dependencies:
|
||||||
|
'@isaacs/fs-minipass': 4.0.1
|
||||||
|
chownr: 3.0.0
|
||||||
|
minipass: 7.1.3
|
||||||
|
minizlib: 3.1.0
|
||||||
|
yallist: 5.0.0
|
||||||
|
|
||||||
tiny-inflate@1.0.3: {}
|
tiny-inflate@1.0.3: {}
|
||||||
|
|
||||||
tinyclip@0.1.12: {}
|
tinyclip@0.1.12: {}
|
||||||
@ -4474,6 +4880,8 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@7.16.0: {}
|
undici-types@7.16.0: {}
|
||||||
|
|
||||||
|
undici@7.25.0: {}
|
||||||
|
|
||||||
unified@11.0.5:
|
unified@11.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
@ -4584,6 +4992,12 @@ snapshots:
|
|||||||
|
|
||||||
web-namespaces@2.0.1: {}
|
web-namespaces@2.0.1: {}
|
||||||
|
|
||||||
|
whatwg-encoding@3.1.1:
|
||||||
|
dependencies:
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
|
||||||
|
whatwg-mimetype@4.0.0: {}
|
||||||
|
|
||||||
which-pm-runs@1.1.0: {}
|
which-pm-runs@1.1.0: {}
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
@ -4592,12 +5006,16 @@ snapshots:
|
|||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
xxhash-wasm@1.1.0: {}
|
xxhash-wasm@1.1.0: {}
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
|
yallist@5.0.0: {}
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
yargs-parser@22.0.0: {}
|
yargs-parser@22.0.0: {}
|
||||||
@ -4612,6 +5030,11 @@ snapshots:
|
|||||||
y18n: 5.0.8
|
y18n: 5.0.8
|
||||||
yargs-parser: 21.1.1
|
yargs-parser: 21.1.1
|
||||||
|
|
||||||
|
yauzl@2.10.0:
|
||||||
|
dependencies:
|
||||||
|
buffer-crc32: 0.2.13
|
||||||
|
fd-slicer: 1.1.0
|
||||||
|
|
||||||
yocto-queue@1.2.2: {}
|
yocto-queue@1.2.2: {}
|
||||||
|
|
||||||
zod@4.3.6: {}
|
zod@4.3.6: {}
|
||||||
|
|||||||
@ -193,11 +193,12 @@ ### Measurable Outcomes
|
|||||||
|
|
||||||
## Planned Follow-on Specs
|
## Planned Follow-on Specs
|
||||||
|
|
||||||
- Spec 216 - Homepage Structure and Section Model
|
- Spec 217 - Homepage Structure and Section Model
|
||||||
- Spec 217 - Product Page Structure
|
- Spec 218 - Homepage Hero Contract
|
||||||
- Spec 218 - Trust Surface
|
- Spec 219 - Product Page Structure
|
||||||
- Spec 219 - Contact / Demo Flow
|
- Spec 220 - Trust Surface
|
||||||
- Spec 220 - Changelog Surface
|
- Spec 221 - Contact / Demo Flow
|
||||||
- Spec 221 - Blog / Resources Surface, if activated
|
- Spec 222 - Changelog Surface
|
||||||
- Spec 222 - Solutions / Use-Case Surfaces, if activated later
|
- Spec 223 - Blog / Resources Surface, if activated
|
||||||
- Spec 223 - Pricing Surface, if activated later
|
- Spec 224 - Solutions / Use-Case Surfaces, if activated later
|
||||||
|
- Spec 225 - Pricing Surface, if activated later
|
||||||
@ -1,66 +0,0 @@
|
|||||||
# Quickstart: Website Homepage Structure & Section Model
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Verify that the homepage in `apps/website` follows the Spec 216 section contract and routes visitors clearly into Product, Trust, Changelog, and Contact.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Node.js 20+
|
|
||||||
- Corepack enabled
|
|
||||||
- Repo dependencies installed with `corepack pnpm install`
|
|
||||||
|
|
||||||
## Run the website locally
|
|
||||||
|
|
||||||
From the repository root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
corepack pnpm dev:website
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternative, inside the website app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd apps/website
|
|
||||||
corepack pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Default local URL: `http://127.0.0.1:4321/`
|
|
||||||
|
|
||||||
## What to verify on the homepage
|
|
||||||
|
|
||||||
Check the homepage in this order:
|
|
||||||
|
|
||||||
1. Header and global navigation expose Product, Trust, Changelog, and Contact, with no prominent links to unsubstantial optional routes.
|
|
||||||
2. Hero shows one dominant primary CTA, one secondary deepening CTA, and a product-near visual.
|
|
||||||
3. Outcome framing explains why the product matters in buyer language rather than route or feature-admin language.
|
|
||||||
4. Capability section groups the product model instead of listing a flat feature wall.
|
|
||||||
5. Trust block appears before the final CTA and routes to `/trust`.
|
|
||||||
6. Progress block shows visible dated product movement and routes to `/changelog`.
|
|
||||||
7. Final CTA offers one clear next step, currently `/contact`.
|
|
||||||
8. Footer keeps Product, Trust, Changelog, Contact, Privacy, and Imprint reachable.
|
|
||||||
|
|
||||||
## Build proof
|
|
||||||
|
|
||||||
From the repository root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
corepack pnpm build:website
|
|
||||||
```
|
|
||||||
|
|
||||||
## Browser smoke proof
|
|
||||||
|
|
||||||
Run the website smoke suite:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd apps/website
|
|
||||||
corepack pnpm exec playwright test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Expected proof points
|
|
||||||
|
|
||||||
- Homepage required blocks are visible in the intended order.
|
|
||||||
- The hero CTA hierarchy remains clear and non-competing.
|
|
||||||
- `/product`, `/trust`, `/changelog`, and `/contact` are reachable from the homepage.
|
|
||||||
- Optional unpublished routes are not surfaced prominently.
|
|
||||||
- The homepage remains readable on desktop and mobile widths.
|
|
||||||
@ -2,10 +2,10 @@ openapi: 3.1.0
|
|||||||
info:
|
info:
|
||||||
title: TenantAtlas Homepage Surface Contract
|
title: TenantAtlas Homepage Surface Contract
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
summary: Structural contract for the `apps/website` homepage in Spec 216.
|
summary: Structural contract for the `apps/website` homepage in Spec 217.
|
||||||
description: >-
|
description: >-
|
||||||
This contract defines the public HTML routes that participate in the
|
This contract defines the public HTML routes that participate in the
|
||||||
homepage journey for Spec 216. The homepage remains a static Astro surface
|
homepage journey for Spec 217. The homepage remains a static Astro surface
|
||||||
and must route visitors into Product, Trust, Changelog, and Contact while
|
and must route visitors into Product, Trust, Changelog, and Contact while
|
||||||
satisfying the required homepage section model.
|
satisfying the required homepage section model.
|
||||||
servers:
|
servers:
|
||||||
@ -1,16 +1,23 @@
|
|||||||
# Implementation Plan: Website Homepage Structure & Section Model
|
# Implementation Plan: Website Homepage Structure & Section Model
|
||||||
|
|
||||||
**Branch**: `216-homepage-structure` | **Date**: 2026-04-19 | **Spec**: `specs/216-homepage-structure/spec.md`
|
**Branch**: `217-homepage-structure` | **Date**: 2026-04-19 | **Spec**: `specs/217-homepage-structure/spec.md`
|
||||||
**Input**: Feature specification from `specs/216-homepage-structure/spec.md`
|
**Input**: Feature specification from `specs/217-homepage-structure/spec.md`
|
||||||
|
|
||||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- Rework the `apps/website` homepage into the explicit section flow required by Spec 216: hero, outcome framing, capability model, trust, progress, CTA, while preserving existing header and footer shells.
|
- Rework the `apps/website` homepage into the explicit section flow required by Spec 217: hero, outcome framing, capability model, trust, progress, CTA, while preserving existing header and footer shells.
|
||||||
- Implement the change by extending the current Astro content-driven homepage model (`src/content/pages/home.ts`) and existing section primitives instead of adding a new section registry or CMS-like composition layer.
|
- Implement the change by extending the current Astro content-driven homepage model (`src/content/pages/home.ts`) and existing section primitives instead of adding a new section registry or CMS-like composition layer.
|
||||||
- Reuse existing Trust and Changelog truth for homepage proof signals, and validate the result with the current website build proof plus focused Playwright smoke coverage.
|
- Reuse existing Trust and Changelog truth for homepage proof signals, and validate the result with the current website build proof plus focused Playwright smoke coverage.
|
||||||
|
|
||||||
|
## Addendum Status
|
||||||
|
|
||||||
|
- A post-implementation refinement now extends Spec 217 with hero art-direction guardrails aimed at avoiding generic neutral drift and generic shadcn-style marketing output.
|
||||||
|
- The original structural homepage work remains completed through T019.
|
||||||
|
- Phase 7 is now completed through T023, with an explicit headline primary anchor, stronger hero content contracts, a split desktop composition, and a governance-specific visual surface on the homepage.
|
||||||
|
- The hero-direction guardrails are now enforced by automated smoke coverage for primary anchor presence, supporting-copy subordination, CTA-anchor reinforcement, governance-specific visual semantics, and desktop/mobile hierarchy.
|
||||||
|
|
||||||
## Technical Context
|
## Technical Context
|
||||||
|
|
||||||
**Language/Version**: Astro 6.0.0 templates + TypeScript 5.9.x
|
**Language/Version**: Astro 6.0.0 templates + TypeScript 5.9.x
|
||||||
@ -74,7 +81,7 @@ ## Project Structure
|
|||||||
### Documentation (this feature)
|
### Documentation (this feature)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
specs/216-homepage-structure/
|
specs/217-homepage-structure/
|
||||||
├── plan.md
|
├── plan.md
|
||||||
├── research.md
|
├── research.md
|
||||||
├── data-model.md
|
├── data-model.md
|
||||||
@ -125,7 +132,7 @@ ### Source Code (repository root)
|
|||||||
└── smoke-helpers.ts
|
└── smoke-helpers.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
**Structure Decision**: Keep the feature completely inside `apps/website`, using the existing Astro page/content/component split. Extend `src/content/pages/home.ts`, reuse current section components where possible, and add only the smallest missing homepage composition pieces required by Spec 216.
|
**Structure Decision**: Keep the feature completely inside `apps/website`, using the existing Astro page/content/component split. Extend `src/content/pages/home.ts`, reuse current section components where possible, and add only the smallest missing homepage composition pieces required by Spec 217.
|
||||||
|
|
||||||
## Complexity Tracking
|
## Complexity Tracking
|
||||||
|
|
||||||
@ -142,11 +149,11 @@ ## Proportionality Review
|
|||||||
|
|
||||||
## Phase 0 — Outline & Research (complete)
|
## Phase 0 — Outline & Research (complete)
|
||||||
|
|
||||||
- Output: `specs/216-homepage-structure/research.md`
|
- Output: `specs/217-homepage-structure/research.md`
|
||||||
- Key decisions captured:
|
- Key decisions captured:
|
||||||
- Keep the homepage local to the static Astro website and preserve runtime separation from `apps/platform`.
|
- Keep the homepage local to the static Astro website and preserve runtime separation from `apps/platform`.
|
||||||
- Extend the current content-driven homepage model instead of adding a new section framework.
|
- Extend the current content-driven homepage model instead of adding a new section framework.
|
||||||
- Reorder the homepage into the explicit Spec 216 narrative flow while preserving optional supporting context only where it helps clarity.
|
- Reorder the homepage into the explicit Spec 217 narrative flow while preserving optional supporting context only where it helps clarity.
|
||||||
- Reuse existing Trust page truth and changelog collection data for homepage proof signals.
|
- Reuse existing Trust page truth and changelog collection data for homepage proof signals.
|
||||||
- Validate via the current website build proof plus focused Playwright smoke coverage.
|
- Validate via the current website build proof plus focused Playwright smoke coverage.
|
||||||
|
|
||||||
@ -154,17 +161,17 @@ ## Phase 1 — Design & Contracts (complete)
|
|||||||
|
|
||||||
### Data model
|
### Data model
|
||||||
|
|
||||||
- Output: `specs/216-homepage-structure/data-model.md`
|
- Output: `specs/217-homepage-structure/data-model.md`
|
||||||
- Model remains file- and route-based. No database schema changes are required.
|
- Model remains file- and route-based. No database schema changes are required.
|
||||||
|
|
||||||
### Public homepage contract
|
### Public homepage contract
|
||||||
|
|
||||||
- Output: `specs/216-homepage-structure/contracts/homepage-surface.openapi.yaml`
|
- Output: `specs/217-homepage-structure/contracts/homepage-surface.openapi.yaml`
|
||||||
- Contract captures the homepage route plus the required onward routes (`/product`, `/trust`, `/changelog`, `/contact`) and the structural rules the homepage must satisfy.
|
- Contract captures the homepage route plus the required onward routes (`/product`, `/trust`, `/changelog`, `/contact`) and the structural rules the homepage must satisfy.
|
||||||
|
|
||||||
### Quickstart
|
### Quickstart
|
||||||
|
|
||||||
- Output: `specs/216-homepage-structure/quickstart.md`
|
- Output: `specs/217-homepage-structure/quickstart.md`
|
||||||
- Quickstart covers local development, homepage verification points, build proof, and smoke-test execution.
|
- Quickstart covers local development, homepage verification points, build proof, and smoke-test execution.
|
||||||
|
|
||||||
### Agent context update
|
### Agent context update
|
||||||
@ -216,10 +223,20 @@ ## Close-Out Notes
|
|||||||
|
|
||||||
**Implementation completed**: All 19 tasks (T001–T019) across 6 phases.
|
**Implementation completed**: All 19 tasks (T001–T019) across 6 phases.
|
||||||
|
|
||||||
|
**Post-close-out refinement**: Spec 217 now also includes a completed Phase 7 hero-direction addendum that sharpens the homepage hero without widening the rest of the homepage contract.
|
||||||
|
|
||||||
### Build & Test Proof
|
### Build & Test Proof
|
||||||
|
|
||||||
- `corepack pnpm build:website`: ✅ 12 pages built, 0 errors
|
- `corepack pnpm build:website`: ✅ 12 pages built, 0 errors
|
||||||
- `cd apps/website && corepack pnpm exec playwright test`: ✅ 20/20 tests pass
|
- `cd apps/website && corepack pnpm exec playwright test`: ✅ 20/20 tests pass
|
||||||
|
- Phase 7 validation on 2026-04-20: `corepack pnpm build:website` ✅ and `cd apps/website && corepack pnpm exec playwright test` ✅ 26/26 tests pass
|
||||||
|
|
||||||
|
### Phase 7 Hero Refinement
|
||||||
|
|
||||||
|
- The homepage hero now uses an explicit `headline` primary anchor with stronger typographic tension and subordinate supporting copy.
|
||||||
|
- The desktop hero now reads as one split composition, with the copy block and governance surface sharing the same first-read layer instead of stacking like separate sections.
|
||||||
|
- The hero visual now emphasizes baseline drift, restore preview, evidence linking, and review queue semantics instead of generic KPI-oriented dashboard language.
|
||||||
|
- Phase 7 browser coverage now proves the addendum requirements directly in `apps/website/tests/smoke/home-product.spec.ts` and `apps/website/tests/smoke/smoke-helpers.ts`.
|
||||||
|
|
||||||
### Summary of Changes
|
### Summary of Changes
|
||||||
|
|
||||||
@ -231,17 +248,17 @@ ### Summary of Changes
|
|||||||
- `apps/website/public/images/hero-product-visual.svg` — product-near hero visual
|
- `apps/website/public/images/hero-product-visual.svg` — product-near hero visual
|
||||||
|
|
||||||
**Modified files**:
|
**Modified files**:
|
||||||
- `apps/website/src/types/site.ts` — 7 new interfaces for Spec 216 section model
|
- `apps/website/src/types/site.ts` — 7 new interfaces for Spec 217 section model
|
||||||
- `apps/website/src/content/pages/home.ts` — full rewrite with Spec 216 content
|
- `apps/website/src/content/pages/home.ts` — full rewrite with Spec 217 content
|
||||||
- `apps/website/src/pages/index.astro` — full rewrite with new section flow
|
- `apps/website/src/pages/index.astro` — full rewrite with new section flow
|
||||||
- `apps/website/src/components/sections/PageHero.astro` — added productVisual + trustSubclaims support
|
- `apps/website/src/components/sections/PageHero.astro` — added productVisual + trustSubclaims support
|
||||||
- `apps/website/src/components/primitives/Section.astro` — forward rest attributes (data-section)
|
- `apps/website/src/components/primitives/Section.astro` — forward rest attributes (data-section)
|
||||||
- `apps/website/src/components/primitives/Card.astro` — forward rest attributes (data-hero-visual)
|
- `apps/website/src/components/primitives/Card.astro` — forward rest attributes (data-hero-visual)
|
||||||
- `apps/website/tests/smoke/home-product.spec.ts` — rewritten for Spec 216 homepage structure
|
- `apps/website/tests/smoke/home-product.spec.ts` — rewritten for Spec 217 homepage structure
|
||||||
- `apps/website/tests/smoke/smoke-helpers.ts` — 4 new assertion helpers
|
- `apps/website/tests/smoke/smoke-helpers.ts` — 4 new assertion helpers
|
||||||
- `apps/website/tests/smoke/visual-foundation-guardrails.spec.ts` — updated CTA labels
|
- `apps/website/tests/smoke/visual-foundation-guardrails.spec.ts` — updated CTA labels
|
||||||
|
|
||||||
### Homepage Section Flow (Spec 216)
|
### Homepage Section Flow (Spec 217)
|
||||||
|
|
||||||
1. **PageHero** — eyebrow, headline, description, primary/secondary CTA, product visual, trust subclaims
|
1. **PageHero** — eyebrow, headline, description, primary/secondary CTA, product visual, trust subclaims
|
||||||
2. **LogoStrip** — ecosystem fit (Microsoft Graph, Entra ID, Intune, Review workflows)
|
2. **LogoStrip** — ecosystem fit (Microsoft Graph, Entra ID, Intune, Review workflows)
|
||||||
80
specs/217-homepage-structure/quickstart.md
Normal file
80
specs/217-homepage-structure/quickstart.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# Quickstart: Website Homepage Structure & Section Model
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Verify that the homepage in `apps/website` follows the Spec 217 section contract and routes visitors clearly into Product, Trust, Changelog, and Contact.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js 20+
|
||||||
|
- Corepack enabled
|
||||||
|
- Repo dependencies installed with `corepack pnpm install`
|
||||||
|
|
||||||
|
## Run the website locally
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
corepack pnpm dev:website
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternative, inside the website app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/website
|
||||||
|
corepack pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Default local URL: `http://127.0.0.1:4321/`
|
||||||
|
|
||||||
|
## What to verify on the homepage
|
||||||
|
|
||||||
|
Check the homepage in this order:
|
||||||
|
|
||||||
|
1. Header and global navigation expose Product, Trust, Changelog, and Contact, with no prominent links to unsubstantial optional routes.
|
||||||
|
2. Hero shows one dominant primary anchor, one dominant primary CTA, one secondary deepening CTA, and a product-near visual.
|
||||||
|
3. Hero typography, spacing, and surface contrast feel deliberate rather than generic or washed out.
|
||||||
|
4. Hero visual reads as governance-, review-, restore-, drift-, or evidence-oriented product truth rather than generic dashboard wallpaper.
|
||||||
|
5. Supporting copy and secondary CTA clearly serve the primary anchor instead of competing with it.
|
||||||
|
6. Accent use supports hierarchy and product truth rather than behaving like decorative garnish.
|
||||||
|
7. Outcome framing explains why the product matters in buyer language rather than route or feature-admin language.
|
||||||
|
8. Capability section groups the product model instead of listing a flat feature wall.
|
||||||
|
9. Trust block appears before the final CTA and routes to `/trust`.
|
||||||
|
10. Progress block shows visible dated product movement and routes to `/changelog`.
|
||||||
|
11. Final CTA offers one clear next step, currently `/contact`.
|
||||||
|
12. Footer keeps Product, Trust, Changelog, Contact, Privacy, and Imprint reachable.
|
||||||
|
|
||||||
|
## Addendum review note
|
||||||
|
|
||||||
|
The hero-direction checks above remain the manual review rubric for overall quality, but they are no longer manual-only. Phase 7 now adds automated smoke proof for the explicit primary anchor, supporting-copy subordination, CTA-anchor reinforcement, governance-specific visual semantics, and desktop/mobile hierarchy.
|
||||||
|
|
||||||
|
## Build proof
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
corepack pnpm build:website
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser smoke proof
|
||||||
|
|
||||||
|
Run the website smoke suite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/website
|
||||||
|
corepack pnpm exec playwright test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected proof points
|
||||||
|
|
||||||
|
- Homepage required blocks are visible in the intended order.
|
||||||
|
- The hero CTA hierarchy remains clear and non-competing.
|
||||||
|
- The hero exposes one explicit primary anchor and keeps supporting copy visually subordinate to it.
|
||||||
|
- The hero has one obvious focal point and does not flatten into neutral mush.
|
||||||
|
- The hero visual conveys TenantAtlas-specific governance truth rather than generic admin or analytics UI.
|
||||||
|
- The hero desktop layout keeps copy and product surface in one split composition instead of stacking them into unrelated blocks.
|
||||||
|
- Supporting copy and the secondary CTA reinforce the focal point instead of competing with it.
|
||||||
|
- Hero hierarchy remains legible on both desktop and mobile widths.
|
||||||
|
- `/product`, `/trust`, `/changelog`, and `/contact` are reachable from the homepage.
|
||||||
|
- Optional unpublished routes are not surfaced prominently.
|
||||||
|
- The homepage remains readable on desktop and mobile widths.
|
||||||
@ -3,7 +3,7 @@ # Research: Website Homepage Structure & Section Model
|
|||||||
## Decision 1: Keep the homepage implementation local to the static Astro website
|
## Decision 1: Keep the homepage implementation local to the static Astro website
|
||||||
|
|
||||||
- **Decision**: Continue treating the homepage as a static `apps/website` route composed from Astro content modules and section components, with no runtime dependency on `apps/platform`.
|
- **Decision**: Continue treating the homepage as a static `apps/website` route composed from Astro content modules and section components, with no runtime dependency on `apps/platform`.
|
||||||
- **Rationale**: Spec 216 is explicitly website-only. The current website already runs as a standalone Astro app, and the required homepage improvements concern structure, sequencing, and public route discoverability rather than dynamic runtime behavior.
|
- **Rationale**: Spec 217 is explicitly website-only. The current website already runs as a standalone Astro app, and the required homepage improvements concern structure, sequencing, and public route discoverability rather than dynamic runtime behavior.
|
||||||
- **Alternatives considered**:
|
- **Alternatives considered**:
|
||||||
- Couple homepage composition to `apps/platform` or a shared API: rejected because the spec forbids platform obligations and the homepage needs no dynamic platform data.
|
- Couple homepage composition to `apps/platform` or a shared API: rejected because the spec forbids platform obligations and the homepage needs no dynamic platform data.
|
||||||
- Introduce a CMS or page-builder layer first: rejected because a single homepage route does not justify that operational overhead.
|
- Introduce a CMS or page-builder layer first: rejected because a single homepage route does not justify that operational overhead.
|
||||||
@ -21,7 +21,7 @@ ## Decision 3: Recompose the homepage into an explicit narrative flow
|
|||||||
- **Decision**: Implement the homepage in the following functional order: header, hero, outcome framing, capability model, trust, progress, CTA, footer. Optional supporting context stays secondary and may only appear if it reinforces clarity.
|
- **Decision**: Implement the homepage in the following functional order: header, hero, outcome framing, capability model, trust, progress, CTA, footer. Optional supporting context stays secondary and may only appear if it reinforces clarity.
|
||||||
- **Rationale**: Exploration of the current homepage showed that the site already has hero, optional ecosystem context, and CTA pieces, but the middle narrative is misaligned: the current feature grid explains route jobs instead of product outcomes or capabilities, trust is too implicit, and progress is only a CTA target.
|
- **Rationale**: Exploration of the current homepage showed that the site already has hero, optional ecosystem context, and CTA pieces, but the middle narrative is misaligned: the current feature grid explains route jobs instead of product outcomes or capabilities, trust is too implicit, and progress is only a CTA target.
|
||||||
- **Alternatives considered**:
|
- **Alternatives considered**:
|
||||||
- Keep the current hero → ecosystem → route-jobs → proof → CTA sequence: rejected because it does not satisfy Spec 216’s required block responsibilities.
|
- Keep the current hero → ecosystem → route-jobs → proof → CTA sequence: rejected because it does not satisfy Spec 217’s required block responsibilities.
|
||||||
- Collapse trust or progress into the CTA block: rejected because the spec requires both to appear explicitly before the final CTA.
|
- Collapse trust or progress into the CTA block: rejected because the spec requires both to appear explicitly before the final CTA.
|
||||||
|
|
||||||
## Decision 4: Reuse existing Trust and Changelog truth for homepage proof blocks
|
## Decision 4: Reuse existing Trust and Changelog truth for homepage proof blocks
|
||||||
@ -34,8 +34,16 @@ ## Decision 4: Reuse existing Trust and Changelog truth for homepage proof block
|
|||||||
|
|
||||||
## Decision 5: Validate through the existing website smoke harness
|
## Decision 5: Validate through the existing website smoke harness
|
||||||
|
|
||||||
- **Decision**: Prove Spec 216 with the existing website build command and focused Playwright smoke updates for homepage section order, CTA hierarchy, and onward route reachability.
|
- **Decision**: Prove Spec 217 with the existing website build command and focused Playwright smoke updates for homepage section order, CTA hierarchy, and onward route reachability.
|
||||||
- **Rationale**: The homepage contract is about public rendering, navigational clarity, and responsive visibility. Browser smoke coverage is the narrowest proving layer that can validate those concerns.
|
- **Rationale**: The homepage contract is about public rendering, navigational clarity, and responsive visibility. Browser smoke coverage is the narrowest proving layer that can validate those concerns.
|
||||||
- **Alternatives considered**:
|
- **Alternatives considered**:
|
||||||
- Build-only proof alone: rejected because static output generation does not prove section order, CTA hierarchy, or visible route reachability.
|
- Build-only proof alone: rejected because static output generation does not prove section order, CTA hierarchy, or visible route reachability.
|
||||||
- Add visual regression or heavier browser matrices immediately: rejected because the feature scope does not require that extra cost.
|
- Add visual regression or heavier browser matrices immediately: rejected because the feature scope does not require that extra cost.
|
||||||
|
|
||||||
|
## Decision 6: The hero must favor distinctive restraint over generic neutral minimalism
|
||||||
|
|
||||||
|
- **Decision**: Treat the homepage hero as a brand and product identity surface with one clear anchor, governance-specific product truth, and enough contrast to avoid neutral drift.
|
||||||
|
- **Rationale**: Structural correctness alone still allows a failure mode where the hero feels like a clean but anonymous shadcn or Tailwind marketing shell. TenantAtlas needs a hero that remains calm while still being memorable, product-near, and clearly governance-oriented.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Keep iterating only on structure and copy while leaving art direction implicit: rejected because that path still permits correct-but-forgettable output.
|
||||||
|
- Solve distinctiveness mainly through stronger color or decoration: rejected because the desired signal is precise, enterprise, and trust-first rather than flashy.
|
||||||
@ -1,9 +1,9 @@
|
|||||||
# Feature Specification: Website Homepage Structure & Section Model
|
# Feature Specification: Website Homepage Structure & Section Model
|
||||||
|
|
||||||
**Feature Branch**: `216-homepage-structure`
|
**Feature Branch**: `217-homepage-structure`
|
||||||
**Created**: 2026-04-19
|
**Created**: 2026-04-19
|
||||||
**Status**: Draft
|
**Status**: Draft
|
||||||
**Input**: User description: "Define Spec 216 as the website-only homepage structure and section model for `apps/website`, covering required sections, ordering, CTA logic, trust signal placement, product visuals, and onward routing."
|
**Input**: User description: "Define Spec 217 as the website-only homepage structure and section model for `apps/website`, covering required sections, ordering, CTA logic, trust signal placement, product visuals, and onward routing."
|
||||||
|
|
||||||
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
|
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
|
||||||
|
|
||||||
@ -113,6 +113,33 @@ ### Edge Cases
|
|||||||
- How does the homepage behave on narrow screens? The same meaning order must survive mobile compression, and trust, progress, product-near context, and the primary CTA must remain visible without horizontal scrolling.
|
- How does the homepage behave on narrow screens? The same meaning order must survive mobile compression, and trust, progress, product-near context, and the primary CTA must remain visible without horizontal scrolling.
|
||||||
- What happens when the changelog surface has only a small amount of published history? The progress signal may stay concise, but it must still indicate real dated movement and link into the actual changelog route.
|
- What happens when the changelog surface has only a small amount of published history? The progress signal may stay concise, but it must still indicate real dated movement and link into the actual changelog route.
|
||||||
|
|
||||||
|
## Hero Direction Addendum *(Homepage Hero Refinement / Anti-Generic Direction)*
|
||||||
|
|
||||||
|
This addendum sharpens Spec 217 without widening scope beyond `apps/website`. The base homepage contract already fixes structure, routing, trust sequencing, and CTA discipline. This refinement adds the missing art-direction guardrail: the hero must not be merely correct and calm; it must also be distinct, product-specific, and immediately recognizable as TenantAtlas.
|
||||||
|
|
||||||
|
### Additional Problem Definition
|
||||||
|
|
||||||
|
- A hero can satisfy the structural contract and still fail if it has no clear visual stance, feels like a neat shadcn or Tailwind midpoint, spreads attention evenly, or uses so many similar neutral surfaces that nothing leads.
|
||||||
|
- TenantAtlas needs a hero that reads as a serious governance surface, not as a generic friendly SaaS shell with better spacing.
|
||||||
|
- The failure mode to avoid is not visual loudness but visual anonymity: a clean hero that nobody remembers after ten seconds is still a failed hero.
|
||||||
|
|
||||||
|
### Direction Principles
|
||||||
|
|
||||||
|
- **Distinctive restraint**: The hero SHOULD remain calm, but calmness MUST still feel intentional and ownable rather than default-neutral.
|
||||||
|
- **One dominant idea**: The hero MUST have one clearly dominant focal point, whether that is the headline, the product visual, or a deliberately weighted composition of both.
|
||||||
|
- **Product-first art direction**: The hero SHOULD feel like an entry into the product, not a generic marketing scaffold decorated with UI.
|
||||||
|
- **Controlled brand signal**: Brand signal MUST come primarily from typography, contrast, composition, product truth, and accent discipline rather than from decorative effects.
|
||||||
|
- **No neutral drift**: Neutral-first styling MAY remain the baseline, but the hero MUST NOT flatten into washed-out sameness, accidental softness, or brandless enterprise blandness.
|
||||||
|
|
||||||
|
### Hero Anti-Patterns
|
||||||
|
|
||||||
|
- **Correct but forgettable**: Everything is structurally right, but nothing leaves a mark.
|
||||||
|
- **Neutral mush**: Too many similar light surfaces and too little hierarchy or focus.
|
||||||
|
- **Dashboard wallpaper**: The product visual exists, but it behaves as decorative scenery rather than as a meaning-carrying surface.
|
||||||
|
- **Generic shadcn marketing**: Good spacing and clean cards, but no product-specific identity or visual stance.
|
||||||
|
- **Over-disciplined minimalism**: The hero becomes so restrained that it stops leading.
|
||||||
|
- **Brandless enterprise**: The page looks professional, but not like TenantAtlas.
|
||||||
|
|
||||||
## Requirements *(mandatory)*
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
This feature changes only the public homepage in `apps/website`. It introduces no Microsoft Graph calls, no platform authorization changes, no Filament surfaces, no queued work, and no runtime coupling to `apps/platform`.
|
This feature changes only the public homepage in `apps/website`. It introduces no Microsoft Graph calls, no platform authorization changes, no Filament surfaces, no queued work, and no runtime coupling to `apps/platform`.
|
||||||
@ -140,6 +167,17 @@ ### Functional Requirements
|
|||||||
- **FR-019**: The homepage MUST stay understandable and structurally equivalent on mobile. Hero, outcome, capability, trust, progress, and CTA blocks MUST remain recognizable on narrow screens, and mobile compression MUST NOT effectively hide trust or product-near context.
|
- **FR-019**: The homepage MUST stay understandable and structurally equivalent on mobile. Hero, outcome, capability, trust, progress, and CTA blocks MUST remain recognizable on narrow screens, and mobile compression MUST NOT effectively hide trust or product-near context.
|
||||||
- **FR-020**: The homepage MUST avoid the following disallowed patterns: template-first SaaS framing, abstract-only storytelling, unstructured feature walls, hidden trust, demo-only pressure, fake social proof, and enterprise-theater claims.
|
- **FR-020**: The homepage MUST avoid the following disallowed patterns: template-first SaaS framing, abstract-only storytelling, unstructured feature walls, hidden trust, demo-only pressure, fake social proof, and enterprise-theater claims.
|
||||||
- **FR-021**: The homepage MUST remain strictly local to `apps/website` and MUST NOT create implementation or contract requirements for `apps/platform`.
|
- **FR-021**: The homepage MUST remain strictly local to `apps/website` and MUST NOT create implementation or contract requirements for `apps/platform`.
|
||||||
|
- **FR-022**: The homepage hero MUST be distinct and memorable enough to signal TenantAtlas as a serious governance surface; structural correctness and visual cleanliness alone are not sufficient.
|
||||||
|
- **FR-023**: The hero MUST establish one clear primary anchor through the headline, the product visual, or a deliberately weighted composition of both. Text, CTA, badge, and product visual MUST NOT all compete as equally weak elements.
|
||||||
|
- **FR-024**: The hero MUST create internal contrast through scale, whitespace, typography, surface hierarchy, accent placement, or a combination of these. Near-identical surface values across the text block, supporting elements, and visual block are not sufficient.
|
||||||
|
- **FR-025**: Hero typography SHOULD create more tension than standard UI copy through a clear size hierarchy, controlled line breaks, display treatment, or similarly intentional framing. The headline MUST remain scannable and formally deliberate rather than blocky or purely utilitarian.
|
||||||
|
- **FR-026**: Supporting copy in the hero MUST serve the headline focus and MUST NOT claim equal visual priority.
|
||||||
|
- **FR-027**: The hero product visual MUST depict governance-, audit-, drift-, review-, restore-, evidence-, or bounded-access-oriented product truth and MUST NOT read as a generic analytics dashboard, vague KPI slab, or decorative admin table.
|
||||||
|
- **FR-028**: The hero product visual SHOULD feel compositionally integrated with the hero surface and MUST NOT read as a generic screenshot box inserted beside unrelated marketing copy.
|
||||||
|
- **FR-029**: Neutral-first color usage MAY remain the baseline, but the hero MUST still provide a clear hierarchy and MUST NOT collapse into washed-out sameness or accidental softness.
|
||||||
|
- **FR-030**: Brand signal in the hero MUST come primarily from typography, contrast, composition, product specificity, and disciplined accent usage rather than from decorative effects or scattered color.
|
||||||
|
- **FR-031**: Hero CTA composition MUST reinforce the primary anchor. The primary CTA MUST remain clearly dominant, and the secondary CTA SHOULD remain legible without reading as a generic outline fallback detached from the rest of the hero.
|
||||||
|
- **FR-032**: The hero MUST avoid the following additional anti-patterns: correct but forgettable, neutral mush, dashboard wallpaper, generic shadcn marketing, over-disciplined minimalism, and brandless enterprise.
|
||||||
|
|
||||||
### Key Entities *(include if feature involves data)*
|
### Key Entities *(include if feature involves data)*
|
||||||
|
|
||||||
@ -148,6 +186,9 @@ ### Key Entities *(include if feature involves data)*
|
|||||||
- **Trust Claim**: A bounded public assertion about hosting, residency, seriousness, isolation, governance posture, or similar credibility signals that must be supportable by the Trust surface.
|
- **Trust Claim**: A bounded public assertion about hosting, residency, seriousness, isolation, governance posture, or similar credibility signals that must be supportable by the Trust surface.
|
||||||
- **Progress Signal**: A homepage block or teaser that shows visible dated product movement and routes to the changelog.
|
- **Progress Signal**: A homepage block or teaser that shows visible dated product movement and routes to the changelog.
|
||||||
- **CTA Target**: A next-question route reached from the homepage, especially Product, Trust, Changelog, or Contact.
|
- **CTA Target**: A next-question route reached from the homepage, especially Product, Trust, Changelog, or Contact.
|
||||||
|
- **Hero Primary Anchor**: The single dominant focal point in the hero, formed by the headline, the product visual, or an intentionally weighted composition of both.
|
||||||
|
- **Governance-Specific Product Visual**: A hero visual that communicates change history, baselines, drift, findings, review workflows, restore planning, evidence, or scoped actions rather than generic KPI output.
|
||||||
|
- **Hero Anti-Pattern**: A hero failure mode such as neutral mush or dashboard wallpaper that can satisfy structural correctness while still failing memorability or product specificity.
|
||||||
|
|
||||||
## Assumptions & Dependencies
|
## Assumptions & Dependencies
|
||||||
|
|
||||||
@ -167,3 +208,8 @@ ### Measurable Outcomes
|
|||||||
- **SC-004**: Trust and progress signals appear before the final CTA and remain discoverable without leaving the homepage, while deeper substantiation stays reachable in one click to `/trust` and `/changelog`.
|
- **SC-004**: Trust and progress signals appear before the final CTA and remain discoverable without leaving the homepage, while deeper substantiation stays reachable in one click to `/trust` and `/changelog`.
|
||||||
- **SC-005**: No released homepage version contains unsupported trust claims, fake logos or badges, placeholder routes, or more than one equally dominant primary conversion action.
|
- **SC-005**: No released homepage version contains unsupported trust claims, fake logos or badges, placeholder routes, or more than one equally dominant primary conversion action.
|
||||||
- **SC-006**: On mobile widths, visitors can still identify the hero, outcome framing, capability model, trust block, progress block, and CTA transition without horizontal scrolling or hidden primary navigation.
|
- **SC-006**: On mobile widths, visitors can still identify the hero, outcome framing, capability model, trust block, progress block, and CTA transition without horizontal scrolling or hidden primary navigation.
|
||||||
|
- **SC-007**: In a homepage review, a reviewer can identify the hero's primary anchor within 10 seconds and can distinguish headline, product visual, and CTA hierarchy without ambiguity.
|
||||||
|
- **SC-008**: The hero visual communicates at least one TenantAtlas-specific governance concept such as drift, review, restore, evidence, or bounded access rather than generic dashboard activity.
|
||||||
|
- **SC-009**: Reviewers can identify at least one typographic or compositional cue and one product-truth cue that distinguish the hero from a generic shadcn or Tailwind marketing layout.
|
||||||
|
- **SC-010**: On desktop and mobile, the hero retains clear contrast between the dominant element, supporting copy, product visual, and CTA instead of flattening into visually equal neutral surfaces.
|
||||||
|
- **SC-011**: The released homepage hero avoids the anti-patterns of neutral mush and correct-but-forgettable minimalism while remaining calm and trust-oriented.
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Tasks: Website Homepage Structure & Section Model
|
# Tasks: Website Homepage Structure & Section Model
|
||||||
|
|
||||||
**Input**: Design documents from `/specs/216-homepage-structure/`
|
**Input**: Design documents from `/specs/217-homepage-structure/`
|
||||||
**Prerequisites**: `plan.md`, `spec.md`, `research.md`, `data-model.md`, `quickstart.md`, `contracts/homepage-surface.openapi.yaml`
|
**Prerequisites**: `plan.md`, `spec.md`, `research.md`, `data-model.md`, `quickstart.md`, `contracts/homepage-surface.openapi.yaml`
|
||||||
|
|
||||||
**Tests**: Browser smoke coverage and the root website build proof are required for this runtime-changing website feature.
|
**Tests**: Browser smoke coverage and the root website build proof are required for this runtime-changing website feature.
|
||||||
@ -49,7 +49,7 @@ ### Tests for User Story 1
|
|||||||
|
|
||||||
### Implementation for User Story 1
|
### Implementation for User Story 1
|
||||||
|
|
||||||
- [X] T006 [P] [US1] Add Spec 216 hero content, product-near visual data, optional bounded trust subclaims, and outcome blocks in `apps/website/src/content/pages/home.ts`
|
- [X] T006 [P] [US1] Add Spec 217 hero content, product-near visual data, optional bounded trust subclaims, and outcome blocks in `apps/website/src/content/pages/home.ts`
|
||||||
- [X] T007 [US1] Implement the hero-to-outcome homepage flow and hero visual rendering in `apps/website/src/pages/index.astro` and `apps/website/src/components/sections/PageHero.astro`
|
- [X] T007 [US1] Implement the hero-to-outcome homepage flow and hero visual rendering in `apps/website/src/pages/index.astro` and `apps/website/src/components/sections/PageHero.astro`
|
||||||
|
|
||||||
**Checkpoint**: The homepage delivers the MVP story of product clarity, buyer relevance, and one clear next step.
|
**Checkpoint**: The homepage delivers the MVP story of product clarity, buyer relevance, and one clear next step.
|
||||||
@ -105,9 +105,20 @@ ## Phase 6: Polish & Cross-Cutting Concerns
|
|||||||
**Purpose**: Validate proof commands, tighten claim wording, and capture close-out notes.
|
**Purpose**: Validate proof commands, tighten claim wording, and capture close-out notes.
|
||||||
|
|
||||||
- [X] T016 [P] Review homepage proof and trust wording against bounded-claim rules in `apps/website/src/content/pages/home.ts`, `apps/website/src/content/pages/trust.ts`, and `apps/website/src/content/pages/changelog.ts`
|
- [X] T016 [P] Review homepage proof and trust wording against bounded-claim rules in `apps/website/src/content/pages/home.ts`, `apps/website/src/content/pages/trust.ts`, and `apps/website/src/content/pages/changelog.ts`
|
||||||
- [X] T017 [P] Run `corepack pnpm build:website` from `package.json` and confirm homepage proof expectations in `specs/216-homepage-structure/quickstart.md`
|
- [X] T017 [P] Run `corepack pnpm build:website` from `package.json` and confirm homepage proof expectations in `specs/217-homepage-structure/quickstart.md`
|
||||||
- [X] T018 [P] Run `cd apps/website && corepack pnpm exec playwright test` against `apps/website/tests/smoke/home-product.spec.ts`, `apps/website/tests/smoke/changelog-core-ia.spec.ts`, and `apps/website/tests/smoke/contact-legal.spec.ts`
|
- [X] T018 [P] Run `cd apps/website && corepack pnpm exec playwright test` against `apps/website/tests/smoke/home-product.spec.ts`, `apps/website/tests/smoke/changelog-core-ia.spec.ts`, and `apps/website/tests/smoke/contact-legal.spec.ts`
|
||||||
- [X] T019 Record the homepage smoke-coverage close-out and verification notes in `specs/216-homepage-structure/plan.md` and `specs/216-homepage-structure/quickstart.md`
|
- [X] T019 Record the homepage smoke-coverage close-out and verification notes in `specs/217-homepage-structure/plan.md` and `specs/217-homepage-structure/quickstart.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Hero Direction Addendum Follow-Up
|
||||||
|
|
||||||
|
**Purpose**: Sharpen the homepage hero so Spec 217 remains product-specific, memorable, and resistant to generic neutral drift.
|
||||||
|
|
||||||
|
- [X] T020 [P] Add homepage proof coverage for a clear hero primary anchor, supporting-copy subordination, CTA-anchor reinforcement, governance-specific product visual semantics, and desktop/mobile anti-generic hierarchy in `apps/website/tests/smoke/home-product.spec.ts` and `apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
- [X] T021 [P] Extend homepage content and hero type contracts for stronger typographic tension, a dominant anchor, and governance-specific visual truth in `apps/website/src/content/pages/home.ts` and `apps/website/src/types/site.ts`
|
||||||
|
- [X] T022 Rework hero composition, accent discipline, and product-visual integration to avoid dashboard-wallpaper, neutral-mush, and detached-outline-CTA outcomes in `apps/website/src/components/sections/PageHero.astro`, `apps/website/src/components/content/HeroDashboard.astro`, and `apps/website/src/pages/index.astro`
|
||||||
|
- [X] T023 [P] Re-run `corepack pnpm build:website` plus `cd apps/website && corepack pnpm exec playwright test`, then refresh Phase 7 proof notes in `specs/217-homepage-structure/quickstart.md` and `specs/217-homepage-structure/plan.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -119,6 +130,7 @@ ### Phase Dependencies
|
|||||||
- **Foundational (Phase 2)**: Depends on Setup and blocks all user stories.
|
- **Foundational (Phase 2)**: Depends on Setup and blocks all user stories.
|
||||||
- **User Stories (Phases 3-5)**: Depend on Foundational. They remain independently testable, but shared homepage assembly in `apps/website/src/pages/index.astro` should land sequentially in story order.
|
- **User Stories (Phases 3-5)**: Depend on Foundational. They remain independently testable, but shared homepage assembly in `apps/website/src/pages/index.astro` should land sequentially in story order.
|
||||||
- **Polish (Phase 6)**: Depends on all desired user stories being complete.
|
- **Polish (Phase 6)**: Depends on all desired user stories being complete.
|
||||||
|
- **Hero Direction Addendum (Phase 7)**: Depends on the structural homepage work in Phases 1-6 and sharpens the hero without expanding the rest of the homepage contract.
|
||||||
|
|
||||||
### User Story Dependencies
|
### User Story Dependencies
|
||||||
|
|
||||||
@ -150,7 +162,7 @@ ## Parallel Example: User Story 1
|
|||||||
```bash
|
```bash
|
||||||
# After the foundations are complete, split the first slice into test + content work:
|
# After the foundations are complete, split the first slice into test + content work:
|
||||||
Task: "T005 [US1] Write failing homepage smoke assertions for hero clarity, outcome framing, and one dominant CTA hierarchy"
|
Task: "T005 [US1] Write failing homepage smoke assertions for hero clarity, outcome framing, and one dominant CTA hierarchy"
|
||||||
Task: "T006 [US1] Add Spec 216 hero and outcome content blocks"
|
Task: "T006 [US1] Add Spec 217 hero and outcome content blocks"
|
||||||
|
|
||||||
# Then assemble the homepage route:
|
# Then assemble the homepage route:
|
||||||
Task: "T007 [US1] Implement the hero-to-outcome homepage flow"
|
Task: "T007 [US1] Implement the hero-to-outcome homepage flow"
|
||||||
@ -198,6 +210,7 @@ ### Incremental Delivery
|
|||||||
3. US2 upgrades the homepage middle narrative into grouped product model, trust, and progress proof.
|
3. US2 upgrades the homepage middle narrative into grouped product model, trust, and progress proof.
|
||||||
4. US3 sharpens onward routing and discoverability for Product, Trust, Changelog, Contact, and legal follow-through.
|
4. US3 sharpens onward routing and discoverability for Product, Trust, Changelog, Contact, and legal follow-through.
|
||||||
5. Polish runs both proof commands, validates wording, and records close-out notes before merge.
|
5. Polish runs both proof commands, validates wording, and records close-out notes before merge.
|
||||||
|
6. Phase 7 refines the hero art direction so the homepage avoids generic neutral drift while preserving the original structure contract.
|
||||||
|
|
||||||
### Suggested MVP Scope
|
### Suggested MVP Scope
|
||||||
|
|
||||||
35
specs/218-homepage-hero/checklists/requirements.md
Normal file
35
specs/218-homepage-hero/checklists/requirements.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Specification Quality Checklist: Website Homepage Hero
|
||||||
|
|
||||||
|
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||||
|
**Created**: 2026-04-19
|
||||||
|
**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 pass 1 completed successfully.
|
||||||
|
- No clarification questions were required because the hero role, required elements, CTA structure, visual constraints, and website-only scope boundaries were explicit in the input.
|
||||||
148
specs/218-homepage-hero/contracts/homepage-hero.openapi.yaml
Normal file
148
specs/218-homepage-hero/contracts/homepage-hero.openapi.yaml
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: TenantAtlas Homepage Hero Contract
|
||||||
|
version: 0.1.0
|
||||||
|
summary: Semantic contract for the `apps/website` homepage hero in Spec 218.
|
||||||
|
description: >-
|
||||||
|
This contract defines the public HTML routes that participate in the
|
||||||
|
homepage hero journey for Spec 218. The homepage remains a static Astro
|
||||||
|
surface and must present a clear category context, one headline, one
|
||||||
|
supporting-copy block, one CTA pair, a product-near visual, and optional
|
||||||
|
bounded trust cues while routing visitors into Product, Trust, Changelog,
|
||||||
|
and Contact.
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:{port}
|
||||||
|
description: Local Astro development or preview server
|
||||||
|
variables:
|
||||||
|
port:
|
||||||
|
default: '4321'
|
||||||
|
tags:
|
||||||
|
- name: Homepage Hero Journey
|
||||||
|
description: Public HTML routes used by the homepage hero contract
|
||||||
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
tags: [Homepage Hero Journey]
|
||||||
|
operationId: getHomepageHero
|
||||||
|
summary: Homepage hero
|
||||||
|
description: >-
|
||||||
|
Product-near homepage hero that positions the product, explains the
|
||||||
|
problem space, offers one clear CTA pair, and establishes bounded
|
||||||
|
first-read credibility without replacing the deeper Product or Trust
|
||||||
|
surfaces.
|
||||||
|
x-tenantatlas-homepage-hero:
|
||||||
|
requiredElements:
|
||||||
|
- category-context
|
||||||
|
- headline
|
||||||
|
- supporting-copy
|
||||||
|
- primary-cta
|
||||||
|
- secondary-cta
|
||||||
|
- product-near-visual
|
||||||
|
optionalElements:
|
||||||
|
- trust-subclaims
|
||||||
|
contentPriority:
|
||||||
|
- product-and-problem-understanding
|
||||||
|
- clear-next-step
|
||||||
|
- product-reality
|
||||||
|
- early-trust
|
||||||
|
- stylistic-finish
|
||||||
|
primaryCtaTargets:
|
||||||
|
- /contact
|
||||||
|
- /demo
|
||||||
|
secondaryCtaTargets:
|
||||||
|
- /product
|
||||||
|
- /trust
|
||||||
|
- /changelog
|
||||||
|
onwardRoutes:
|
||||||
|
- /product
|
||||||
|
- /trust
|
||||||
|
- /changelog
|
||||||
|
- /contact
|
||||||
|
mobileMeaningOrder:
|
||||||
|
- headline
|
||||||
|
- supporting-copy
|
||||||
|
- cta-pair
|
||||||
|
- product-near-visual
|
||||||
|
- trust-subclaims
|
||||||
|
productVisualRules:
|
||||||
|
- derived-from-real-product-structure
|
||||||
|
- no-generic-dashboard-wallpaper
|
||||||
|
- no-fake-metrics
|
||||||
|
- alt-text-must-be-product-specific
|
||||||
|
trustSubclaimRules:
|
||||||
|
- factually-supportable
|
||||||
|
- concise
|
||||||
|
- supportable-by-trust-surface
|
||||||
|
- no-legal-or-compliance-guarantees
|
||||||
|
- no-badge-wall
|
||||||
|
forbiddenPatterns:
|
||||||
|
- generic-startup-hero
|
||||||
|
- abstract-only-hero
|
||||||
|
- dashboard-wallpaper-hero
|
||||||
|
- badge-overload-hero
|
||||||
|
- sales-pressure-hero
|
||||||
|
- compliance-theater-hero
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Homepage HTML
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HtmlDocument'
|
||||||
|
/product:
|
||||||
|
get:
|
||||||
|
tags: [Homepage Hero Journey]
|
||||||
|
operationId: getHomepageHeroProductTarget
|
||||||
|
summary: Product target route
|
||||||
|
description: Deeper product-model route linked from the homepage hero.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Product page HTML
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HtmlDocument'
|
||||||
|
/trust:
|
||||||
|
get:
|
||||||
|
tags: [Homepage Hero Journey]
|
||||||
|
operationId: getHomepageHeroTrustTarget
|
||||||
|
summary: Trust target route
|
||||||
|
description: Bounded trust route that supports any public trust-adjacent hero language.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Trust page HTML
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HtmlDocument'
|
||||||
|
/changelog:
|
||||||
|
get:
|
||||||
|
tags: [Homepage Hero Journey]
|
||||||
|
operationId: getHomepageHeroChangelogTarget
|
||||||
|
summary: Changelog target route
|
||||||
|
description: Dated progress route that may be used as a secondary deepening destination from the homepage journey.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Changelog page HTML
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HtmlDocument'
|
||||||
|
/contact:
|
||||||
|
get:
|
||||||
|
tags: [Homepage Hero Journey]
|
||||||
|
operationId: getHomepageHeroContactTarget
|
||||||
|
summary: Contact target route
|
||||||
|
description: Primary next-step route used by the homepage hero until a distinct public `/demo` route exists.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Contact page HTML
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HtmlDocument'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
HtmlDocument:
|
||||||
|
type: string
|
||||||
|
description: Server-rendered static HTML document
|
||||||
100
specs/218-homepage-hero/data-model.md
Normal file
100
specs/218-homepage-hero/data-model.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Data Model: Website Homepage Hero
|
||||||
|
|
||||||
|
This feature introduces no database schema and no platform-side persistence. The model is file- and route-based inside `apps/website` and defines how the homepage hero expresses product truth, CTA hierarchy, early trust cues, and the next-step path.
|
||||||
|
|
||||||
|
## 1. Homepage Hero Source Object
|
||||||
|
|
||||||
|
Represents the hero content exported from `apps/website/src/content/pages/home.ts`.
|
||||||
|
|
||||||
|
| Field | Type | Description | Rules |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `eyebrow` | string | Short positioning cue or category context | Required; must anchor the hero in a believable category or problem space |
|
||||||
|
| `title` | string | Primary positioning headline | Required; must frame product, problem, or outcome without hype language |
|
||||||
|
| `description` | string | Supporting copy | Required; must sharpen the headline in plain product language |
|
||||||
|
| `primaryCta` | `CtaLink` | Dominant next-step action | Required; exactly one dominant primary CTA |
|
||||||
|
| `secondaryCta` | `CtaLink` | Secondary deepening action | Required for Spec 218 hero contract; must remain lower-emphasis than the primary CTA |
|
||||||
|
| `productVisual` | `HeroVisualContent` | Product-near screenshot or stylized product shot | Required by the hero contract unless a temporary explicit exemption is documented |
|
||||||
|
| `trustSubclaims` | string[] | Optional early trust cues | Optional; must remain short, factual, and bounded |
|
||||||
|
| `highlights` | string[] | Optional lightweight support points | Optional; should not compete with trust subclaims when both exist |
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
- Rendered by `apps/website/src/components/sections/PageHero.astro`
|
||||||
|
- Consumed by `apps/website/src/pages/index.astro`
|
||||||
|
- Secondary truth for trust-facing claims must remain compatible with `apps/website/src/content/pages/trust.ts`
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
- Exactly one primary CTA and one secondary CTA must remain visible as the hero action pair
|
||||||
|
- Headline and description must not collapse into generic SaaS phrasing or stacked claims
|
||||||
|
- Trust subclaims must never become a badge wall or imply legal or compliance guarantees
|
||||||
|
|
||||||
|
## 2. Hero CTA Pair
|
||||||
|
|
||||||
|
Represents the two hero actions as one intentional routing pair.
|
||||||
|
|
||||||
|
| Field | Type | Description | Rules |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `primary` | `CtaLink` | Primary next-step action | Must lead to `/contact`, `/demo`, or an equivalent explicitly approved next step |
|
||||||
|
| `secondary` | `CtaLink` | Lower-emphasis exploration route | Must lead to a maintained informational surface such as `/product`, `/trust`, or `/changelog` |
|
||||||
|
| `dominance` | derived UI property | Relative emphasis between the two CTAs | Primary must remain visually dominant |
|
||||||
|
| `reachability` | derived route truth | Whether the linked routes are real and maintained | Hero must not point to placeholder or immature surfaces |
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
- The pair must communicate one clear next step and one clear deepening path
|
||||||
|
- Multiple equally dominant primary sales actions are forbidden
|
||||||
|
|
||||||
|
## 3. Hero Visual Asset
|
||||||
|
|
||||||
|
Represents the product-near media used in the hero.
|
||||||
|
|
||||||
|
| Field | Type | Description | Rules |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `src` | string | Public asset path | Must resolve to a maintained hero asset in `apps/website/public` or equivalent |
|
||||||
|
| `alt` | string | Accessibility text for the visual | Must describe product-relevant UI truth, not generic marketing scenery |
|
||||||
|
| `truthBasis` | derived review rule | Why the visual is considered product-near | Must be traceable to real product structure or a truthful simplification |
|
||||||
|
| `mobilePersistence` | derived render rule | Whether the visual remains visible on small screens | Must stay visible when it is a key credibility signal |
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
- No fantasy metrics, fake dashboards, or unrelated analytics wallpaper
|
||||||
|
- Cropping or stylization is allowed only when product structure remains clear
|
||||||
|
|
||||||
|
## 4. Trust Subclaim Set
|
||||||
|
|
||||||
|
Represents the optional early-trust layer inside the hero.
|
||||||
|
|
||||||
|
| Field | Type | Description | Rules |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `claims` | string[] | Short factual trust cues | Optional; should remain few and concise |
|
||||||
|
| `supportRoute` | route or derived surface | Where deeper trust context lives | Normally `/trust` |
|
||||||
|
| `visibility` | derived render rule | When trust cues are shown | Show only when claims are supportable and do not compete with the hero core |
|
||||||
|
|
||||||
|
**Validation rules**:
|
||||||
|
- Claims must be factual and publicly supportable
|
||||||
|
- Claims must not imply absolute compliance, security, or hosting guarantees
|
||||||
|
- Trust cues must support the hero, not overrun it
|
||||||
|
|
||||||
|
## 5. Hero Render Contract
|
||||||
|
|
||||||
|
Represents the semantic structure that `PageHero.astro` must preserve.
|
||||||
|
|
||||||
|
| Element | Role | Requirement |
|
||||||
|
|---|---|---|
|
||||||
|
| Category context | Early positioning cue | Must appear before or adjacent to the headline |
|
||||||
|
| Headline | Primary positioning statement | Must remain the dominant text signal |
|
||||||
|
| Supporting copy | Headline clarification | Must remain directly associated with the headline |
|
||||||
|
| CTA pair | Action transition | Must keep one dominant primary and one lower-emphasis secondary action |
|
||||||
|
| Product-near visual | Product truth signal | Must remain part of the hero composition |
|
||||||
|
| Optional trust subclaims | Early credibility cues | Must stay secondary to product and CTA understanding |
|
||||||
|
|
||||||
|
**Mobile meaning order**:
|
||||||
|
- headline and supporting copy
|
||||||
|
- CTA pair
|
||||||
|
- product-near visual
|
||||||
|
- optional trust signals
|
||||||
|
|
||||||
|
## Derived State and Availability
|
||||||
|
|
||||||
|
- No independent state machine is added by this feature.
|
||||||
|
- Hero route availability remains derived from the homepage route at `/`.
|
||||||
|
- Hero CTA reachability remains derived from canonical public routes in `apps/website`.
|
||||||
|
- Trust-subclaim legitimacy remains derived from public website truth, especially the Trust surface.
|
||||||
|
- Product-visual readiness remains asset-based; if the current visual stops being truthful enough, it must be replaced rather than papered over with abstraction.
|
||||||
204
specs/218-homepage-hero/plan.md
Normal file
204
specs/218-homepage-hero/plan.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Implementation Plan: Website Homepage Hero
|
||||||
|
|
||||||
|
**Branch**: `218-homepage-hero` | **Date**: 2026-04-19 | **Spec**: `specs/218-homepage-hero/spec.md`
|
||||||
|
**Input**: Feature specification from `specs/218-homepage-hero/spec.md`
|
||||||
|
|
||||||
|
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Tighten the `apps/website` homepage hero so the first screen satisfies Spec 218: category context, precise headline and supporting copy, one clear CTA pair, a product-near visual, and bounded early trust signals.
|
||||||
|
- Implement the change by reusing the current `homeHero` content object and `PageHero.astro`, adding only the smallest missing semantic hooks, ordering guarantees, and asset or copy refinements instead of introducing a new hero framework.
|
||||||
|
- Validate the result with `corepack pnpm build:website` plus focused Playwright smoke coverage for hero composition, CTA hierarchy, visual truthfulness, and mobile meaning order.
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
**Language/Version**: Astro 6.0.0 templates + TypeScript 5.9.x
|
||||||
|
**Primary Dependencies**: Astro 6, Tailwind CSS v4, existing Astro content modules and section primitives, Playwright browser smoke tests
|
||||||
|
**Storage**: Static filesystem content and assets under `apps/website/src` and `apps/website/public`; no database
|
||||||
|
**Testing**: `corepack pnpm build:website` plus Playwright smoke coverage in `apps/website/tests/smoke`
|
||||||
|
**Validation Lanes**: fast-feedback
|
||||||
|
**Target Platform**: Static public website for modern desktop and mobile browsers
|
||||||
|
**Project Type**: Web application in a monorepo (`apps/platform` plus `apps/website`)
|
||||||
|
**Performance Goals**: Keep the hero server-rendered and readable without required client-side hydration, preserve fast first-read clarity on desktop and mobile, and keep the product visual and CTA visible on narrow screens
|
||||||
|
**Constraints**: Stay strictly inside `apps/website`; preserve canonical core routes (`/`, `/product`, `/trust`, `/changelog`, `/contact`); keep one dominant primary CTA and one lower-emphasis secondary CTA; avoid unsupported trust claims, generic dashboard visuals, and any runtime coupling to `apps/platform`
|
||||||
|
**Scale/Scope**: One homepage route, one shared hero component, one hero content object, one product-near asset, and a focused extension of the existing homepage smoke coverage
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Plan
|
||||||
|
|
||||||
|
- **Guardrail scope**: no operator-facing surface change
|
||||||
|
- **Native vs custom classification summary**: N/A - public Astro website surface only
|
||||||
|
- **Shared-family relevance**: none
|
||||||
|
- **State layers in scope**: page
|
||||||
|
- **Handling modes by drift class or surface**: N/A
|
||||||
|
- **Repository-signal treatment**: report-only
|
||||||
|
- **Special surface test profiles**: N/A
|
||||||
|
- **Required tests or manual smoke**: manual-smoke plus homepage-focused browser smoke coverage
|
||||||
|
- **Exception path and spread control**: none
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||||
|
|
||||||
|
- Inventory-first / snapshots / Graph contract / deterministic capabilities / RBAC-UX / Filament guardrails: N/A for this feature because all work stays on the public Astro website and changes no `/admin`, `/admin/t/{tenant}/...`, or `/system` runtime surfaces.
|
||||||
|
- Read/write separation: Pass. The homepage hero remains a static public read surface. No writes, remote calls, queued work, or contact submission backend are introduced in this feature.
|
||||||
|
- Workspace and tenant isolation: Pass. The hero stays runtime-independent from `apps/platform`, with no shared auth, session, tenant data, or scoped route behavior.
|
||||||
|
- Data minimization: Pass. The feature only refines public copy, CTA paths, visual assets, and render semantics already owned by `apps/website`.
|
||||||
|
- Test governance: Pass. Proof remains in `fast-feedback` through static build output and focused browser smoke coverage, with no database, membership, provider, or heavy-suite defaults.
|
||||||
|
- Proportionality / no premature abstraction: Pass. The plan reuses `home.ts`, `PageHero.astro`, current CTA primitives, and the existing smoke harness instead of introducing a hero registry, CMS layer, or presentation framework.
|
||||||
|
- Persisted truth / new state: Pass. No database artifacts, queues, or independent state machines are added. Hero trust signals and the product-near visual remain file-based and derived from public website truth.
|
||||||
|
- UI semantics / few layers: Pass. The hero contract maps directly from `homeHero` content into `PageHero.astro`, with only thin render hooks or test markers if needed.
|
||||||
|
|
||||||
|
Status: ✅ No constitution violations identified before research.
|
||||||
|
|
||||||
|
## Test Governance Check
|
||||||
|
|
||||||
|
- **Test purpose / classification by changed surface**: Browser
|
||||||
|
- **Affected validation lanes**: fast-feedback
|
||||||
|
- **Why this lane mix is the narrowest sufficient proof**: The feature changes only public hero rendering, CTA emphasis, visual truthfulness, and responsive visibility. Browser smoke coverage is the narrowest layer that can prove those concerns without introducing backend or heavy browser-matrix cost.
|
||||||
|
- **Narrowest proving command(s)**: `corepack pnpm build:website` and `cd apps/website && corepack pnpm exec playwright test tests/smoke/home-product.spec.ts`
|
||||||
|
- **Fixture / helper / factory / seed / context cost risks**: none; public routes do not require database, auth, provider, workspace, or tenant setup
|
||||||
|
- **Expensive defaults or shared helper growth introduced?**: no; any helper additions stay inside the existing `apps/website/tests/smoke` harness and remain homepage-focused
|
||||||
|
- **Heavy-family additions, promotions, or visibility changes**: none
|
||||||
|
- **Surface-class relief / special coverage rule**: N/A
|
||||||
|
- **Closing validation and reviewer handoff**: Re-run the website build and the focused homepage smoke file after hero changes. If shared smoke helpers change materially, reviewers may also run the full website smoke suite. Reviewers should verify required hero elements, one dominant CTA pair, visible product-near media, bounded trust cues, and mobile visibility order.
|
||||||
|
- **Budget / baseline / trend follow-up**: none beyond a small increase in homepage smoke assertions
|
||||||
|
- **Review-stop questions**: Does proof stay homepage-focused and browser-only? Did the change accidentally introduce a new abstraction or shared helper burden? Does the mobile layout keep CTA and product-near visual visible? Are trust claims still bounded and supportable?
|
||||||
|
- **Escalation path**: document-in-feature
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage
|
||||||
|
- **Why no dedicated follow-up spec is needed**: Validation remains feature-local to the homepage hero and the existing website smoke harness. A separate follow-up spec is only needed if screenshot governance, visual-regression tooling, or multi-page hero conventions become shared structural concerns.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
specs/218-homepage-hero/
|
||||||
|
├── plan.md
|
||||||
|
├── research.md
|
||||||
|
├── data-model.md
|
||||||
|
├── quickstart.md
|
||||||
|
├── contracts/
|
||||||
|
│ └── homepage-hero.openapi.yaml
|
||||||
|
└── tasks.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
|
||||||
|
```text
|
||||||
|
apps/website/
|
||||||
|
├── package.json
|
||||||
|
├── public/
|
||||||
|
│ └── images/
|
||||||
|
│ └── hero-product-visual.svg
|
||||||
|
├── src/
|
||||||
|
│ ├── content/
|
||||||
|
│ │ └── pages/
|
||||||
|
│ │ ├── home.ts
|
||||||
|
│ │ ├── product.ts
|
||||||
|
│ │ └── trust.ts
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── content/
|
||||||
|
│ │ ├── primitives/
|
||||||
|
│ │ └── sections/
|
||||||
|
│ │ └── PageHero.astro
|
||||||
|
│ ├── pages/
|
||||||
|
│ │ └── index.astro
|
||||||
|
│ └── types/
|
||||||
|
│ └── site.ts
|
||||||
|
└── tests/
|
||||||
|
└── smoke/
|
||||||
|
├── home-product.spec.ts
|
||||||
|
└── smoke-helpers.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure Decision**: Keep the feature completely inside `apps/website`, reusing the existing `HeroContent` data shape, `PageHero.astro`, and homepage smoke suite. Prefer small edits to `home.ts`, `PageHero.astro`, and homepage smoke assertions over new hero components, registries, or cross-page content frameworks.
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Proportionality Review
|
||||||
|
|
||||||
|
- **Current operator problem**: The homepage hero is the highest-risk public screen for drift into generic marketing or weak product truth, so small regressions there have disproportionate impact on credibility and next-step clarity.
|
||||||
|
- **Existing structure is insufficient because**: Spec 217 establishes homepage section flow broadly, but the current implementation still needs a hero-specific contract to lock down required semantics, CTA hierarchy, product-near truth, and mobile meaning order.
|
||||||
|
- **Narrowest correct implementation**: Reuse the existing `homeHero` content object and `PageHero.astro`, tightening content, render semantics, and smoke coverage only where Spec 218 requires stronger guarantees.
|
||||||
|
- **Ownership cost created**: Ongoing maintenance of explicit hero content rules, a product-near visual asset, and a slightly richer homepage smoke suite.
|
||||||
|
- **Alternative intentionally rejected**: A generic hero framework or a separate CMS-like hero configuration layer was rejected because only one homepage hero needs this contract now and the current Astro content model already fits the problem.
|
||||||
|
- **Release truth**: Current-release truth
|
||||||
|
|
||||||
|
## Phase 0 — Outline & Research (complete)
|
||||||
|
|
||||||
|
- Output: `specs/218-homepage-hero/research.md`
|
||||||
|
- Key decisions captured:
|
||||||
|
- Keep the hero local to the static Astro website and preserve runtime separation from `apps/platform`.
|
||||||
|
- Reuse the existing `homeHero` content object and `PageHero.astro` instead of adding a new hero abstraction.
|
||||||
|
- Treat hero semantics as explicit render responsibilities that can be tested directly.
|
||||||
|
- Keep the product visual truthful and derived from real product structure rather than decorative SaaS wallpaper.
|
||||||
|
- Validate through focused Playwright homepage smoke coverage plus build proof.
|
||||||
|
|
||||||
|
## Phase 1 — Design & Contracts (complete)
|
||||||
|
|
||||||
|
### Data model
|
||||||
|
|
||||||
|
- Output: `specs/218-homepage-hero/data-model.md`
|
||||||
|
- Model remains file- and route-based. No database schema changes are required.
|
||||||
|
|
||||||
|
### Public hero contract
|
||||||
|
|
||||||
|
- Output: `specs/218-homepage-hero/contracts/homepage-hero.openapi.yaml`
|
||||||
|
- Contract captures the homepage route plus the hero-only semantic requirements and downstream route expectations for Product, Trust, Changelog, and Contact.
|
||||||
|
|
||||||
|
### Quickstart
|
||||||
|
|
||||||
|
- Output: `specs/218-homepage-hero/quickstart.md`
|
||||||
|
- Quickstart covers local development, hero-specific verification points, build proof, and focused smoke-test execution.
|
||||||
|
|
||||||
|
### Agent context update
|
||||||
|
|
||||||
|
- Completed via `.specify/scripts/bash/update-agent-context.sh copilot` after plan artifacts were generated.
|
||||||
|
|
||||||
|
### Constitution re-check (post-design)
|
||||||
|
|
||||||
|
- ✅ The plan remains website-only and static, with no platform-runtime coupling.
|
||||||
|
- ✅ No new persistence, state machines, background operations, or auth flows are introduced.
|
||||||
|
- ✅ The chosen shape reuses existing Astro content modules, CTA primitives, and section components instead of adding speculative abstraction.
|
||||||
|
- ✅ Validation remains cheap, local, and aligned with the current website smoke harness.
|
||||||
|
|
||||||
|
## Phase 2 — Implementation Plan (next)
|
||||||
|
|
||||||
|
### Story 1 (P1): Understand the product from the first screen
|
||||||
|
|
||||||
|
- Review and tighten `apps/website/src/content/pages/home.ts` so the hero eyebrow, headline, supporting copy, CTA pair, and visual all align with Spec 218 language and positioning rules without hype or stacked claims.
|
||||||
|
- Keep hero composition inside `apps/website/src/components/sections/PageHero.astro`; add only the smallest missing semantic hooks and DOM structure needed to expose category context, headline, supporting copy, CTA pair, product-near visual, and optional trust subclaims as explicit hero elements.
|
||||||
|
- Preserve one dominant primary CTA to `/contact` and one secondary deepening CTA to `/product`, unless a stronger already-supported secondary destination is chosen during copy review.
|
||||||
|
- Tests / validation:
|
||||||
|
- Extend `apps/website/tests/smoke/home-product.spec.ts` and `apps/website/tests/smoke/smoke-helpers.ts` only as needed to assert the required hero elements and one clear CTA pair.
|
||||||
|
- Re-run `corepack pnpm build:website`.
|
||||||
|
|
||||||
|
### Story 2 (P2): Establish credibility without overclaiming
|
||||||
|
|
||||||
|
- Review `homeHero.trustSubclaims` against the current `/trust` public truth so any hero subclaim remains bounded, factual, concise, and supportable.
|
||||||
|
- Confirm `apps/website/public/images/hero-product-visual.svg` and its alt text remain product-near and truthfully derived from real UI structure; replace or tighten only if the asset reads like a generic dashboard or decorative placeholder.
|
||||||
|
- Ensure the hero stays product-near even if the visual remains stylized, and avoid introducing a separate trust badge, compliance matrix, or homepage-only proof system.
|
||||||
|
- Tests / validation:
|
||||||
|
- Add smoke assertions for visible product-near media, concise trust signals, and continued homepage reachability to `/trust` and other downstream routes.
|
||||||
|
- Keep assertions inside homepage smoke coverage; do not add a new visual-regression matrix.
|
||||||
|
|
||||||
|
### Story 3 (P3): Preserve correct next-step routing on desktop and mobile
|
||||||
|
|
||||||
|
- Verify the hero meaning order stays stable across desktop and narrow screens: category context and headline, supporting copy, CTA pair, product-near visual, and optional trust signals.
|
||||||
|
- Tighten responsive composition inside `PageHero.astro` only as needed to keep CTA and product visual visible without reordering semantics or hiding credibility cues on mobile.
|
||||||
|
- Finalize targeted smoke coverage for mobile visibility and hero-first route reachability into `/product`, `/contact`, and supporting public surfaces.
|
||||||
|
- Tests / validation:
|
||||||
|
- Extend `apps/website/tests/smoke/home-product.spec.ts` with narrow-screen hero checks or extracted helper assertions in `smoke-helpers.ts`.
|
||||||
|
- Run `cd apps/website && corepack pnpm exec playwright test tests/smoke/home-product.spec.ts`.
|
||||||
|
|
||||||
|
## Implementation Close-out
|
||||||
|
|
||||||
|
- Homepage hero delivery stayed local to `apps/website` and reused the existing `homeHero` content object plus `PageHero.astro` with no new hero framework or platform coupling.
|
||||||
|
- The shipped hero now exposes explicit semantic hooks for category context, headline, supporting copy, CTA pair, product-near visual, and trust cues, while keeping one primary CTA to `/contact` and one secondary deepening CTA to `/product`.
|
||||||
|
- The product visual was replaced with a product-near operating-record illustration that avoids fake KPI cards or generic analytics-dashboard theater.
|
||||||
|
- Shared smoke-helper growth stayed homepage-focused in `apps/website/tests/smoke/smoke-helpers.ts`; no auth, backend, provider, or database fixtures were introduced.
|
||||||
|
- Validation completed on 2026-04-19 with `corepack pnpm build:website` and `cd apps/website && corepack pnpm exec playwright test tests/smoke/home-product.spec.ts`.
|
||||||
91
specs/218-homepage-hero/quickstart.md
Normal file
91
specs/218-homepage-hero/quickstart.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# Quickstart: Website Homepage Hero
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Verify that the homepage hero in `apps/website` follows the Spec 218 contract: clear category context, precise copy, one CTA pair, product-near visual truth, bounded trust cues, and stable mobile meaning order.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js 20+
|
||||||
|
- Corepack enabled
|
||||||
|
- Repo dependencies installed with `corepack pnpm install`
|
||||||
|
|
||||||
|
## Run the website locally
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
corepack pnpm dev:website
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternative, inside the website app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/website
|
||||||
|
corepack pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Default local URL: `http://127.0.0.1:4321/`
|
||||||
|
|
||||||
|
## What to verify in the homepage hero
|
||||||
|
|
||||||
|
Check the hero in this order:
|
||||||
|
|
||||||
|
1. A clear category context or positioning cue appears above or adjacent to the main headline.
|
||||||
|
2. The main headline explains the product category, problem space, or intended outcome without generic startup language.
|
||||||
|
3. Supporting copy makes the headline easier to understand instead of repeating it.
|
||||||
|
4. Exactly one dominant primary CTA is visible, plus one lower-emphasis secondary CTA.
|
||||||
|
5. The hero includes a product-near visual that looks derived from real product structure rather than a generic dashboard placeholder.
|
||||||
|
6. Any trust subclaims remain concise, factual, and secondary to the product explanation.
|
||||||
|
7. The hero still routes cleanly into maintained downstream pages such as `/product`, `/trust`, `/changelog`, and `/contact`.
|
||||||
|
|
||||||
|
## Mobile verification
|
||||||
|
|
||||||
|
On a narrow viewport, verify:
|
||||||
|
|
||||||
|
1. Headline and supporting copy remain first in the reading flow.
|
||||||
|
2. CTA pair remains clearly visible without being pushed below decorative content.
|
||||||
|
3. Product-near visual still appears when it is a key credibility signal.
|
||||||
|
4. Optional trust cues stay visible only if they do not bury the main message.
|
||||||
|
|
||||||
|
## Build proof
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
corepack pnpm build:website
|
||||||
|
```
|
||||||
|
|
||||||
|
## Focused browser smoke proof
|
||||||
|
|
||||||
|
Run the homepage-focused smoke file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/website
|
||||||
|
corepack pnpm exec playwright test tests/smoke/home-product.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Optional broader browser proof
|
||||||
|
|
||||||
|
If shared smoke helpers or route behavior changed more broadly, run the full website smoke suite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/website
|
||||||
|
corepack pnpm exec playwright test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected proof points
|
||||||
|
|
||||||
|
- Hero required elements are all visible.
|
||||||
|
- One dominant primary CTA and one secondary CTA remain clearly differentiated.
|
||||||
|
- Product-near visual remains visible and believable.
|
||||||
|
- Trust subclaims stay bounded and concise.
|
||||||
|
- Homepage still routes cleanly into deeper informational and action surfaces.
|
||||||
|
- Mobile layout preserves the intended meaning order without hiding CTA or product reality.
|
||||||
|
|
||||||
|
## Feature Close-out
|
||||||
|
|
||||||
|
- Validated on 2026-04-19 with `corepack pnpm build:website` from the repository root.
|
||||||
|
- Validated on 2026-04-19 with `cd apps/website && corepack pnpm exec playwright test tests/smoke/home-product.spec.ts`.
|
||||||
|
- Homepage smoke coverage now checks the hero semantic structure, CTA pair, product-near visual alt text, bounded trust cues, and narrow-screen meaning order.
|
||||||
|
- Mobile proof keeps the CTA pair above the fold and verifies the product visual remains present within the hero in the correct sequence.
|
||||||
41
specs/218-homepage-hero/research.md
Normal file
41
specs/218-homepage-hero/research.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Research: Website Homepage Hero
|
||||||
|
|
||||||
|
## Decision 1: Keep the hero implementation local to the static Astro website
|
||||||
|
|
||||||
|
- **Decision**: Continue treating the homepage hero as a static `apps/website` concern composed from the current Astro content module and section component, with no runtime dependency on `apps/platform`.
|
||||||
|
- **Rationale**: Spec 218 is explicitly website-only. The current website already runs as a standalone Astro app, and the hero changes concern semantics, copy discipline, asset truthfulness, and responsive ordering rather than dynamic runtime behavior.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Couple hero content to `apps/platform` or a shared API: rejected because the spec forbids platform obligations and the hero needs no dynamic platform data.
|
||||||
|
- Introduce a CMS or hero-builder layer first: rejected because one homepage hero does not justify that operational overhead.
|
||||||
|
|
||||||
|
## Decision 2: Reuse the existing `homeHero` content object and `PageHero.astro`
|
||||||
|
|
||||||
|
- **Decision**: Preserve `apps/website/src/content/pages/home.ts` as the canonical source for hero content and refine `apps/website/src/components/sections/PageHero.astro` as the render surface instead of creating a new hero abstraction.
|
||||||
|
- **Rationale**: The current site already uses typed content exports and a shared `PageHero` component. This is the narrowest correct place to express hero-specific semantics without adding a second content shape, a render registry, or a page-builder concept.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Build a homepage-hero-specific component tree parallel to `PageHero.astro`: rejected because the existing component already supports eyebrow, CTA pair, product visual, and trust subclaims.
|
||||||
|
- Hardcode all hero content directly in `index.astro`: rejected because it would bypass the current typed content pattern and make later hero iteration less disciplined.
|
||||||
|
|
||||||
|
## Decision 3: Make hero semantics explicit through direct render structure, not through a new framework
|
||||||
|
|
||||||
|
- **Decision**: If stronger hero guarantees are needed, add small render markers and explicit DOM ordering to `PageHero.astro` rather than inventing a new semantic layer.
|
||||||
|
- **Rationale**: Spec 218 requires stable verification of category context, headline, supporting copy, CTA hierarchy, product-near visual, and optional trust cues. Those guarantees can be expressed directly in the hero render surface and smoke tests.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Add a separate hero registry or semantic presenter layer: rejected as premature abstraction for one public surface.
|
||||||
|
- Rely on text-only selectors in browser tests: rejected because stable hero markers make long-term regression checks clearer and less brittle.
|
||||||
|
|
||||||
|
## Decision 4: Treat the hero visual as a curated product-truth asset
|
||||||
|
|
||||||
|
- **Decision**: Keep the hero visual tied to a product-near asset such as the current `hero-product-visual.svg`, and only replace it with another asset that remains truthfully derived from real product structure.
|
||||||
|
- **Rationale**: Spec 218 explicitly rejects generic dashboard wallpaper and fantasy metrics. The visual must support credibility by showing product-adjacent structure, even if it is stylized for the website.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Use abstract shapes or decorative illustration only: rejected because the spec requires product-near credibility.
|
||||||
|
- Pull in a theme-provided analytics dashboard placeholder: rejected because it would weaken product truth and create a generic SaaS feel.
|
||||||
|
|
||||||
|
## Decision 5: Validate through focused homepage smoke coverage
|
||||||
|
|
||||||
|
- **Decision**: Prove Spec 218 with the current website build command and focused Playwright updates in `apps/website/tests/smoke/home-product.spec.ts`, adding shared helpers only if they improve clarity without broadening scope.
|
||||||
|
- **Rationale**: The hero contract is about visible public rendering, CTA hierarchy, mobile meaning order, and route reachability. Browser smoke coverage is the narrowest proving layer that can validate those concerns.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Build-only proof alone: rejected because static output generation does not prove CTA hierarchy, product-visual presence, or mobile ordering.
|
||||||
|
- Add full visual regression or multi-browser matrices immediately: rejected because the feature scope does not require that extra cost.
|
||||||
189
specs/218-homepage-hero/spec.md
Normal file
189
specs/218-homepage-hero/spec.md
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# Feature Specification: Website Homepage Hero
|
||||||
|
|
||||||
|
**Feature Branch**: `218-homepage-hero`
|
||||||
|
**Created**: 2026-04-19
|
||||||
|
**Status**: Draft
|
||||||
|
**Input**: User description: "Define Spec 218 as the website-only homepage hero contract for `apps/website`, covering hero role, required elements, copy and CTA rules, product-near visual constraints, trust subclaims, layout logic, responsive behavior, and explicit anti-patterns."
|
||||||
|
|
||||||
|
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||||
|
|
||||||
|
- **Problem**: Without a clear homepage hero contract, the first screen of `apps/website` can drift into generic SaaS language, abstract visuals, weak product truth, or premature CTA pressure that makes the product feel less credible than it is.
|
||||||
|
- **Today's failure**: A first-time visitor can see a polished hero yet still fail to understand what the product is, why it matters, why it should be trusted, or which next route is the right one.
|
||||||
|
- **User-visible improvement**: Visitors can classify the product, understand the problem space, see a product-near signal, and choose a sensible next step from the hero alone.
|
||||||
|
- **Smallest enterprise-capable version**: One homepage-hero-only contract that defines the hero's role, mandatory elements, content priorities, CTA structure, product-visual rules, trust-subclaim rules, and excluded anti-patterns, without prescribing final copy or detailed visual implementation.
|
||||||
|
- **Explicit non-goals**: No full homepage structure beyond hero responsibilities, no final visual design, no final production copy, no complete Product or Trust page spec, no Pricing or Docs surface, no platform UI work, no Filament theming, no motion-spec detail, and no implementation detail for Astro or Tailwind.
|
||||||
|
- **Permanent complexity imported**: A stable website-local hero vocabulary covering category context, headline, supporting copy, CTA roles, product-near visual rules, trust-subclaim rules, information-density guardrails, and hero review criteria.
|
||||||
|
- **Why now**: The homepage structure is already defined in Spec 217, and the hero needs a tighter contract before screenshot strategy, final copy work, and Stitch exploration harden around weak defaults.
|
||||||
|
- **Why not local**: A one-off hero iteration would not reliably prevent generic marketing patterns, screenshot drift, overclaiming, or multi-CTA pressure as future design and copy passes land.
|
||||||
|
- **Approval class**: Core Enterprise
|
||||||
|
- **Red flags triggered**: New website-local hero taxonomy; risk of drifting into design detail; risk of unsupported trust or compliance language. The scope remains justified because it is narrow, homepage-only, and directly improves product clarity and credibility.
|
||||||
|
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 10/12**
|
||||||
|
- **Decision**: approve
|
||||||
|
|
||||||
|
## Spec Scope Fields *(mandatory)*
|
||||||
|
|
||||||
|
- **Scope**: workspace
|
||||||
|
- **Primary Routes**: `/` with onward routing from the hero into `/product`, `/trust`, `/changelog`, and `/contact`
|
||||||
|
- **Data Ownership**: Website-owned homepage-hero semantics, CTA targets, product-visual expectations, trust-subclaim boundaries, and responsive ordering inside `apps/website`; no tenant-owned records, platform runtime data, or shared persistence.
|
||||||
|
- **RBAC**: None. This feature applies to a public website surface and introduces no authorization model.
|
||||||
|
|
||||||
|
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||||
|
|
||||||
|
- **New source of truth?**: no
|
||||||
|
- **New persisted entity/table/artifact?**: no
|
||||||
|
- **New abstraction?**: yes
|
||||||
|
- **New enum/state/reason family?**: no
|
||||||
|
- **New cross-domain UI framework/taxonomy?**: yes, but only within `apps/website`
|
||||||
|
- **Current operator problem**: Website contributors and reviewers do not yet have a bounded semantic contract for the most important public screen, so hero work can regress into generic marketing patterns or conflicting hero decisions.
|
||||||
|
- **Existing structure is insufficient because**: Spec 217 defines the homepage section model broadly, but it does not define the hero's specific semantic responsibilities, content priorities, screenshot truth rules, or CTA boundaries tightly enough for repeatable hero work.
|
||||||
|
- **Narrowest correct implementation**: One hero-only specification that locks the homepage hero into required semantic parts, bounded trust language, product-near visual expectations, and responsive meaning order without expanding into full page design or implementation detail.
|
||||||
|
- **Ownership cost**: Future homepage hero reviews must enforce category clarity, CTA discipline, visual truthfulness, and anti-pattern avoidance instead of allowing ad hoc exceptions to accumulate.
|
||||||
|
- **Alternative intentionally rejected**: Designing the hero directly in a theme or in Stitch without a semantic contract was rejected because it would let aesthetics re-decide product truth, CTA weight, and screenshot honesty each time.
|
||||||
|
- **Release truth**: Current-release truth
|
||||||
|
|
||||||
|
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||||
|
|
||||||
|
- **Test purpose / classification**: Browser
|
||||||
|
- **Validation lane(s)**: fast-feedback
|
||||||
|
- **Why this classification and these lanes are sufficient**: This specification governs public hero rendering, claim placement, CTA hierarchy, and responsive information order on a static website surface. Browser smoke coverage and website build proof are the narrowest honest validation; no auth, tenant, database, or platform-runtime setup is required.
|
||||||
|
- **New or expanded test families**: Focused website smoke coverage for homepage hero presence, CTA reachability, visible product-near media, and mobile meaning-order checks.
|
||||||
|
- **Fixture / helper cost impact**: low; no workspace, tenant, auth, provider, or database fixtures are required.
|
||||||
|
- **Heavy-family visibility / justification**: none
|
||||||
|
- **Special surface test profile**: N/A
|
||||||
|
- **Standard-native relief or required special coverage**: Homepage-specific browser assertions are sufficient; no platform or operator-surface coverage is required.
|
||||||
|
- **Reviewer handoff**: Reviewers should confirm that the released hero includes the required semantic elements, maintains one dominant primary CTA, routes to real downstream pages, avoids unsupported trust language, and keeps product-near visibility on desktop and mobile.
|
||||||
|
- **Budget / baseline / trend impact**: none beyond small website smoke-suite growth.
|
||||||
|
- **Escalation needed**: document-in-feature
|
||||||
|
- **Active feature PR close-out entry**: Smoke Coverage
|
||||||
|
- **Planned validation commands**: `corepack pnpm build:website` and `cd apps/website && corepack pnpm exec playwright test tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
## User Scenarios & Testing *(mandatory)*
|
||||||
|
|
||||||
|
### User Story 1 - Understand the product from the first screen (Priority: P1)
|
||||||
|
|
||||||
|
A first-time buyer or technical evaluator lands on the homepage and can understand what the product roughly is, why it matters, and what to do next before leaving the hero.
|
||||||
|
|
||||||
|
**Why this priority**: If the hero fails to create immediate product clarity, every deeper page loses value.
|
||||||
|
|
||||||
|
**Independent Test**: This can be tested by visiting the homepage only and confirming that the hero provides category context, product/problem framing, and a clear primary next step without requiring deeper page visits.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a first-time visitor opens the homepage, **When** they read the hero, **Then** they can describe what kind of product this is and why it is relevant.
|
||||||
|
2. **Given** a visitor wants to know what to do next, **When** they inspect the hero CTAs, **Then** they can distinguish the primary next step from the secondary deepening route.
|
||||||
|
3. **Given** a visitor is unsure whether the product is real or just marketing, **When** they inspect the hero visual, **Then** they see a product-near signal rather than pure abstraction.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 2 - Assess credibility before committing (Priority: P2)
|
||||||
|
|
||||||
|
A serious evaluator can leave the hero with a preliminary sense that the product is real, technically serious, and careful about trust claims without the hero trying to carry the full Trust page.
|
||||||
|
|
||||||
|
**Why this priority**: The hero must establish enough credibility to earn deeper exploration, but not by overclaiming.
|
||||||
|
|
||||||
|
**Independent Test**: This can be tested by reviewing the hero only and confirming that trust language remains bounded, product-near evidence is present, and deeper trust routes are available.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** the hero includes trust subclaims, **When** a reviewer inspects them, **Then** each claim is concise, supportable, and not presented as a legal or compliance guarantee.
|
||||||
|
2. **Given** a technical stakeholder scans the hero, **When** they read the copy and view the product-near visual, **Then** they see signs of governance, audit, recovery, or drift seriousness without being overwhelmed by detail.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - Choose the right deeper route (Priority: P3)
|
||||||
|
|
||||||
|
A qualified visitor can use the hero to move into the Product, Trust, Changelog, or Contact flow instead of being forced into immediate sales contact or a dead-end route.
|
||||||
|
|
||||||
|
**Why this priority**: The hero should route intentionally, not absorb the whole website or pressure every visitor into the same action.
|
||||||
|
|
||||||
|
**Independent Test**: This can be tested by starting on the homepage hero and verifying that the visible CTA pair leads into real, maintained downstream routes with clear intent.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a visitor wants deeper product detail, **When** they use the secondary hero CTA, **Then** they reach a real downstream informational surface such as `/product` or `/trust`.
|
||||||
|
2. **Given** a visitor is ready to engage, **When** they use the primary hero CTA, **Then** they reach the intended primary next-step route without competing primary actions.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- What happens when a publishable real screenshot is not yet ready? The hero may use a curated or stylized product-near visual, but it must still reflect real product structure and must not fall back to generic dashboard wallpaper.
|
||||||
|
- How does the hero handle trust or compliance language that cannot be substantiated publicly? The claim must be softened or removed instead of implied as a guarantee.
|
||||||
|
- What happens when mobile space compresses the layout? The meaning order must remain headline, supporting copy, CTA, product-near visual, and optional trust signals, without hiding the CTA or product reality.
|
||||||
|
- What happens when too many chips, badges, or CTA ideas compete for space? The hero must reduce visible signals rather than turning into several mini-sections at once.
|
||||||
|
- What happens when a secondary CTA would lead to a weak or placeholder route? The hero must route to a stronger maintained surface or suppress that CTA until the downstream page is real.
|
||||||
|
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
**Constitution alignment (required):** This feature changes only the public homepage hero in `apps/website`. It introduces no Microsoft Graph calls, no queueing, no long-running operations, no authorization changes, and no Filament or operator-facing surface changes. Its contract is explicitly local to `apps/website` and must not create obligations for `apps/platform`.
|
||||||
|
|
||||||
|
**Constitution alignment (UI-SEM-001 / LAYER-001 / BLOAT-001):** This feature intentionally introduces a website-local hero semantic layer because ad hoc hero styling and copy decisions are insufficient to preserve product truth, CTA hierarchy, and trust boundaries. The layer remains narrow, homepage-only, and must not expand into a shared website-platform design contract.
|
||||||
|
|
||||||
|
**Implementation boundary:** Any implementation under this specification MUST preserve the existing website working contract by keeping `@tenantatlas/website`, `WEBSITE_PORT`, and the root `dev:website` / `build:website` workflows intact, and it MUST NOT introduce runtime or package coupling to `apps/platform`.
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: The homepage hero MUST act as a positioning, product-near, and trust-capable entry point for `apps/website`, not as a decorative splash screen.
|
||||||
|
- **FR-002**: Within the hero alone, a first-time visitor MUST be able to answer what the product roughly is, why it matters, who it is plausibly for, why it is worth exploring further, and what the next sensible step is.
|
||||||
|
- **FR-003**: The hero MUST functionally include category context, a primary headline, supporting copy, one dominant primary CTA, one secondary CTA, and a product-near visual. Trust subclaims MAY be included when supportable.
|
||||||
|
- **FR-004**: The hero MUST prioritize product and problem understanding first, a clear next step second, product reality third, early trust signals fourth, and stylistic finish fifth.
|
||||||
|
- **FR-005**: The hero MUST include a positioning eyebrow, category context, or equivalent descriptor that anchors the product category or problem space, and it MUST NOT be pure marketing filler or the only place where category context exists.
|
||||||
|
- **FR-006**: The primary headline MUST frame the product category, problem space, or intended outcome clearly enough for quick orientation, and it MUST NOT rely on buzzwords, stacked messages, or unsupported superlatives.
|
||||||
|
- **FR-007**: Supporting copy MUST translate the headline into clearer product and problem language, MUST sharpen relevance, and MUST NOT become a miniature product page or a simple paraphrase of the headline.
|
||||||
|
- **FR-008**: The hero MUST present exactly one dominant primary CTA aligned to the current maturity of the website and product story. It MUST NOT present several equally loud primary sales actions in parallel.
|
||||||
|
- **FR-009**: The hero MUST present one secondary CTA that deepens understanding through a real maintained downstream route. It MUST remain lower-emphasis than the primary CTA and MUST NOT point to an immature or empty surface.
|
||||||
|
- **FR-010**: The hero MUST include a product-near visual. That visual MUST be credible, relevant to the product, compositionally integrated into the hero, and stronger than pure abstraction as a proof-of-product signal.
|
||||||
|
- **FR-011**: If the hero uses a screenshot, crop, or stylized product shot, it SHOULD be derived from real product reality and truthful simplification. It MUST NOT invent fantasy product UI, fake metrics, or generic analytics-template dashboards.
|
||||||
|
- **FR-012**: Optional trust subclaims or trust chips MAY appear only when they are factually correct, concise, and supportable by deeper public context such as `/trust`. They MUST NOT turn into a badge wall or imply legal, compliance, or security guarantees that the website cannot responsibly substantiate.
|
||||||
|
- **FR-013**: The hero MUST keep information density controlled: one main headline, one supporting-copy block, one dominant primary CTA, one secondary CTA, one visual focus, and only a small number of trust signals.
|
||||||
|
- **FR-014**: The hero MUST include a clear text core and a clear visual focus. It MAY use left-right or top-bottom composition, but text clarity and CTA visibility MUST remain primary.
|
||||||
|
- **FR-015**: The product-near visual SHOULD feel like part of the same hero composition rather than a detached block. The hero MUST NOT become image-only, text-only where product-near material is available, or readability-damaging decorative layering.
|
||||||
|
- **FR-016**: Any design or Stitch exploration based on this specification MUST preserve the semantic hero structure of category context, headline, supporting copy, primary CTA, secondary CTA, product-near visual, and optional trust chips. Exploration MUST NOT re-decide homepage IA or product positioning from scratch.
|
||||||
|
- **FR-017**: On mobile, the hero MUST preserve the meaning order of headline, supporting copy, CTA, product-near visual, and optional trust signals.
|
||||||
|
- **FR-018**: Mobile simplification MAY crop, reduce, or reorder the visual, but it MUST NOT remove a product-near visual entirely when that visual is a key credibility signal, and it MUST NOT bury the CTA.
|
||||||
|
- **FR-019**: The hero MUST avoid the following disallowed patterns: generic startup hero, abstract-only hero, dashboard-wallpaper hero, badge-overload hero, sales-pressure hero, and compliance-theater hero.
|
||||||
|
- **FR-020**: The hero MUST support both buyer-oriented clarity and first-pass technical plausibility by signaling that governance, audit, recovery, drift, or similar operational concerns are real parts of the product without trying to explain the full product in one screen.
|
||||||
|
- **FR-021**: This specification MUST remain strictly local to `apps/website` and MUST NOT create implementation, design, routing, or runtime obligations for `apps/platform`.
|
||||||
|
- **FR-022**: This specification MUST define, at minimum, the hero's functional role, mandatory elements, copy rules, CTA rules, product-visual rules, trust-subclaim rules, information-density rules, prohibited anti-patterns, and the hero semantic structure required for downstream design exploration.
|
||||||
|
|
||||||
|
#### Out of Scope
|
||||||
|
|
||||||
|
- Full homepage composition beyond the hero
|
||||||
|
- Final pixel-level visual design
|
||||||
|
- Final production copy
|
||||||
|
- Full Product page, Trust page, Pricing page, Docs surface, or Contact flow specification
|
||||||
|
- Platform UI, Filament theming, or `apps/platform` behavior
|
||||||
|
- Astro, Tailwind, animation, or implementation-detail prescriptions
|
||||||
|
- A full screenshot strategy beyond hero truthfulness and credibility boundaries
|
||||||
|
|
||||||
|
### Key Entities *(include if feature involves data)*
|
||||||
|
|
||||||
|
- **Category Context**: The short eyebrow, descriptor, or context cue that anchors the hero in a believable product category or problem space.
|
||||||
|
- **Product-Near Visual**: A screenshot, crop, or truthful stylized product shot that signals real product existence and supports positioning.
|
||||||
|
- **Hero CTA Pair**: The primary action and lower-emphasis secondary deepening route that move visitors into the correct next step.
|
||||||
|
- **Trust Subclaim**: A concise early trust signal that is factual, bounded, and supportable by a deeper public trust surface.
|
||||||
|
- **Hero Semantic Structure**: The ordered set of content roles that design exploration must preserve even as visual execution evolves.
|
||||||
|
|
||||||
|
## Assumptions & Dependencies
|
||||||
|
|
||||||
|
- This specification builds on [Spec 214](../214-website-visual-foundation/spec.md), [Spec 215](../215-website-core-pages/spec.md), and [Spec 217](../217-homepage-structure/spec.md).
|
||||||
|
- `/product`, `/trust`, `/changelog`, and `/contact` remain the canonical downstream routes surfaced from the homepage hero.
|
||||||
|
- A publishable product-near screenshot or truthful visual approximation can be prepared without inventing product functionality.
|
||||||
|
- Trust, hosting, residency, governance, and compliance-adjacent language will stay limited to claims the team can support publicly.
|
||||||
|
- Later hero copy exploration, screenshot strategy, and Stitch design work must treat this specification as a semantic boundary rather than as optional inspiration.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: A first-time visitor can state what the product roughly is, why it matters, and the next sensible action within 60 seconds of reading the homepage hero.
|
||||||
|
- **SC-002**: The released hero includes all required functional roles: category context, primary headline, supporting copy, one dominant primary CTA, one secondary CTA, and a product-near visual on both desktop and mobile presentations.
|
||||||
|
- **SC-003**: Zero released hero variants contain unsupported compliance or security-guarantee language, fake trust badges, or fabricated product metrics.
|
||||||
|
- **SC-004**: From the hero alone, a visitor can reach at least one deeper informational surface and one primary next-step surface in one click, with no competing equally dominant primary actions.
|
||||||
|
- **SC-005**: On representative desktop and mobile widths, the primary CTA and the product-near visual remain visible without horizontal scrolling or loss of headline-first reading order.
|
||||||
|
- **SC-006**: Reviewers can map the shipped hero to the allowed semantic structure and confirm that it does not match any prohibited anti-pattern family defined by this specification.
|
||||||
|
|
||||||
|
## Planned Follow-on Work
|
||||||
|
|
||||||
|
- Product-visual and screenshot strategy
|
||||||
|
- Final hero copy exploration
|
||||||
|
- Stitch-based hero design exploration
|
||||||
|
- Downstream homepage-section detail work that assumes this hero contract rather than redefining it
|
||||||
202
specs/218-homepage-hero/tasks.md
Normal file
202
specs/218-homepage-hero/tasks.md
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# Tasks: Website Homepage Hero
|
||||||
|
|
||||||
|
**Input**: Design documents from `/specs/218-homepage-hero/`
|
||||||
|
**Prerequisites**: `plan.md`, `spec.md`, `research.md`, `data-model.md`, `quickstart.md`, `contracts/homepage-hero.openapi.yaml`
|
||||||
|
|
||||||
|
**Tests**: Browser smoke coverage and the root website build proof are required for this runtime-changing website feature.
|
||||||
|
|
||||||
|
## Test Governance Checklist
|
||||||
|
|
||||||
|
- [X] Lane assignment stays `Browser` in `fast-feedback`, which is the narrowest sufficient proof for this homepage-hero-only change.
|
||||||
|
- [X] New or changed tests stay in the existing website smoke suite instead of widening into a heavier family.
|
||||||
|
- [X] Shared helpers stay cheap by default; no backend, auth, database, or provider fixtures are introduced.
|
||||||
|
- [X] Planned validation commands remain the feature-local website build proof and focused Playwright smoke coverage.
|
||||||
|
- [X] No additional budget, baseline, or escalation path is required beyond `document-in-feature` for this slice.
|
||||||
|
|
||||||
|
## Phase 1: Setup (Shared Infrastructure)
|
||||||
|
|
||||||
|
**Purpose**: Establish stable hero-specific render hooks before story work begins.
|
||||||
|
|
||||||
|
- [X] T001 Add stable homepage-hero element markers and an explicit hero root hook in `apps/website/src/components/sections/PageHero.astro`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational (Blocking Prerequisites)
|
||||||
|
|
||||||
|
**Purpose**: Add the shared hero contract and smoke helpers that every story slice depends on.
|
||||||
|
|
||||||
|
**⚠️ CRITICAL**: No user story work should begin until this phase is complete.
|
||||||
|
|
||||||
|
- [X] T002 [P] Extend reusable hero smoke assertions for semantic structure, CTA hierarchy, product-near visual presence, and mobile ordering in `apps/website/tests/smoke/smoke-helpers.ts`
|
||||||
|
- [X] T003 [P] Tighten the shared hero content object for CTA pair, product visual metadata, and trust-subclaim availability in `apps/website/src/content/pages/home.ts`
|
||||||
|
|
||||||
|
**Checkpoint**: Homepage-hero foundations are ready. User-story work can proceed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 - Understand the Product from the First Screen (Priority: P1) 🎯 MVP
|
||||||
|
|
||||||
|
**Goal**: Make the homepage hero explain what TenantAtlas is, why it matters, and what the next step is in the first reading pass.
|
||||||
|
|
||||||
|
**Independent Test**: Visit `/` and confirm the hero shows category context, a precise headline, supporting copy, and exactly one dominant primary CTA plus one lower-emphasis secondary CTA without opening any other route.
|
||||||
|
|
||||||
|
### Tests for User Story 1
|
||||||
|
|
||||||
|
> **NOTE**: Write this test first and confirm it fails before implementing the story.
|
||||||
|
|
||||||
|
- [X] T004 [P] [US1] Write failing hero-clarity smoke assertions for category context, headline and supporting-copy separation, and one CTA pair in `apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 1
|
||||||
|
|
||||||
|
- [X] T005 [P] [US1] Refine the hero eyebrow, headline, supporting copy, and CTA labels to meet Spec 218 positioning rules in `apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T006 [US1] Render the explicit hero text core and CTA cluster in `apps/website/src/components/sections/PageHero.astro`
|
||||||
|
|
||||||
|
**Checkpoint**: The homepage hero delivers the MVP story of product clarity, buyer relevance, and one clear next step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: User Story 2 - Establish Credibility Without Overclaiming (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: Show product reality and bounded early trust inside the hero without collapsing into badge theater or generic SaaS visuals.
|
||||||
|
|
||||||
|
**Independent Test**: Visit `/` and confirm the hero visual reads as product-near, trust cues stay concise and supportable, and the hero feels credible without requiring the full Trust page.
|
||||||
|
|
||||||
|
### Tests for User Story 2
|
||||||
|
|
||||||
|
> **NOTE**: Write this test first and confirm it fails before implementing the story.
|
||||||
|
|
||||||
|
- [X] T007 [P] [US2] Write failing hero smoke assertions for product-near visual truth and bounded trust subclaims in `apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 2
|
||||||
|
|
||||||
|
- [X] T008 [P] [US2] Tighten the hero visual asset, alt text, and bounded trust-subclaim copy in `apps/website/public/images/hero-product-visual.svg` and `apps/website/src/content/pages/home.ts`
|
||||||
|
- [X] T009 [US2] Refine hero visual and trust-subclaim presentation so credibility cues stay secondary and supportable in `apps/website/src/components/sections/PageHero.astro`
|
||||||
|
|
||||||
|
**Checkpoint**: The hero shows believable product reality and early trust without fake proof systems or inflated claims.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Story 3 - Preserve Correct Next-Step Routing on Desktop and Mobile (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: Keep the hero meaning order, CTA visibility, and route intent intact across narrow and wide screens.
|
||||||
|
|
||||||
|
**Independent Test**: Visit `/` on a narrow viewport and confirm the hero preserves headline and copy first, keeps the CTA pair visible, keeps the product-near visual visible, and routes into the intended hero destinations.
|
||||||
|
|
||||||
|
### Tests for User Story 3
|
||||||
|
|
||||||
|
> **NOTE**: Write this test first and confirm it fails before implementing the story.
|
||||||
|
|
||||||
|
- [X] T010 [P] [US3] Write failing hero smoke assertions for narrow-screen meaning order and hero route reachability in `apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
|
||||||
|
### Implementation for User Story 3
|
||||||
|
|
||||||
|
- [X] T011 [P] [US3] Tighten the hero responsive composition so the CTA pair and product-near visual remain visible on narrow screens in `apps/website/src/components/sections/PageHero.astro`
|
||||||
|
- [X] T012 [US3] Finalize the hero CTA routing and secondary deepening intent in `apps/website/src/content/pages/home.ts`
|
||||||
|
|
||||||
|
**Checkpoint**: The hero preserves correct mobile meaning order and intentional next-step routing without hiding the CTA or product reality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
**Purpose**: Validate proof commands, tighten anti-pattern compliance, and capture close-out notes.
|
||||||
|
|
||||||
|
- [X] T013 [P] Review hero copy, trust wording, and anti-pattern compliance in `apps/website/src/content/pages/home.ts`, `apps/website/src/content/pages/trust.ts`, and `apps/website/public/images/hero-product-visual.svg`
|
||||||
|
- [X] T014 [P] Run `corepack pnpm build:website` from `package.json` and verify hero proof steps in `specs/218-homepage-hero/quickstart.md`
|
||||||
|
- [X] T015 [P] Run `cd apps/website && corepack pnpm exec playwright test tests/smoke/home-product.spec.ts` against the hero smoke coverage in `apps/website/tests/smoke/home-product.spec.ts`
|
||||||
|
- [X] T016 Record the focused hero smoke-coverage close-out and any helper-growth notes in `specs/218-homepage-hero/plan.md` and `specs/218-homepage-hero/quickstart.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & Execution Order
|
||||||
|
|
||||||
|
### Phase Dependencies
|
||||||
|
|
||||||
|
- **Setup (Phase 1)**: Starts immediately.
|
||||||
|
- **Foundational (Phase 2)**: Depends on Setup and blocks all user stories.
|
||||||
|
- **User Stories (Phases 3-5)**: Depend on Foundational. They remain independently testable, but shared edits to `apps/website/src/components/sections/PageHero.astro`, `apps/website/src/content/pages/home.ts`, and `apps/website/tests/smoke/home-product.spec.ts` should land sequentially in story order.
|
||||||
|
- **Polish (Phase 6)**: Depends on all desired user stories being complete.
|
||||||
|
|
||||||
|
### User Story Dependencies
|
||||||
|
|
||||||
|
- **User Story 1 (P1)**: Starts after Foundational and is the MVP slice.
|
||||||
|
- **User Story 2 (P2)**: Starts after Foundational for test and content work, and remains independently valuable because it upgrades credibility and product-near truth inside the hero. Shared `PageHero.astro` work should follow US1.
|
||||||
|
- **User Story 3 (P3)**: Starts after Foundational for responsive and routing work, and remains independently valuable because it hardens mobile meaning order and hero route intent. Shared `PageHero.astro` and `home.ts` work should follow US2.
|
||||||
|
|
||||||
|
### Within Each User Story
|
||||||
|
|
||||||
|
- Write the story-specific browser smoke assertions first and confirm they fail.
|
||||||
|
- Update content and supporting truth sources before final hero render adjustments in `apps/website/src/components/sections/PageHero.astro`.
|
||||||
|
- Treat `apps/website/src/components/sections/PageHero.astro`, `apps/website/src/content/pages/home.ts`, and `apps/website/tests/smoke/home-product.spec.ts` as shared assembly points: helper or asset work may branch, but shared file edits should land sequentially.
|
||||||
|
- Finish the hero render or content wiring before running the story proof commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallel Opportunities
|
||||||
|
|
||||||
|
- `T002` and `T003` can run in parallel after `T001`.
|
||||||
|
- In US1, `T004` and `T005` can run in parallel before `T006`.
|
||||||
|
- In US2, `T007` and `T008` can run in parallel before `T009`.
|
||||||
|
- In US3, `T010` and `T011` can run in parallel before `T012`.
|
||||||
|
- In polish, `T013`, `T014`, and `T015` can run in parallel before `T016`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallel Example: User Story 1
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# After the hero foundations are complete, split the first slice into test + content work:
|
||||||
|
Task: "T004 [US1] Write failing hero-clarity smoke assertions for category context, headline and supporting-copy separation, and one CTA pair"
|
||||||
|
Task: "T005 [US1] Refine the hero eyebrow, headline, supporting copy, and CTA labels"
|
||||||
|
|
||||||
|
# Then finish the shared hero render:
|
||||||
|
Task: "T006 [US1] Render the explicit hero text core and CTA cluster"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parallel Example: User Story 2
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Safe split inside US2 is limited to assertions plus content and asset prep before shared hero render work:
|
||||||
|
Task: "T007 [US2] Write failing hero smoke assertions for product-near visual truth and bounded trust subclaims"
|
||||||
|
Task: "T008 [US2] Tighten the hero visual asset, alt text, and bounded trust-subclaim copy"
|
||||||
|
|
||||||
|
# Then finish the shared hero presentation work:
|
||||||
|
Task: "T009 [US2] Refine hero visual and trust-subclaim presentation"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parallel Example: User Story 3
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Split mobile assertions from responsive render work first:
|
||||||
|
Task: "T010 [US3] Write failing hero smoke assertions for narrow-screen meaning order and hero route reachability"
|
||||||
|
Task: "T011 [US3] Tighten the hero responsive composition so the CTA pair and product-near visual remain visible on narrow screens"
|
||||||
|
|
||||||
|
# Then finalize route intent in the shared hero content:
|
||||||
|
Task: "T012 [US3] Finalize the hero CTA routing and secondary deepening intent"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### MVP First
|
||||||
|
|
||||||
|
1. Complete Phase 1: Setup.
|
||||||
|
2. Complete Phase 2: Foundational.
|
||||||
|
3. Complete Phase 3: User Story 1.
|
||||||
|
4. Run the website build proof and the homepage hero smoke coverage.
|
||||||
|
5. Demo the homepage hero MVP with clear product explanation and one dominant next step.
|
||||||
|
|
||||||
|
### Incremental Delivery
|
||||||
|
|
||||||
|
1. Foundations establish stable render hooks, the shared hero data contract, and smoke helpers.
|
||||||
|
2. US1 locks in immediate product clarity and CTA discipline.
|
||||||
|
3. US2 upgrades hero credibility through truthful product-near media and bounded trust cues.
|
||||||
|
4. US3 hardens mobile meaning order and route intent without changing the broader homepage section model.
|
||||||
|
5. Polish runs the proof commands, validates anti-pattern compliance, and records close-out notes before merge.
|
||||||
|
|
||||||
|
### Suggested MVP Scope
|
||||||
|
|
||||||
|
- Deliver through **User Story 1** for the smallest independently valuable slice.
|
||||||
|
- Add **User Story 2** next for stronger visual truth and bounded credibility cues.
|
||||||
|
- Finish with **User Story 3** for deliberate narrow-screen behavior and route integrity.
|
||||||
Loading…
Reference in New Issue
Block a user