From bb4c9aacc15237da354ecc398bcd41d4e2dceb55 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Thu, 21 May 2026 23:31:47 +0200 Subject: [PATCH] feat(website): implement public website launch readiness changes --- Agents.md | 1 + apps/website/astro.config.mjs | 56 +- apps/website/src/components/BrandLogo.astro | 57 +- apps/website/src/components/Meta.astro | 66 +- .../src/components/pages/ContactPage.astro | 41 ++ .../src/components/pages/HomePage.astro | 65 ++ .../src/components/pages/LegalPage.astro | 35 + .../src/components/pages/PlatformPage.astro | 98 +++ .../src/components/pages/PricingPage.astro | 25 + .../src/components/pages/TextPage.astro | 24 + .../src/components/pages/TrustPage.astro | 37 + .../sections/landing/ClientsSection.astro | 13 +- .../sections/landing/HeroSection.astro | 9 +- .../sections/misc/Authentication.astro | 35 +- .../sections/misc/ContactSection.astro | 60 +- .../navbar&footer/FooterSection.astro | 54 +- .../sections/navbar&footer/Navbar.astro | 22 +- .../sections/pricing/PricingSection.astro | 27 +- .../testimonials/TestimonialItem.astro | 2 +- .../src/components/ui/LanguagePicker.astro | 58 ++ .../components/ui/blocks/MainSection.astro | 6 +- .../ui/blocks/ReviewComponent.astro | 28 +- .../ui/forms/input/EmailContactInput.astro | 2 +- .../ui/forms/input/EmailFooterInput.astro | 26 +- .../src/components/ui/starlight/Head.astro | 24 +- .../components/ui/starlight/SiteTitle.astro | 8 +- .../en/guides/first-project-checklist.mdx | 15 + .../docs/en/guides/getting-started.mdx | 18 + .../src/content/docs/en/guides/intro.mdx | 18 + .../docs/en/platform/evidence-review.mdx | 17 + .../src/content/docs/en/welcome-to-docs.mdx | 31 + .../docs/guides/first-project-checklist.mdx | 18 +- .../content/docs/guides/getting-started.mdx | 16 +- .../website/src/content/docs/guides/intro.mdx | 20 +- .../content/docs/platform/evidence-review.mdx | 16 +- .../src/content/docs/welcome-to-docs.mdx | 19 +- apps/website/src/data_files/constants.ts | 14 +- apps/website/src/data_files/faqs.json | 8 +- apps/website/src/data_files/features.json | 16 +- apps/website/src/data_files/pricing.json | 10 +- apps/website/src/data_files/site-copy.ts | 651 ++++++++++++++++++ apps/website/src/i18n.ts | 75 ++ .../src/images/tenantial-logo-lockup-mask.png | Bin 0 -> 30240 bytes apps/website/src/layouts/MainLayout.astro | 11 +- apps/website/src/pages/contact.astro | 33 +- apps/website/src/pages/en/blog/index.astro | 3 + apps/website/src/pages/en/contact.astro | 5 + apps/website/src/pages/en/imprint.astro | 5 + apps/website/src/pages/en/index.astro | 5 + .../website/src/pages/en/insights/index.astro | 3 + apps/website/src/pages/en/legal.astro | 5 + apps/website/src/pages/en/platform.astro | 5 + apps/website/src/pages/en/pricing.astro | 5 + apps/website/src/pages/en/privacy.astro | 5 + apps/website/src/pages/en/product.astro | 3 + .../website/src/pages/en/products/index.astro | 3 + apps/website/src/pages/en/services.astro | 3 + apps/website/src/pages/en/terms.astro | 5 + apps/website/src/pages/en/trust.astro | 5 + apps/website/src/pages/imprint.astro | 15 +- apps/website/src/pages/index.astro | 73 +- apps/website/src/pages/legal.astro | 26 +- apps/website/src/pages/platform.astro | 97 +-- apps/website/src/pages/pricing.astro | 22 +- apps/website/src/pages/privacy.astro | 15 +- apps/website/src/pages/terms.astro | 15 +- apps/website/src/pages/trust.astro | 43 +- apps/website/src/utils/navigation.ts | 39 +- apps/website/src/utils/ui.ts | 2 +- apps/website/tests/smoke/interaction.spec.ts | 147 +++- .../website/tests/smoke/public-routes.spec.ts | 276 +++++++- apps/website/tests/smoke/smoke-helpers.ts | 314 ++++++++- .../contracts/public-site-contract.md | 164 +++++ .../data-model.md | 114 +++ .../launch-readiness.md | 127 ++++ .../plan.md | 220 ++++++ .../quickstart.md | 119 ++++ .../research.md | 72 ++ .../spec.md | 421 +++++++++++ .../tasks.md | 255 +++++++ 80 files changed, 3841 insertions(+), 680 deletions(-) create mode 100644 apps/website/src/components/pages/ContactPage.astro create mode 100644 apps/website/src/components/pages/HomePage.astro create mode 100644 apps/website/src/components/pages/LegalPage.astro create mode 100644 apps/website/src/components/pages/PlatformPage.astro create mode 100644 apps/website/src/components/pages/PricingPage.astro create mode 100644 apps/website/src/components/pages/TextPage.astro create mode 100644 apps/website/src/components/pages/TrustPage.astro create mode 100644 apps/website/src/components/ui/LanguagePicker.astro create mode 100644 apps/website/src/content/docs/en/guides/first-project-checklist.mdx create mode 100644 apps/website/src/content/docs/en/guides/getting-started.mdx create mode 100644 apps/website/src/content/docs/en/guides/intro.mdx create mode 100644 apps/website/src/content/docs/en/platform/evidence-review.mdx create mode 100644 apps/website/src/content/docs/en/welcome-to-docs.mdx create mode 100644 apps/website/src/data_files/site-copy.ts create mode 100644 apps/website/src/i18n.ts create mode 100644 apps/website/src/images/tenantial-logo-lockup-mask.png create mode 100644 apps/website/src/pages/en/blog/index.astro create mode 100644 apps/website/src/pages/en/contact.astro create mode 100644 apps/website/src/pages/en/imprint.astro create mode 100644 apps/website/src/pages/en/index.astro create mode 100644 apps/website/src/pages/en/insights/index.astro create mode 100644 apps/website/src/pages/en/legal.astro create mode 100644 apps/website/src/pages/en/platform.astro create mode 100644 apps/website/src/pages/en/pricing.astro create mode 100644 apps/website/src/pages/en/privacy.astro create mode 100644 apps/website/src/pages/en/product.astro create mode 100644 apps/website/src/pages/en/products/index.astro create mode 100644 apps/website/src/pages/en/services.astro create mode 100644 apps/website/src/pages/en/terms.astro create mode 100644 apps/website/src/pages/en/trust.astro create mode 100644 specs/403-public-website-launch-readiness/contracts/public-site-contract.md create mode 100644 specs/403-public-website-launch-readiness/data-model.md create mode 100644 specs/403-public-website-launch-readiness/launch-readiness.md create mode 100644 specs/403-public-website-launch-readiness/plan.md create mode 100644 specs/403-public-website-launch-readiness/quickstart.md create mode 100644 specs/403-public-website-launch-readiness/research.md create mode 100644 specs/403-public-website-launch-readiness/spec.md create mode 100644 specs/403-public-website-launch-readiness/tasks.md diff --git a/Agents.md b/Agents.md index d3f69e6b..4adac4fa 100644 --- a/Agents.md +++ b/Agents.md @@ -944,6 +944,7 @@ ## Active Technologies - Tailwind CSS v4 - TypeScript 5.9, Astro 6 static components, HTML, CSS + Astro 6.0.0, Tailwind CSS 4.2.2 through CSS-first `@theme` and `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, Playwright 1.59.1 (400-tenantial-homepage-visual-rebuild) - TypeScript 5.9.3, Astro 6.0.0, Tailwind CSS v4.2.2 via `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, and Playwright smoke tests for the static website; no database, CMS, API, customer data, tenant data, or runtime persistence. (401-tenantial-platform-page) +- TypeScript 6.0.3, Astro 6.3.3, Node.js >=20.0.0, pnpm 10.33.0 + Astro, `@astrojs/starlight`, `@astrojs/sitemap`, `@astrojs/mdx`, Tailwind CSS v4, `@tailwindcss/vite`, Preline 4, Lenis, GSAP, Sharp, Playwright; static website content only, no database or product persistence. (feat/403-public-website-launch-readiness) ## Recent Changes - 066-rbac-ui-enforcement-helper-v2-session-1769732329: Planned UiEnforcement v2 (spec + plan + design artifacts) diff --git a/apps/website/astro.config.mjs b/apps/website/astro.config.mjs index d038fca8..7bf81bd2 100644 --- a/apps/website/astro.config.mjs +++ b/apps/website/astro.config.mjs @@ -11,6 +11,11 @@ const redirectOnlyPaths = new Set([ '/services/', '/blog/', '/insights/', + '/en/product/', + '/en/products/', + '/en/services/', + '/en/blog/', + '/en/insights/', ]); const isRedirectOnlySitemapPath = page => { @@ -30,23 +35,14 @@ export default defineConfig({ image: { domains: ['images.unsplash.com'], }, - // i18n: { - // defaultLocale: "en", - // locales: ["en", "fr"], - // fallback: { - // fr: "en", - // }, - // routing: { - // prefixDefaultLocale: false, - // }, - // }, prefetch: true, integrations: [ sitemap({ filter: page => !isRedirectOnlySitemapPath(page), i18n: { - defaultLocale: 'en', + defaultLocale: 'de', locales: { + de: 'de', en: 'en', }, }, @@ -60,6 +56,10 @@ export default defineConfig({ // If both an Astro and Starlight i18n configurations are provided, an error is thrown. locales: { root: { + label: 'Deutsch', + lang: 'de', + }, + en: { label: 'English', lang: 'en', }, @@ -67,20 +67,20 @@ export default defineConfig({ // https://starlight.astro.build/guides/sidebar/ sidebar: [ { - label: 'Evaluation Guides', + label: 'Evaluierungsleitfaeden', translations: { - de: 'Schnellstartanleitungen', - es: 'Guías de Inicio Rápido', - fa: 'راهنمای شروع سریع', - fr: 'Guides de Démarrage Rapide', - ja: 'クイックスタートガイド', - 'zh-cn': '快速入门指南', + en: 'Evaluation Guides', }, items: [{ autogenerate: { directory: 'guides' } }], }, { - label: 'Platform Notes', - items: [{ label: 'Evidence Review', link: 'platform/evidence-review/' }], + label: 'Plattform-Notizen', + translations: { + en: 'Platform Notes', + }, + items: [ + { label: 'Evidence Review', link: 'platform/evidence-review/' }, + ], }, ], social: [], @@ -94,22 +94,6 @@ export default defineConfig({ './src/components/ui/starlight/MobileMenuFooter.astro', ThemeSelect: './src/components/ui/starlight/ThemeSelect.astro', }, - head: [ - { - tag: 'meta', - attrs: { - property: 'og:image', - content: 'https://tenantial.com' + '/social.webp', - }, - }, - { - tag: 'meta', - attrs: { - property: 'twitter:image', - content: 'https://tenantial.com' + '/social.webp', - }, - }, - ], }), mdx(), ], diff --git a/apps/website/src/components/BrandLogo.astro b/apps/website/src/components/BrandLogo.astro index cf1ed9f1..a90fbdae 100644 --- a/apps/website/src/components/BrandLogo.astro +++ b/apps/website/src/components/BrandLogo.astro @@ -1,13 +1,54 @@ +--- +import logoLockupMask from '@images/tenantial-logo-lockup-mask.png'; + +const { + class: className, + style: inlineStyle, + 'aria-label': ariaLabel = 'Tenantial', + ...attrs +} = Astro.props; + +const logoMaskUrl = + typeof logoLockupMask === 'string' ? logoLockupMask : logoLockupMask.src; +const logoStyle = `--tenantial-logo-mask: url("${logoMaskUrl}");${inlineStyle ?? ''}`; +--- + - T - Tenantial + + + diff --git a/apps/website/src/components/Meta.astro b/apps/website/src/components/Meta.astro index 5c078eae..31204278 100644 --- a/apps/website/src/components/Meta.astro +++ b/apps/website/src/components/Meta.astro @@ -3,6 +3,14 @@ import { getImage } from 'astro:assets'; import { OG, SEO, SITE } from '@data/constants'; import faviconSvgSrc from '@images/icon.svg'; import faviconSrc from '@images/icon.png'; +import { + getLocaleFromPath, + isLocale, + localeOg, + localizedPath, + stripLocalePrefix, + type Locale, +} from '@/i18n'; // Default properties for the Meta component. These values are used if props are not provided. // 'meta' sets a default description meta tag to describe the page content. @@ -22,8 +30,11 @@ const { structuredData = defaultProps.structuredData, customDescription = defaultProps.customDescription, customOgTitle = defaultProps.customOgTitle, + locale: rawLocale = getLocaleFromPath(Astro.url.pathname), } = Astro.props; +const locale: Locale = isLocale(rawLocale) ? rawLocale : 'de'; + // Use custom description if provided, otherwise use default meta const description = customDescription || meta; // Use custom OG title if provided, otherwise use default OG title @@ -33,32 +44,22 @@ const ogDescription = customDescription || OG.description; // Define the metadata for your website and individual pages const siteURL = `${Astro.site}`; // Set the website URL in astro.config.mjs const author = SITE.author; -const canonical = new URL(Astro.url.pathname, Astro.site || Astro.url.origin) - .href; -const basePath = Astro.url.pathname; +const cleanPath = stripLocalePrefix(Astro.url.pathname); +const canonical = new URL( + localizedPath(cleanPath, locale), + Astro.site || Astro.url.origin +).href; const socialImageRes = await getImage({ src: OG.image, width: 1200, height: 600, + format: 'png', }); -const socialImage = Astro.url.origin + socialImageRes.src; // Get the full URL of the image (https://stackoverflow.com/a/9858694) +const socialImage = new URL(socialImageRes.src, Astro.site || Astro.url.origin) + .href; +const twitterDomain = new URL(siteURL).hostname; -const languages: { [key: string]: string } = { - en: '', -}; - -function createHref(lang: string, prefix: string, path: string): string { - // Remove any existing language prefix - const cleanPath = path.replace(/^\/(en)\//, '/'); - - // Add the correct language prefix if needed - const basePath = prefix ? `/${prefix}${cleanPath}` : cleanPath; - const normalizedBasePath = basePath.replace(/\/\/+/g, '/'); - - return `${siteURL.slice(0, -1)}${normalizedBasePath}`; -} - -const fullPath: string = Astro.url.pathname; +const alternateLocales: Locale[] = ['de', 'en']; // Generate and optimize the favicon images const faviconSvg = await getImage({ @@ -93,19 +94,24 @@ const appleTouchIcon = await getImage({ { - Object.entries(languages).map(([lang, prefix]) => { - const cleanPath = fullPath.replace(/^\/(en)\//, '/'); - const href = createHref(lang, prefix, cleanPath); + alternateLocales.map(lang => { + const href = new URL( + localizedPath(cleanPath, lang), + Astro.site || Astro.url.origin + ).href; return ; }) } + {/* Facebook Meta Tags */} - - + + @@ -117,8 +123,8 @@ const appleTouchIcon = await getImage({ {/* Twitter Meta Tags */} - - + + diff --git a/apps/website/src/components/pages/ContactPage.astro b/apps/website/src/components/pages/ContactPage.astro new file mode 100644 index 00000000..092835f8 --- /dev/null +++ b/apps/website/src/components/pages/ContactPage.astro @@ -0,0 +1,41 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import ContactSection from '@components/sections/misc/ContactSection.astro'; +import { SITE } from '@data/constants'; +import { siteCopy } from '@data/site-copy'; +import { localeHtmlLang, localizedPath, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].contact; +const siteDescription = siteCopy[locale].site.description; +const canonicalPath = localizedPath('/contact', locale); +--- + + + + diff --git a/apps/website/src/components/pages/HomePage.astro b/apps/website/src/components/pages/HomePage.astro new file mode 100644 index 00000000..98677c06 --- /dev/null +++ b/apps/website/src/components/pages/HomePage.astro @@ -0,0 +1,65 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import HeroSection from '@components/sections/landing/HeroSection.astro'; +import FeaturesGeneral from '@components/sections/features/FeaturesGeneral.astro'; +import FeaturesNavs from '@components/sections/features/FeaturesNavs.astro'; +import PricingSection from '@components/sections/pricing/PricingSection.astro'; +import FAQ from '@components/sections/misc/FAQ.astro'; +import heroImage from '@images/tenantial-dashboard.avif'; +import featureImage from '@images/tenantial-review-board.avif'; +import reviewImage from '@images/tenantial-evidence-intake.avif'; +import evidenceImage from '@images/tenantial-decision-review.avif'; +import governanceImage from '@images/tenantial-restore-plan.avif'; +import { + faqsByLocale, + featuresByLocale, + pricingByLocale, + siteCopy, +} from '@data/site-copy'; +import { localizeHref, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].home; +const tabs = copy.tabs.map((tab: any, index: number) => ({ + ...tab, + src: [reviewImage, evidenceImage, governanceImage][index], +})); +--- + + + + + + + + + + + + diff --git a/apps/website/src/components/pages/LegalPage.astro b/apps/website/src/components/pages/LegalPage.astro new file mode 100644 index 00000000..4798d5f3 --- /dev/null +++ b/apps/website/src/components/pages/LegalPage.astro @@ -0,0 +1,35 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; +import SecondaryCTA from '@components/ui/buttons/SecondaryCTA.astro'; +import { siteCopy } from '@data/site-copy'; +import { localizeHref, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].legal; +--- + + + +
+
+ + + +
+
+
diff --git a/apps/website/src/components/pages/PlatformPage.astro b/apps/website/src/components/pages/PlatformPage.astro new file mode 100644 index 00000000..609fd9cb --- /dev/null +++ b/apps/website/src/components/pages/PlatformPage.astro @@ -0,0 +1,98 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import LeftSection from '@components/ui/blocks/LeftSection.astro'; +import RightSection from '@components/ui/blocks/RightSection.astro'; +import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; +import dashboard from '@images/tenantial-dashboard.avif'; +import evidenceImage from '@images/tenantial-evidence-panel.avif'; +import reviewImage from '@images/tenantial-drift-workflow.avif'; +import restoreImage from '@images/tenantial-rollout-plan.avif'; +import { SITE } from '@data/constants'; +import { siteCopy } from '@data/site-copy'; +import { + localeHtmlLang, + localizeHref, + localizedPath, + type Locale, +} from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].platform; +const siteDescription = siteCopy[locale].site.description; +const canonicalPath = localizedPath('/platform', locale); +--- + + + + + + + + + + + + diff --git a/apps/website/src/components/pages/PricingPage.astro b/apps/website/src/components/pages/PricingPage.astro new file mode 100644 index 00000000..c02292f7 --- /dev/null +++ b/apps/website/src/components/pages/PricingPage.astro @@ -0,0 +1,25 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import PricingSection from '@components/sections/pricing/PricingSection.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import { pricingByLocale, siteCopy } from '@data/site-copy'; +import type { Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].pricingIntro; +--- + + + + + diff --git a/apps/website/src/components/pages/TextPage.astro b/apps/website/src/components/pages/TextPage.astro new file mode 100644 index 00000000..a4614357 --- /dev/null +++ b/apps/website/src/components/pages/TextPage.astro @@ -0,0 +1,24 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import { siteCopy } from '@data/site-copy'; +import type { Locale } from '@/i18n'; + +const { locale, page } = Astro.props; + +interface Props { + locale: Locale; + page: 'privacy' | 'terms' | 'imprint'; +} + +const copy = siteCopy[locale][page]; +--- + + + + diff --git a/apps/website/src/components/pages/TrustPage.astro b/apps/website/src/components/pages/TrustPage.astro new file mode 100644 index 00000000..90b604a8 --- /dev/null +++ b/apps/website/src/components/pages/TrustPage.astro @@ -0,0 +1,37 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; +import { siteCopy } from '@data/site-copy'; +import { localizeHref, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].trust; +--- + + + + + diff --git a/apps/website/src/components/sections/landing/ClientsSection.astro b/apps/website/src/components/sections/landing/ClientsSection.astro index 33b0a3e4..1d9d2924 100644 --- a/apps/website/src/components/sections/landing/ClientsSection.astro +++ b/apps/website/src/components/sections/landing/ClientsSection.astro @@ -14,6 +14,10 @@ interface Props { subTitle?: string; partners: Partner[]; } + +const visiblePartners = partners.filter( + partner => partner.href && partner.icon +); ---
{/* Clients Group SVGs */} { - partners.map(partner => ( - + visiblePartners.map(partner => ( +
)) diff --git a/apps/website/src/components/sections/landing/HeroSection.astro b/apps/website/src/components/sections/landing/HeroSection.astro index 5ea47f48..27359661 100644 --- a/apps/website/src/components/sections/landing/HeroSection.astro +++ b/apps/website/src/components/sections/landing/HeroSection.astro @@ -15,7 +15,6 @@ const { secondaryBtnURL, withReview, avatars, - starCount, rating, reviews, src, @@ -32,7 +31,6 @@ interface Props { secondaryBtnURL?: string; withReview?: boolean; avatars?: Array; - starCount?: number; rating?: string; reviews?: string; src?: any; @@ -81,12 +79,7 @@ interface Props { } { withReview ? ( - + ) : ( '' ) diff --git a/apps/website/src/components/sections/misc/Authentication.astro b/apps/website/src/components/sections/misc/Authentication.astro index 537d9af6..6be8c104 100644 --- a/apps/website/src/components/sections/misc/Authentication.astro +++ b/apps/website/src/components/sections/misc/Authentication.astro @@ -1,5 +1,36 @@ --- -import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; +import { localizeHref, type Locale } from '@/i18n'; +import { siteCopy } from '@data/site-copy'; + +const { locale = 'de' } = Astro.props; + +interface Props { + locale?: Locale; +} + +const userSVG = ` + + + `; + +const copy = siteCopy[locale].auth; --- - + + + {copy.walkthrough} + diff --git a/apps/website/src/components/sections/misc/ContactSection.astro b/apps/website/src/components/sections/misc/ContactSection.astro index 1d648a96..7dd532b6 100644 --- a/apps/website/src/components/sections/misc/ContactSection.astro +++ b/apps/website/src/components/sections/misc/ContactSection.astro @@ -7,14 +7,16 @@ import EmailContactInput from '@components/ui/forms/input/EmailContactInput.astr import PhoneInput from '@components/ui/forms/input/PhoneInput.astro'; import TextAreaInput from '@components/ui/forms/input/TextAreaInput.astro'; import Icon from '@components/ui/icons/Icon.astro'; +import { localizeHref, type Locale } from '@/i18n'; +import { siteCopy } from '@data/site-copy'; -// Define the variables that will be used in this component -const title: string = 'Contact Tenantial'; -const subTitle: string = - 'Request a walkthrough or start a scoped rollout conversation. Do not send secrets, credentials, or tenant exports through this public website.'; -const formTitle: string = 'Prepare a walkthrough request'; -const formSubTitle: string = - 'The public website form is static; use email for business contact context.'; +const { locale = 'de' } = Astro.props; + +interface Props { + locale?: Locale; +} + +const copy = siteCopy[locale].contact; --- {/* Contact Us */} @@ -24,10 +26,10 @@ const formSubTitle: string =

- {title} + {copy.title}

- {subTitle} + {copy.subtitle}

@@ -36,7 +38,7 @@ const formSubTitle: string =

- {formTitle} + {copy.formTitle}

{ /* Form for user input with various input fields.--> @@ -47,31 +49,31 @@ const formSubTitle: string =
- - + +
- +

- {formSubTitle} + {copy.formSubtitle}

@@ -82,35 +84,35 @@ const formSubTitle: string = }
; +}; ---
@@ -21,21 +22,23 @@ const sectionThreeContent: string =
{/* Brand Logo */} - +
{/* An array of links for Product and Company sections */} { - strings.footerLinks.map(section => ( -
-

+ copy.sections.map((section: FooterSection) => ( +
+

{section.section}

    - {section.links.map((link, index) => ( + {section.links.map(link => (
  • {link.name} @@ -46,17 +49,20 @@ const sectionThreeContent: string = )) } -
    -

    - {sectionThreeTitle} +
    +

    + {copy.conversationTitle}

    -
    - -

    - {sectionThreeContent} +

    + +

    + {copy.conversationContent}

    - +

    @@ -64,9 +70,9 @@ const sectionThreeContent: string = class="mt-9 grid gap-y-2 sm:mt-12 sm:flex sm:items-center sm:justify-between sm:gap-y-0" >
    -

    +

    © - {SITE.title}. Public website content only. + {SITE.title}. {copy.copyrightSuffix}

diff --git a/apps/website/src/components/sections/navbar&footer/Navbar.astro b/apps/website/src/components/sections/navbar&footer/Navbar.astro index 63e2bbd0..f40b981a 100644 --- a/apps/website/src/components/sections/navbar&footer/Navbar.astro +++ b/apps/website/src/components/sections/navbar&footer/Navbar.astro @@ -3,11 +3,18 @@ import ThemeIcon from '@components/ThemeIcon.astro'; import NavLink from '@components/ui/links/NavLink.astro'; import Authentication from '../misc/Authentication.astro'; -import enStrings from '@utils/navigation.ts'; import BrandLogo from '@components/BrandLogo.astro'; +import LanguagePicker from '@components/ui/LanguagePicker.astro'; +import { getLocaleFromPath, localizeHref } from '@/i18n'; +import { siteCopy } from '@data/site-copy'; -const strings = enStrings; -const homeUrl = '/'; +const locale = getLocaleFromPath(Astro.url.pathname); +const strings = siteCopy[locale]; +const homeUrl = localizeHref('/', locale); +type NavItem = { + name: string; + url: string; +}; --- {/* Main header component */} @@ -26,7 +33,7 @@ const homeUrl = '/'; href={homeUrl} aria-label="Brand" > - + {/* Collapse toggle for smaller screens */}
@@ -84,12 +91,13 @@ const homeUrl = '/'; > {/* Navigation links and Authentication component */} { - strings.navBarLinks.map(link => ( - + strings.nav.map((link: NavItem) => ( + )) } - + + {/* ThemeIcon component specifically for larger screens */}

diff --git a/apps/website/src/components/sections/testimonials/TestimonialItem.astro b/apps/website/src/components/sections/testimonials/TestimonialItem.astro index 6c6d92bc..87796f77 100644 --- a/apps/website/src/components/sections/testimonials/TestimonialItem.astro +++ b/apps/website/src/components/sections/testimonials/TestimonialItem.astro @@ -27,7 +27,7 @@ interface Props { Avatar Description diff --git a/apps/website/src/components/ui/LanguagePicker.astro b/apps/website/src/components/ui/LanguagePicker.astro new file mode 100644 index 00000000..84829489 --- /dev/null +++ b/apps/website/src/components/ui/LanguagePicker.astro @@ -0,0 +1,58 @@ +--- +import { + getLocaleFromPath, + localeLabels, + localizedPath, + stripLocalePrefix, +} from '@/i18n'; +import Icon from './icons/Icon.astro'; + +const currentLocale = getLocaleFromPath(Astro.url.pathname); +const currentPath = stripLocalePrefix(Astro.url.pathname); +--- + +
+ + + +
diff --git a/apps/website/src/components/ui/blocks/MainSection.astro b/apps/website/src/components/ui/blocks/MainSection.astro index 5ecf462b..b450cc8d 100644 --- a/apps/website/src/components/ui/blocks/MainSection.astro +++ b/apps/website/src/components/ui/blocks/MainSection.astro @@ -18,16 +18,16 @@ interface Props {
-
+
{/* Section title */}

{title}

{/* Section subtitle */}

{subTitle}

diff --git a/apps/website/src/components/ui/blocks/ReviewComponent.astro b/apps/website/src/components/ui/blocks/ReviewComponent.astro index 1d78bc23..5c133e6d 100644 --- a/apps/website/src/components/ui/blocks/ReviewComponent.astro +++ b/apps/website/src/components/ui/blocks/ReviewComponent.astro @@ -1,13 +1,10 @@ --- import Avatar from '@components/ui/avatars/Avatar.astro'; -import FullStar from '@components/ui/stars/FullStar.astro'; -import HalfStar from '@components/ui/stars/HalfStar.astro'; -const { avatars, starCount = 0, rating, reviews } = Astro.props; +const { avatars, rating, reviews } = Astro.props; interface Props { avatars?: Array; - starCount?: number; rating?: string; reviews?: string; } @@ -19,12 +16,16 @@ interface Props {
{/* Avatar Group */}
- {avatars?.map(src => )} + { + avatars?.map(src => ( + + )) + } 7k+Demo
@@ -33,26 +34,15 @@ interface Props { class="mx-auto h-px w-32 border-t border-neutral-400 sm:mx-0 sm:h-8 sm:w-auto sm:border-s sm:border-t-0 dark:border-neutral-500" >
- {/* Review Ratings */}
-
- {/* Your star ratings */} - { - Array(starCount) - .fill(0) - .map((_, i) => ) - } - {/* Adding additional half-star */} - -

- +

- +

diff --git a/apps/website/src/components/ui/forms/input/EmailContactInput.astro b/apps/website/src/components/ui/forms/input/EmailContactInput.astro index 69e42d00..bfe6215b 100644 --- a/apps/website/src/components/ui/forms/input/EmailContactInput.astro +++ b/apps/website/src/components/ui/forms/input/EmailContactInput.astro @@ -21,6 +21,6 @@ interface Props { id={id} autocomplete="email" class="block w-full rounded-lg border border-neutral-200 bg-neutral-50 px-4 py-3 text-sm text-neutral-700 placeholder:text-neutral-500 focus:border-neutral-200 focus:ring-3 focus:ring-neutral-400 focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-600 dark:bg-neutral-700/30 dark:text-neutral-300 dark:placeholder:text-neutral-400 dark:focus:ring-1" - placeholder="Email" + placeholder={label} />
diff --git a/apps/website/src/components/ui/forms/input/EmailFooterInput.astro b/apps/website/src/components/ui/forms/input/EmailFooterInput.astro index 54e8ab1d..b0c3b045 100644 --- a/apps/website/src/components/ui/forms/input/EmailFooterInput.astro +++ b/apps/website/src/components/ui/forms/input/EmailFooterInput.astro @@ -1,36 +1,18 @@ --- -const { - label = 'Search', - title = Astro.currentLocale === 'fr' ? "S'abonner" : 'Subscribe', - id = 'footer-input', -} = Astro.props; +const { title = 'Kontaktseite oeffnen', url = '/contact' } = Astro.props; interface Props { - label?: string; title?: string; - id?: string; + url?: string; } - -const placeholder = - Astro.currentLocale === 'fr' ? 'Entrez votre email' : 'Enter your email'; ---
-
- - -
{title} diff --git a/apps/website/src/components/ui/starlight/Head.astro b/apps/website/src/components/ui/starlight/Head.astro index c53ed163..e5527d42 100644 --- a/apps/website/src/components/ui/starlight/Head.astro +++ b/apps/website/src/components/ui/starlight/Head.astro @@ -1,11 +1,33 @@ --- import type { Props } from '@astrojs/starlight/props'; +import { getImage } from 'astro:assets'; import StarlightHead from '@astrojs/starlight/components/Head.astro'; import VtbotStarlight from 'astro-vtbot/components/starlight/Base.astro'; +import { OG } from '@data/constants'; + +const canonical = new URL(Astro.url.pathname, Astro.site || Astro.url.origin) + .href; +const socialImageRes = await getImage({ + src: OG.image, + width: 1200, + height: 600, + format: 'png', +}); +const socialImage = new URL(socialImageRes.src, Astro.site || Astro.url.origin) + .href; --- - + + + + + + + + + + diff --git a/apps/website/src/components/ui/starlight/SiteTitle.astro b/apps/website/src/components/ui/starlight/SiteTitle.astro index afad17d8..b0881f1d 100644 --- a/apps/website/src/components/ui/starlight/SiteTitle.astro +++ b/apps/website/src/components/ui/starlight/SiteTitle.astro @@ -1,9 +1,13 @@ --- -const docs = '/welcome-to-docs/'; +import { getLocaleFromPath, localizeHref } from '@/i18n'; + +const locale = getLocaleFromPath(Astro.url.pathname); +const home = localizeHref('/', locale); +const docs = localizeHref('/welcome-to-docs/', locale); --- - + diff --git a/apps/website/src/content/docs/en/guides/first-project-checklist.mdx b/apps/website/src/content/docs/en/guides/first-project-checklist.mdx new file mode 100644 index 00000000..4244ed70 --- /dev/null +++ b/apps/website/src/content/docs/en/guides/first-project-checklist.mdx @@ -0,0 +1,15 @@ +--- +title: Evaluation Checklist +description: A conservative checklist for early Tenantial evaluation. +sidebar: + label: Evaluation Checklist + order: 3 +--- + +Use this checklist before a product walkthrough: + +- Confirm which Microsoft tenant workflows are in scope. +- Separate read-only review needs from any future write or restore needs. +- Identify audit and approval expectations. +- Plan staging validation before production rollout. +- Keep private tenant data out of public website communication. diff --git a/apps/website/src/content/docs/en/guides/getting-started.mdx b/apps/website/src/content/docs/en/guides/getting-started.mdx new file mode 100644 index 00000000..cf7cadf1 --- /dev/null +++ b/apps/website/src/content/docs/en/guides/getting-started.mdx @@ -0,0 +1,18 @@ +--- +title: Getting Started +description: How evaluators should approach a Tenantial walkthrough. +sidebar: + label: Getting Started + order: 2 +--- + +Start with a scoped conversation about the tenant governance problem you want to solve. + +Useful preparation: + +- Identify the Microsoft tenant administration workflows that need review. +- List which policy families, backup flows, or restore planning scenarios matter first. +- Decide who needs to participate in evidence review and approval. +- Avoid sending private tenant exports or credentials through public website contact paths. + +Tenantial walkthroughs should stay focused on review clarity, least-privilege rollout planning, and staging validation before production use. diff --git a/apps/website/src/content/docs/en/guides/intro.mdx b/apps/website/src/content/docs/en/guides/intro.mdx new file mode 100644 index 00000000..371dce1d --- /dev/null +++ b/apps/website/src/content/docs/en/guides/intro.mdx @@ -0,0 +1,18 @@ +--- +title: Introduction to Tenantial +description: Public introduction to Tenantial's evidence-first Microsoft tenant governance model. +sidebar: + label: Introduction + order: 1 +--- + +Tenantial is positioned around careful Microsoft tenant governance. Public materials focus on observed inventory, immutable policy snapshots, drift review, findings, exceptions, audit context, and defensive restore planning. + +The website documentation is intentionally public and static. It does not expose live tenant data, operational evidence, private support material, credentials, or connected Microsoft accounts. + +## What to review first + +- How policy evidence is collected and normalized for review. +- How snapshots support comparison and restore planning. +- How findings and exceptions make decisions attributable. +- How restore is treated as preview-first work. diff --git a/apps/website/src/content/docs/en/platform/evidence-review.mdx b/apps/website/src/content/docs/en/platform/evidence-review.mdx new file mode 100644 index 00000000..216ca749 --- /dev/null +++ b/apps/website/src/content/docs/en/platform/evidence-review.mdx @@ -0,0 +1,17 @@ +--- +title: Evidence Review +description: Public platform note for Tenantial evidence review workflows. +sidebar: + label: Evidence Review + order: 1 +--- + +Evidence review is the public framing for Tenantial's product direction. + +The model is intentionally cautious: + +- Inventory is treated as observed state. +- Snapshots are explicit records for comparison and planning. +- Drift and findings become review work. +- Exceptions and audit notes keep decisions attributable. +- Restore is planned through preview, validation, selective scope, and explicit confirmation. diff --git a/apps/website/src/content/docs/en/welcome-to-docs.mdx b/apps/website/src/content/docs/en/welcome-to-docs.mdx new file mode 100644 index 00000000..8a3cb6be --- /dev/null +++ b/apps/website/src/content/docs/en/welcome-to-docs.mdx @@ -0,0 +1,31 @@ +--- +title: Tenantial Docs +head: + - tag: title + content: Tenantial Docs +description: Public notes for understanding Tenantial's evidence-first Microsoft tenant governance model. +editUrl: false +lastUpdated: false +next: false +hero: + title: Tenantial documentation + tagline: Public notes for evidence review, snapshot discipline, drift review, and cautious restore planning. + actions: + - text: Get started + icon: right-arrow + variant: primary + link: /en/guides/getting-started/ +--- + +import '@styles/starlight_main.css'; +import { Card, CardGrid } from '@astrojs/starlight/components'; + + + + Understand the public product model before planning a rollout conversation. + + + + Review how Tenantial frames inventory evidence, snapshots, findings, and restore planning. + + diff --git a/apps/website/src/content/docs/guides/first-project-checklist.mdx b/apps/website/src/content/docs/guides/first-project-checklist.mdx index 4244ed70..7a44399a 100644 --- a/apps/website/src/content/docs/guides/first-project-checklist.mdx +++ b/apps/website/src/content/docs/guides/first-project-checklist.mdx @@ -1,15 +1,15 @@ --- -title: Evaluation Checklist -description: A conservative checklist for early Tenantial evaluation. +title: Evaluierungscheckliste +description: Eine konservative Checkliste für eine frühe Tenantial-Evaluierung. sidebar: - label: Evaluation Checklist + label: Evaluierungscheckliste order: 3 --- -Use this checklist before a product walkthrough: +Nutze diese Checkliste vor einem Produkt-Walkthrough: -- Confirm which Microsoft tenant workflows are in scope. -- Separate read-only review needs from any future write or restore needs. -- Identify audit and approval expectations. -- Plan staging validation before production rollout. -- Keep private tenant data out of public website communication. +- Bestätige, welche Microsoft-Tenant-Workflows im Scope sind. +- Trenne Read-only-Review-Bedarf von künftigem Write- oder Restore-Bedarf. +- Identifiziere Audit- und Approval-Erwartungen. +- Plane Staging-Validierung vor einem Produktionsrollout. +- Halte private Tenant-Daten aus öffentlicher Website-Kommunikation heraus. diff --git a/apps/website/src/content/docs/guides/getting-started.mdx b/apps/website/src/content/docs/guides/getting-started.mdx index cf7cadf1..0ca9ed85 100644 --- a/apps/website/src/content/docs/guides/getting-started.mdx +++ b/apps/website/src/content/docs/guides/getting-started.mdx @@ -1,18 +1,18 @@ --- title: Getting Started -description: How evaluators should approach a Tenantial walkthrough. +description: Wie Evaluatoren einen Tenantial Walkthrough vorbereiten sollten. sidebar: label: Getting Started order: 2 --- -Start with a scoped conversation about the tenant governance problem you want to solve. +Starte mit einem scoped Gespräch über das Tenant-Governance-Problem, das gelöst werden soll. -Useful preparation: +Sinnvolle Vorbereitung: -- Identify the Microsoft tenant administration workflows that need review. -- List which policy families, backup flows, or restore planning scenarios matter first. -- Decide who needs to participate in evidence review and approval. -- Avoid sending private tenant exports or credentials through public website contact paths. +- Identifiziere die Microsoft-Tenant-Administrationsworkflows, die Review brauchen. +- Liste die Policy-Familien, Backup-Flows oder Restore-Planungsszenarien, die zuerst relevant sind. +- Entscheide, wer an Evidence Review und Approval teilnehmen muss. +- Sende keine privaten Tenant-Exporte oder Credentials über öffentliche Website-Kontaktpfade. -Tenantial walkthroughs should stay focused on review clarity, least-privilege rollout planning, and staging validation before production use. +Tenantial Walkthroughs sollten auf Review-Klarheit, Least-Privilege-Rollout-Planung und Staging-Validierung vor Produktionseinsatz fokussiert bleiben. diff --git a/apps/website/src/content/docs/guides/intro.mdx b/apps/website/src/content/docs/guides/intro.mdx index 371dce1d..f37e0bab 100644 --- a/apps/website/src/content/docs/guides/intro.mdx +++ b/apps/website/src/content/docs/guides/intro.mdx @@ -1,18 +1,18 @@ --- -title: Introduction to Tenantial -description: Public introduction to Tenantial's evidence-first Microsoft tenant governance model. +title: Einführung in Tenantial +description: Öffentliche Einführung in Tenantials evidenzbasiertes Microsoft-Tenant-Governance-Modell. sidebar: - label: Introduction + label: Einführung order: 1 --- -Tenantial is positioned around careful Microsoft tenant governance. Public materials focus on observed inventory, immutable policy snapshots, drift review, findings, exceptions, audit context, and defensive restore planning. +Tenantial ist auf vorsichtige Microsoft-Tenant-Governance ausgerichtet. Öffentliche Materialien fokussieren beobachtetes Inventory, unveränderliche Policy-Snapshots, Drift Review, Findings, Exceptions, Audit-Kontext und defensive Restore-Planung. -The website documentation is intentionally public and static. It does not expose live tenant data, operational evidence, private support material, credentials, or connected Microsoft accounts. +Die Website-Dokumentation ist bewusst öffentlich und statisch. Sie legt keine Live-Tenant-Daten, operative Evidence, private Support-Materialien, Credentials oder verbundenen Microsoft-Konten offen. -## What to review first +## Was zuerst geprüft werden sollte -- How policy evidence is collected and normalized for review. -- How snapshots support comparison and restore planning. -- How findings and exceptions make decisions attributable. -- How restore is treated as preview-first work. +- Wie Policy Evidence für Reviews gesammelt und normalisiert wird. +- Wie Snapshots Vergleich und Restore-Planung unterstützen. +- Wie Findings und Exceptions Entscheidungen zuordenbar machen. +- Wie Restore als preview-first Arbeit behandelt wird. diff --git a/apps/website/src/content/docs/platform/evidence-review.mdx b/apps/website/src/content/docs/platform/evidence-review.mdx index 216ca749..8dfdaa9a 100644 --- a/apps/website/src/content/docs/platform/evidence-review.mdx +++ b/apps/website/src/content/docs/platform/evidence-review.mdx @@ -1,17 +1,17 @@ --- title: Evidence Review -description: Public platform note for Tenantial evidence review workflows. +description: Öffentliche Plattform-Notiz für Tenantial Evidence-Review-Workflows. sidebar: label: Evidence Review order: 1 --- -Evidence review is the public framing for Tenantial's product direction. +Evidence Review ist die öffentliche Rahmung für Tenantials Produktrichtung. -The model is intentionally cautious: +Das Modell ist bewusst vorsichtig: -- Inventory is treated as observed state. -- Snapshots are explicit records for comparison and planning. -- Drift and findings become review work. -- Exceptions and audit notes keep decisions attributable. -- Restore is planned through preview, validation, selective scope, and explicit confirmation. +- Inventory wird als beobachteter Zustand behandelt. +- Snapshots sind explizite Records für Vergleich und Planung. +- Drift und Findings werden zu Review-Arbeit. +- Exceptions und Audit-Notizen halten Entscheidungen zuordenbar. +- Restore wird über Preview, Validierung, selektiven Scope und explizite Bestätigung geplant. diff --git a/apps/website/src/content/docs/welcome-to-docs.mdx b/apps/website/src/content/docs/welcome-to-docs.mdx index 57cf6773..b81cb6f6 100644 --- a/apps/website/src/content/docs/welcome-to-docs.mdx +++ b/apps/website/src/content/docs/welcome-to-docs.mdx @@ -3,28 +3,29 @@ title: Tenantial Docs head: - tag: title content: Tenantial Docs -description: Public notes for understanding Tenantial's evidence-first Microsoft tenant governance model. +description: Öffentliche Notizen zum evidenzbasierten Microsoft-Tenant-Governance-Modell von Tenantial. editUrl: false lastUpdated: false next: false hero: - title: Tenantial documentation - tagline: Public notes for evidence review, snapshot discipline, drift review, and cautious restore planning. + title: Tenantial Dokumentation + tagline: Öffentliche Notizen zu Evidence Review, Snapshot-Disziplin, Drift Review und vorsichtiger Restore-Planung. actions: - - text: Get started + - text: Starten icon: right-arrow variant: primary link: /guides/getting-started/ --- -import "@styles/starlight_main.css"; + +import '@styles/starlight_main.css'; import { Card, CardGrid } from '@astrojs/starlight/components'; - - Understand the public product model before planning a rollout conversation. + + Verstehe das öffentliche Produktmodell, bevor ein Rollout-Gespräch geplant wird. - - Review how Tenantial frames inventory evidence, snapshots, findings, and restore planning. + + Prüfe, wie Tenantial Inventory Evidence, Snapshots, Findings und Restore-Planung rahmt. diff --git a/apps/website/src/data_files/constants.ts b/apps/website/src/data_files/constants.ts index 21a80165..f9b0a3da 100644 --- a/apps/website/src/data_files/constants.ts +++ b/apps/website/src/data_files/constants.ts @@ -2,11 +2,11 @@ import ogImageSrc from '@images/social.png'; export const SITE = { title: 'Tenantial', - tagline: 'Evidence-first Microsoft tenant governance', + tagline: 'Evidenzbasierte Microsoft-Tenant-Governance', description: - 'Tenantial helps Microsoft tenant administrators review policy evidence, snapshots, drift, findings, and restore plans before high-impact changes move forward.', + 'Tenantial hilft Microsoft-Tenant-Administratoren, Policy-Evidence, Snapshots, Drift, Findings und Restore-Pläne vor kritischen Änderungen zu prüfen.', description_short: - 'Evidence-first governance workflows for Microsoft tenant review, backup, drift, and restore planning.', + 'Evidenzbasierte Governance-Workflows für Microsoft-Tenant-Review, Backup, Drift und Restore-Planung.', url: 'https://tenantial.com', author: 'Tenantial', }; @@ -17,7 +17,7 @@ export const SEO = { structuredData: { '@context': 'https://schema.org', '@type': 'WebPage', - inLanguage: 'en-US', + inLanguage: 'de', '@id': SITE.url, url: SITE.url, name: SITE.title, @@ -32,12 +32,12 @@ export const SEO = { }; export const OG = { - locale: 'en_US', + locale: 'de_DE', type: 'website', url: SITE.url, - title: `${SITE.title}: Evidence-first Microsoft tenant governance`, + title: `${SITE.title}: Evidenzbasierte Microsoft-Tenant-Governance`, description: - 'Review tenant inventory, snapshots, drift, findings, exceptions, and restore plans with conservative, audit-friendly workflows.', + 'Prüfe Tenant Inventory, Snapshots, Drift, Findings, Exceptions und Restore-Pläne mit konservativen, auditfreundlichen Workflows.', image: ogImageSrc, }; diff --git a/apps/website/src/data_files/faqs.json b/apps/website/src/data_files/faqs.json index 69a8349d..a409fc17 100644 --- a/apps/website/src/data_files/faqs.json +++ b/apps/website/src/data_files/faqs.json @@ -1,6 +1,10 @@ { "subTitle": "Straight answers for public website visitors.", "faqs": [ + { + "question": "What does Tenantial help teams understand?", + "answer": "Tenantial is positioned around backup evidence, restore planning, drift detection, findings, auditability, exceptions, and reviews for Microsoft tenant configuration." + }, { "question": "Does this public website connect to a live tenant?", "answer": "No. Product previews and examples are static demonstrations and do not authenticate visitors or read tenant data." @@ -9,10 +13,6 @@ "question": "Is restore positioned as an automatic outcome?", "answer": "No. Tenantial describes restore as a cautious workflow with preview, validation, selective scope, explicit confirmation, and review context." }, - { - "question": "Are pricing plans self-serve?", - "answer": "Not from this website. Pricing is framed around evaluation and scoped rollout conversations until commercial packaging is finalized." - }, { "question": "Does Tenantial publish customer proof here?", "answer": "No customer logos, third-party endorsements, external assurance statements, service-level commitments, or recovery promises are published without supplied and reviewed evidence." diff --git a/apps/website/src/data_files/features.json b/apps/website/src/data_files/features.json index a822fdfe..c7c7337f 100644 --- a/apps/website/src/data_files/features.json +++ b/apps/website/src/data_files/features.json @@ -1,22 +1,22 @@ [ { - "heading": "Inventory evidence", - "content": "Normalize observed policy metadata so reviews start from what was last seen, not scattered exports.", + "heading": "Backup evidence", + "content": "Review observed policy metadata and backup snapshots from one readable source before decisions start.", "svg": "frame" }, { - "heading": "Immutable snapshots", - "content": "Keep point-in-time policy records that can be compared, reviewed, and used for restore planning.", + "heading": "Restore planning", + "content": "Compare immutable snapshots and plan restore work with validation, selective scope, and explicit review.", "svg": "dashboard" }, { - "heading": "Drift review", - "content": "Turn differences and findings into readable review work before administrators decide what to change.", + "heading": "Drift detection", + "content": "Turn differences, findings, and evidence into readable review work before administrators decide what to change.", "svg": "verified" }, { - "heading": "Restore caution", - "content": "Frame restore around preview, validation, conflict awareness, selective scope, and explicit confirmation.", + "heading": "Auditability and exceptions", + "content": "Keep exceptions, review notes, and auditability visible so decisions stay attributable.", "svg": "checkCircle" } ] diff --git a/apps/website/src/data_files/pricing.json b/apps/website/src/data_files/pricing.json index 40977f80..392cdd6a 100644 --- a/apps/website/src/data_files/pricing.json +++ b/apps/website/src/data_files/pricing.json @@ -1,12 +1,12 @@ { "title": "Evaluation and rollout options", - "subTitle": "Commercial packaging is intentionally conservative on the public website. No self-serve checkout or fixed entitlement claim is implied.", + "subTitle": "Commercial packaging is intentionally conservative on the public website. No instant payment path or fixed access package is implied.", "badge": "Scoped", - "thirdOption": "Need a narrower advisory conversation?", - "btnText": "Contact us", + "thirdOption": "Need a narrower evaluation conversation?", + "btnText": "Contact Tenantial", "starterKit": { "name": "Evaluation", - "description": "For teams reviewing fit before rollout.", + "description": "For teams reviewing fit before any rollout commitment.", "price": "Contact", "cents": "", "billingFrequency": "for scope", @@ -20,7 +20,7 @@ }, "professionalToolbox": { "name": "Managed rollout", - "description": "For planned staging and operator review.", + "description": "For planned staging and operator review conversations.", "price": "Scoped", "cents": "", "billingFrequency": "quote", diff --git a/apps/website/src/data_files/site-copy.ts b/apps/website/src/data_files/site-copy.ts new file mode 100644 index 00000000..a9307cd7 --- /dev/null +++ b/apps/website/src/data_files/site-copy.ts @@ -0,0 +1,651 @@ +import type { Locale } from '@/i18n'; + +type Feature = { + heading: string; + content: string; + svg: string; +}; + +type PricingProduct = { + name: string; + description: string; + price: string; + cents: string; + billingFrequency: string; + features: string[]; + purchaseBtnTitle: string; + purchaseLink: string; +}; + +type Pricing = { + title: string; + subTitle: string; + badge: string; + thirdOption: string; + btnText: string; + starterKit: PricingProduct; + professionalToolbox: PricingProduct; +}; + +type FaqGroup = { + subTitle: string; + faqs: Array<{ question: string; answer: string }>; +}; + +export const siteCopy: Record = { + de: { + site: { + tagline: 'Evidenzbasierte Microsoft-Tenant-Governance', + description: + 'Tenantial hilft Microsoft-Tenant-Administratoren, Policy-Evidence, Snapshots, Drift, Findings und Restore-Pläne vor kritischen Änderungen zu prüfen.', + descriptionShort: + 'Evidenzbasierte Governance-Workflows für Microsoft-Tenant-Reviews, Backup, Drift und Restore-Planung.', + }, + nav: [ + { name: 'Start', url: '/' }, + { name: 'Plattform', url: '/platform' }, + { name: 'Preise', url: '/pricing' }, + { name: 'Vertrauen', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + { name: 'Kontakt', url: '/contact' }, + ], + footer: { + sections: [ + { + section: 'Produkt', + links: [ + { name: 'Plattform', url: '/platform' }, + { name: 'Preise', url: '/pricing' }, + { name: 'Vertrauen', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + ], + }, + { + section: 'Rechtliches', + links: [ + { name: 'Datenschutz', url: '/privacy' }, + { name: 'Nutzungsbedingungen', url: '/terms' }, + { name: 'Rechtliches', url: '/legal' }, + { name: 'Impressum', url: '/imprint' }, + ], + }, + ], + conversationTitle: 'Scoped Conversation starten', + conversationContent: + 'Nutze die Kontaktseite oder E-Mail nur für Business-Kontext. Sende keine Secrets, Credentials oder Tenant-Exporte über die öffentliche Website.', + contactButton: 'Kontaktseite öffnen', + copyrightSuffix: 'Nur öffentliche Website-Inhalte.', + }, + auth: { + walkthrough: 'Walkthrough anfragen', + }, + home: { + pageTitle: 'Tenantial | Evidenzbasierte Microsoft-Tenant-Governance', + metaDescription: + 'Tenantial ist evidenzbasierte Governance für Microsoft-Tenant-Konfiguration mit Backup, Restore, Drift Detection, Findings, Evidence, Auditability, Exceptions und Reviews.', + heroTitle: + 'Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration', + heroSubtitle: + 'Tenantial hilft Administratoren, Backup-Evidence, unveränderliche Snapshots, Drift Detection, Findings, Exceptions, Auditability und Restore-Pläne zu prüfen, bevor kritische Änderungen umgesetzt werden.', + primaryCta: 'Walkthrough anfragen', + secondaryCta: 'Plattform ansehen', + heroAlt: 'Statische Tenantial Governance-Dashboard-Vorschau', + featureTitle: 'Backup, Restore und Drift Detection mit Review-Kontext', + featureSubtitle: + 'Tenantial hält Backup-Snapshots, Findings, Evidence, Auditability, Exceptions und Reviews sichtbar, damit Restore-Entscheidungen preview-first bleiben. Die öffentliche Vorschau ist statisch und verbindet sich nicht mit Live-Tenant-Daten.', + featureAlt: 'Statische Tenantial Review-Board-Vorschau', + workflowTitle: + 'Ein Review-Workflow für evidenzbasierte Tenant-Governance.', + tabs: [ + { + heading: 'Evidence Intake', + content: + 'Normalisiere Inventory-, Backup- und Snapshot-Kontext, damit Reviews mit einer lesbaren Sicht auf den beobachteten Policy-Zustand starten.', + svg: 'frame', + alt: 'Statische Tenantial Evidence-Intake-Vorschau', + first: true, + }, + { + heading: 'Decision Review', + content: + 'Mache Drift Detection, Findings, Exceptions, Auditability und Review-Notizen sichtbar, bevor Administratoren die nächste Aktion wählen.', + svg: 'dashboard', + alt: 'Statische Tenantial Decision-Review-Vorschau', + second: true, + }, + { + heading: 'Restore-Planung', + content: + 'Behandle Restore als preview-first Arbeit mit Validierung, Konfliktbewusstsein, selektivem Scope und expliziter Bestätigung.', + svg: 'verified', + alt: 'Statische Tenantial Restore-Planungs-Vorschau', + }, + ], + faqTitle: 'Häufige
Fragen', + }, + platform: { + pageTitle: 'Plattform | Tenantial', + metaDescription: + 'Tenantial öffentliches Produktmodell für Microsoft-Tenant Backup-Evidence, Restore-Planung, Drift Detection, Findings, Auditability, Exceptions und Reviews.', + heading: 'Plattform-Review-Modell', + subtitle: + 'Tenantial ist auf Backup-Evidence, unveränderliche Policy-Snapshots, strukturierte Diffs, Drift Detection, Findings, Exceptions, Auditability, Governance-Reviews und vorsichtige Restore-Workflows für Microsoft-Tenant-Administratoren ausgerichtet.', + backupTitle: 'Backup- und Snapshot-Evidence', + backupSubtitle: + 'Administratoren brauchen einen reproduzierbaren Nachweis dessen, was beobachtet wurde, bevor entschieden wird, ob eine Änderung sicher ist. Tenantial rahmt Backup-Snapshots als Review-Evidence, nicht als versteckte Automation.', + dashboardAlt: 'Statische Tenantial Inventory-Dashboard-Vorschau', + evidenceAlt: 'Statische Tenantial Evidence-Review-Vorschau', + driftTitle: 'Drift Detection, Findings und Exceptions', + driftSubtitle: + 'Unterschiede und Findings sollen lesbare Entscheidungsarbeit werden. Exceptions, Evidence, Review-Notizen und Auditability halten die Begründung sichtbar, ohne zu behaupten, dass die öffentliche Website mit Live-Tenant-Daten verbunden ist.', + driftAlt: 'Statische Tenantial Drift-Workflow-Vorschau', + trustCta: 'Trust-Posture ansehen', + restoreTitle: 'Restore-Planung bleibt defensiv', + restoreSubtitle: + 'Restore-Pfade werden als preview-first Workflows mit Validierung, Konfliktbewusstsein, selektivem Scope und expliziter Bestätigung vor sensiblen Aktionen beschrieben.', + restoreAlt: 'Statische Tenantial Rollout-Readiness-Plan-Vorschau', + rolloutCta: 'Rollout besprechen', + boundaryTitle: 'Grenzen der öffentlichen Vorschau', + boundarySubtitle: + 'Die Website nutzt statische Demo-Previews, um Richtung und visuelle Grundlage zu zeigen. Sie authentifiziert keine Besucher, liest keinen Microsoft Tenant, führt keine Operationen aus und speichert keine Tenant-Exporte.', + stats: [ + { stat: 'Statisch', description: 'Produkt-Previews' }, + { stat: 'Preview', description: 'vor Restore-Ausführung' }, + { stat: 'Review', description: 'vor kritischen Änderungen' }, + ], + mainStatTitle: '0', + mainStatSubTitle: + 'Live-Tenant-Datensätze werden von dieser öffentlichen Website genutzt', + }, + pricingIntro: { + pageTitle: 'Preise | Tenantial', + metaDescription: + 'Tenantial Preise nutzen konservative Evaluierungs- und Rollout-Gespräche statt sofortiger Zahlung oder Access-Claims.', + heading: 'Preise bleiben scoped', + subtitle: + 'Tenantial behauptet auf der öffentlichen Website keine Sofortzahlung, keinen festen Paket-Zugang und keine automatische Tenant-Verbindung.', + }, + contact: { + pageTitle: 'Kontakt | Tenantial', + metaDescription: + 'Tenantial Walkthrough anfragen oder ein scoped Rollout-Gespräch starten. Die öffentliche Website sammelt keine Live-Tenant-Daten.', + title: 'Kontakt zu Tenantial', + subtitle: + 'Fordere einen Walkthrough an oder starte ein scoped Rollout-Gespräch. Sende keine Secrets, Credentials oder Tenant-Exporte über diese öffentliche Website.', + formTitle: 'Walkthrough-Anfrage vorbereiten', + formSubtitle: + 'Dieses statische Formular sendet nicht an ein Backend. Nutze es, um Kontext vor einer E-Mail an Tenantial vorzubereiten.', + firstName: 'Vorname', + lastName: 'Nachname', + email: 'E-Mail', + phone: 'Telefonnummer', + details: 'Details', + submit: 'E-Mail-Entwurf vorbereiten', + blocks: { + docsHeading: 'Knowledgebase', + docsContent: 'Lies öffentliche Notizen zum Tenantial-Review-Modell.', + docsLink: 'Docs besuchen', + faqHeading: 'FAQ', + faqContent: + 'Durchsuche die FAQ für kurze, klare Antworten auf häufige Fragen.', + faqLink: 'FAQ besuchen', + boundaryHeading: 'Grenze der Produktvorschau', + boundaryContent: + 'Die Website verbindet sich nicht mit Live-Tenant-Daten und startet keine Backend-Workflows.', + emailHeading: 'Kontakt per E-Mail', + emailContent: 'Lieber schriftlich? Schreibe uns eine E-Mail an', + }, + }, + trust: { + pageTitle: 'Vertrauen | Tenantial', + metaDescription: + 'Tenantial öffentliche Trust-Posture mit konservativen Claims und klaren Grenzen für statische Previews.', + heading: 'Vertrauen beginnt mit klaren Grenzen', + subtitle: + 'Tenantial Public Copy vermeidet Customer-Logo-Proof, Third-Party-Endorsements, externe Assurance-Aussagen, Service-Level-Commitments und Recovery-Versprechen, solange sie nicht geliefert und geprüft wurden.', + cta: 'Tenantial kontaktieren', + statsTitle: 'Public Website Posture', + statsSubtitle: + 'Die Website beschreibt eine vorsichtige Produktrichtung. Sie legt keine privaten Tenant-Daten offen, führt keine Tenant-Operationen aus und ist kein Support-Evidence-Portal.', + mainStatTitle: 'Statisch', + mainStatSubTitle: 'nur Produkt-Previews', + stats: [ + { stat: 'Kein', description: 'Customer-Logo-Proof' }, + { stat: 'Kein', description: 'externer Endorsement-Claim' }, + { stat: 'Keine', description: 'Live-Tenant-Verbindung' }, + ], + }, + legal: { + pageTitle: 'Rechtliches | Tenantial', + metaDescription: + 'Tenantial öffentliche rechtliche Website-Informationen.', + heading: 'Öffentliche rechtliche Informationen', + subtitle: + 'Diese Seiten enthalten konservative Informationen zur öffentlichen Website und sollten vor einer produktiven Veröffentlichung in regulierten Kontexten geprüft werden.', + privacy: 'Datenschutz', + terms: 'Nutzungsbedingungen', + imprint: 'Impressum', + }, + privacy: { + pageTitle: 'Datenschutz | Tenantial', + metaDescription: 'Tenantial öffentlicher Website-Datenschutzhinweis.', + heading: 'Datenschutzhinweis', + subtitle: + 'Diese statische öffentliche Website beschreibt Tenantial und die Produktrichtung. Produkt-Previews verbinden sich nicht mit einem Live-Tenant. Kontaktlinks können deinen E-Mail-Client öffnen; sende keine Secrets, Credentials oder privaten Tenant-Exporte.', + }, + terms: { + pageTitle: 'Nutzungsbedingungen | Tenantial', + metaDescription: 'Tenantial öffentliche Website-Nutzungsbedingungen.', + heading: 'Website-Nutzungsbedingungen', + subtitle: + 'Die öffentliche Website beschreibt Tenantial-Positionierung und statische Produktkonzepte. Sie stellt keinen Produktionsservice bereit, verbindet sich nicht mit einem Tenant und führt keine administrativen Aktionen aus. Kommerzielle Bedingungen erfordern eine separate schriftliche Vereinbarung.', + }, + imprint: { + pageTitle: 'Impressum | Tenantial', + metaDescription: + 'Tenantial öffentliche Website-Veröffentlichungsinformationen.', + heading: 'Veröffentlichungsinformationen', + subtitle: + 'Publisher: Tenantial. Business-Kontakt: hello@tenantial.com. Formale Registrierungs- und Verantwortlichkeitsangaben sollten im schriftlichen Evaluierungsprozess bestätigt werden, bevor diese Seite für regulierte Publikationsanforderungen genutzt wird.', + }, + }, + en: { + site: { + tagline: 'Evidence-first Microsoft tenant governance', + description: + 'Tenantial helps Microsoft tenant administrators review policy evidence, snapshots, drift, findings, and restore plans before high-impact changes move forward.', + descriptionShort: + 'Evidence-first governance workflows for Microsoft tenant review, backup, drift, and restore planning.', + }, + nav: [ + { name: 'Home', url: '/' }, + { name: 'Platform', url: '/platform' }, + { name: 'Pricing', url: '/pricing' }, + { name: 'Trust', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + { name: 'Contact', url: '/contact' }, + ], + footer: { + sections: [ + { + section: 'Product', + links: [ + { name: 'Platform', url: '/platform' }, + { name: 'Pricing', url: '/pricing' }, + { name: 'Trust', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + ], + }, + { + section: 'Legal', + links: [ + { name: 'Privacy', url: '/privacy' }, + { name: 'Terms', url: '/terms' }, + { name: 'Legal', url: '/legal' }, + { name: 'Imprint', url: '/imprint' }, + ], + }, + ], + conversationTitle: 'Start a scoped conversation', + conversationContent: + 'Use the contact page or email for business context only. Do not send secrets, credentials, or tenant exports through the public website.', + contactButton: 'Open contact page', + copyrightSuffix: 'Public website content only.', + }, + auth: { + walkthrough: 'Request walkthrough', + }, + home: { + pageTitle: 'Tenantial | Evidence-first Microsoft tenant governance', + metaDescription: + 'Tenantial is evidence-first governance for Microsoft tenant configuration, with backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews.', + heroTitle: + 'Evidence-first governance for Microsoft tenant configuration', + heroSubtitle: + 'Tenantial helps administrators review backup evidence, immutable snapshots, drift detection, findings, exceptions, auditability, and restore plans before high-impact changes move forward.', + primaryCta: 'Request walkthrough', + secondaryCta: 'Explore platform', + heroAlt: 'Static Tenantial governance dashboard preview', + featureTitle: 'Backup, restore, and drift detection with review context', + featureSubtitle: + 'Tenantial keeps backup snapshots, findings, evidence, auditability, exceptions, and reviews visible so restore decisions stay preview-first. The public preview is static and does not connect to live tenant data.', + featureAlt: 'Static Tenantial review board preview', + workflowTitle: + 'A review workflow for evidence-first tenant governance.', + tabs: [ + { + heading: 'Evidence intake', + content: + 'Normalize inventory, backup, and snapshot context so reviews start from one readable view of observed policy state.', + svg: 'frame', + alt: 'Static Tenantial evidence intake preview', + first: true, + }, + { + heading: 'Decision review', + content: + 'Surface drift detection, findings, exceptions, auditability, and review notes before administrators choose the next action.', + svg: 'dashboard', + alt: 'Static Tenantial decision review preview', + second: true, + }, + { + heading: 'Restore planning', + content: + 'Treat restore as preview-first work with validation, conflict awareness, selective scope, and explicit confirmation.', + svg: 'verified', + alt: 'Static Tenantial restore planning preview', + }, + ], + faqTitle: 'Frequently
asked questions', + }, + platform: { + pageTitle: 'Platform | Tenantial', + metaDescription: + 'Tenantial public product model for Microsoft tenant backup evidence, restore planning, drift detection, findings, auditability, exceptions, and reviews.', + heading: 'Platform review model', + subtitle: + 'Tenantial is positioned around backup evidence, immutable policy snapshots, structured diffs, drift detection, findings, exceptions, auditability, governance reviews, and cautious restore workflows for Microsoft tenant administrators.', + backupTitle: 'Backup and snapshot evidence', + backupSubtitle: + 'Administrators need a reproducible record of what was observed before deciding whether a change is safe. Tenantial frames backup snapshots as review evidence, not as hidden automation.', + dashboardAlt: 'Static Tenantial inventory dashboard preview', + evidenceAlt: 'Static Tenantial evidence review panel preview', + driftTitle: 'Drift detection, findings, and exceptions', + driftSubtitle: + 'Differences and findings should become readable decision work. Exceptions, evidence, review notes, and auditability keep the reasoning visible without claiming that the public website is connected to live tenant data.', + driftAlt: 'Static Tenantial drift workflow preview', + trustCta: 'Review trust posture', + restoreTitle: 'Restore planning stays defensive', + restoreSubtitle: + 'Restore paths are described as preview-first workflows with validation, conflict awareness, selective scope, and explicit confirmation before any sensitive action.', + restoreAlt: 'Static Tenantial rollout readiness plan preview', + rolloutCta: 'Discuss rollout', + boundaryTitle: 'Public preview boundaries', + boundarySubtitle: + 'The website uses static/demo previews to demonstrate the direction and visual foundation. It does not authenticate visitors, read a Microsoft tenant, execute operations, or store tenant exports.', + stats: [ + { stat: 'Static', description: 'product previews' }, + { stat: 'Preview', description: 'before restore execution' }, + { stat: 'Review', description: 'before high-impact changes' }, + ], + mainStatTitle: '0', + mainStatSubTitle: 'live tenant records used by this public website', + }, + pricingIntro: { + pageTitle: 'Pricing | Tenantial', + metaDescription: + 'Tenantial pricing uses conservative evaluation and rollout conversation paths, not instant public payment or access claims.', + heading: 'Pricing stays scoped', + subtitle: + 'Tenantial does not claim instant payment, fixed package access, or automatic tenant connection from the public website.', + }, + contact: { + pageTitle: 'Contact | Tenantial', + metaDescription: + 'Request a Tenantial walkthrough or start a scoped rollout conversation. The public website does not collect live tenant data.', + title: 'Contact Tenantial', + subtitle: + 'Request a walkthrough or start a scoped rollout conversation. Do not send secrets, credentials, or tenant exports through this public website.', + formTitle: 'Draft a walkthrough request', + formSubtitle: + 'This static form does not submit to a backend. Use it to draft context before emailing Tenantial.', + firstName: 'First Name', + lastName: 'Last Name', + email: 'Email', + phone: 'Phone Number', + details: 'Details', + submit: 'Prepare email draft', + blocks: { + docsHeading: 'Knowledgebase', + docsContent: 'Read public notes about the Tenantial review model.', + docsLink: 'Visit docs', + faqHeading: 'FAQ', + faqContent: + 'Explore our FAQ for quick, clear answers to common queries.', + faqLink: 'Visit FAQ', + boundaryHeading: 'Product preview boundary', + boundaryContent: + 'The website does not connect to live tenant data or start backend workflows.', + emailHeading: 'Contact us by email', + emailContent: 'Prefer the written word? Drop us an email at', + }, + }, + trust: { + pageTitle: 'Trust | Tenantial', + metaDescription: + 'Tenantial public trust posture with conservative claims and clear static preview boundaries.', + heading: 'Trust starts with clear boundaries', + subtitle: + 'Tenantial public copy avoids customer-logo proof, third-party endorsement language, external assurance statements, service-level commitments, and recovery promises unless supplied and reviewed.', + cta: 'Contact Tenantial', + statsTitle: 'Public website posture', + statsSubtitle: + 'The website describes a cautious product direction. It does not expose private tenant data, run tenant operations, or act as a support evidence portal.', + mainStatTitle: 'Static', + mainStatSubTitle: 'product previews only', + stats: [ + { stat: 'No', description: 'customer-logo proof' }, + { stat: 'No', description: 'external endorsement claim' }, + { stat: 'No', description: 'live tenant connection' }, + ], + }, + legal: { + pageTitle: 'Legal | Tenantial', + metaDescription: 'Tenantial public website legal information.', + heading: 'Public legal information', + subtitle: + 'These pages provide conservative public website information and should be reviewed before production publication in any regulated context.', + privacy: 'Privacy', + terms: 'Terms', + imprint: 'Imprint', + }, + privacy: { + pageTitle: 'Privacy | Tenantial', + metaDescription: 'Tenantial public website privacy notice.', + heading: 'Privacy notice', + subtitle: + 'This static public website describes Tenantial and its product direction. Product previews do not connect to a live tenant. Contact links may open your email client; do not send secrets, credentials, or private tenant exports.', + }, + terms: { + pageTitle: 'Terms | Tenantial', + metaDescription: 'Tenantial public website terms.', + heading: 'Website terms', + subtitle: + 'The public website describes Tenantial positioning and static product concepts. It does not provide a production service, connect to a tenant, or execute administrative actions. Commercial terms require a separate written agreement.', + }, + imprint: { + pageTitle: 'Imprint | Tenantial', + metaDescription: 'Tenantial public website publication information.', + heading: 'Publication information', + subtitle: + 'Publisher: Tenantial. Business contact: hello@tenantial.com. Formal registration and responsible-party details should be confirmed through the written evaluation process before relying on this page for regulated publication needs.', + }, + }, +}; + +export const featuresByLocale: Record = { + de: [ + { + heading: 'Backup-Evidence', + content: + 'Prüfe beobachtete Policy-Metadaten und Backup-Snapshots aus einer lesbaren Quelle, bevor Entscheidungen beginnen.', + svg: 'frame', + }, + { + heading: 'Restore-Planung', + content: + 'Vergleiche unveränderliche Snapshots und plane Restore-Arbeit mit Validierung, selektivem Scope und explizitem Review.', + svg: 'dashboard', + }, + { + heading: 'Drift Detection', + content: + 'Verwandle Unterschiede, Findings und Evidence in lesbare Review-Arbeit, bevor Administratoren Änderungen entscheiden.', + svg: 'verified', + }, + { + heading: 'Auditability und Exceptions', + content: + 'Halte Exceptions, Review-Notizen und Auditability sichtbar, damit Entscheidungen zuordenbar bleiben.', + svg: 'checkCircle', + }, + ], + en: [ + { + heading: 'Backup evidence', + content: + 'Review observed policy metadata and backup snapshots from one readable source before decisions start.', + svg: 'frame', + }, + { + heading: 'Restore planning', + content: + 'Compare immutable snapshots and plan restore work with validation, selective scope, and explicit review.', + svg: 'dashboard', + }, + { + heading: 'Drift detection', + content: + 'Turn differences, findings, and evidence into readable review work before administrators decide what to change.', + svg: 'verified', + }, + { + heading: 'Auditability and exceptions', + content: + 'Keep exceptions, review notes, and auditability visible so decisions stay attributable.', + svg: 'checkCircle', + }, + ], +}; + +export const pricingByLocale: Record = { + de: { + title: 'Evaluierungs- und Rollout-Optionen', + subTitle: + 'Kommerzielle Pakete bleiben auf der öffentlichen Website bewusst konservativ. Es wird kein Sofortkauf oder fester Access-Plan behauptet.', + badge: 'Scoped', + thirdOption: 'Brauchst du ein engeres Evaluierungsgespräch?', + btnText: 'Tenantial kontaktieren', + starterKit: { + name: 'Evaluation', + description: 'Für Teams, die Fit vor einer Rollout-Zusage prüfen.', + price: 'Kontakt', + cents: '', + billingFrequency: 'für Scope', + features: [ + 'Geführter Produkt-Walkthrough', + 'Statische Demo-Review', + 'Deployment-Fit-Gespräch', + ], + purchaseBtnTitle: 'Walkthrough anfragen', + purchaseLink: '/contact', + }, + professionalToolbox: { + name: 'Managed Rollout', + description: 'Für geplante Staging- und Operator-Review-Gespräche.', + price: 'Scoped', + cents: '', + billingFrequency: 'Angebot', + features: [ + 'Readiness Review', + 'Staging-Validierungsplan', + 'Operational Handoff Notes', + 'Trust- und Legal-Copy-Review', + ], + purchaseBtnTitle: 'Rollout besprechen', + purchaseLink: '/contact', + }, + }, + en: { + title: 'Evaluation and rollout options', + subTitle: + 'Commercial packaging is intentionally conservative on the public website. No instant payment path or fixed access package is implied.', + badge: 'Scoped', + thirdOption: 'Need a narrower evaluation conversation?', + btnText: 'Contact Tenantial', + starterKit: { + name: 'Evaluation', + description: 'For teams reviewing fit before any rollout commitment.', + price: 'Contact', + cents: '', + billingFrequency: 'for scope', + features: [ + 'Guided public product walkthrough', + 'Static demo review', + 'Deployment-fit discussion', + ], + purchaseBtnTitle: 'Request walkthrough', + purchaseLink: '/contact', + }, + professionalToolbox: { + name: 'Managed rollout', + description: 'For planned staging and operator review conversations.', + price: 'Scoped', + cents: '', + billingFrequency: 'quote', + features: [ + 'Readiness review', + 'Staging validation plan', + 'Operational handoff notes', + 'Trust and legal copy review', + ], + purchaseBtnTitle: 'Discuss rollout', + purchaseLink: '/contact', + }, + }, +}; + +export const faqsByLocale: Record = { + de: { + subTitle: 'Klare Antworten für Besucher der öffentlichen Website.', + faqs: [ + { + question: 'Was hilft Tenantial Teams zu verstehen?', + answer: + 'Tenantial ist auf Backup-Evidence, Restore-Planung, Drift Detection, Findings, Auditability, Exceptions und Reviews für Microsoft-Tenant-Konfiguration ausgerichtet.', + }, + { + question: + 'Verbindet sich diese öffentliche Website mit einem Live-Tenant?', + answer: + 'Nein. Produkt-Previews und Beispiele sind statische Demonstrationen und authentifizieren keine Besucher oder lesen Tenant-Daten.', + }, + { + question: 'Wird Restore als automatisches Ergebnis dargestellt?', + answer: + 'Nein. Tenantial beschreibt Restore als vorsichtigen Workflow mit Preview, Validierung, selektivem Scope, expliziter Bestätigung und Review-Kontext.', + }, + { + question: 'Veröffentlicht Tenantial hier Customer Proof?', + answer: + 'Ohne gelieferte und geprüfte Evidence werden keine Kundenlogos, Third-Party-Endorsements, externen Assurance-Aussagen, Service-Level-Commitments oder Recovery-Versprechen veröffentlicht.', + }, + ], + }, + en: { + subTitle: 'Straight answers for public website visitors.', + faqs: [ + { + question: 'What does Tenantial help teams understand?', + answer: + 'Tenantial is positioned around backup evidence, restore planning, drift detection, findings, auditability, exceptions, and reviews for Microsoft tenant configuration.', + }, + { + question: 'Does this public website connect to a live tenant?', + answer: + 'No. Product previews and examples are static demonstrations and do not authenticate visitors or read tenant data.', + }, + { + question: 'Is restore positioned as an automatic outcome?', + answer: + 'No. Tenantial describes restore as a cautious workflow with preview, validation, selective scope, explicit confirmation, and review context.', + }, + { + question: 'Does Tenantial publish customer proof here?', + answer: + 'No customer logos, third-party endorsements, external assurance statements, service-level commitments, or recovery promises are published without supplied and reviewed evidence.', + }, + ], + }, +}; diff --git a/apps/website/src/i18n.ts b/apps/website/src/i18n.ts new file mode 100644 index 00000000..9cb92569 --- /dev/null +++ b/apps/website/src/i18n.ts @@ -0,0 +1,75 @@ +export const defaultLocale = 'de'; +export const supportedLocales = ['de', 'en'] as const; + +export type Locale = (typeof supportedLocales)[number]; + +export const localeLabels: Record = { + de: 'Deutsch', + en: 'English', +}; + +export const localeHtmlLang: Record = { + de: 'de', + en: 'en', +}; + +export const localeOg: Record = { + de: 'de_DE', + en: 'en_US', +}; + +export function isLocale(value: string | undefined): value is Locale { + return supportedLocales.includes(value as Locale); +} + +export function getLocaleFromPath(pathname: string): Locale { + const firstSegment = pathname.split('/').filter(Boolean)[0]; + + return isLocale(firstSegment) ? firstSegment : defaultLocale; +} + +export function stripLocalePrefix(pathname: string): string { + const segments = pathname.split('/').filter(Boolean); + + if (isLocale(segments[0])) { + segments.shift(); + } + + const stripped = `/${segments.join('/')}`; + + return stripped === '/' ? '/' : stripped.replace(/\/+$/, ''); +} + +export function localizedPath(pathname: string, locale: Locale): string { + const basePath = stripLocalePrefix(pathname); + const keepTrailingSlash = pathname !== '/' && pathname.endsWith('/'); + const withTrailingSlash = (path: string) => + keepTrailingSlash && !path.endsWith('/') ? `${path}/` : path; + + if (locale === defaultLocale) { + return withTrailingSlash(basePath === '' ? '/' : basePath); + } + + if (basePath === '/' || basePath === '') { + return `/${locale}/`; + } + + return withTrailingSlash(`/${locale}${basePath}`); +} + +export function localizeHref(href: string, locale: Locale): string { + if ( + href.startsWith('http://') || + href.startsWith('https://') || + href.startsWith('mailto:') || + href.startsWith('tel:') || + href.startsWith('#') + ) { + return href; + } + + const [path, hash] = href.split('#'); + const localized = localizedPath(path || '/', locale); + + return hash ? `${localized}#${hash}` : localized; +} diff --git a/apps/website/src/images/tenantial-logo-lockup-mask.png b/apps/website/src/images/tenantial-logo-lockup-mask.png new file mode 100644 index 0000000000000000000000000000000000000000..154f1f9b83be864432c3d6f03bb7702ae28b4e2b GIT binary patch literal 30240 zcmZ6ybzECbw>FFf2%g|h2yVsQ5(w^Aq)2ctP^35{SfFU3E$;3VDHW_xq{Y2Zth6|V z7A^X5Kj*!lbAI0+$==B%d)8dD)-_9J5)AazNr@PUu&}U5H8l`MSXj8|`};~D{{1^5 z>U;40h0sUC!tcIe`=1wfTd0u-78V3c69G2~T096R$l`EnI}1GDAMy0J_>Py!p+WVc z2t&~T2{O#&uL6GT#P5kpr0b~4>hV$4F{7e7CBk5^8^NCdTpU$J(2lyM{jsqzo%r2` zU%R`%vJV?Cx5CEW$JaD8`k9XJYy}q%Pn+6?uYchF;F4hEo&bY@fB+oSKNlKuN6`Pf z{pW%y`af+kK%)O`Lh>Xa-wg{@ENoR{TkT7~pUaeo{pSh)`N_5oLYNtjpBvuz%3N)2 zl{F)t_rF~Lmi0+hU$=c$!SAwJiGOC@+r{#NfaiakZyLc?13)`&Iuy_$&ImrJ9N}F@ z2&ej2LI4h&;Qryt9cQOUT;zo(KIQR3jBAnXM|k9B!52*7C%<{gUj=t8^ns}oNZz%# z%Io|evGRcUvve1@KX6QbRn^O&1r6`m*az?tEIp^XQ1_TENKWus<@zq*d3DsGljX%#IrH3JmlycKm}Y2Mj(EzmwA6ofA&m)|Wv6!2&hnW1yMzkDV18f5 znj@Ht2S2Ts|0i2}-bw&D_QrGhV%$lnBARL2*Bug4F6ABB=Doge8Y4s_`Wo&lph!1F*xx=)2lsGd+bhP0N-y_z zJQ2Goh^Rk4;_GM^u|WR!>937cB*5SF1;u>_+rds;6vq3;$wiMP52qxSh06|zMcE9+ z??MSc>+e?ja3Oi6QIL7_&{VWtzjPt^IxK2LqE$0J`lTLwkO3iYH`9l}+m@;(>hpCBlGEoS&4i)s{$lJ@J44stE1)+^2OB@`zkL=mH)8-ZgB? zRi_2fpq2T=ZL)LT;&6b-W8Ki%abg!2TsN)0{J6h+rLqdokkP{DU-(dR zMQa_@ZJH`Y7Pr%cPo^s|6XsQtT1d)hqiHZs_i*pQHp|NS_)mqL}R zx&5~p%UOB>`rk3YMKMKjt1Z8Jv7qKCu0yh!>Hr){Q3@BRG{c{_t5=7yEVmklipyK$ zI5^AXBDC)bCgEWbs@v9Cm0QQ4QMUip)O#@cDd2bEFZo*9LAP6XyX$IgMc36urz$L% zB2I+Cf9(bcPKreM;IYTDg9yJ(_Pv+)G~sKyEBy6$3k1*nKdXv)CPH{v0$sayycK*Q zXkFM?B!jzQ7Imp;O|acFuMGH?IdJZK&I0%9z%E^gc5#F%J!aCo@q6)0%2bg|5Ca*u z{|*&}6rq4;%uIG50P4kp=tAhFql6HCMzjVpwgi>ob71=Jj7U60C|b4(1phzy3acT6 zsVUgPN^=f#&CG~PgmYa#>;h;PrOoTgRj5ni{(q9F0-6Q;DHXy{zpTN3C+i{pAywM_ z`s%o+ogFjEC7&;E3BmunVBxZ%-KwJH`uhrps|GiJlGX0jo?!bSYAUuvuJQNh&wYH< zaJt?+S7g~=Cu^GkbqOr+eKMu&4NwNKmyYn#$2T~?3W|cF!0EH`ajCkPg<&j@gZ#G zJZ9dz3R-}V?imXZ8aC?FKi7c2_Ef+5+rq>CJCI!I#D~^Nqid{>5(jPO1%`W>AMDww z=wON(sy$$SXN;CdIjzRHS@;`Mch}1>gdjt_OrjAHmrvq;`OF89lcv{A zPI)SMRq(-cX4zz*`=6GlwCrd-fKDm;_Rx7Jr@@>6J@sEI-PIC>{Z-uXfjN1G&ZYT27e;di&~BytGk6@> z=Qvbh!a#^>UAe*nUL`AvbD2#KwjrdcX8@Ky%QTb*U}y(yCLG#PD6kg!9Y9`q<|7t+ z?9Dx1(Eas}1E)KwXB>J&jCH&06=TbZ^=7*5_W7@kt(d||hMS^z+LnM1$s=io`QKt#^J*b&Zm*1wr5wVCo?UIcG=VlAQ-l+JxEYl*z-j$F|9mg4 ztC|L3_XGyxpAhJRZ`c!pNjTA04@y+Ore&Iba?45EMf#SuJdE!(Epra>BOXyt%HO%=g=Xdx`}y@0z3EuwvEyn&C6U{V znT)8+SyWbl`}XY4-FttP0O4!@>C4kJg7@SayE$aLbYhFa02khyv4FLN;qz*`qXAXX;$GnP9xl32h1vKA5G(SOjO)7UjnKY|86lor_OC1`K6n7 z_O)owKpF5Tkz|)?^NQ92k;V#ng;FWDpwh!2w}vLUsydzVF0WOL1cdHs#C>75BNwbt zKi8R5BJd^mIu+jPw@9j`A@Evm4=J^P?*q}Dz~TOY0K@{@5rEuk?_aQ*uqwxy@mDHS z)q`+nj5xgd-fllZ*m2$ zQB0)=z0dJRwWo{$UhT_uM;>k|7cO;PN%pifMAzi7GLI}7PgWGE({l+3dfHm99?n~{8ET`QbID)cA$Ga6kw#{ktH zBLZB5cJ(JxVH{7JE4V(~M=emA=|>A7`Vn04halLeyMK&*yV1Ss?l%_ss_COWg&#d& zIhECxD7pgTv!9Ut7b?8&)f(}oWM0EeDpwvvMq1%=`vIcdF~D{eKq)kWUZO)dQb;n{ z!>x#`=oOpf#e-=sJ3{bvr7v?Fy+Pm&v-Xy?PK^_ktiCdNMUiJyS2@br3R@3-to-U7s^p`B8^_`GM@KQXyI<(S6Me z>3$3&irv}UVZb}s%KbG zzg7&osT#gM_;Gu3S{EOD)r-xt|37HmoXbOurYH2OMzqA3wj=9FH}52P%yv0#@aPd4 zFe2ur4I*6}%Ef|H+f|D6tYS9wpaI|!HnspDlm11oo(lA$_Y(r}sJA;E?5Q%|Tt?Z; zCk~y>C?kV1Bu0#{GD%J18>c`50v;|^unTf#1z99kUFL3QyLYLluE6F6UXSuH`$X-- z65m`*eMYJ>tcK7QbW>v2^Sq2EMX39<#37nS$i|`J0!_i+Pu4C=qtfAb0dM2;e+q-| z-5ON~aa$6X9-2hJ9BIPZfx8VrM4@QUmKev6ad_pUCp6cMXaRZkQ{%u!F8}VuZR5-! z@x6;g7sYM7TVf+DCXVcb0Z!EgAB*9pvl3r^S>xB=p^O-RpF8tmYNi}vGxpmjE}k|t zC?Vc>CJfwu_gnn(Hetr7>}wo5Y3J7QsW(91mdVCE?=eL)e-B%krX#!B`7svr<{>t; z%WAXWe*G38YR(esd7FuQ>PiWf2IHdMZ+*z&Ns!@)ZvMZ_jYA>-LWlTyr&!j`6tvsuMf113}@qB2_^zBw-zUHKLjq*9Ow)^=2fmo z;(ZQ#T{okiv07Wicb;uYdr2vU4Lo9WxC{UKfIqALGPG%fsbfw)^J<%-fHd<{TVT%( z2u~T{kQ_dXmOrE-wPzuElA#A+Zg=~p`{U4)T;I)JI+fyOE#B;c30Aeo8`>ZCY%x1D z8lR`IgLbB{ckON^uD_-MADRmm*+?yYa=cdGxgEd(7XK%rp^M<&e;S1~H$0Fl!GRWo z0S58?$<6IsQsYx=|3+ZMKtD|kivGKe=LX!DSiLA2X74R*qD7<_e+T6otnXT zuf^pf!=L{Q3eM!iUKaPRuFINnOft`jng{%uOc=@oiox?KDBq0zOLY>_p1n{G>g5Wa zETvL=N(lp48;H*4PLZU;h#CC$<^@>_a7zSV``i=fe*#*B!F>pPKXOhq-GfL9)u~Mi z7@*Pu+|=-;rfJ3RX&TlDp9K%j{|jp6a0Bl@-RDB0fGNW8q%TDkyuT1l*tv!; z6@~h?C}-{tsUVLKrZX^K0t5y_Afx$H5duq{&kI zvm>zY6JJE+OEvYXK90@*{bQ9s0Yz`(y(WQrKfwWFI*I3$;=pOg?SXvYP5<>0|Lk`#K04@3m-@u{m(UrF!R3lKPClOce_;8pO*-p%q_sfeOi{v+Q=Y7q zjtVE+d&U))0>dEXNz=gY#?j`g4q}U2HYU%?X}5^9QqQMauV;J z?+LSouhQb}-PT9@<{aPpE%f#U9to(?(u?R2gPGjxD&8I(p2|_QS02{WKzR^p8@wE3 z8xut@?5B=8b<&5S3RMi&4(W=Ii{b+t8jhrYkTldj=ORqxQUp4 zxSEXo2S-Zq`}6(Ay=4U^t%2XUZu^ZzzGs6sax9O}jszuE$IpmQJg;?L&Ibpf;-iOL zAI&;v4?btJAZKSA?~jpgJZ<7XU>5r0)0IQ}@k(IyDi?c|OVpqAK=+mePpu+eD!gNT6B)s7@h&>=l7DMT(Z`#{9PC^#aIGCa zY?p2fq0fK3)pgH9fu(+DZ&_AgF543RU6-L_sk32Mt+{TizsJ$3!5u?=^eic*f4jKz zTXtfcCAaIk*=Nzt8hV1Q&;86Df^`AMJ&{!T@y`;0DbLx8Tw(qN9gMip?J1Q!T+wYK zS=a1_zufL!QblQ0_xgD|OC>~z`Bt8A@mKArc6jYFHplGn;Kxtz?~%opFNqc8b1_=) zNME-Kzi$w?6y$dm2#Y@VCoeQPK6p}5)a}mSh=1@l^*PuVOdld=DDD)~;PExB zJU0bl6)M}ab~NZfPRWa^55cN^Q-Qk&lZ>o|KhN z(KwZw4I{3GekDKn;OxeH-P1=flpX;3B1;4X@kY0$?MzBT0?Yrtm6C~*J}bZa%ktj) z8Hqh5bUXETEe5u)%^I#gw2sgT+b2If=lH0|QUL3gTZs+lz$+VL%_x=s@M3OaRsNgq z)mOPH624_e5_N~oFlIZ4Og8&a9GSx~?%kV{Z=13Up<(YY1IY`Eo8@9R2Z4;aB9en* zp3L{|C>Qw^TzUq5RLz`$LB0Sh<-gnQj?j!R1O0>K3aDE+b?=E6ff?%UbzD?UfbCA3N;p z=GE_ZV##+j3>)%}C=)kN>zp^PCUxvW$6}=9mrg1VOJ9|=32Vijb{obV{)+nBkl;@^ zAg%2eo;VOekwi&6&<$T3KZYkhRLG!@Tv%Rl6p8{k!8^pRK}mejX8J>lrtX(__lz z<7M#&5yBA(YArc>`R&5ai|gW{ zeve#=;6###=k{4LNJQ0VK0fWO=>A{Mg{#x7y_Pxq<1UKtz`y*e zleRjBgC;|_Z}Y(0t)O1V^{}J;k%_)Wn9Tts0f{ZiKr88|%J>Ic1h_|hPnRz-kW}S; z9mJ1$?0AiYGs>8iG{z%2RpGd2C!G}1Wm-tYR(5fPZFi-AmcoK!(-Yv$floPk6kCurKXwY8f^_611quExgdP7Bq_$vV(p-6|F2<@N~)G>xd?C^=N~B+(gC_K*XFb!}y5_=troGN%puv8pbSdpv1R(QZHVGxOSv!-z0*dH zuvt0BIPz)*xn@KwFtfk5H_DBh%+Ijvekh)z3i%?2ycn4r%<&lh`sj+`K21Jg?n9*G3{d z8WmIS)M}2uDiyb7*H;WBPp<6DldTSG^aH-)$w1~gLGtUNPpIb*%yWnr2XHdb83kRY zMbMr45NstE`kEd>>kwX7Elb;=EjJ~2nH61?7x>w&GQEr2AGv^!Xo3f32l6OrHMuoB z#PL+SzH+;KFSxeW{q9qz1EYDR)W_4i=&?MeOj_QMLbsr9A@ORn{jFQ2k6kvZUmw_# z`ub-sD2{G*kXnS3EzNsDo{=dGS~R(RMn2UySlkh}>QkzkJK}kQc2tgWzh@4>;K%QU z#e{~jqmB~@15|VMv>9Yn7JUl zX?l^x{UjsS#k~An9ry37Zog9q$_v8z1b2V#VYKi>GLf+Aq+=46R0fQEf`+Od(PLTA!1A=I_}SipG{4 z9^e|B`ui+t+qYz0I8{XIxnSD#*vBl%q6lMk>{i3SUNsID&p{#?IvB&v(04+pMH5;m zu_+{!h@=6k?e(DfcO$MvAs%kW$E4>J?3^-~h&>7CKC#Jx;uMUnGVfAf>((z#qP_r{ z+3eWP!B}x;UEqG&%_nDYhG^}pw%(oxeT!>XSvHJ!NViZWv*WhBFIPo$H3voH(HX9| z(!fOQD;Ii_@A7qC1pPRE_j*ruCpVrK`52=ce>KiDYD-)Y3oR~uIJl1APemRf-B3)5E- z&Z@geZka@a{1*^J)2CibQpzzjCYPCFiWLC%H?LpghGxE1<6z(cK^IIt)rst77EZX= z3|`xn3PultRn@hS981`HOU?s1eSq2^cVcca8qYtn&Nwch5Whh9N$D#OfQPp%)@^MPtmwX++4i8^mt>Q7a=7rsYw|;3r(V-tc(>$3r zWnrvBF$1jOi-5vBn#Pp$Osa#%Gf2tHb8C$x3(xR9lQhUieNP$#44l@p&<-di+8j`W zm}_h%Hr;yocjCPhB0d`}!3OuASTGJDSb!R0+f^@#t4kJ9PdnB-4>jD&l#08Tu{c_= z&#lmVf?`gWkMJF(D$D~{$dRNFQsQ_+|uvvMep|rKQ2#XO$Y2HgW(tdxY59!33ipZW4pSaMz%uF-h zrWkv&W-^P{^zwL*Gp_pYC#*hv(l-`IQGTK}&I72?51&j`R34{9u#%z$%1s)Q-3~0d zkPTOzEw}1k^&JcWcZSE~r1Tp__jz?e7whacvnS?&4kEpr)c6_i2gz=ZlB1#_E7)pV zYQj^VeK&)I8l9wC(VKc;S`CE3OgoUHmi~JFDX*7bLNPnv^!MZ6k#}y0f|C#R(S8H| zx9uC>4#f@D(6C~?NE83D4;8T@3$H8@uBEG9-`BnaRwHkS2IQA3JC$%#=H3_rj3)=x zNzU)&%hq;(*P)gT?c8q5qg=+AW2mb{QbX>TK(zwWi8sK#jhy|f4G}i9(?UG4|AFlG z$?d1-42hk%#KIac6jiU9@RdX5oO_nOVL3GDeHW%@gWl7Jf?UzVUbV|7T$r_}^=v6| z@sp5-rc2%=s+ZZn_awi@>m+%n;4s%MN=v5(Peo%s|G}P(-`Ba>rfMA#5&7|%?MDdy zkdKT#(-*HOb-NZ&^CbRULz8zZIWoyCjt;Wavv8?#%5imrg^<<3Z-b#NK-ZH-9A}O`@v8;4d;hlQcaCNpH{Y)yZtd*k zr(!y=GDjs#f>P{&)@riz6v{Q0M2_o9%WvFEZ0L%+mTY!S6rVrM(0*-P5)j`B`v5Vz z>|5~OU+=Lfof55Q5>5}%&}|g})BIMzdKoV_^c|kdvj?U?`a|`LGhHKzWLQ8}V27>n)MS|JsM5qmL0@f$o=vXLTq?O#r zFFAbH=_u|eNz{VLx@NM8;>~B{ZfsNZu{hl%=`bTk`=}8DvTSTwHM2;-H!@c@5LewK z3D0Ds@t$O#Ilp^TS8k%ZM|6?PHN`t;Rlf*)ccdIAx;$#enQ=<{ZBDHsu}ksUgt#bW z>q9CYG%IfUxQWg|CKdh2YLq~X=n181U98E>g%n*!X+q_c-HkIlQYW`u6TcTFTNB`z zmoqn>(kfg??h=0UCk`Q))O7j`xQUWRej$7|_p=EEKR5|t3Ol6F9LS0F;DuTG)yE!F z=Tp)#3rJ-5QCEeI!IQZwp11$GrLpR6Sh|VM@&4WZA=N(RJ`dbOoIO}I!e!EpL4v1X z#1OTjPLSxjROwD*8d;=8Pj4YZ+MgpElfkMYf}&aFT~qTI;VON0HDt8WkET@1nJcsPb1&hJ z0F@nRS}(VVeh|}?&p5V&zrx6=ZXAN${nmF}zT)v19^Dg~ppDAKs`9Ne9|bz62$Sm* zx`IObxv^cwq@IVpY!ZZVxx?3aaV$|*BClCMhCi#P;V~62_5n8|ZN>-i8oa$tMKQIp z_wOO^B#GBT--0)F%^Gz!ORO=C3hLRNGo3`y&SIln-SU)J_4 zs0IIMrqQF+74*GzPE0%Ma|>p@0$lPsusS;Ix4Ks0&0b}d&Ah|`;4#2hbV$+}=r+ZQ zYAl>imG*Gl%P;doa#8-1$7>{cF=`9cM^m|U@KMkVqnN;x?@?DMbho^!RLyx##7r1$x*eeEAVtb}3ny_T&Oqe*yZfRl)KN&h+AkAkHT#bjtm7-pdn+TCI=;qgsti<07I?@B!(3dWqeIMOt5(UEN zO+0ZtW{K(=%@Yc0^?u(G&!{Dn3I92{-zjHg5bE5U_3%9iJQB|%^%C~CjR8jYB`(gB z4%{C%A*_OfFOV3(juR?ZCgxr1Gc&#?&sk?@0=*zy4J-*4REH_anC zi;`;-4UB4lw_2RHO4r?|QH4(M%@6pF6Ou_lck;i^RZ=-IIl!CaX}eVV9lLR%2ljv5 zH6FF0AAq@U~!dX3?ig(XxA%gRjD+DlZ3 z5WJIAkAPcz87Unmu;D>QiHVBfT+a4KNoeUkwhoZY*X*qw3y8A=!2UEDh-MliB`au< zR)ce_17`@KU(7!`1;-xN@`{HLl z@Fd$-21u@xvM8>t6T5d+ABA9kjAF}Fi;BcMOUN#2SBDCJO?y(|(fiBzn6#)3TXe2c z5w-m{95lf1!Lw>j!iIj(iWic0=@_1K3BQfcp|uYZ2@eyLD81!sTgU=%ZdIKNMG-9E z50-qn1o5?xNBYBDR;RaF+C(Ht=U>JKH?Igwv@>Aa3jVobX(($}G9I1Zx?@Z)6mumF z8|i!M$})!KD?*(>2<3JX=Au@Z*Y)WpH_BNoIxnegrUAF;Otz35*9?TPv}lV$#-)c@KzZE&3$Sx`EOAQ-SqnETEIO60|y{6HT1 zu!%|!P7SAHt*FGOd;2RCC(qJb~LN4|lG%3u%D*g(DlrMtP@Iycgn@Am$FCMYXI22uI@Xw7tB$d)>!i>CN7)eC0B zAqN^jC|6ImPZ*ZWulAXsobvS*d??4~iHU3+&2t>brc9K8Yw6P6r3>Pc?D0m(jYn8w zjyK{~Mk65)ccTt#9L8uSp@BEU^7kzWJCm<0u;<*1W<8U_VL;C`gbzndmX)?&7|xV!E&hRAV$=NQx|LTHWv$xzH9-xfLOb3T z*-CAr{MCaHmQ>{#9}4KPM8`{fP%YmVp8gc9ko!7*`d=DZkGHNy@1BK@8m?V@{wQr` zLD*Bmf%aGz78r;ES_*4Y(J4yxH|^BJ(h$%NjvgRGyiG|9QhF3^C!w2r!x0oo&E$%Q zsguWvG-KEQR-d`p>7fzh75ohU2ax%~n?ptA(9Talmy>`%j|yD(aox!ffee0StTHXW z;+CbvDymaopN`5Vz`@+)JVBE1Cx(2W66JX*K|e0eUdxWX<9uruLVXfA>u?DPQzTVW zmg#{7vf+G3|GubYsftel&{4|X{H_&$h=)lrq-Frx!st8nGj7 zhL&=Tob2zh^-%O3?V3UOS#g#aSEdHi4t-(vKsR#s*I}$3nHXEuapE0lL}7_V*gd8?$H?jTM{nS94J9aZGZNC~(=@7U>_d^N<_GY$GkH zo>2Yh^w%;l0X7Wqole%r)kwWxzuO8)3hXjw3UPSy-x+XcI|V%V7L$e;gO$c(M$TO( z{OVD~xi0aZ^hxT(4FP1kFR9pHAMP6{M$SwK%MmA4%g=aam(eJaSnyGUSd7H)Ru`=4@t|5Hixcvt`qghYMi%l=@62;~M<2ppv|}IF5J^U- zNYAi+y5dA=sgZ6yrSk3FQ$jOY`3Vq76XIV{NssCD8&RnW5`10yEo;tbib1G?;mpgj z6~s6Z?E8^rk2uihVo-Ayqb1B>nWkep$+=?LXi) zd^W4Jn_kYI%a-hD?`hME{t-e(vh9`@^8nr3MDNaO(?@pfZs(E?yUXekh9x=hZr1r2o*P{V2^wU+Ql} z+T=A$7IDsrQ}Hd7I4*?Dos>WAvxiEI4}J-oM+gE|0OYNvOnc&_+~0B(+7ZW?8P}Fq zj!X?mTNyadGQs+K%5FU0bNZ->>q{$*A1u=f6S7p-=m;i>Q2Kav!SW0KV=xo_mY0ubJ2$M;urqL*Yr42qq@Qsvo|N5{a`;|PMCXi1f@wt6S`y+{TU=;vCe*c*}p)_hy zn&+ROh$aGQM)}njbUm>rY+ffHREOg3U3>ixj*D4;K|$ZO4MK~IZ=AEUU^ET3mA+nB z8~a=o1u)zivjc3k;WUK9#tkfs%kKEzTM)zo&%__>nR?HF^`ks^>1!^j*A`pq-fVFC zM^)e&dA;Wh>Dk(C7Qdq&{V7oU<+Dh3gtfjA+T#tXF0zjJyKQ!qOiA~-YSvO#h-=#f z3(j6>*v9w20ynj+03t@?LZW~aN}s^7p0EP@^PbSaU0{jit|C!2y3>^$724<5%bUh2 z-s6*pSAv>6lr1a!3X%O&z_n9ZuZE>N|7R>yb{?sqbqpd+fTHny62qvcWL^cPQ7n>m zeP&`YolC5+Kr{`7C_LS5W%7vbC^gsVPC%N|57gQ<);8G`_+cMNeRKyj+uUV%>JRw` zvm7b-^VVA{a8<%&lu7d<#dLxwmW)lJu*OlCqynWw`^h0{Wk6^yg{0oPBc8i=G#A>m z3U}g^J(Tb(=-#Qa2$?hP@v}yq-tXX6h;e4Kf9b^1!`pVcr!1N@r68JOjD2OnS46hy z*#;5*4TG^ZL!eb2tCB7LgSKLB=X&QW;=!ifgafEq<>Vst+tTF5XE;gw)C%GxYFX=T za4*4c#&NC!vZU>>@JB2;d9T=(|75cfP3AxU`091$rYz+d0}uKny;?PI)dM(J+T7#-XG+UXgxP4BL5SL)G%>?0hkS#OeP%o1Y=b0bEDjj zM6PZgAjX68?Jx&mWH@9tI1Gi_T`hUE5m0*`6b)RCr`+rQ5pd4 zI<^oN%q1yvs}N@K=}C($YPRTw#br&OrBD%{9o~K{?{2OXi>T1cGdQ-X+=JV(8P}G$ z>f(^cJKc9y@$<_Kl;8K7gxnmYUFg7dfgmsmrB-6iRuUAQj17W+d-@J8o%{LR&$PVxg=oCObmX_`Ge6<=L9fhq!W^${65TgPJS%^DNT| z*T4xx6Y%x-no;MH`=bnSF^5}#hHkS(ijtO+K0#1SEl9KOd5~(wIF8g#TFJEutnVlf z8(b$sc*{gb@Wy}A$4Zl^UiL|bSRJLngnTH~&_yI;S}aer@a&JT<q&1^+;O#$S-b`)YyITS(6<#r@mpw#D4Md6%^sfGzJeb(>+c%I+m&k) zGH!e@Sa#2_GUAS9DQejH^&hWyslESEcS(Z)@UMl58j=d)f~_WdCCcB?f%mz=I;iA) z=++3pQJrc*#yG0o%@EVyd~%)vx+lpcU7xjiQuZ^` zIpkbqr;wWAm4DRq-lsI#GZ5YF1H@b+a=WN1?$#!GhAW!v8?{$e-ewU&HZRyvfcjkB zy_nR%YOc?1HP*HD5XV?|H5Voc{|_xH#2tVr>+RYoe1V)517(N z6&~%$s?#E*9CLtv@TO6uH(9{Ajs!tCE~0a#GP_nva7{vn&svj|OyxSmO4>9^cah8q0z#KvxQ?g_B$|>q|!hsYIs+K#1`n zGzc`5W@e87w3+>RRR^(w+*dE|_wKg&5he9ic@&blW?gqX1`P~@Hdl{5#1%=@!c!6; zMP_N*zvPadvL2$-G!sKK?P{_Zo0Q_<^O_18e@=b1bI+3t^-)q?{YFtbMO7<;)11&C zNkm#+T8N<)uBwKcQ5xTVq2^%hNQ=n`6C1aRNtxEUfg|sVs&weHGzPC;zm8Xzk!SYd zT|`Uq2#nr2%=$z; z5>A;;v-CFBUE7U0O0-2;qXacdFp$JJx6`j+HgxBw-|p~`uaAEh{h|ifVS%r!(n$XC zD(Mr=lz;TaMwpEvd(~=wo|7jBFFvRkxlvPSM=BApEb;BD$TJ%lcCK&G!`&oK-2~f` z43It!g`GUJpF?nFGS8zzQh6CR)QH0!PuJhOylU8rkCTW}#ZbtmP1S7m`q|y|zW_qV zq9x?;`pG?xj#m62?nW`d-K$Vgy)8C9A+<_#86Obr2Clthfc1}vGkcM0e&?sqBod97 zyq)BY#@ob5>tK-f7$ni{uY;z;g}4J0Us4*+le)I0OBYbkgOmQ5@P~}|wXe_apH`=1 z0|XJmIE_)7?x&gpT#3MUYlNW|Bjgq+NCLuO>-sLCJT~V9obM3e=S^ssp2SE$Gk=Cp zbI{To#VZz1#ULBNgO0?O3lj4RO7L4dogJuOLK}?W8kF8|NIkGwQ|igebGHjOfeUN| z=uo#ahUnqNJ*qDy`7BQ89`?Y8e?u4~`IK%qL|WOa_vaR|jKF|6PKJ)--@0Kp)0?g_1p6?@ztHH((T z2Cw0;Hzguw13fA5^gant#2z1ig%G`p7gVw zgG#vPn>;+TC>1X6ZJ;kqU{mp3H?>G$yGI^&*(-I2FKlGLp(k%olD~5U!8!o2ju^p= znyi?1TuRFQu_x=&_Q%ATRNH<#s_w+ zJMzTEUi_E5QxGeL=wTG_`}MR#I^OsPQp4?4!$iv(V#LC#T*McqN$NfZcqhS)*@*Gf zB+p9949Hs8->U+;+r!XQq-KzM=c^2$qy$?Hk7d2HOGaY%qnTWbTjWt&b%8H$6VD7O zXD4v{G*MQt6E(FsXn75Q zHAv_C=>jbwSce20CYv;yRGgtd58TC^&-eJ|&eTaq-WF0Bur3Hln5ZLKkQvwas=VT9 zQfoeKP?>7j%ahs!t$xaeLYINzgILrHuHs;;>A}75P>q-YvW_t=7{WcLdLfykhkyLM z`?zPJ2E`FR(_&nw*EHMrS6cYYUtxj+fwThc@+covtk52kCE!%pTk-!Ps!B~N>O;yTP!bp;wu32ad{PN3Sl+KVF z611LE`}~?bU(^QmqhagC^0+^gM^>I$tFc(ntmu70i8|7s;f)ugYrWM2_lpR@-2*nd ze`>V7gX?MPMn>WsXr*nxPObm1skaV`^83C(i2;V5p=8Km2q|$uKtOti?i5f-X%Li< zkQ!>}R6@E_Ndu)3P`bMmq)WQ*%jbKa-@VTRe=+aNIdjh0XYaN5TKoQNDjZJ;6f;&#wrMj-E|)955)kRMBw>85DXhHhJjpd=OMkt4Aubom=`N^Kjm(~yy zRC=z^t;>!1v)oD1kq8iC)W>(Lk@ig398s@+S_~Vo^OGE}1Ym65r$$z2B!A(PgwGF@ zD>#w@Z3{>IjtlMLEOp{aY>T@Vi$o>sVGu|aoFU&)`IQsva_16L;R$CbxF{&_a;hrM z916C=br8y)4BL+(3J{S^6T*T;;UcB7JT~ohk26U(zqcrxi-&+MWopgb*<$=Sg-r&i zeuZ_`Xu_mraGf17e^*%HRFU>MCFxIs?7$!Jv(Y7!A9Lsw%WLXck{z~Wf2Q9wEVTQ}gT7HN)E?&3Q^{Q|YW;6|9!^R`&zU!z zZE{kj9Q=5>OpsJay1w{G*`h8Z+>UiFII)6IU0zmZztI;B=ee!{{f<8e$iK#7(GKur zU28>e7{fd(qb0mU%HhZl!W;1mTqwJg}aa%lko-RGO#NK37ijtB7o@W7S9f)TFV{k8aU$X&?c2 zQjI^V)e!-1xYzALddmlDPiiHV!msHsLhrqZEV`F}(a&)j*nmef{desESVYIJoujJ9 zQHqlc5Q}a(eaKt2%0TyL&KZ*OycsHyn64X#6WV?rA(ZSPvGRdf z{LZ1$cHlWuEZa$2ng0P)kYm+*^0nz`vEv=Yq%`ym?su2M(DN1{u{=XgBi7&(p)s?7 zpYh=7DvCWaG2`3GS;?~baS{&laCeck-XaSUra13kQbg|{*~nk;^YLCDZyNY(`C@O5 zwjG){e|$vjy$8?ETV2MIEj?lTR||_g%+JdFFmc8>c3Ef1?WDC^-m=Cgv>Sk%7c9UO z7O8ytFZHBKEfn1OeJmaD5;@t9MI#RZgl{)khOm?eG2IYOWl3 zdpfvCHuE7R#=Pj@$&h&Jb6+gAoS#i&KT@Qw<(Kfk9DM-*rh>Mz5**e>s>Lh{%HJ}( zzQs-mp$cXT;+`38WJp)hh`neyO5YZw0XKcoF4niQ=h69<>)+QVcW-p{jIgc$IS!wg z(-&?f7Uc#~qPKbTF)ve~TA}DwO@MqNqHJ6zhx|J$I=<`-r_m$cZ+a~6AY07iBOP5f zw%W>6H^~49UNjU{;>y2Zciwr+r!w74aWf+tooc6~ue)-XP3fymL1sF?K=lC6Ver*o zc{WTn7e;#NhI{Lg^<+R~*uh0v{8^@E35u6sn=!A$l1c31@Nz<=Ci{ik02Dzl2MPd( zXHh4WS zowD5P2NzM{!B5~AdsW+SaYvebHHydH$}=8ItC9LyNr`mnHaaEw03Ri|pFI;%FoF@# zt~{c8iYvnN7N?C_w7Ga4cJu5W_y<2TMnbM5LKdWhEntxc-Q%J^&>2fh+)|xB*l;C( zXF>ipPuWE`RQOP0(Qg>o4oEZ7LH#F3IvT(+^F9$5l{ zrv5P*W0Pmn+FN6GN9x0L<-F+EA~+KAM>r*^X`*iz-s`G*OvjTGRuK^CB}h>^2L_0@ zIxf>kb`wgSypnZu$fPTN&(~b}<76hrftOa8_dY|f%a#9Ut^$fmW+n=&2e>@h1SnMf z$T+$GW*RvJy6jNcms8R&`qRnJLJ3Vx^w;%NWt$Fg& zWnXpJzg=2@x}goHCq#I07AVk75gI`er>XeFK~No;n$N`!&kUbp%>%HDS#=){jUEG} z+gMqDN`{}5j9K!fX^~{?>ck>!n2B4K9XA@O580v{&z`{?h(>}uB7N(KpN%0w$!EGesRcPNTXS)Ir-^01ZhV6tmBP%mSoh zV7kI2cqF?>Gh2yTl`RCM01%$x5V>3a5kMW@7a2$O60ezko{TW3Cq|=| z(nXK&QX{Z_b55tT0e(8zoN9;lCUeOrfbCEPHP7cp=H5Qr&IG-bx{8Z&9Z#ofb-;o4 zlCeQ6kO(7lPuzDZcB`A2w*$JZ5qi`m;vhFfrbCA8oMwB>6^xzlgAN_S) z9u9^E9hMDTbnO3IqP1|WumT_L!7%s*s~zH$kqvXp z>0-lV{t>dvG6h+oi*5Nm5k}-}7(1{T^Dy!;*8B~2(UWDT_-Kp-ntTlXD*`-mJAqjS z|LkurveO>K=LD2LU%?)m9k*x4w0kCgZbbitaf zfktkq4~7I%w|@S-mt_?n7Sic7zj#&O(i@DsEvHM&5+H1+mE&6xWbwQ{p20^i{7*IC zc8bVf96`1XmAe?N{Ht4ISFK1Nv&>`XAjM0*5`$ed3j#NU9Xl7_S~Z}=_ebH$0MY1T zxVVJ-RO>Kk_n{oZK?!uYwz9FJFnQ;2da%*g<)BKBaiZYkit@N_N$;o{Uo;aPJ@ifU z?GCd$4EM$u)}Jn>S-ze?Zd+>#RsV7?sc5BkNUC!kQzn zi&#kOB$Zr%#`m|^T^zn1%(muQG0R8Aq_TdL`G?Uf6qR;*Do4{)c!F+SPfSvGW3 z!&Xx?!op>`%tQ<9x3Y=wzk*A3KHN?fI3);-v?-Yg#)P>R3OA$;5m!|+69PStf24lD z@WKuM7p@m0-|gq9lNchY`#uJFho`$tZcmubYpg45+N1tiyEShv@lt*)^kL&ivbYFE zgyI0=x|uziII>+o5UdIq0rNZr=DI+-r@c>DhbB5-A!%yi%_qpK zWoF*t)~Hes@oD!s`z0hmq1^&KZVTJ0I+vwJoN_9=u^1XG(7>MRoTMa-4o*T_g|XC< zB!8ZXs*{gREHN$&Fx7ZpiQ2Iy*;cai{?8h0rGoPi0a@D4a%?l49=n_u5##$a(Z06>xCwVy;bJhM8#py;oLwu`GgO?w`cBC%>=pWyJmAD;7t*uu2El z%EthB$lA1Yrq?-Kn0skO`Nny%5lb;d*!^Ol%GAm8P7e2qyEkbiALu7gINcpJF!IJH z^*i?J`}{YH?Qd03#a!iplUmV-D?iAbUOmNOZ?%r8=ZmIs=o-00tqzLA_|$PDi~_Ip zscT4QX1f;p`^Bj99uOa4BBL7$*;Kt=E@T57Y>$9iiEXLSc*P|^?C!d0|MgmV7)+RJ z_ogp|S}rs3y;VUOnOQirHR|Fy^&Ad*(0YO%JsxF_TxBct(VjwFc%L$YB+fNJCpmuT zM^`rM66#x4jVw*?_MEE7Vty#dM&?5lK(BXNiUQt+$1F0*wx;8W2|KxtvO~YCV;6t3 zVdTQER9JR&NF$Z3(>cV!7mpi&%z)fAY*JP!OkK)Bkh+xwB1ei{ER%j#o~2|~eD;(x zyfb6zoIhywBspZ_J{ZbmiYR$W7PIXXh|B2yVM-Ryqi%}=$@MNA(p;TdBygW}FKqK5 zs<`6q?cK&POHTzAX2Z}`p2&Ur&cFeEs_3})aPQinJvxLWv&-ZB+g*-|Ez9I*y9{0w zfO^+|yZhzRuEvosNnVhr@|WgWPDBai=EE{?4@JQ`zyWwda3bQ~Na0jR+a63c@;C8s zf%0b^Gfp(}E83NFYk?DTz+0Q#O#Ji1!wWj6*%6;sZxWfdn!spwN}ENEIg>?OJl4;N-ZRw(}rdtB=*|%8ZZ(g8EvKjAWQnm75-FL?E%NoWH0a3&mMITdaB-bm+`-~n* zNF9j>`lc*74t!pNPBAwEE z?HNITu9?r~3u!{?F}MCY(o+N@gCD0!Op~ciP&WgWt4#b%%(V!U{}dJpHY8F9)hAps z8M(Xz{KfWqm)z#7r}96|n}0|H=U7HW_{Z_Tl9Pegk)`PZYD{cX}|L>D6gnQw7cN2tn*v+#*VIr>fjqCOi&B?|2So(!iEx|&02c>jkb zuleP&9o0E2C3Ty~DB86zFoZ)B-_o+-n$&&Hpv3KPcZHMK%TQWya-#hwOaJlSE=$(* zT?Bvy1OO=+8_?>+&PJVu-FdquD^?T^os+$?%>$+ro=izxNCY-TUZec(p^9L?dm4ugVmQ@LVUdU}Q-mm?Pm{TR4vx`+ zuRhKcmsNKxNu1Tb)=qab$%&$F1!C@r*cj3^C*7)>_vo17+^6QNxLa2~nmeVRUT~W^ zCxEZ%uuf$$jxEjmnTnhEOdlx3Q2g!t&y=QVE&eoA^w=8K{v(doJQoQHcNf&w2d7@h z>{p?PY+I`Ln&X!Zj7gapJ1(DAg#rdm2yrT$j;wW%llV%ZaMZ|wxs<9?T0!xCiKqr5 zpJJj_w-&X^#s(3jBGcZm0<`rgxox;#PI&l-1QzP4-556NwO`0ydwLY=`X$!=t5ya; ztcSEF#Gn215?PpWq}aGYUM+C^v8MXldjbgI;PX%qHdykvDLZwQsEB-Bcs2Td;U|SYpQH3YHpw>4rDX}=0 zg?PTcX)i`!#?hZ_IX?yz5mBQ+N>fZI{6n%Y6pUbtvB9AU^ZM$ODmod*^wEOY*J805 zi#s@uwnT^^Q8Ce{Vzo@DJU%q?8ePRYRR0GaqZt+Qmdtn2IG&{en#^E)hKN3Ws$ z#`L}}>A$%K!!o=h0XJ8i)j7zq+4AolI;$5o?CL;G)YGBHX!lSG!ISLx>$lvh)oJT)D zD$ZZBdS_I1gF+hQej(EH^S`IY?WLTVfC@kk6dP)aheQ}&CYzzrk$4CbB;eU-F3=x( zhByCk#BzG|OY6a8SjdvU!tMh6Zp|%7158-%X@y#=6`5j zX`9P+BC305+orVMIs_*P5Ve+_(6{|$z3(x-Ci{9XwXMoEhUy= zM_xdyR{=>v8jtXCiHjj^{SC)Rw#8f4Yjh>t0VEU0b8V23;J@&~I+B2oId}iQw-bn~ zhuEtTT+VYtTatJbR-j(2b0=E-kAk7| z;i4lmac^5aAi;{TtFgI^>q2#WbE4lL;;r}id^URCgu24oYXM#Sbp|>kFd2L_g|HjsQnl!vR0)Y@>9UN z_x2yqGa^pyl~1Pdq!c#%0N4;tlmAfHZ2N@^+cSBPfy)KkMw1d>SEmBw zi;^2>UvkQG2EJ$@UG8?j0|tBcR0Vn%rz>%7P%z|b^8UBiOM6!6(IYdUmXJ3(LB*i~ zvzGHo{8L~_uK!s=*MOH;=HEF}4F|B&y6v}-$+M6v1D5MM$BOFqV+m%BU87{|oxqJB zRv!(fvy#Po$tGfIaHO6VcK^+%I9$p}Q>2kDpU%tUI$^$|WB~L6_ujAd!2&)E+t{vG6n7Tx~@&5 zX4L7w2e3>l+Oao~fEwja{(k&MZ8V1l())XC$|(21r$#yEKO@Y^^GX58iYvColv=Jr zL5eu*r(%(RacErR;+3f2^TG8+5WVf|c-7rv?yH@9Ec5Enq#nELoA`}3XVD2#SJOMj zyT*;#lje(~lLJLY!5b37`u&VltuF3j*WYvxSt4>up=s`jQ+N0#!C#uz3-?8sWlxaW zMKVJJnG>56zgdW15AIRV8=N7MVFje$+0Dgk7R`c9qq*OHTcqSGZi^fd>1GsS^G`qN z(c$3Gez@%s(B(rPy|=r1!JjTlB#OL)d|*I3_Q@e8W>t}401%A4pa}guoOK|8_??|k z6)OYEsP^(yYPIn7iV<<1fLb{}pb31ler6DIR+Kp?lh=#&WS;GkgUQnM&ykcAZf;hz zo{&@>1Fi)E;l(R_Q87}GwxKIn5uxNvf-Rn6!@4^dX8hf zXseBLP3Jwk8S^07n9`udw$@mvlC@p8iR)>9TaX^!?rJ8yUwEFgP?`-Bd>7M$_hDOI zneUU`&AplfWgU`t5nWAS`zyP(WCP#(nJH z8FPT-0@geOyVz8<|F5rN0zDYCnhRID!DD-Zk^?FGzEp2zD~K0#&NvlHcwS zMVr1?U~LQJjiE*A7q9RAYTY$2bC0C}-HC>hO6C~sX(}mzn3WGcE$+Q+r9Rg&Wc(rz zI6G)iuV%m+*L>Ap=!!tQ#fZjQ)K|1zhieGh3Bs*~nz zpZyTT?uN&7*VwfaV4PzYsSF3oX6$*8{m$GT+$}8D4~+-4T*Qw9g(FNoXoA$1Z@i?^ZmfEB*@i{ijQt&yy%gw95=?Q?vD z&kCK>DGB;2(zWREqIXIfbuMs)N~5cuh&QQlVamB#jf0@(WgDYB|M=%L?sxwQb>hT; z7018`C_goHmVVIGVvxR=e01TiG^yLLWoa_41i$;X{^+^q6s|Sukb}`uGJh!Qs8>W= zsjjsu(kFls%#ql}bO@D9j<{O@uoTIGwhDH;NKMr|@zhO53xACf6XzAC4i8ouP9FIX zK(X9}=yzC_dz82`rvK@SI*S-5Trh!z=hIhh$)R9CF{b5&BE)2x2};x3!d)dYyCUiv7hY8bj!y}+!YSVYH5j$#z;DZr2-|GZo$eB<}QsOs(zF7?n440{3f zzu6;&oC^?V@OEx?G;$w}q~5=BTZH8XWr=X`oduS>=HxgJ;!qygRY{He5%@slml$6l zs*pnGg_E}q4sHqWC@ug5Q$fk9sQqjw`C3HZ6SiSMJt7s%eBol~I@xYe1~JftqJ^RO zBoh2?RQ{&DPzr(hg9{T2hn(qa^F0pYZYp()(rw|>sXhDHZu-uOLNDoD zmOx&}7@k1@QA6d{;fYm+fPp%dX=cPaTHB?@SFSlFjZPo+ZOC;VFPA|<9#5@7znEj~ z9#ps#cLnW=U)d!#+f@_)QAuT1zWFa)s&*0&)&tav@*c|0badR$z=?Y)(h@uic<+PYDGrZE5uaQ= zmKo>Fdja4i9T59TA0ILUobW4+cC*+hQRLSw=ResW?w)g#~!-zlXH zy%9aM({;rrlS_rk*}I8GomyNalY5ZcNzU*ij2-2w-6<$SD9g|0Rn!_r?*sN%NFSwy z>Ike``WBU*qCXJy`unWBJ|sVQmehqPUOq)Z!(lPvGue-)HR`UYp!`1zxp50FqZkv5 z+|ZH+Nb3?8CLeb_%D$vfD!$_h&DMa)m}h?=m=0AV%>*YDo&88)+sg;YMT8O8SLXZ+ zX|4uWM3}3`(?5arCj-qTBnnAzV|jOyW2T!0@mpUrG2x$h@onWd^!*Tq0QFCPoqym` z&)Frn{2`fDc;`Y+iTCMU0ifx5$yiU@^OtNXk4Tf!!)MRJQi$$5k#pq}aA+5+UQ!m= zSJ2haszt|-EE2hzFU?k^CKMCg<+6D2JzqhgPHZvaIfqq8NI51Ck@|xPs3*`3pGWfr z3QOSLOjwjz8H`g`_LEs{#Vsp|4q$vuMo>b+ijHSphx3f|-U|6ogW$oW30XZRbE+Ym$~_6GFKC_GW}(4$cUc# z_ZeJ{k8Q{2s!0`bBwM(2ZmDMnhskswUfzK-8&8_KRd5XjRea=0wUQwgyJL9hoe`&h zaHU8qHdMrB(D=*YFDb4^x9CMydtRX2knv8z%0L`nB4TLtB39Zj3uNeBZ`QXfgB~YN;NN*q3xHP-!`V$L zcGncp+N{E>4+f$Mc*7|!D}IqpS_WE_!`r?@h!!&}tGWOQV69QG@)}M z>rfTEDujHlguh|w^Y+z|XuY<&OAxLQ!3C+095Y12%Lc=a@#on2=59s~QAiE)p&`X) z?`VlDty^m8Uv1__*vc|=FwLu7)b<^}QE#0^Ux?Xs>o2`o8Iqk3ggL(QvN~t@epfNl z!|E?Fvf~i;Z+B~UaCs>c;x`mF>Bm1e9eVLT=;lJ+=wYeoYKOJCuU+sl?w4j>`nAau z*H~-PLaI+J@8m0=;X7fJTGmDK=}cDahA1lkdD$tG*fw`skV7grCr#f|!eJ|c={qH( zHunZkz!f4gm`1&*(n?m)@IK_}CV#Htl(={I@KD;_o1s5j@g;)&Evqhju6KH21Xph! z%0V#YFNvn|zMDSarw%0jWMD*IjJGNf*~p_>Dia$sN53R#^oKH-y_}GuGG?*kN8@p- zfNPfHR8wUOTXD-*+aOeDz+B(>{2)ZG`FORMUwsq6;KK?*>#Z^wV=jk}?j@^r1%!Mc z9bw%-1{KS4O;D)#jqtEAuAeyK_C30q7~pxU3O1}kY@M_%eR!`sVS(xiA~IlXp{#Rl z`O(+Xb#%syT&C&WByp@_ey3|n5kdFrCH-&Y}ct_}PdK$FQ zWotsXYLQlG^|e9+&BD-f$M{M=VLl)4rn!YFIoYo8{$buNArq}iIZ@1<_xld0I6Pff=$^+_$)!inP>=aGTR^c8#2-k=o$J`OZ8JVid?FDbq)!6Uoij`V;;wKEOx&zvDpio)Xeu z@YkJi57(xe$5!~_`SjTkPTIoA-^loGCe^(V4{E7NA=Cq}*ylB5{FYYsFr*>(ReV2j z&WeoXE-KKgxJ&=_U&Kk8U^-vM7OQ+he>jZTF)#^MYx}SF>u3d$J;8AWQ~WQIne07- zoameb{2I}=?gJHv+?bG0`B8NX%P`iV{#?BVv7+5@ze+M(`t7PycJ?9}QK{g>2AC8p zMV1oKkV=?G@rLKo_jCKILVPqqr*S~*LbY%$qDSz)(9uJu$Y+82S>a>jzf>OTvK%O5 z^vvkkUE3ecwBJoE&pS~Qn2sn5!~}Z`ktGh!oeC9SG8IAY9LfphUEOC;P*Je zWB#l3Pxs5ue5UP11O5gwDIVqM^%5U{7uEfyl-lR{&@tsVA$s@9q%MWht62pmA^zrN z?L>(;ZGQ~%^;F(;r-op5mL}7%RMK0VoGj27Y?5rt5 z{P_Fk*5;$M+V69M8m;ry)C#H0bTO;D+i_hQEEEsweF_N(N;KM`KUV7PP zkJPv6lD2XE8%lP}uqHARgwrM$N4}t#++*R0QZ7_ZR;IGsBxdDJUAq4kw{wj_g1<@qKoY>zf{#W5-RUH&s(C7@UA>(0SnSOZ z|2{t`KOldwm2%?kS7_uj|Jjbxl!)kr117Ml$EzU`B!zwt!*l#ed?tdBvR>l^zWAWa z_JGg0TYl?YyFYjBiM#~(loz*5bk{DQfCVe?*PW4tgAO<_Lw?niLcbM0;epqr(2owD zW@^SoJ)}I3?5NQbN50pwXx8J+niAcx#v6m>XwNCB1&!ktYGa=?m6>3%6evHg$lS+- zKdQDwYsuYD>?tQ7SmXajb}VHjU>>Mm4)%9c!Rv zUl%&U?EgK@8~+ou3~mVis~o#kptOE=q4PID(CB3QH;3v$07U2|ORkjmqo(meKYr$M zY>ugsOPGSgNdovCDn0g`XaE;qGr)*3@xy1Siz&4d8_Ne?8oc}i4gI=rs+!iN|B>wQ zV2}|8WX{L4pV)OzpH919PRBRm53jr~QM_Qo~zoK6LTaJVB zqKG{8*!ZO=1ZhTfwmEXY67HXup|<2Gu*zW)!y9-5NEz#v%}4Rv^S|S{$0>rB=ui_^ z{4+-^h^&h`f=MRU`}8r{V!ED2KTuEXBfLWpu2;8B@-O2iJvvj8T8l*0+LKM>;A6p# zA1ADQAXn9aS#xWzKA z`n^Cf)w^50Et~UM$u9@;C5miMTK2ZKygpOOYMh^YARye zy@3s9Z=ZE5)3g1U&R>(#%zYK~3=f^xX7cE;{5bJZ+5==iyT;bhtOO^yb3d2% z-ZTv0clWz^2dLEuzAy5}q`nYQMx0(k+9gJ8^F1q6I>IL6nH1kozLq=En!pu>Khv0a z`BdfPYomo`J$z~?9&W|m2=02$VXh$)%fvRo!M>-i`q7UQ%HG`h$6bOWORIBfN8>nH z2la>Vg-FVJysxs4VvAQdOILn|eQ-rINAJFV68E4fO`2Fx2KFOI4fa&)6(N%b%fT~* z!;cij_}pX*_W+GvlF2kGlF~4n0}m@zeeVFZ>-okD~Xt_s$n`ye(>mIjU%{P!90 z*ODR=vRW51^M8Hh*f1|S{qxy$H`iH~A$OkvAl19mse6f3E-ufp6QC{O^@j-Fr7UA@TaJ`L68%dP literal 0 HcmV?d00001 diff --git a/apps/website/src/layouts/MainLayout.astro b/apps/website/src/layouts/MainLayout.astro index 847457f9..b196ab18 100644 --- a/apps/website/src/layouts/MainLayout.astro +++ b/apps/website/src/layouts/MainLayout.astro @@ -4,6 +4,7 @@ import Meta from '@components/Meta.astro'; import Navbar from '@components/sections/navbar&footer/Navbar.astro'; import FooterSection from '@components/sections/navbar&footer/FooterSection.astro'; import { SITE } from '@data/constants'; +import { getLocaleFromPath, localeHtmlLang, type Locale } from '@/i18n'; import '@styles/global.css'; // Setting expected props @@ -11,28 +12,32 @@ const { title = SITE.title, meta, structuredData, - lang = 'en', + lang, customDescription = null, customOgTitle = null, } = Astro.props; +const locale = (lang || getLocaleFromPath(Astro.url.pathname)) as Locale; +const htmlLang = localeHtmlLang[locale] || localeHtmlLang.de; + // Interface to type-check the properties interface Props { title?: string; meta?: string; structuredData?: object; - lang?: string; + lang?: Locale; customDescription?: string | null; customOgTitle?: string | null; } --- - + {/* Adding metadata to the HTML document */} diff --git a/apps/website/src/pages/contact.astro b/apps/website/src/pages/contact.astro index 65c1786f..a89d03f1 100644 --- a/apps/website/src/pages/contact.astro +++ b/apps/website/src/pages/contact.astro @@ -1,34 +1,5 @@ --- -// Import the necessary components -import MainLayout from '@/layouts/MainLayout.astro'; -import ContactSection from '@components/sections/misc/ContactSection.astro'; -import { SITE } from '@data/constants'; - -const pageTitle: string = `Contact | ${SITE.title}`; -const metaDescription = - 'Request a Tenantial walkthrough or start a scoped rollout conversation. The public website does not collect live tenant data.'; -const ogTitle = 'Contact | Tenantial'; +import ContactPage from '@components/pages/ContactPage.astro'; --- - - - + diff --git a/apps/website/src/pages/en/blog/index.astro b/apps/website/src/pages/en/blog/index.astro new file mode 100644 index 00000000..4f89caa4 --- /dev/null +++ b/apps/website/src/pages/en/blog/index.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 302); +--- diff --git a/apps/website/src/pages/en/contact.astro b/apps/website/src/pages/en/contact.astro new file mode 100644 index 00000000..2da4723c --- /dev/null +++ b/apps/website/src/pages/en/contact.astro @@ -0,0 +1,5 @@ +--- +import ContactPage from '@components/pages/ContactPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/imprint.astro b/apps/website/src/pages/en/imprint.astro new file mode 100644 index 00000000..ca4d8ed0 --- /dev/null +++ b/apps/website/src/pages/en/imprint.astro @@ -0,0 +1,5 @@ +--- +import TextPage from '@components/pages/TextPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/index.astro b/apps/website/src/pages/en/index.astro new file mode 100644 index 00000000..24804b32 --- /dev/null +++ b/apps/website/src/pages/en/index.astro @@ -0,0 +1,5 @@ +--- +import HomePage from '@components/pages/HomePage.astro'; +--- + + diff --git a/apps/website/src/pages/en/insights/index.astro b/apps/website/src/pages/en/insights/index.astro new file mode 100644 index 00000000..4f89caa4 --- /dev/null +++ b/apps/website/src/pages/en/insights/index.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 302); +--- diff --git a/apps/website/src/pages/en/legal.astro b/apps/website/src/pages/en/legal.astro new file mode 100644 index 00000000..5fdccf08 --- /dev/null +++ b/apps/website/src/pages/en/legal.astro @@ -0,0 +1,5 @@ +--- +import LegalPage from '@components/pages/LegalPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/platform.astro b/apps/website/src/pages/en/platform.astro new file mode 100644 index 00000000..b721543c --- /dev/null +++ b/apps/website/src/pages/en/platform.astro @@ -0,0 +1,5 @@ +--- +import PlatformPage from '@components/pages/PlatformPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/pricing.astro b/apps/website/src/pages/en/pricing.astro new file mode 100644 index 00000000..20118b67 --- /dev/null +++ b/apps/website/src/pages/en/pricing.astro @@ -0,0 +1,5 @@ +--- +import PricingPage from '@components/pages/PricingPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/privacy.astro b/apps/website/src/pages/en/privacy.astro new file mode 100644 index 00000000..5a37e88c --- /dev/null +++ b/apps/website/src/pages/en/privacy.astro @@ -0,0 +1,5 @@ +--- +import TextPage from '@components/pages/TextPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/product.astro b/apps/website/src/pages/en/product.astro new file mode 100644 index 00000000..4bfb0c43 --- /dev/null +++ b/apps/website/src/pages/en/product.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 301); +--- diff --git a/apps/website/src/pages/en/products/index.astro b/apps/website/src/pages/en/products/index.astro new file mode 100644 index 00000000..4bfb0c43 --- /dev/null +++ b/apps/website/src/pages/en/products/index.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 301); +--- diff --git a/apps/website/src/pages/en/services.astro b/apps/website/src/pages/en/services.astro new file mode 100644 index 00000000..4bfb0c43 --- /dev/null +++ b/apps/website/src/pages/en/services.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 301); +--- diff --git a/apps/website/src/pages/en/terms.astro b/apps/website/src/pages/en/terms.astro new file mode 100644 index 00000000..36116caf --- /dev/null +++ b/apps/website/src/pages/en/terms.astro @@ -0,0 +1,5 @@ +--- +import TextPage from '@components/pages/TextPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/trust.astro b/apps/website/src/pages/en/trust.astro new file mode 100644 index 00000000..f6208fb9 --- /dev/null +++ b/apps/website/src/pages/en/trust.astro @@ -0,0 +1,5 @@ +--- +import TrustPage from '@components/pages/TrustPage.astro'; +--- + + diff --git a/apps/website/src/pages/imprint.astro b/apps/website/src/pages/imprint.astro index fcc59108..8dde866c 100644 --- a/apps/website/src/pages/imprint.astro +++ b/apps/website/src/pages/imprint.astro @@ -1,16 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import { SITE } from '@data/constants'; +import TextPage from '@components/pages/TextPage.astro'; --- - - - + diff --git a/apps/website/src/pages/index.astro b/apps/website/src/pages/index.astro index 815818dd..ae6646ea 100644 --- a/apps/website/src/pages/index.astro +++ b/apps/website/src/pages/index.astro @@ -1,74 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import HeroSection from '@components/sections/landing/HeroSection.astro'; -import FeaturesGeneral from '@components/sections/features/FeaturesGeneral.astro'; -import FeaturesNavs from '@components/sections/features/FeaturesNavs.astro'; -import PricingSection from '@components/sections/pricing/PricingSection.astro'; -import FAQ from '@components/sections/misc/FAQ.astro'; -import heroImage from '@images/tenantial-dashboard.avif'; -import faqs from '@data/faqs.json'; -import features from '@data/features.json'; -import pricing from '@data/pricing.json'; -import featureImage from '@images/tenantial-review-board.avif'; -import reviewImage from '@images/tenantial-evidence-intake.avif'; -import evidenceImage from '@images/tenantial-decision-review.avif'; -import governanceImage from '@images/tenantial-restore-plan.avif'; +import HomePage from '@components/pages/HomePage.astro'; --- - - Microsoft tenants` - subTitle="Tenantial helps administrators review policy evidence, snapshots, drift, findings, exceptions, and restore plans before high-impact changes move forward." - primaryBtn="Request walkthrough" - primaryBtnURL="/contact" - secondaryBtn="Explore platform" - secondaryBtnURL="/platform" - withReview={false} - src={heroImage} - alt="Static Tenantial governance dashboard preview" - /> - - - - stable website foundation adapted for Tenantial review workflows.` - tabs={[ - { - heading: 'Evidence intake', - content: - 'Normalize inventory and snapshot context so reviews start from one readable view of observed policy state.', - svg: 'frame', - src: reviewImage, - alt: 'Static Tenantial evidence intake preview', - first: true, - }, - { - heading: 'Decision review', - content: - 'Surface drift, findings, exceptions, and audit context before administrators choose the next action.', - svg: 'dashboard', - src: evidenceImage, - alt: 'Static Tenantial decision review preview', - second: true, - }, - { - heading: 'Restore planning', - content: - 'Treat restore as preview-first work with validation, conflict awareness, selective scope, and explicit confirmation.', - svg: 'verified', - src: governanceImage, - alt: 'Static Tenantial restore planning preview', - }, - ]} - /> - - - - - + diff --git a/apps/website/src/pages/legal.astro b/apps/website/src/pages/legal.astro index 49c076da..c74e34c6 100644 --- a/apps/website/src/pages/legal.astro +++ b/apps/website/src/pages/legal.astro @@ -1,27 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; -import SecondaryCTA from '@components/ui/buttons/SecondaryCTA.astro'; -import { SITE } from '@data/constants'; - -const pageTitle = `Legal | ${SITE.title}`; +import LegalPage from '@components/pages/LegalPage.astro'; --- - - -
-
- - - -
-
-
+ diff --git a/apps/website/src/pages/platform.astro b/apps/website/src/pages/platform.astro index f66eade5..27b691bf 100644 --- a/apps/website/src/pages/platform.astro +++ b/apps/website/src/pages/platform.astro @@ -1,98 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import LeftSection from '@components/ui/blocks/LeftSection.astro'; -import RightSection from '@components/ui/blocks/RightSection.astro'; -import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; -import dashboard from '@images/tenantial-dashboard.avif'; -import evidenceImage from '@images/tenantial-evidence-panel.avif'; -import reviewImage from '@images/tenantial-drift-workflow.avif'; -import restoreImage from '@images/tenantial-rollout-plan.avif'; -import { SITE } from '@data/constants'; - -const pageTitle: string = `Platform | ${SITE.title}`; -const metaDescription = - 'Tenantial platform overview for Microsoft tenant evidence, snapshots, drift review, findings, audit context, exceptions, and restore planning.'; -const ogTitle = 'Platform | Tenantial'; +import PlatformPage from '@components/pages/PlatformPage.astro'; --- - - - - - - - - - - - + diff --git a/apps/website/src/pages/pricing.astro b/apps/website/src/pages/pricing.astro index cfb3aee7..04fc0112 100644 --- a/apps/website/src/pages/pricing.astro +++ b/apps/website/src/pages/pricing.astro @@ -1,23 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import PricingSection from '@components/sections/pricing/PricingSection.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import pricing from '@data/pricing.json'; -import { SITE } from '@data/constants'; - -const pageTitle = `Pricing | ${SITE.title}`; -const metaDescription = - 'Tenantial pricing is presented as conservative evaluation and rollout conversation paths, not unsupported self-serve billing.'; +import PricingPage from '@components/pages/PricingPage.astro'; --- - - - - + diff --git a/apps/website/src/pages/privacy.astro b/apps/website/src/pages/privacy.astro index 31ed1cd7..e3315552 100644 --- a/apps/website/src/pages/privacy.astro +++ b/apps/website/src/pages/privacy.astro @@ -1,16 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import { SITE } from '@data/constants'; +import TextPage from '@components/pages/TextPage.astro'; --- - - - + diff --git a/apps/website/src/pages/terms.astro b/apps/website/src/pages/terms.astro index ff3b5927..249750fb 100644 --- a/apps/website/src/pages/terms.astro +++ b/apps/website/src/pages/terms.astro @@ -1,16 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import { SITE } from '@data/constants'; +import TextPage from '@components/pages/TextPage.astro'; --- - - - + diff --git a/apps/website/src/pages/trust.astro b/apps/website/src/pages/trust.astro index a6b871dc..07c15764 100644 --- a/apps/website/src/pages/trust.astro +++ b/apps/website/src/pages/trust.astro @@ -1,44 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; -import { SITE } from '@data/constants'; - -const pageTitle = `Trust | ${SITE.title}`; -const metaDescription = - 'Tenantial public trust posture with conservative claims and clear static preview boundaries.'; +import TrustPage from '@components/pages/TrustPage.astro'; --- - - - - + diff --git a/apps/website/src/utils/navigation.ts b/apps/website/src/utils/navigation.ts index 2df373fc..9dc10942 100644 --- a/apps/website/src/utils/navigation.ts +++ b/apps/website/src/utils/navigation.ts @@ -1,39 +1,10 @@ -const navBarLinks = [ - { name: 'Home', url: '/' }, - { name: 'Platform', url: '/platform' }, - { name: 'Pricing', url: '/pricing' }, - { name: 'Trust', url: '/trust' }, - { name: 'Contact', url: '/contact' }, -]; +import { siteCopy } from '@data/site-copy'; -const footerLinks = [ - { - section: 'Product', - links: [ - { name: 'Platform', url: '/platform' }, - { name: 'Pricing', url: '/pricing' }, - { name: 'Trust', url: '/trust' }, - { name: 'Docs', url: '/welcome-to-docs/' }, - ], - }, - { - section: 'Legal', - links: [ - { name: 'Privacy', url: '/privacy' }, - { name: 'Terms', url: '/terms' }, - { name: 'Legal', url: '/legal' }, - { name: 'Imprint', url: '/imprint' }, - ], - }, -]; +const navBarLinks = siteCopy.de.nav; -const socialLinks = { - facebook: '#', - x: '#', - github: '#', - google: '#', - slack: '#', -}; +const footerLinks = siteCopy.de.footer.sections; + +const socialLinks = {}; export default { navBarLinks, diff --git a/apps/website/src/utils/ui.ts b/apps/website/src/utils/ui.ts index b4cd8123..005a2b9d 100644 --- a/apps/website/src/utils/ui.ts +++ b/apps/website/src/utils/ui.ts @@ -1,4 +1,4 @@ export const languages = { + de: 'Deutsch', en: 'English', - fr: 'Français', }; diff --git a/apps/website/tests/smoke/interaction.spec.ts b/apps/website/tests/smoke/interaction.spec.ts index 1bc9b113..002a2e17 100644 --- a/apps/website/tests/smoke/interaction.spec.ts +++ b/apps/website/tests/smoke/interaction.spec.ts @@ -1,5 +1,8 @@ import { expect, test } from '@playwright/test'; -import { expectNoHorizontalOverflow } from './smoke-helpers'; +import { + expectCoreCapabilitiesVisible, + expectNoHorizontalOverflow, +} from './smoke-helpers'; test('mobile navigation opens with vendored foundation behavior', async ({ page, @@ -13,10 +16,13 @@ test('mobile navigation opens with vendored foundation behavior', async ({ const mobilePanel = page.locator('#navbar-collapse-with-animation'); await expect( - mobilePanel.getByRole('link', { name: 'Platform', exact: true }), + mobilePanel.getByRole('link', { name: 'Plattform', exact: true }) ).toBeVisible(); await expect( - mobilePanel.getByRole('link', { name: 'Request walkthrough' }), + mobilePanel.getByRole('link', { name: 'Docs', exact: true }) + ).toHaveAttribute('href', '/welcome-to-docs/'); + await expect( + mobilePanel.getByRole('link', { name: 'Walkthrough anfragen' }) ).toBeVisible(); await expectNoHorizontalOverflow(page); }); @@ -25,10 +31,137 @@ test('theme toggle keeps page content visible', async ({ page }) => { await page.goto('/'); await page.locator('button[aria-label="Dark Theme Toggle"]:visible').click(); - await expect(page.getByRole('heading', { name: /Evidence-first governance/ })) - .toBeVisible(); + await expect( + page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/, + }) + ).toBeVisible(); await page.locator('button[aria-label="Light Theme Toggle"]:visible').click(); - await expect(page.getByRole('heading', { name: /Evidence-first governance/ })) - .toBeVisible(); + await expect( + page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/, + }) + ).toBeVisible(); +}); + +test('homepage mobile first viewport remains readable', async ({ + page, + isMobile, +}) => { + test.skip(!isMobile, 'mobile viewport is covered by the mobile project'); + + await page.goto('/'); + + const heading = page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/i, + }); + await expect(heading).toBeVisible(); + + const box = await heading.boundingBox(); + expect(box?.y ?? Number.POSITIVE_INFINITY).toBeLessThan(360); + await expectCoreCapabilitiesVisible(page); + await expectNoHorizontalOverflow(page); +}); + +test('desktop keyboard reaches navigation, CTAs, footer, and contact controls', async ({ + page, + isMobile, +}) => { + test.skip( + isMobile, + 'desktop keyboard order is covered by the desktop project' + ); + + await page.goto('/contact'); + + const reached = new Set(); + + for (let index = 0; index < 80; index += 1) { + await page.keyboard.press('Tab'); + + const active = await page.evaluate(() => { + const element = document.activeElement as HTMLElement | null; + + return { + id: element?.id || '', + href: element?.getAttribute('href') || '', + text: element?.textContent?.replace(/\s+/g, ' ').trim() || '', + aria: element?.getAttribute('aria-label') || '', + tag: element?.tagName || '', + }; + }); + + for (const value of [ + active.id, + active.href, + active.text, + active.aria, + active.tag, + ]) { + if (value) { + reached.add(value); + } + } + } + + expect([...reached].join('\n')).toContain('/platform'); + expect([...reached].join('\n')).toContain('/welcome-to-docs/'); + expect([...reached].join('\n')).toContain('Kontakt'); + expect([...reached].join('\n')).toContain('/privacy'); + expect([...reached].join('\n')).toContain('hs-firstname-contacts'); + expect([...reached].join('\n')).toContain('E-Mail-Entwurf vorbereiten'); +}); + +test('reduced motion keeps preview pages understandable', async ({ page }) => { + await page.emulateMedia({ reducedMotion: 'reduce' }); + + for (const route of ['/', '/platform', '/pricing'] as const) { + await page.goto(route); + + await expect(page.locator('h1').first()).toBeVisible(); + await expect(page.locator('main')).toContainText('Tenantial'); + await expectNoHorizontalOverflow(page); + } +}); + +test.describe('without JavaScript', () => { + test.use({ javaScriptEnabled: false }); + + test('primary content and links remain usable', async ({ page }) => { + for (const route of ['/', '/platform', '/contact'] as const) { + await page.goto(route); + + await expect(page.locator('h1').first()).toBeVisible(); + await expect( + page.getByRole('link', { name: /Kontakt|Walkthrough/i }).first() + ).toBeVisible(); + await expectNoHorizontalOverflow(page); + } + }); +}); + +test('language picker switches between German default and English routes', async ({ + page, + isMobile, +}) => { + test.skip(isMobile, 'desktop language picker is covered by this interaction'); + + await page.goto('/platform'); + + await page.getByLabel('Sprache wechseln').click(); + await page.getByRole('link', { name: 'English', exact: true }).click(); + + await expect(page).toHaveURL(/\/en\/platform\/?$/); + await expect( + page.getByRole('heading', { name: /Platform review model/i }) + ).toBeVisible(); + + await page.getByLabel('Sprache wechseln').click(); + await page.getByRole('link', { name: 'Deutsch', exact: true }).click(); + + await expect(page).toHaveURL(/\/platform\/?$/); + await expect( + page.getByRole('heading', { name: /Plattform-Review-Modell/i }) + ).toBeVisible(); }); diff --git a/apps/website/tests/smoke/public-routes.spec.ts b/apps/website/tests/smoke/public-routes.spec.ts index 3da9b140..5ba9a544 100644 --- a/apps/website/tests/smoke/public-routes.spec.ts +++ b/apps/website/tests/smoke/public-routes.spec.ts @@ -1,42 +1,288 @@ import { expect, test } from '@playwright/test'; import { - expectNoForbiddenPublicText, + canonicalSiteUrl, + docsRoutes, + expectCoreCapabilitiesVisible, + expectMetadataForRoute, + expectNoForbiddenPublicClaims, expectNoHorizontalOverflow, + expectNoPlaceholderLinks, + expectPublicLinksAreIntentional, + expectSitemapExcludesRoutes, + expectSitemapIncludesRoutes, + readSitemapUrls, + redirectRouteExpectations, redirectRoutes, renderedRoutes, } from './smoke-helpers'; +import { readFile } from 'node:fs/promises'; +import { globby } from 'globby'; + +const routeMetadata = { + '/': { + title: /Tenantial.*Evidenzbasierte Microsoft-Tenant-Governance/i, + description: /evidenzbasierte Governance/i, + }, + '/platform': { + title: /Plattform \| Tenantial/i, + description: /öffentliches Produktmodell/i, + }, + '/pricing': { + title: /Preise \| Tenantial/i, + description: /Evaluierungs.*Rollout/i, + }, + '/contact': { + title: /Kontakt \| Tenantial/i, + description: /Walkthrough.*Rollout/i, + }, + '/trust': { + title: /Vertrauen \| Tenantial/i, + description: /Trust-Posture.*statische.*Previews/i, + }, + '/legal': { + title: /Rechtliches \| Tenantial/i, + description: /rechtliche Website-Informationen/i, + }, + '/privacy': { + title: /Datenschutz \| Tenantial/i, + description: /Website-Datenschutzhinweis/i, + }, + '/terms': { + title: /Nutzungsbedingungen \| Tenantial/i, + description: /Website-Nutzungsbedingungen/i, + }, + '/imprint': { + title: /Impressum \| Tenantial/i, + description: /Veröffentlichungsinformationen/i, + }, + '/welcome-to-docs/': { + title: /Tenantial Docs/i, + description: /Microsoft-Tenant-Governance-Modell/i, + }, + '/guides/intro/': { + title: /Einführung in Tenantial/i, + description: /Microsoft-Tenant-Governance-Modell/i, + }, + '/guides/getting-started/': { + title: /Getting Started/i, + description: /Tenantial Walkthrough/i, + }, + '/guides/first-project-checklist/': { + title: /Evaluierungscheckliste/i, + description: /frühe Tenantial-Evaluierung/i, + }, + '/platform/evidence-review/': { + title: /Evidence Review/i, + description: /Evidence-Review-Workflows/i, + }, + '/en/': { + title: /Tenantial.*Evidence-first Microsoft tenant governance/i, + description: + /evidence-first governance for Microsoft tenant configuration/i, + }, + '/en/platform': { + title: /Platform \| Tenantial/i, + description: /public product model for Microsoft tenant/i, + }, + '/en/pricing': { + title: /Pricing \| Tenantial/i, + description: /evaluation and rollout conversation paths/i, + }, + '/en/contact': { + title: /Contact \| Tenantial/i, + description: /walkthrough.*scoped rollout conversation/i, + }, + '/en/trust': { + title: /Trust \| Tenantial/i, + description: /conservative claims.*static preview boundaries/i, + }, + '/en/legal': { + title: /Legal \| Tenantial/i, + description: /public website legal information/i, + }, + '/en/privacy': { + title: /Privacy \| Tenantial/i, + description: /public website privacy notice/i, + }, + '/en/terms': { + title: /Terms \| Tenantial/i, + description: /public website terms/i, + }, + '/en/imprint': { + title: /Imprint \| Tenantial/i, + description: /public website publication information/i, + }, + '/en/welcome-to-docs/': { + title: /Tenantial Docs/i, + description: /evidence-first Microsoft tenant governance model/i, + }, + '/en/guides/intro/': { + title: /Introduction to Tenantial/i, + description: /evidence-first Microsoft tenant governance model/i, + }, + '/en/guides/getting-started/': { + title: /Getting Started/i, + description: /Tenantial walkthrough/i, + }, + '/en/guides/first-project-checklist/': { + title: /Evaluation Checklist/i, + description: /conservative checklist for early Tenantial evaluation/i, + }, + '/en/platform/evidence-review/': { + title: /Evidence Review/i, + description: /Tenantial evidence review workflows/i, + }, +} as const; for (const route of renderedRoutes) { test(`renders intentional route ${route}`, async ({ page }) => { await page.goto(route); await expect(page.locator('body')).toContainText('Tenantial'); - await expectNoForbiddenPublicText(page); + await expectNoForbiddenPublicClaims(page); await expectNoHorizontalOverflow(page); }); } -for (const route of redirectRoutes) { - test(`redirects ${route} intentionally`, async ({ page }) => { +test('homepage first viewport explains core Tenantial capabilities', async ({ + page, +}) => { + await page.goto('/'); + + const heading = page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/i, + }); + await expect(heading).toBeVisible(); + + const box = await heading.boundingBox(); + expect(box?.y ?? Number.POSITIVE_INFINITY).toBeLessThan(420); + await expect( + page.getByRole('link', { name: /Walkthrough anfragen/i }).first() + ).toHaveAttribute('href', '/contact'); + await expect( + page.getByRole('link', { name: /Plattform ansehen/i }).first() + ).toHaveAttribute('href', '/platform'); + await expectCoreCapabilitiesVisible(page); +}); + +test('/platform explains the public product model without internal runtime terms', async ({ + page, +}) => { + await page.goto('/platform'); + + await expect( + page.getByRole('heading', { name: /Plattform-Review-Modell/i }) + ).toBeVisible(); + await expectCoreCapabilitiesVisible(page); + await expect(page.locator('body')).toContainText(/statische Demo-Previews/i); + await expect(page.locator('body')).toContainText( + /authentifiziert keine Besucher, liest keinen Microsoft Tenant, führt keine Operationen aus und speichert keine Tenant-Exporte/i + ); + + const text = await page.locator('body').innerText(); + expect(text).not.toMatch(/\b(Laravel|Filament|Livewire)\b/i); + expect(text).not.toMatch(/\bGraph runtime\b/i); +}); + +for (const route of renderedRoutes) { + test(`public links on ${route} resolve intentionally`, async ({ + page, + request, + }) => { await page.goto(route); - await expect(page).toHaveURL(/\/platform\/?$/); - await expect(page.getByRole('heading', { name: /Platform review model/ })) - .toBeVisible(); + await expectNoPlaceholderLinks(page); + await expectPublicLinksAreIntentional(page, request, route); + }); +} + +test('built HTML emits no placeholder href values', async () => { + const htmlFiles = await globby('dist/**/*.html', { + absolute: true, + cwd: process.cwd(), + }); + + expect(htmlFiles.length).toBeGreaterThan(0); + + for (const file of htmlFiles) { + const html = await readFile(file, 'utf8'); + expect(html, file).not.toMatch(/href=(["'])#\1/); + expect(html, file).not.toMatch(/href=\{["']#["']\}/); + } +}); + +for (const route of [ + '/trust', + '/pricing', + '/legal', + '/privacy', + '/terms', + '/imprint', + ...docsRoutes, +] as const) { + test(`claim-sensitive route ${route} stays conservative`, async ({ + page, + }) => { + await page.goto(route); + + await expectNoForbiddenPublicClaims(page); + await expectNoHorizontalOverflow(page); + }); +} + +for (const [route, expected] of Object.entries(routeMetadata)) { + test(`metadata is route-specific for ${route}`, async ({ page }) => { + await page.goto(route); + + await expectMetadataForRoute(page, route, expected); + }); +} + +for (const route of redirectRoutes) { + test(`redirects ${route} intentionally`, async ({ page, request }) => { + const response = await request.get(route, { maxRedirects: 0 }); + const expected = redirectRouteExpectations[route]; + + if (response.status() >= 300 && response.status() < 400) { + expect(response.status()).toBe(expected.status); + expect(response.headers().location).toBe(expected.target); + } else { + expect(response.status()).toBe(200); + const html = await response.text(); + + expect(html).toContain('name="robots" content="noindex"'); + expect(html).toContain( + `rel="canonical" href="${canonicalSiteUrl}${expected.target}"` + ); + expect(html).toContain(`href="${expected.target}"`); + } + + await page.goto(route); + + await expect(page).toHaveURL(new RegExp(`${expected.target}/?$`)); + await expect( + page.getByRole('heading', { + name: /Plattform-Review-Modell|Platform review model/, + }) + ).toBeVisible(); }); } test('robots and sitemap are available', async ({ page }) => { await page.goto('/robots.txt'); - await expect(page.locator('body')).toContainText('Sitemap:'); + await expect(page.locator('body')).toContainText( + `Sitemap: ${canonicalSiteUrl}/sitemap-index.xml` + ); await page.goto('/sitemap-index.xml'); await expect(page.locator('body')).toContainText('sitemap-0.xml'); - - await page.goto('/sitemap-0.xml'); - const sitemap = await page.locator('body').innerText(); - - for (const route of redirectRoutes) { - expect(sitemap).not.toContain(`https://tenantial.com${route}/`); - } +}); + +test('sitemap exposes canonical routes and excludes redirect aliases', async ({ + page, +}) => { + const sitemapUrls = await readSitemapUrls(page); + + expectSitemapIncludesRoutes(sitemapUrls, renderedRoutes); + expectSitemapExcludesRoutes(sitemapUrls, redirectRoutes); }); diff --git a/apps/website/tests/smoke/smoke-helpers.ts b/apps/website/tests/smoke/smoke-helpers.ts index 0220b342..e1bda7b5 100644 --- a/apps/website/tests/smoke/smoke-helpers.ts +++ b/apps/website/tests/smoke/smoke-helpers.ts @@ -1,4 +1,4 @@ -import { expect, type Page } from '@playwright/test'; +import { expect, type APIRequestContext, type Page } from '@playwright/test'; export const renderedRoutes = [ '/', @@ -11,39 +11,142 @@ export const renderedRoutes = [ '/terms', '/imprint', '/welcome-to-docs/', + '/guides/intro/', + '/guides/getting-started/', + '/guides/first-project-checklist/', + '/platform/evidence-review/', + '/en/', + '/en/platform', + '/en/pricing', + '/en/contact', + '/en/trust', + '/en/legal', + '/en/privacy', + '/en/terms', + '/en/imprint', + '/en/welcome-to-docs/', + '/en/guides/intro/', + '/en/guides/getting-started/', + '/en/guides/first-project-checklist/', + '/en/platform/evidence-review/', ] as const; -export const redirectRoutes = [ - '/product', - '/products', - '/services', - '/blog', - '/insights', +export const docsRoutes = [ + '/welcome-to-docs/', + '/guides/intro/', + '/guides/getting-started/', + '/guides/first-project-checklist/', + '/platform/evidence-review/', + '/en/welcome-to-docs/', + '/en/guides/intro/', + '/en/guides/getting-started/', + '/en/guides/first-project-checklist/', + '/en/platform/evidence-review/', ] as const; -const forbiddenPublicText = [ - 'ScrewFast', - 'construction', - 'hardware', - 'template', - 'open-source', - 'TenantAtlas', - 'TenantPilot', - 'TenantCTRL', - 'trusted by', - 'SOC 2', - 'ISO 27001', - '99.9%', - 'Microsoft endorsed', - 'guaranteed recovery', - 'guaranteed compliance', +export const redirectRouteExpectations = { + '/product': { status: 301, target: '/platform' }, + '/products': { status: 301, target: '/platform' }, + '/services': { status: 301, target: '/platform' }, + '/blog': { status: 302, target: '/platform' }, + '/insights': { status: 302, target: '/platform' }, + '/en/product': { status: 301, target: '/en/platform' }, + '/en/products': { status: 301, target: '/en/platform' }, + '/en/services': { status: 301, target: '/en/platform' }, + '/en/blog': { status: 302, target: '/en/platform' }, + '/en/insights': { status: 302, target: '/en/platform' }, +} as const; + +export const redirectRoutes = Object.keys(redirectRouteExpectations) as Array< + keyof typeof redirectRouteExpectations +>; + +export const staticRoutes = [ + '/robots.txt', + '/sitemap-index.xml', + '/manifest.json', + '/favicon.ico', +] as const; + +export const canonicalSiteUrl = 'https://tenantial.com'; + +const forbiddenPublicPatterns = [ + { label: 'ScrewFast residue', pattern: /ScrewFast/i }, + { label: 'construction residue', pattern: /\bconstruction\b/i }, + { label: 'hardware residue', pattern: /\bhardware\b/i }, + { label: 'template residue', pattern: /\btemplate\b/i }, + { label: 'open-source residue', pattern: /open-source/i }, + { label: 'TenantAtlas residue', pattern: /TenantAtlas/i }, + { label: 'TenantPilot residue', pattern: /TenantPilot/i }, + { label: 'TenantCTRL residue', pattern: /TenantCTRL/i }, + { label: 'fake social proof', pattern: /trusted by/i }, + { label: 'SOC 2 claim', pattern: /SOC\s*2/i }, + { label: 'ISO 27001 claim', pattern: /ISO\s*27001/i }, + { label: 'uptime claim', pattern: /99\.9%|guaranteed uptime/i }, + { label: 'Microsoft endorsement', pattern: /Microsoft endorsed/i }, + { label: 'Microsoft certification claim', pattern: /Microsoft certified/i }, + { label: 'recovery guarantee', pattern: /guaranteed recovery/i }, + { label: 'compliance guarantee', pattern: /guaranteed compliance/i }, + { label: 'fake checkout CTA', pattern: /\b(buy now|checkout)\b/i }, + { + label: 'fake trial CTA', + pattern: /\b(start free trial|create account)\b/i, + }, + { label: 'fake app access CTA', pattern: /\b(sign in|log in|login)\b/i }, ]; +const metadataSelectors = [ + 'title', + 'meta[name="description"]', + 'meta[property="og:title"]', + 'meta[property="og:description"]', + 'meta[property="og:url"]', + 'meta[property="og:image"]', + 'meta[property="twitter:url"]', + 'meta[name="twitter:title"]', + 'meta[name="twitter:description"]', + 'meta[name="twitter:image"]', +].join(','); + +function normalizePath(path: string): string { + if (path === '') { + return '/'; + } + + return path === '/' || path.includes('.') || path.endsWith('/') + ? path + : `${path}/`; +} + +function normalizeRouteForSitemap(route: string): string { + return route === '/' ? '/' : normalizePath(route); +} + +function routeToCanonicalPath(route: string): string { + return route === '' ? '/' : route; +} + export async function expectNoForbiddenPublicText(page: Page): Promise { const text = await page.locator('body').innerText(); - for (const term of forbiddenPublicText) { - expect(text.toLowerCase()).not.toContain(term.toLowerCase()); + for (const { label, pattern } of forbiddenPublicPatterns) { + expect(text, label).not.toMatch(pattern); + } +} + +export async function expectNoForbiddenPublicClaims(page: Page): Promise { + await expectNoForbiddenPublicText(page); + + const metadataText = await page + .locator(metadataSelectors) + .evaluateAll(nodes => + nodes + .map(node => node.textContent || node.getAttribute('content') || '') + .join('\n') + ); + + for (const { label, pattern } of forbiddenPublicPatterns) { + expect(metadataText, `${label} in metadata`).not.toMatch(pattern); } } @@ -55,3 +158,164 @@ export async function expectNoHorizontalOverflow(page: Page): Promise { expect(metrics.scrollWidth).toBeLessThanOrEqual(metrics.clientWidth + 1); } + +export async function expectCoreCapabilitiesVisible(page: Page): Promise { + const text = (await page.locator('body').innerText()).toLowerCase(); + + for (const terms of [ + ['backup'], + ['restore'], + ['drift detection', 'drift'], + ['findings'], + ['evidence'], + ['auditability'], + ['exceptions'], + ['reviews', 'review'], + ]) { + expect( + terms.some(term => text.includes(term)), + `missing capability term: ${terms.join(' or ')}` + ).toBe(true); + } +} + +export async function expectNoPlaceholderLinks(page: Page): Promise { + const placeholders = await page + .locator('a[href="#"], a[href=""], area[href="#"], area[href=""]') + .evaluateAll(nodes => + nodes.map(node => ({ + text: node.textContent?.trim() || '', + href: node.getAttribute('href'), + })) + ); + + expect(placeholders).toEqual([]); +} + +export async function expectPublicLinksAreIntentional( + page: Page, + request: APIRequestContext, + sourceRoute: string +): Promise { + const allowedRoutes = new Set( + [ + ...renderedRoutes, + ...redirectRoutes, + ...staticRoutes, + '/sitemap-0.xml', + ].map(route => normalizePath(route)) + ); + + const hrefs = await page + .locator('a[href], area[href]') + .evaluateAll(nodes => + nodes + .map(node => node.getAttribute('href')?.trim()) + .filter((href): href is string => Boolean(href)) + ); + + for (const href of hrefs) { + expect(href, `placeholder link on ${sourceRoute}`).not.toBe('#'); + + if ( + href.startsWith('mailto:') || + href.startsWith('tel:') || + href.startsWith('javascript:') + ) { + continue; + } + + const url = new URL(href, canonicalSiteUrl); + + if (url.origin !== canonicalSiteUrl) { + expect(['http:', 'https:']).toContain(url.protocol); + continue; + } + + const normalizedPath = normalizePath(url.pathname); + expect( + allowedRoutes.has(normalizedPath), + `unexpected public link ${href} on ${sourceRoute}` + ).toBe(true); + + if (url.hash && normalizePath(sourceRoute) === normalizedPath) { + const targetId = url.hash.slice(1); + await expect( + page.locator(`#${targetId}`), + `missing anchor target ${url.hash} on ${sourceRoute}` + ).toHaveCount(1); + } + + if (!url.hash || normalizePath(sourceRoute) !== normalizedPath) { + const response = await request.get(url.pathname); + expect( + response.status(), + `unexpected status for linked route ${href} on ${sourceRoute}` + ).toBeLessThan(400); + } + } +} + +export async function expectMetadataForRoute( + page: Page, + route: string, + expected: { title: RegExp; description: RegExp } +): Promise { + await expect(page).toHaveTitle(expected.title); + await expect(page.locator('meta[name="description"]')).toHaveAttribute( + 'content', + expected.description + ); + + const canonicalUrl = `${canonicalSiteUrl}${routeToCanonicalPath(route)}`; + + await expect(page.locator('link[rel="canonical"]')).toHaveAttribute( + 'href', + canonicalUrl + ); + await expect(page.locator('meta[property="og:url"]').last()).toHaveAttribute( + 'content', + canonicalUrl + ); + await expect( + page + .locator('meta[property="twitter:url"], meta[name="twitter:url"]') + .last() + ).toHaveAttribute('content', canonicalUrl); + await expect( + page.locator('meta[property="og:image"]').last() + ).toHaveAttribute('content', /\/_astro\/social\.[^/]+\.png$/); + await expect( + page.locator('meta[name="twitter:image"]').last() + ).toHaveAttribute('content', /\/_astro\/social\.[^/]+\.png$/); +} + +export function extractSitemapUrls(xml: string): string[] { + return Array.from(xml.matchAll(/([^<]+)<\/loc>/g)).map(([, url]) => url); +} + +export async function readSitemapUrls(page: Page): Promise { + await page.goto('/sitemap-0.xml'); + + return extractSitemapUrls(await page.content()); +} + +export function expectSitemapIncludesRoutes( + urls: string[], + routes: readonly string[] +): void { + for (const route of routes) { + const url = `${canonicalSiteUrl}${normalizeRouteForSitemap(route)}`; + expect(urls, `sitemap missing ${url}`).toContain(url); + } +} + +export function expectSitemapExcludesRoutes( + urls: string[], + routes: readonly string[] +): void { + for (const route of routes) { + const url = `${canonicalSiteUrl}${normalizeRouteForSitemap(route)}`; + expect(urls, `sitemap should exclude ${url}`).not.toContain(url); + } +} diff --git a/specs/403-public-website-launch-readiness/contracts/public-site-contract.md b/specs/403-public-website-launch-readiness/contracts/public-site-contract.md new file mode 100644 index 00000000..6deeba5a --- /dev/null +++ b/specs/403-public-website-launch-readiness/contracts/public-site-contract.md @@ -0,0 +1,164 @@ +# Public Site Contract: Spec 403 Launch Readiness + +This feature has no REST, GraphQL, Laravel, Filament, Livewire, Microsoft Graph, database, queue, job, policy, RBAC, or product runtime API contract. + +The contract is the public static website behavior that reviewers and smoke tests must verify. + +## Rendered Canonical Routes + +The following German default routes must render intentionally and expose Tenantial-specific page content and metadata: + +- `/` +- `/platform` +- `/pricing` +- `/contact` +- `/trust` +- `/legal` +- `/privacy` +- `/terms` +- `/imprint` +- `/welcome-to-docs/` +- `/guides/intro/` +- `/guides/getting-started/` +- `/guides/first-project-checklist/` +- `/platform/evidence-review/` + +The following English route mirrors must render intentionally under `/en/...`: + +- `/en/` +- `/en/platform` +- `/en/pricing` +- `/en/contact` +- `/en/trust` +- `/en/legal` +- `/en/privacy` +- `/en/terms` +- `/en/imprint` +- `/en/welcome-to-docs/` +- `/en/guides/intro/` +- `/en/guides/getting-started/` +- `/en/guides/first-project-checklist/` +- `/en/platform/evidence-review/` + +## Redirect-Only Aliases + +The following aliases must redirect intentionally to `/platform` and must not appear as canonical sitemap entries: + +- `/product` +- `/products` +- `/services` +- `/blog` +- `/insights` + +The English aliases must redirect intentionally to `/en/platform` and must not appear as canonical sitemap entries: + +- `/en/product` +- `/en/products` +- `/en/services` +- `/en/blog` +- `/en/insights` + +Current source behavior may use permanent or temporary redirects, but launch review must document which aliases are retained and why. + +Current planning baseline: + +- `/product`, `/products`, and `/services` use 301 redirects. +- `/blog` and `/insights` use 302 redirects. + +## Static Generated Routes + +The following static outputs must be available after build: + +- `/robots.txt` +- `/sitemap-index.xml` +- generated sitemap child files such as `/sitemap-0.xml` +- `/manifest.json` +- `/favicon.ico` + +`robots.txt` must point to `/sitemap-index.xml` for the configured public site URL. + +## Metadata Contract + +Each rendered canonical route must provide: + +- Tenantial-specific page title +- Tenantial-specific page description +- route-appropriate canonical, Open Graph, and Twitter URL values +- German default and English alternate `hreflang` links, plus `x-default` pointing at German +- locale-appropriate Open Graph locale values +- existing social image URL and MIME/type metadata +- no ScrewFast, construction/hardware, template, TenantAtlas, TenantPilot, or TenantCTRL residue in public metadata +- no unsupported proof claims in title, description, Open Graph, Twitter, schema, or docs metadata + +## CTA Contract + +Every public CTA, navigation item, footer link, docs link, and visible form-like control must satisfy: + +- resolves to an intentional route, anchor, static asset, or legitimate external URL +- no public `href="#"` placeholders +- demo/contact paths route to `/contact` or an intentional contact section +- localized public links remain in the current locale where a localized route exists +- no login, account creation, billing, checkout, backend submission, guaranteed provisioning, or app access implication unless implemented +- pricing CTAs remain contact/demo-oriented +- static form controls must not imply backend submission, guaranteed response, account creation, provisioning, or subscription workflow +- footer email/newsletter controls must be removed or reframed if no newsletter workflow exists + +## Trust And Pricing Claim Contract + +Public copy and metadata must avoid unsupported: + +- SOC 2, ISO, or other certification claims +- Microsoft endorsement claims +- compliance guarantees +- recovery guarantees +- uptime guarantees +- fake customer logos +- fake testimonials +- fake "trusted by" claims +- self-serve billing, checkout, subscription, or entitlement claims +- production legal details left as explicit replacement placeholders + +## Product Preview Contract + +Static product previews must: + +- be framed as illustrative, static, or demo content +- avoid implying live tenant data +- avoid internal Laravel/Filament implementation details +- avoid turning visual labels into product runtime taxonomy +- communicate meaning through text or labels, not color alone + +## Browser And Accessibility Contract + +Validated public routes must: + +- avoid body-level horizontal overflow on representative desktop and mobile viewports +- keep primary navigation, CTAs, footer links, and visible controls keyboard reachable +- show visible focus states for interactive elements +- remain understandable with reduced motion +- keep primary content and links usable if JavaScript fails where reasonably possible for a static marketing site + +## Launch Review Risk Register + +Implementation should explicitly resolve or document these current source risks: + +- Starlight child docs routes are public and indexed but not fully covered by current smoke route lists. +- Main-layout `og:url` and `twitter:url` are root-oriented rather than route-specific. +- Social image metadata may reference a missing or type-mismatched asset. +- `/imprint` contains placeholder production details. +- Contact form copy may imply a backend workflow that does not exist. +- Footer `Subscribe` wording may imply a newsletter workflow that does not exist. +- Source placeholder social links with `#` must not be emitted publicly. + +## Scope Contract + +Implementation and validation must not touch: + +- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` +- Laravel providers, migrations, policies, jobs, queues, database, Filament resources, Livewire components, Blade views, tenant/workspace/RBAC code, Microsoft Graph code, or AuditLog behavior + +Scope proof command: + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && git status --short -- apps/platform +``` diff --git a/specs/403-public-website-launch-readiness/data-model.md b/specs/403-public-website-launch-readiness/data-model.md new file mode 100644 index 00000000..1f4a9609 --- /dev/null +++ b/specs/403-public-website-launch-readiness/data-model.md @@ -0,0 +1,114 @@ +# Data Model: `apps/website` Public Website Launch Readiness + +This feature introduces no database tables, product runtime models, persisted entities, enums, status families, provider contracts, or shared product taxonomy. + +The records below are planning and validation concepts for static website artifacts only. + +## Public Route + +Represents an intentional public URL exposed by the static website. + +**Fields** + +- `path`: public URL path, such as `/`, `/platform`, or `/welcome-to-docs/` +- `routeKind`: `rendered`, `redirect`, `static`, or `docs` +- `canonicalTarget`: canonical page path when applicable +- `sitemapPolicy`: included or excluded from generated sitemap +- `navigationPolicy`: primary nav, footer nav, docs nav, hidden, or redirect-only +- `metadataRequired`: whether route-specific title and description must be reviewed +- `claimReviewRequired`: whether visible copy and metadata require proof-safety review + +**Validation Rules** + +- Rendered canonical routes must return successful HTML. +- Redirect-only aliases must resolve intentionally and be excluded from sitemap output. +- Static routes such as `/robots.txt` and `/sitemap-index.xml` must be available after build. +- Public route copy and metadata must not contain forbidden residue terms listed in Spec 403. + +## CTA Link + +Represents a public call-to-action, navigation item, footer link, docs link, or visible interactive route target. + +**Fields** + +- `label`: visible CTA or link text +- `sourceSurface`: page or component where the CTA appears +- `href`: route, anchor, or external URL target +- `targetType`: rendered route, anchor, static asset, external proof link, or redirect alias +- `supportedWorkflow`: contact/demo, product explanation, pricing review, trust/legal review, docs review, or navigation +- `claimRisk`: whether the CTA could imply login, billing, checkout, provisioning, account creation, backend submission, or unsupported workflow + +**Validation Rules** + +- Public CTAs must resolve to intentional routes or anchors. +- Public CTAs must not use `href="#"` placeholders. +- Demo/contact CTAs must lead to `/contact` or an intentional contact section. +- CTAs must not imply login, account creation, billing, checkout, backend submission, or automated onboarding unless implemented. + +## Public Claim + +Represents visible copy or metadata that could create a trust, compliance, product, pricing, or proof claim. + +**Fields** + +- `surface`: route, docs page, component, metadata source, or generated HTML surface +- `claimText`: reviewed copy or metadata phrase +- `claimCategory`: product positioning, trust, pricing, legal, preview framing, social proof, or deployment +- `proofStatus`: supplied proof, conservative placeholder, unsupported, or remove +- `forbiddenResidueCheck`: whether the phrase contains forbidden public residue terms + +**Validation Rules** + +- No unsupported SOC 2, ISO, Microsoft endorsement, uptime, recovery, compliance, or customer adoption claims. +- No fake customer logos, fake testimonials, or fake "trusted by" claims. +- Pricing copy must avoid self-serve subscription, billing, checkout, and entitlement claims unless workflow exists. +- Metadata must stay Tenantial-specific and route-appropriate. + +## Static Product Preview + +Represents product-like static/demo content shown on public pages. + +**Fields** + +- `surface`: route/component where preview appears +- `previewName`: human-readable preview name +- `framingCopy`: text that identifies the preview as static, illustrative, or demo +- `capabilityShown`: backup, restore, drift detection, findings, evidence, auditability, exceptions, reviews, or related product positioning +- `dataProvenance`: static/demo only +- `colorMeaningSupport`: text/icon/label support so meaning does not rely on color alone + +**Validation Rules** + +- Preview content must not imply live tenant data. +- Preview status-like values must not become shared product taxonomy. +- Preview meaning must not rely on color alone. +- Preview wording must avoid internal Laravel/Filament implementation details. + +## Launch Readiness Note + +Represents the implementation handoff artifact required by FR-034. + +**Fields** + +- `routeExposure`: canonical routes, docs routes, static routes, redirect aliases, and hidden routes +- `ctaAssumptions`: supported public CTA paths and intentionally absent workflows +- `trustPricingPosture`: conservative claim posture and unsupported claims avoided +- `sitemapBehavior`: sitemap inclusion/exclusion and robots entrypoint +- `deploymentExpectations`: build, smoke, static output, and deploy notes +- `platformScopeCheck`: recorded confirmation that `apps/platform` remains untouched + +**Validation Rules** + +- Must be stored inside the feature's website-local implementation or release handoff path. +- Must not introduce product runtime truth. +- Must document the exact validation commands reviewers should run. + +## Relationships + +- A `Public Route` may contain many `CTA Link` and `Public Claim` review points. +- A `Static Product Preview` belongs to one public route or docs route. +- The `Launch Readiness Note` summarizes all reviewed public routes, CTAs, claims, sitemap behavior, and deployment expectations. + +## State Transitions + +No product state transitions are introduced. Review progress may be tracked in tasks or checklists during implementation, but it must not become a product runtime status family. diff --git a/specs/403-public-website-launch-readiness/launch-readiness.md b/specs/403-public-website-launch-readiness/launch-readiness.md new file mode 100644 index 00000000..9b5910ed --- /dev/null +++ b/specs/403-public-website-launch-readiness/launch-readiness.md @@ -0,0 +1,127 @@ +# Launch Readiness Handoff: Spec 403 + +## Scope + +Runtime changes are limited to `apps/website`. Planning and handoff artifacts live in `specs/403-public-website-launch-readiness`. + +`apps/platform` remains out of scope and was checked with: + +```bash +git status --short -- apps/platform +``` + +The command printed no output. + +## Route Exposure + +Canonical rendered German default routes: + +- `/` +- `/platform` +- `/pricing` +- `/contact` +- `/trust` +- `/legal` +- `/privacy` +- `/terms` +- `/imprint` +- `/welcome-to-docs/` +- `/guides/intro/` +- `/guides/getting-started/` +- `/guides/first-project-checklist/` +- `/platform/evidence-review/` + +Canonical rendered English routes: + +- `/en/` +- `/en/platform` +- `/en/pricing` +- `/en/contact` +- `/en/trust` +- `/en/legal` +- `/en/privacy` +- `/en/terms` +- `/en/imprint` +- `/en/welcome-to-docs/` +- `/en/guides/intro/` +- `/en/guides/getting-started/` +- `/en/guides/first-project-checklist/` +- `/en/platform/evidence-review/` + +Redirect-only aliases: + +- `/product` -> `/platform` +- `/products` -> `/platform` +- `/services` -> `/platform` +- `/blog` -> `/platform` +- `/insights` -> `/platform` +- `/en/product` -> `/en/platform` +- `/en/products` -> `/en/platform` +- `/en/services` -> `/en/platform` +- `/en/blog` -> `/en/platform` +- `/en/insights` -> `/en/platform` + +Static Astro output renders redirect aliases as noindex redirect HTML pages. Smoke coverage verifies the noindex marker, canonical target, and visible redirect link. + +Top navigation exposes `/welcome-to-docs/` as `Docs`, matching the public docs route instead of keeping documentation discoverable only through footer or contact-page links. + +German remains the unprefixed default locale. English is available as an additional locale under `/en/...`; the language picker switches between the current German route and its English counterpart where available. + +## CTA Assumptions + +Supported public CTA paths are contact/demo and product-evaluation paths only. + +- Homepage CTAs route to `/contact` and `/platform`. +- Platform CTAs route to `/contact` and `/trust`. +- Pricing CTAs route to `/contact`. +- Docs links route to intentional localized docs pages. +- Footer contact CTA routes to `/contact`. + +The public website does not claim login, account creation, self-service payment, automated provisioning, live tenant connection, or backend form submission. + +## Trust And Pricing Posture + +Public copy remains conservative: + +- no customer-logo proof +- no testimonials or third-party endorsement claims +- no external assurance or certification claims +- no uptime, recovery, or compliance guarantees +- no fixed package access or instant payment claims +- no live tenant data in static previews + +Static product previews are framed as static/demo/illustrative content and do not imply live Microsoft tenant access from the public website. + +## Sitemap And Robots + +`robots.txt` points to: + +```text +https://tenantial.com/sitemap-index.xml +``` + +Generated sitemap coverage includes canonical rendered German and English routes and excludes redirect-only aliases. Route-specific metadata checks cover title, description, canonical URL, alternate language links, Open Graph URL, Open Graph locale, Twitter URL, and generated PNG social image metadata. + +## Deployment Expectations + +Reviewer validation commands: + +```bash +corepack pnpm build:website +WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke +git diff --check +git status --short -- apps/platform +! rg "href=\"#\"|href=\\{\"#\"\\}|href=\\{'#'\\}" apps/website/src +``` + +The build produces static output in `apps/website/dist`. Deploy the website artifact only; no Laravel, Filament, database, queue, Sail, Microsoft Graph, or `apps/platform` deployment step is required for this feature. + +## Review Evidence + +- Website build passed with existing Astro hints only. +- Public routes smoke suite passed: 230 passed. +- Interaction smoke suite passed: 10 passed, 4 intentionally skipped. +- Integrated browser review opened `http://127.0.0.1:4321/` with German default navigation and the language menu showing `Deutsch` and `English`. +- `git diff --check` passed. +- Source placeholder href scan passed. +- `apps/platform` scope check passed. diff --git a/specs/403-public-website-launch-readiness/plan.md b/specs/403-public-website-launch-readiness/plan.md new file mode 100644 index 00000000..05183472 --- /dev/null +++ b/specs/403-public-website-launch-readiness/plan.md @@ -0,0 +1,220 @@ +# Implementation Plan: `apps/website` Public Website Launch Readiness + +**Branch**: `feat/403-public-website-launch-readiness` | **Date**: 2026-05-21 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/spec.md` +**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/spec.md` + +## Summary + +Make the existing Spec 402 ScrewFast-derived Tenantial public website launch-ready without rebuilding the foundation or touching `apps/platform`. The implementation will stay inside `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` and focus on German-default plus English-prefixed public copy, CTA targets, route exposure, metadata, sitemap/robots behavior, conservative trust/pricing wording, static preview framing, and browser/static validation. + +## Technical Context + +**Language/Version**: TypeScript 6.0.3, Astro 6.3.3, Node.js >=20.0.0, pnpm 10.33.0 +**Primary Dependencies**: Astro, `@astrojs/starlight`, `@astrojs/sitemap`, `@astrojs/mdx`, Tailwind CSS v4, `@tailwindcss/vite`, Preline 4, Lenis, GSAP, Sharp, Playwright +**Storage**: N/A - static website content and generated build output only; no database or product persistence +**Testing**: Astro check/build plus Playwright smoke tests under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke` +**Validation Lanes**: website build, public smoke, manual browser review, whitespace check, `apps/platform` scope check +**Target Platform**: Static Astro public website deployed from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`, with public site URL `https://tenantial.com` +**Project Type**: Web - standalone Astro public website inside monorepo +**Performance Goals**: No body-level horizontal overflow on validated desktop/mobile routes; static pages remain usable when JavaScript fails where reasonably possible +**Constraints**: Runtime/source changes are scoped to `apps/website`; Spec Kit artifacts may live under `specs/403-public-website-launch-readiness`; preserve Spec 402 ScrewFast-derived substrate; no backend contact/demo workflow; no authentication, billing, Microsoft Graph, Laravel, Filament, Livewire, database, queue, job, provider, policy, RBAC, or `apps/platform` changes +**Scale/Scope**: Public launch-readiness pass for canonical German default routes, English `/en/...` route mirrors, redirect aliases, docs routes, static metadata assets, navigation, footer CTAs, pricing/trust/legal/contact content, and Playwright smoke coverage + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: no admin/operator-facing product surface change; public website launch-readiness workflow only +- **Native vs custom classification summary**: ScrewFast-derived Astro website; no Filament/Blade/admin surface +- **Shared-family relevance**: none for Laravel/Filament shared interaction families +- **State layers in scope**: localized static page content, public route metadata, navigation/footer link data, Starlight docs configuration, Playwright smoke expectations +- **Audience modes in scope**: public visitor, buyer/reviewer, site owner/reviewer; no authenticated operator/MSP/support-platform modes +- **Decision/diagnostic/raw hierarchy plan**: public copy must be decision-first for visitors; diagnostics are limited to launch-readiness notes and tests, not visible runtime UI +- **Raw/support gating plan**: N/A - no raw/support product evidence surface +- **One-primary-action / duplicate-truth control**: each public page should keep a clear demo/contact path and avoid competing fake login, billing, checkout, or account CTAs +- **Handling modes by drift class or surface**: public route/copy/CTA drift is review-mandatory inside this feature; platform/admin drift is a hard stop +- **Repository-signal treatment**: `apps/platform` changes are a hard stop; generated website build artifacts are reviewed only if they become tracked changes +- **Special surface test profiles**: N/A - not a Filament surface +- **Required tests or manual smoke**: public smoke plus manual browser review across light/dark/mobile/desktop and keyboard reachability +- **Exception path and spread control**: none +- **Active feature PR close-out entry**: Smoke Coverage + +## Shared Pattern & System Fit + +- **Cross-cutting feature marker**: no +- **Systems touched**: `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` public website only +- **Shared abstractions reused**: existing Spec 402 ScrewFast-derived Astro structure, page components, Starlight docs, sitemap integration, and Playwright smoke helper patterns +- **New abstraction introduced? why?**: none +- **Why the existing abstraction was sufficient or insufficient**: The website already has route, layout, navigation, metadata, docs, and smoke-test structure. Launch readiness can be implemented by tightening those local artifacts rather than adding runtime abstractions. +- **Bounded deviation / spread control**: none + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no +- **Central contract reused**: N/A +- **Delegated UX behaviors**: N/A +- **Surface-owned behavior kept local**: N/A +- **Queued DB-notification policy**: N/A +- **Terminal notification path**: N/A +- **Exception path**: none + +## Provider Boundary & Portability Fit + +- **Shared provider/platform boundary touched?**: no +- **Provider-owned seams**: N/A +- **Platform-core seams**: N/A +- **Neutral platform terms / contracts preserved**: Public product language may refer to Microsoft tenant configuration as positioning only; no product runtime seam changes. +- **Retained provider-specific semantics and why**: Microsoft tenant wording remains because Tenantial's public positioning is currently Microsoft tenant governance. +- **Bounded extraction or follow-up path**: none + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +- Inventory-first: PASS - no inventory, snapshot, or backup runtime behavior is changed. +- Read/write separation: PASS - no product write/change behavior is introduced. +- Graph contract path: PASS - no Microsoft Graph calls or contract registry changes. +- Deterministic capabilities: PASS - no capability resolver or capability derivation changes. +- RBAC-UX: PASS - no `/admin`, `/system`, tenant context, workspace context, capability, policy, or authorization behavior changes. +- Workspace isolation: PASS - no workspace data or routes. +- RBAC-UX destructive-like actions: PASS - no destructive actions. +- RBAC-UX global search: PASS - no Filament resources or global search changes. +- Tenant isolation: PASS - no tenant data, tenant routes, or cross-tenant views. +- Run observability: PASS - no long-running, remote, queued, scheduled, or security-relevant DB action. +- OperationRun start UX: PASS - no OperationRun behavior. +- Ops-UX 3-surface feedback: PASS - no OperationRun notifications. +- Ops-UX lifecycle: PASS - no OperationRun status/outcome transitions. +- Ops-UX summary counts: PASS - no summary counts. +- Ops-UX guards: PASS - no Ops-UX guard changes. +- Ops-UX system runs: PASS - no system runs. +- Automation: PASS - no queued/scheduled operations. +- Data minimization: PASS - public copy and static preview content only; no secrets or tokens. +- Test governance (TEST-GOV-001): PASS - Browser/static website classification, explicit website smoke lane, no hidden Laravel/Filament/provider/database setup. +- Proportionality (PROP-001): PASS - local website hardening, no new product runtime structure. +- No premature abstraction (ABSTR-001): PASS - no new factories, registries, resolvers, strategies, interfaces, or frameworks. +- Persisted truth (PERSIST-001): PASS - no persisted product truth. +- Behavioral state (STATE-001): PASS - no status/state/reason family. +- UI semantics (UI-SEM-001): PASS - static presentation remains local and does not become product taxonomy. +- Shared pattern first (XCUT-001): PASS - no shared operator interaction family touched. +- Provider boundary (PROV-001): PASS - public Microsoft wording only; no shared provider/platform seam changes. +- V1 explicitness / few layers (V1-EXP-001, LAYER-001): PASS - direct website-local edits only. +- Spec discipline / bloat check (SPEC-DISC-001, BLOAT-001): PASS - no new enum, DTO, presenter, persisted entity, interface, registry, resolver, taxonomy, or cross-domain framework. +- Badge semantics (BADGE-001): PASS - no product badge semantics; preview labels must not become shared status truth. +- Filament-native UI (UI-FIL-001): PASS - no Filament UI. +- UI/UX surface taxonomy: PASS - no operator-facing surface. +- Decision-first operating model: PASS - public website copy is visitor-decision oriented; no operator surface. +- Audience-aware disclosure: PASS - no raw/support product detail surface. +- UI/UX inspect model: PASS - no list/detail operator surface. +- UI/UX action hierarchy: PASS - no Filament actions. +- UI/UX scope, truth, and naming: PASS - no admin scope labels; public copy must avoid implementation-first wording. +- UI/UX placeholder ban: PASS - no Filament action groups. +- UI naming: PASS - no operator-facing action labels, run titles, notifications, or audit prose. +- Operator surfaces: PASS - no `/admin` surface. +- Filament UI Action Surface Contract: PASS - no Filament Resource/RelationManager/Page. +- Filament UI UX-001: PASS - no Filament screen. +- Action-surface discipline: PASS - no operator action surface. +- UI review workflow: PASS - plan carries N/A decisions forward and keeps `apps/platform` as hard stop. + +**Initial Gate Result**: PASS - no constitution violations or unresolved clarifications. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: Browser/static website +- **Affected validation lanes**: website build, public smoke, manual browser review, whitespace/scope checks +- **Why this lane mix is the narrowest sufficient proof**: The feature changes public static pages, route/metadata exposure, CTAs, and browser rendering. Laravel/Pest/Filament lanes would not prove the changed behavior. +- **Narrowest proving command(s)**: `cd /Users/ahmeddarrazi/Documents/projects/wt-website && corepack pnpm build:website`; `cd /Users/ahmeddarrazi/Documents/projects/wt-website && WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`; `cd /Users/ahmeddarrazi/Documents/projects/wt-website && git diff --check`; `cd /Users/ahmeddarrazi/Documents/projects/wt-website && git status --short -- apps/platform` +- **Fixture / helper / factory / seed / context cost risks**: none - no database, provider, workspace, membership, session, queue, Sail, Laravel, Filament, or Livewire setup +- **Expensive defaults or shared helper growth introduced?**: no +- **Heavy-family additions, promotions, or visibility changes**: Browser review remains explicit in website smoke/manual review; no heavy-governance lane +- **Surface-class relief / special coverage rule**: N/A - public website +- **Closing validation and reviewer handoff**: Reviewers should rely on website build, Playwright smoke, manual light/dark/mobile/desktop checks, no horizontal overflow, no dead CTAs/placeholders, sitemap/robots inspection, and `apps/platform` untouched status. +- **Budget / baseline / trend follow-up**: none expected +- **Review-stop questions**: lane fit, browser breadth, hidden platform cost, CTA honesty, unsupported claim posture, route exposure +- **Escalation path**: document-in-feature +- **Active feature PR close-out entry**: Smoke Coverage +- **Why no dedicated follow-up spec is needed**: The browser/static review cost is local to this launch-readiness pass unless repeated website release gates become a recurring process problem. + +## Project Structure + +### Documentation (this feature) + +```text +/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/ +├── plan.md +├── research.md +├── data-model.md +├── quickstart.md +├── contracts/ +│ └── public-site-contract.md +└── tasks.md +``` + +### Source Code (repository root) + +```text +/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/ +├── astro.config.mjs +├── package.json +├── playwright.config.ts +├── process-html.mjs +├── public/ +├── src/ +│ ├── components/ +│ ├── content/ +│ ├── data_files/ +│ ├── images/ +│ ├── layouts/ +│ ├── pages/ +│ └── utils/ +└── tests/ + └── smoke/ +``` + +**Structure Decision**: Use the existing `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` Astro app from Spec 402. Do not create new base source folders and do not touch `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform`. + +## Complexity Tracking + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| None | N/A | N/A | + +## Proportionality Review + +- **Current operator problem**: Public launch review lacks a single bounded plan for route exposure, copy clarity, CTA honesty, metadata, claim safety, and browser checks after the foundation rebuild. +- **Existing structure is insufficient because**: Spec 402 established the Astro/ScrewFast substrate but did not complete a release-readiness pass. +- **Narrowest correct implementation**: Website-local copy, route, metadata, CTA, smoke, and launch-note work inside `apps/website`. +- **Ownership cost created**: Maintenance of website-local public copy, smoke expectations, and launch-readiness note. +- **Alternative intentionally rejected**: Rebuilding the foundation, adding backend contact/account workflows, or introducing product runtime abstractions. +- **Release truth**: Current-release public website readiness. + +## Phase 0 Research + +Research output is captured in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/research.md`. + +**Resolved clarifications**: + +- The feature uses the existing Astro 6 website and does not need a new framework decision. +- Public routes and redirect behavior are defined by `apps/website/src/pages`, `apps/website/astro.config.mjs`, and current Playwright smoke helpers. +- Starlight child docs routes are publicly emitted and must be included in launch review, not only the docs root. +- Current launch risks to resolve include route-specific social metadata, social image metadata consistency, `/imprint` placeholder details, static contact form wording, footer subscription wording, source `href="#"` placeholders, and the German-default/English-prefixed language route model. +- German is the default locale at unprefixed public routes; English is available under `/en/...`. +- There are no API endpoints or data entities to contract; route, CTA, metadata, and launch-check contracts are sufficient. +- Validation stays in website build and Playwright smoke lanes. + +## Phase 1 Design + +Design output is captured in: + +- `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/data-model.md` +- `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/contracts/public-site-contract.md` +- `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md` + +No REST, GraphQL, database, Laravel, Filament, Livewire, Microsoft Graph, queue, job, policy, RBAC, or product runtime contracts are introduced. + +## Post-Design Constitution Check + +**Post-Design Gate Result**: PASS + +- The Phase 1 design remains website-local. +- All clarification markers are resolved. +- No product persistence, abstractions, state families, provider/platform seams, OperationRun behavior, RBAC behavior, Filament behavior, or Graph calls are introduced. +- Browser/static validation remains explicit and bounded to `apps/website`. +- `apps/platform` remains out of scope and must be verified by `git status --short -- apps/platform`. diff --git a/specs/403-public-website-launch-readiness/quickstart.md b/specs/403-public-website-launch-readiness/quickstart.md new file mode 100644 index 00000000..b638270f --- /dev/null +++ b/specs/403-public-website-launch-readiness/quickstart.md @@ -0,0 +1,119 @@ +# Quickstart: Spec 403 Public Website Launch Readiness + +## Scope + +Work only in: + +```bash +/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website +/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness +``` + +Do not touch: + +```bash +/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform +``` + +## Install Dependencies If Needed + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && corepack pnpm install +``` + +## Run The Website Locally + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && WEBSITE_PORT=4321 corepack pnpm dev:website +``` + +Open: + +```text +http://127.0.0.1:4321 +``` + +## Build Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && corepack pnpm build:website +``` + +## Smoke Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke +``` + +## Static Git Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && git diff --check +cd /Users/ahmeddarrazi/Documents/projects/wt-website && git status --short -- apps/platform +``` + +The `apps/platform` command must print no output. + +## Manual Browser Review + +Review these routes in desktop and mobile widths, light and dark modes: + +- `/` +- `/platform` +- `/pricing` +- `/contact` +- `/trust` +- `/legal` +- `/privacy` +- `/terms` +- `/imprint` +- `/welcome-to-docs/` +- `/guides/intro/` +- `/guides/getting-started/` +- `/guides/first-project-checklist/` +- `/platform/evidence-review/` + +Check: + +- homepage explains Tenantial within the first viewport +- `/platform` explains the public product model, not the internal Laravel/Filament app +- all CTAs resolve to intentional routes or anchors +- no `href="#"` placeholders are visible or emitted +- contact form and footer copy do not imply backend submission or newsletter subscription workflows that do not exist +- `/imprint` contains no production replacement placeholder +- pricing remains contact/demo oriented +- trust and legal pages avoid unsupported certification, endorsement, uptime, recovery, compliance, or customer adoption claims +- static product previews are framed as illustrative/static/demo content +- keyboard users can reach navigation, CTAs, footer links, and visible controls +- visible focus states are present +- no body-level horizontal overflow occurs +- reduced-motion mode leaves content understandable + +## Sitemap And Robots Review + +After build, inspect: + +```text +/robots.txt +/sitemap-index.xml +/sitemap-0.xml +``` + +Confirm: + +- `robots.txt` points to `/sitemap-index.xml` +- canonical rendered routes are included +- redirect-only aliases are excluded +- metadata is Tenantial-specific and route-appropriate +- Open Graph/Twitter URLs and social image metadata are route-appropriate and point to existing assets + +## Launch Readiness Handoff + +Implementation must add or update a launch-readiness note documenting: + +- route exposure +- CTA assumptions +- trust/pricing claim posture +- sitemap behavior +- deployment expectations +- `apps/platform` untouched confirmation diff --git a/specs/403-public-website-launch-readiness/research.md b/specs/403-public-website-launch-readiness/research.md new file mode 100644 index 00000000..69a3d42c --- /dev/null +++ b/specs/403-public-website-launch-readiness/research.md @@ -0,0 +1,72 @@ +# Phase 0 Research: `apps/website` Public Website Launch Readiness + +## Decision: Keep the Spec 402 Astro/ScrewFast-derived foundation + +**Rationale**: The active website is a standalone Astro app at `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` with Starlight docs, sitemap generation, Tailwind v4, Preline, Lenis, and Playwright smoke tests already in place. Spec 403 is a launch-readiness pass, not another rebuild. + +**Alternatives considered**: + +- Rebuild the website foundation again: rejected by scope. +- Introduce a new design system or app shell: rejected because Spec 402 already selected the substrate. +- Move launch checks into the Laravel platform: rejected because `apps/platform` is out of scope. + +## Decision: Treat route, CTA, metadata, and sitemap behavior as the public contract + +**Rationale**: This feature has no API endpoints or product runtime entities. Public launch behavior is expressed through static pages, redirect aliases, generated metadata, navigation/footer links, docs exposure, `robots.txt`, and sitemap output. + +**Alternatives considered**: + +- Generate OpenAPI or GraphQL contracts: rejected because the public website exposes no API for this feature. +- Model route readiness as product runtime state: rejected because it would create unnecessary taxonomy/persistence. + +## Decision: Use conservative public copy and proof-safe claim checks + +**Rationale**: The spec assumes no verified customer logos, testimonials, certifications, Microsoft endorsements, uptime guarantees, compliance guarantees, recovery guarantees, self-serve billing, or account provisioning workflow. Copy and metadata must therefore stay contact/demo-oriented and avoid unsupported proof claims. + +**Alternatives considered**: + +- Use aspirational enterprise proof language: rejected because it creates public trust risk. +- Keep placeholder social proof from the substrate: rejected unless hidden, removed, or rewritten as neutral content. + +## Decision: Keep static product previews illustrative + +**Rationale**: Product-like preview content can help visitors understand Tenantial, but it must not imply live Microsoft tenant data, a running Graph integration on the website, or shared product status taxonomy. + +**Alternatives considered**: + +- Present previews as live tenant output: rejected because the public website has no authenticated tenant runtime. +- Remove all previews: rejected because static/demo previews are useful and allowed when clearly framed. + +## Decision: Validate with website build, Playwright smoke, manual browser review, and scope checks + +**Rationale**: The current package scripts support `corepack pnpm build:website` and `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`. Playwright is already configured for desktop and mobile projects and starts the Astro preview server. These lanes prove the public static website without pulling in Laravel runtime cost. + +**Alternatives considered**: + +- Run Laravel/Pest/Filament tests: rejected because this feature does not touch `apps/platform`. +- Rely only on static text review: rejected because launch readiness requires browser/mobile/light/dark/overflow/CTA validation. + +## Decision: Use a website-local launch-readiness note as the implementation handoff artifact + +**Rationale**: FR-034 requires a launch-readiness note documenting route exposure, CTA assumptions, trust/pricing claim posture, sitemap behavior, and deployment expectations. This is a website release artifact, not product persistence. + +**Alternatives considered**: + +- Store launch readiness in database tables: rejected as out of scope and unnecessary. +- Skip the note and rely on PR comments: rejected because reviewers need a durable handoff artifact. + +## Current Source Observations + +- Rendered smoke routes currently include `/`, `/platform`, `/pricing`, `/contact`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, and `/welcome-to-docs/`. +- Public Starlight docs routes also include `/guides/intro/`, `/guides/getting-started/`, `/guides/first-project-checklist/`, and `/platform/evidence-review/`; current smoke coverage only lists the docs root. +- Redirect smoke routes currently include `/product`, `/products`, `/services`, `/blog`, and `/insights`, all resolving to `/platform`. +- `/product`, `/products`, and `/services` currently use 301 redirects; `/blog` and `/insights` currently use 302 redirects. The launch note should confirm this split is intentional. +- `astro.config.mjs` filters redirect-only paths out of the generated sitemap. +- `robots.txt` points to the generated sitemap index through `import.meta.env.SITE`. +- `apps/website/src/utils/navigation.ts` still contains `socialLinks` with `#` placeholder targets; implementation should remove, hide, or replace public placeholders if they are emitted. +- Existing smoke helpers already check forbidden public terms and horizontal overflow; Spec 403 may need broader CTA, metadata, docs, sitemap, and keyboard coverage. +- Main-layout canonical URLs are page-specific, but current social metadata uses the site root for `og:url` and `twitter:url` on non-doc pages; launch implementation should make route metadata consistent. +- Current social image metadata has launch-review risk: main-layout image type is hardcoded as PNG while generated social output may be WebP, and Starlight docs metadata references `https://tenantial.com/social.webp` without an obvious matching public file. +- `/imprint` contains placeholder production details that must be replaced or made explicitly conservative before launch. +- The contact page includes a static form and `Prepare Email` submit button without a backend workflow; copy/behavior must not imply a guaranteed submission workflow. +- The footer includes an email field and `Subscribe` link to `/contact`; this should be reframed or removed to avoid implying a newsletter workflow. diff --git a/specs/403-public-website-launch-readiness/spec.md b/specs/403-public-website-launch-readiness/spec.md new file mode 100644 index 00000000..ae18bea4 --- /dev/null +++ b/specs/403-public-website-launch-readiness/spec.md @@ -0,0 +1,421 @@ +# Feature Specification: `apps/website` Public Website Launch Readiness + +**Feature Branch**: `feat/403-public-website-launch-readiness` +**Created**: 2026-05-20 +**Status**: Draft +**Input**: User description: "Make the current Tenantial public website release-ready after the Spec 402 ScrewFast-derived rebuild, scoped strictly to `apps/website`." + +## Spec Candidate Check + +- **Problem**: The public website now has the correct foundation, but it still needs a focused launch-readiness pass for message clarity, CTA integrity, route exposure, SEO metadata, trust/pricing claim safety, and browser review. +- **Today's failure**: A technically passing static build could still publish unclear positioning, unsupported claims, dead or misleading CTAs, route residue, or metadata that is not ready for public release. +- **User-visible improvement**: Visitors can quickly understand Tenantial in German by default or English under `/en`, follow honest contact/demo paths, review conservative trust and pricing language, and browse only intentional public routes. +- **Smallest enterprise-capable version**: Keep the Spec 402 ScrewFast-derived foundation, change only website-local copy, metadata, route exposure, CTA behavior, public assets, smoke checks, and launch-readiness notes. +- **Explicit non-goals**: No rebuild, no new design system, no backend contact workflow, no authentication, no billing, no Microsoft Graph calls, no Laravel/Filament/Livewire/admin behavior, and no `apps/platform` changes. +- **Permanent complexity imported**: Website-local launch copy, route/metadata hygiene, static preview framing, browser/static validation expectations, and a launch-readiness note. No new product runtime abstractions or persistence. +- **Why now**: Spec 402 intentionally solved the foundation; launch readiness is the next release gate before public exposure. +- **Why not local**: This is local to `apps/website`; the spec explicitly rejects broader platform/runtime changes. +- **Approval class**: Core Enterprise +- **Red flags triggered**: None. This is not a new foundation, framework, status taxonomy, persistence layer, or abstraction. +- **Score**: Nutzen 2 | Dringlichkeit 2 | Scope 2 | Komplexitaet 2 | Produktnaehe 2 | Wiederverwendung 1 | **Gesamt: 11/12** +- **Decision**: approve + +## Spec Scope Fields + +- **Scope**: N/A - public website only, strictly `apps/website` +- **Primary Routes**: `/`, `/platform`, `/pricing`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, `/contact`, docs routes, `/en/*` localized route mirrors, redirect aliases, `/robots.txt`, `/sitemap-index.xml` +- **Data Ownership**: No workspace-owned or tenant-owned product records impacted; no persistence changes. +- **RBAC**: N/A - no authenticated/admin/platform runtime behavior, no tenant/workspace membership behavior, no authorization changes. + +For canonical-view specs: + +- **Default filter behavior when tenant-context is active**: N/A - no canonical admin view or tenant context. +- **Explicit entitlement checks preventing cross-tenant leakage**: N/A - no tenant data or authenticated runtime. + +## Cross-Cutting / Shared Pattern Reuse + +- **Cross-cutting feature?**: no +- **Interaction class(es)**: N/A - public website navigation and CTAs only; no shared operator interaction family. +- **Systems touched**: `apps/website` static/public routes, metadata, navigation, footer, docs/legal/trust/pricing/contact surfaces, smoke checks. +- **Existing pattern(s) to extend**: Spec 402 ScrewFast-derived website structure. +- **Shared contract / presenter / builder / renderer to reuse**: N/A - no Laravel/Filament shared operator contract. +- **Why the existing shared path is sufficient or insufficient**: The website-local ScrewFast-derived structure is sufficient; this spec only hardens launch readiness. +- **Allowed deviation and why**: none +- **Consistency impact**: Public copy, CTAs, metadata, route exposure, sitemap, and browser review must stay aligned across the website. +- **Review focus**: Verify no `apps/platform` dependency or admin/runtime shared pattern is introduced. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no +- **Shared OperationRun UX contract/layer reused**: N/A +- **Delegated start/completion UX behaviors**: N/A +- **Local surface-owned behavior that remains**: N/A +- **Queued DB-notification policy**: N/A +- **Terminal notification path**: N/A +- **Exception required?**: none + +## Provider Boundary / Platform Core Check + +- **Shared provider/platform boundary touched?**: no +- **Boundary classification**: N/A +- **Seams affected**: N/A +- **Neutral platform terms preserved or introduced**: Public copy may mention Microsoft tenant configuration as product positioning only. +- **Provider-specific semantics retained and why**: Bounded public positioning for Microsoft tenant configuration; no runtime contracts, models, Graph calls, or platform-core taxonomy changes. +- **Why this does not deepen provider coupling accidentally**: The feature changes only static public website artifacts and does not alter product runtime boundaries. +- **Follow-up path**: none + +## UI / Surface Guardrail Impact + +`N/A - no admin/operator-facing product surface change. This spec changes public marketing website surfaces only.` + +| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | +|---|---|---|---|---|---|---| +| Public website launch-readiness pass | no | ScrewFast-derived Astro website | none | static pages, route metadata, navigation, footer | no | Public website only; no Laravel/Filament/Livewire surface | + +## Decision-First Surface Role + +N/A - no admin/operator-facing product surface is added or changed. + +## Audience-Aware Disclosure + +N/A - no authenticated detail/status surface is added or changed. Static product previews must remain clearly illustrative/static/demo content. + +## UI/UX Surface Classification + +N/A - no Filament/operator list, detail, queue, audit, config, or report surface is added or changed. + +## Operator Surface Contract + +N/A - no admin/operator-facing product page is added or materially refactored. + +## Proportionality Review + +- **New source of truth?**: no +- **New persisted entity/table/artifact?**: no product runtime persistence. A website-local launch-readiness note may be added as a release handoff artifact. +- **New abstraction?**: no +- **New enum/state/reason family?**: no +- **New cross-domain UI framework/taxonomy?**: no +- **Current operator problem**: Public launch review currently lacks a bounded, reviewable checklist for messaging, CTAs, claims, metadata, routes, and responsive behavior after the website foundation rebuild. +- **Existing structure is insufficient because**: Spec 402 established the substrate; it did not exhaustively certify public release quality or claim posture. +- **Narrowest correct implementation**: Website-local edits plus static/browser validation, no platform/runtime changes. +- **Ownership cost**: Maintaining website copy, metadata, smoke coverage, and the launch-readiness note. +- **Alternative intentionally rejected**: Rebuilding the website foundation again or introducing backend workflows for contact/demo/account behavior. +- **Release truth**: Current-release launch readiness for the public website. + +### Compatibility posture + +This feature assumes a pre-production environment for the public website. It introduces no product runtime compatibility, migration, legacy alias, or data-shape requirement. + +## Testing / Lane / Runtime Impact + +- **Test purpose / classification**: Browser/static website +- **Validation lane(s)**: website build, public smoke, browser review, whitespace/scope checks +- **Why this classification and these lanes are sufficient**: The feature changes static public website behavior, metadata, route exposure, CTAs, and responsive rendering rather than Laravel runtime behavior. +- **New or expanded test families**: May update website smoke checks if route/CTA/sitemap validation gaps are found. +- **Fixture / helper cost impact**: none expected; no provider, workspace, membership, database, session, Filament, Livewire, or Laravel setup. +- **Heavy-family visibility / justification**: Browser review is explicit and limited to public website pages. +- **Special surface test profile**: N/A - public website static/browser validation. +- **Standard-native relief or required special coverage**: N/A - no Filament surface. +- **Reviewer handoff**: Confirm lane fit, no hidden platform runtime cost, and exact validation commands listed in this spec. +- **Budget / baseline / trend impact**: none expected. +- **Escalation needed**: document-in-feature +- **Active feature PR close-out entry**: Smoke Coverage +- **Planned validation commands**: + - `corepack pnpm build:website` + - `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + - `git diff --check` + - `git status --short -- apps/platform` + +## Status + +Draft + +## Scope + +Runtime/source implementation for this spec is strictly local to `apps/website`. + +Spec Kit artifacts for planning, tasks, analysis, and launch handoff may live under `specs/403-public-website-launch-readiness/`. + +It builds on Spec 402, where `apps/website` was replaced with a Tenantial-branded public website derived from the pinned ScrewFast foundation. + +This spec does not rebuild the website foundation again. It does not replace the ScrewFast substrate. It focuses on launch-readiness of public copy, navigation, CTA flows, metadata, route exposure, trust wording, and browser review. + +## Important Scope Clarification + +In this feature, the word "platform" refers only to the public website route `/platform` inside `apps/website`. + +It does not refer to the Laravel/Filament application in `apps/platform`. + +`apps/platform` is completely out of scope: + +- do not modify it +- do not import from it +- do not run platform-specific migrations, tests, providers, resources, policies, Livewire, Filament, Blade, jobs, queues, database, tenant/workspace, RBAC, or Microsoft Graph code +- only verify through `git diff` or `git status` that it remains untouched + +Planning artifacts and the launch-readiness handoff note are allowed under `specs/403-public-website-launch-readiness/`; they must not introduce platform runtime behavior. + +## Goal + +Make the current Tenantial public website release-ready after the Spec 402 ScrewFast-derived rebuild. + +The website should clearly communicate Tenantial's public product positioning, expose only intentional routes, avoid unsupported public claims, provide coherent CTAs, and pass manual/browser/static launch checks. + +## Non-Goals + +This spec does not: + +- recreate `apps/website` +- replace the ScrewFast-derived foundation +- redesign the website from scratch +- remove unused vendored ScrewFast substrate files unless they are publicly emitted or create release risk +- add backend contact/demo workflow +- add authentication, login, customer portal, billing, subscription, or app access behavior +- introduce Microsoft Graph calls +- introduce platform/admin runtime behavior +- touch `apps/platform` +- change Filament, Livewire, Laravel, Blade, providers, policies, migrations, jobs, queues, database, tenant isolation, workspace behavior, RBAC, or AuditLog behavior + +## Problem + +Spec 402 established the correct website substrate and public route model. + +However, a foundation replacement is not the same as launch readiness. Before release, the public website needs a focused pass over: + +1. message clarity +2. CTA consistency +3. SEO metadata +4. route exposure +5. trust and pricing safety +6. docs/legal/contact completeness +7. mobile and keyboard usability +8. public claim discipline +9. deployment/reviewer handoff + +Without this pass, the website may technically build and smoke-test while still being unclear, under-polished, incorrectly localized, or risky from a public-claims perspective. + +## User Scenarios & Testing + +### User Story 1 - Understand Tenantial Quickly (Priority: P1) + +A first-time visitor opens the homepage and understands within one minute that Tenantial is an evidence-first governance product for Microsoft tenant configuration, with emphasis on backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews. + +**Why this priority**: The homepage is the main conversion and positioning surface. If the message is unclear, the website is not launch-ready even if the build passes. + +**Independent Test**: Open `/` in desktop and mobile widths and verify that the hero, section sequence, CTAs, and FAQ communicate the product category and core value without requiring platform knowledge. + +**Acceptance Scenarios**: + +1. **Given** a visitor opens `/`, **When** the hero loads, **Then** Tenantial's product category and primary value proposition are visible without scrolling. +2. **Given** a visitor scans the homepage, **When** they move through sections, **Then** they can identify at least three core capabilities. +3. **Given** a visitor reaches the final CTA, **When** they decide what to do next, **Then** the CTA path is clear and does not imply unsupported login, billing, or backend workflow behavior. + +--- + +### User Story 2 - Evaluate The Public Product Route (Priority: P1) + +A buyer opens `/platform` and understands Tenantial's public product model without confusing it with the internal Laravel/Filament app. + +**Why this priority**: The `/platform` page is the primary product explanation route. It must explain the product clearly while staying within public website scope. + +**Independent Test**: Open `/platform` and verify that the page explains the public product model, not internal implementation details. + +**Acceptance Scenarios**: + +1. **Given** a visitor opens `/platform`, **When** the page loads, **Then** the page explains backup, restore, drift detection, findings, evidence, audit trail, exceptions, and reviews. +2. **Given** the page uses product-like previews, **When** they are reviewed, **Then** they are clearly static/demo previews and do not imply live tenant data. +3. **Given** the page references Microsoft tenants, **When** copy is reviewed, **Then** the language remains public positioning and does not imply a runtime Microsoft Graph integration on the website. + +--- + +### User Story 3 - Follow A Safe CTA Path (Priority: P2) + +A visitor who wants to evaluate Tenantial can follow a clear contact/demo path without encountering fake forms, dead links, fake account access, or unsupported self-serve purchase claims. + +**Why this priority**: CTA integrity is essential for trust. Public CTAs must match what the website can actually support. + +**Independent Test**: Click primary and secondary CTAs from homepage, `/platform`, `/pricing`, `/trust`, docs, and footer. Verify that each CTA leads to an intentional route or anchor. + +**Acceptance Scenarios**: + +1. **Given** a visitor clicks a demo/contact CTA, **When** the link resolves, **Then** it leads to `/contact` or an intentional page section. +2. **Given** a visitor sees pricing, **When** they review package language, **Then** the page avoids unsupported billing, checkout, plan entitlement, or self-serve subscription claims. +3. **Given** no backend form workflow exists, **When** a contact surface renders, **Then** it does not imply a guaranteed workflow, automated provisioning, or account creation. + +--- + +### User Story 4 - Trust The Claims (Priority: P2) + +A security-conscious buyer reads trust, legal, pricing, and product pages and sees conservative, proof-safe wording. + +**Why this priority**: Tenantial is an enterprise governance product. Unsupported claims about compliance, certifications, Microsoft endorsement, uptime, recovery guarantees, or customer adoption would be risky. + +**Independent Test**: Review visible copy and metadata on `/`, `/platform`, `/pricing`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, and docs routes. + +**Acceptance Scenarios**: + +1. **Given** no verified certifications are supplied, **When** trust pages render, **Then** they do not claim SOC 2, ISO, Microsoft endorsement, guaranteed compliance, guaranteed uptime, or guaranteed recovery. +2. **Given** no customer proof is supplied, **When** social-proof areas render, **Then** they do not show fake customer logos, fake testimonials, or "trusted by" claims. +3. **Given** static product previews are shown, **When** reviewed, **Then** they are framed as illustrative/static/demo content rather than live tenant output. + +--- + +### User Story 5 - Review Launch Metadata And Route Exposure (Priority: P3) + +A site owner can review the public website output and confirm routes, metadata, sitemap, robots, and redirects are intentional. + +**Why this priority**: Launch readiness includes not only visual pages, but also search engine exposure, route hygiene, and public metadata. + +**Independent Test**: Run static route and metadata checks against the built website output. + +**Acceptance Scenarios**: + +1. **Given** the website builds, **When** sitemap output is inspected, **Then** only canonical renderable routes are included. +2. **Given** redirect aliases exist, **When** sitemap output is inspected, **Then** redirect-only routes are excluded. +3. **Given** `robots.txt` is opened, **When** reviewed, **Then** it points to the intended sitemap index. +4. **Given** public pages are opened, **When** metadata is reviewed, **Then** titles and descriptions are Tenantial-specific and route-appropriate. + +### Edge Cases + +- If a route is retained from ScrewFast but not ready for Tenantial content, it must be hidden from navigation, redirected intentionally, or given restrained placeholder content. +- If docs pages are exposed, they must not imply product behavior that does not exist yet. +- If pricing is not final, pricing must remain conservative and contact/demo-oriented. +- If trust/legal wording is not legally reviewed, pages must use restrained placeholder language. +- If a CTA cannot be backed by a real workflow, it must link to `/contact` or be removed. +- If product previews include status-like values, they must not become shared product taxonomy or imply runtime truth. +- If visual assets are retained from the vendored ScrewFast source but unused, they are not release blockers unless they are imported, emitted, linked, or visible. +- If localized or editorial routes are not ready, they must not appear in primary navigation. +- If localization is enabled, German must remain the unprefixed default and English must use `/en/...` route prefixes. +- If reduced-motion is enabled, content must remain understandable without animation. +- If JavaScript fails, primary content and links must remain usable where reasonably possible for a static marketing site. + +## Assumptions + +- Spec 402 is already implemented and validated. +- `apps/website` is now ScrewFast-derived and Tenantial-branded. +- `apps/platform` is untouched and remains out of scope. +- No verified customer logos, testimonials, certifications, Microsoft endorsements, uptime guarantees, compliance guarantees, or recovery guarantees have been supplied. +- No self-serve billing or account provisioning workflow is available from the public website. +- The public domain is `tenantial.com`. +- The generated sitemap entrypoint is `/sitemap-index.xml`. +- German is the default public website locale at unprefixed routes. +- English is an additional public locale under `/en/...`. + +## Requirements + +### Scope And Architecture + +- **FR-001**: Runtime/source implementation MUST remain strictly scoped to `apps/website`; Spec Kit artifacts and launch handoff notes MAY live under `specs/403-public-website-launch-readiness/`. +- **FR-002**: The implementation MUST NOT touch `apps/platform`. +- **FR-003**: The implementation MUST NOT replace or rebuild the ScrewFast-derived foundation again. +- **FR-004**: Changes SHOULD focus on copy, metadata, route exposure, CTA behavior, public assets, and launch documentation. +- **FR-005**: Unused vendored ScrewFast substrate files MAY remain if they are not publicly emitted or referenced. + +### Public Positioning + +- **FR-006**: The homepage MUST clearly position Tenantial as evidence-first governance for Microsoft tenant configuration. +- **FR-007**: The homepage MUST communicate backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews either directly or through linked product sections. +- **FR-008**: The `/platform` route MUST explain the public product model without referencing internal Laravel/Filament implementation details. +- **FR-009**: Public copy MUST avoid implementation-first labels where user-facing product language is clearer. + +### CTA And Navigation + +- **FR-010**: All public CTAs MUST resolve to intentional routes or anchors. +- **FR-011**: Public CTAs MUST NOT imply login, account creation, billing, checkout, backend submission, or automated onboarding unless such workflow exists. +- **FR-012**: Primary navigation MUST expose only intentional, release-ready public routes. +- **FR-013**: Footer navigation MUST expose legal, trust, contact, and core product routes intentionally. +- **FR-014**: Redirect aliases MUST remain intentional. Aliases that render HTML MUST be noindex; aliases implemented as pure redirects MUST be documented by redirect status/target and excluded from sitemap output. +- **FR-014a**: Language switching MUST keep German as the unprefixed default locale and English as prefixed `/en/...` routes. +- **FR-014b**: Navigation, footer links, CTAs, metadata, and docs links MUST resolve to the current locale where a localized route exists. + +### Trust, Pricing, And Claims + +- **FR-015**: Trust copy MUST avoid unsupported SOC 2, ISO, Microsoft endorsement, uptime, recovery, compliance, or customer adoption claims. +- **FR-016**: Pricing copy MUST avoid unsupported plan entitlement, self-serve subscription, billing, checkout, or purchase claims. +- **FR-017**: Static product previews MUST be framed as illustrative/static/demo content and MUST NOT imply live tenant data. +- **FR-018**: Public pages MUST NOT publish fake customer logos, fake testimonials, or fake "trusted by" claims. + +### Metadata And SEO + +- **FR-019**: Each canonical public route MUST have Tenantial-specific title and description metadata. +- **FR-020**: The generated sitemap MUST include only intentional canonical public routes. +- **FR-021**: Redirect-only aliases MUST be excluded from sitemap output. +- **FR-022**: `robots.txt` MUST point to `/sitemap-index.xml`. +- **FR-023**: Public metadata MUST NOT contain ScrewFast, construction/hardware, template, TenantAtlas, TenantPilot, or TenantCTRL residue. +- **FR-023a**: Public metadata MUST expose correct locale signals for German default pages, English `/en/...` pages, and alternate language URLs. + +### Accessibility And Responsive Review + +- **FR-024**: Public pages MUST remain usable on representative mobile and desktop viewport sizes. +- **FR-025**: Body-level horizontal overflow MUST NOT occur on validated public routes. +- **FR-026**: Keyboard users MUST be able to reach primary navigation, CTAs, footer links, and visible form controls. +- **FR-027**: Focus-visible states MUST be visible for interactive elements. +- **FR-028**: Reduced-motion preferences MUST be respected for retained animations. +- **FR-029**: Status or preview meaning MUST NOT rely on color alone. + +### Validation + +- **FR-030**: Build validation MUST pass with `corepack pnpm build:website`. +- **FR-031**: Public smoke validation MUST pass with `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`. +- **FR-032**: Whitespace/conflict validation MUST pass with `git diff --check`. +- **FR-033**: Scope validation MUST confirm `git status --short -- apps/platform` is empty. +- **FR-034**: A launch-readiness note MUST document route exposure, CTA assumptions, trust/pricing claim posture, sitemap behavior, and deployment expectations. + +## Constitution Alignment + +- This feature introduces no Microsoft Graph calls. +- This feature introduces no product write/change behavior. +- This feature introduces no queued or scheduled work. +- This feature introduces no OperationRun behavior. +- This feature introduces no AuditLog behavior. +- This feature introduces no tenant/workspace isolation behavior. +- This feature introduces no RBAC behavior. +- This feature introduces no Laravel/Filament/Livewire/admin behavior. +- This feature introduces no database or persisted product truth. +- This feature introduces no shared product status taxonomy. +- This feature introduces no provider/platform seam. +- This feature is limited to public website artifacts and public website validation. +- Livewire v4.0+ compliance: N/A for implementation because no Livewire code is in scope; the platform remains on Livewire v4 per application info. +- Provider registration location: N/A for implementation because no Laravel providers are added or changed; Laravel 11+/12 provider registration remains outside scope. +- Globally searchable resources: N/A because no Filament resources are added or changed. +- Destructive actions: none. +- Asset strategy: website-local Astro/ScrewFast assets only; no Filament assets are registered, so `filament:assets` is not part of this feature's deploy path. +- Testing plan: website build, public smoke validation, browser review, `git diff --check`, and `apps/platform` untouched scope check. + +## Success Criteria + +- **SC-001**: 100% of canonical public routes render successfully during smoke validation. +- **SC-002**: 100% of redirect-only aliases redirect intentionally and are excluded from sitemap output. +- **SC-003**: 0 public `href="#"` placeholders remain in source or emitted HTML. +- **SC-004**: 0 forbidden public residue terms appear in visible copy or metadata across validated public routes. +- **SC-005**: 100% of public CTAs resolve to intentional routes or anchors. +- **SC-006**: 100% of trust, pricing, and preview claims are conservative or backed by supplied proof. +- **SC-007**: 100% of validated mobile and desktop review sizes show no body-level horizontal overflow. +- **SC-008**: Primary navigation, CTAs, footer links, and visible controls are reachable by keyboard. +- **SC-009**: `corepack pnpm build:website` passes. +- **SC-010**: `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` passes. +- **SC-011**: `git diff --check` passes. +- **SC-012**: `git status --short -- apps/platform` returns empty. + +## Reviewer Notes + +Reviewers should focus on launch quality, not on replacing the website foundation again. + +The main review questions are: + +1. Does the homepage explain Tenantial clearly? +2. Does `/platform` explain the public product model without touching or implying `apps/platform` implementation details? +3. Are CTAs honest and usable? +4. Are trust and pricing claims conservative? +5. Are sitemap, robots, metadata, and route exposure intentional? +6. Is the website still ScrewFast-derived rather than a new custom design system? +7. Is `apps/platform` untouched? + +## Suggested Validation Commands + +```bash +corepack pnpm build:website +WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke +git diff --check +git status --short -- apps/platform +``` diff --git a/specs/403-public-website-launch-readiness/tasks.md b/specs/403-public-website-launch-readiness/tasks.md new file mode 100644 index 00000000..76a514b8 --- /dev/null +++ b/specs/403-public-website-launch-readiness/tasks.md @@ -0,0 +1,255 @@ +# Tasks: `apps/website` Public Website Launch Readiness + +**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/` +**Prerequisites**: [plan.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/plan.md), [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/spec.md), [research.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/research.md), [data-model.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/data-model.md), [public-site-contract.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/contracts/public-site-contract.md), [quickstart.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md) + +**Tests**: Browser/static website validation is required by Spec 403. Use Playwright smoke tests and static source/build checks only; do not add Pest, Laravel, Filament, Livewire, database, Sail, Microsoft Graph, queue, job, policy, RBAC, or `apps/platform` test coverage. + +## Phase 1: Setup + +**Purpose**: Establish the website-only scope and current launch-readiness baseline before editing. + +- [X] T001 Inspect current git scope and confirm `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` is untouched with `git status --short -- apps/platform` from `/Users/ahmeddarrazi/Documents/projects/wt-website` +- [X] T002 [P] Review current public route sources in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages` +- [X] T003 [P] Review current navigation and footer link data in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/utils/navigation.ts` +- [X] T004 [P] Review current metadata components in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/Meta.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/starlight/Head.astro` +- [X] T005 [P] Review current Playwright smoke coverage in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` + +--- + +## Phase 2: Foundational + +**Purpose**: Add shared launch-readiness validation scaffolding required before user-story work. + +**Critical**: Complete this phase before story-specific implementation. + +- [X] T006 Update canonical rendered route and docs route lists in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` to include `/`, `/platform`, `/pricing`, `/contact`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, `/welcome-to-docs/`, `/guides/intro/`, `/guides/getting-started/`, `/guides/first-project-checklist/`, and `/platform/evidence-review/` +- [X] T007 Update redirect alias expectations in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` for `/product`, `/products`, `/services`, `/blog`, and `/insights` +- [X] T008 Add shared forbidden metadata and public claim assertion helpers in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T009 Add shared public link and anchor validation helpers in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T010 Add shared sitemap parsing helpers in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T011 Confirm no foundational task touches `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` by running `git status --short -- apps/platform` from `/Users/ahmeddarrazi/Documents/projects/wt-website` + +**Checkpoint**: Public route, link, metadata, and sitemap smoke helpers are ready for user-story implementation. + +--- + +## Phase 3: User Story 1 - Understand Tenantial Quickly (Priority: P1) MVP + +**Goal**: A first-time visitor understands from `/` that Tenantial is evidence-first governance for Microsoft tenant configuration, including backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews. + +**Independent Test**: Open `/` in desktop and mobile widths and verify that the hero, section sequence, CTAs, and FAQ communicate the product category and core value without requiring platform knowledge. + +### Tests For User Story 1 + +- [X] T012 [P] [US1] Add homepage smoke assertions for first-viewport positioning and core capability terms in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T013 [P] [US1] Add homepage mobile overflow and heading visibility assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` + +### Implementation For User Story 1 + +- [X] T014 [US1] Refine homepage hero copy and primary CTA labels in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T015 [US1] Refine homepage capability section copy for backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T016 [P] [US1] Refine homepage FAQ copy to remove implementation-first wording in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/faqs.json` +- [X] T017 [US1] Verify homepage metadata title and description are Tenantial-specific in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T018 [US1] Run fresh-build US1 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 1 is independently shippable as the MVP. + +--- + +## Phase 4: User Story 2 - Evaluate The Public Product Route (Priority: P1) + +**Goal**: A buyer can open `/platform` and understand Tenantial's public product model without confusing it with the internal Laravel/Filament app. + +**Independent Test**: Open `/platform` and verify that the page explains backup, restore, drift detection, findings, evidence, audit trail, exceptions, and reviews, and that previews are static/demo content. + +### Tests For User Story 2 + +- [X] T019 [P] [US2] Add `/platform` product model smoke assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T020 [US2] Add static/demo preview framing assertions for `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` + +### Implementation For User Story 2 + +- [X] T021 [US2] Refine `/platform` product model copy for backup, restore, drift detection, findings, evidence, audit trail, exceptions, and reviews in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T022 [US2] Add or tighten static/demo preview framing text in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T023 [US2] Remove internal Laravel, Filament, Livewire, provider, Graph runtime, or admin implementation wording from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T024 [P] [US2] Review static product preview component copy for color-independent meaning in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/blocks/ReviewComponent.astro` +- [X] T025 [US2] Verify `/platform` metadata title and description are public-product focused in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T026 [US2] Run fresh-build US2 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 2 is independently shippable once `/platform` communicates the public product model safely. + +--- + +## Phase 5: User Story 3 - Follow A Safe CTA Path (Priority: P2) + +**Goal**: A visitor can follow clear demo/contact paths without fake forms, dead links, fake account access, unsupported billing, or unsupported self-serve purchase claims. + +**Independent Test**: Click primary and secondary CTAs from homepage, `/platform`, `/pricing`, `/trust`, docs, and footer; each CTA resolves to an intentional route or anchor. + +### Tests For User Story 3 + +- [X] T027 [P] [US3] Add public CTA crawl assertions for rendered routes in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T028 [US3] Add emitted `href="#"` placeholder assertions for rendered HTML in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T029 [P] [US3] Add keyboard reachability smoke assertions for primary navigation, CTAs, footer links, and visible form controls in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` + +### Implementation For User Story 3 + +- [X] T030 [US3] Remove or replace placeholder social link values in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/utils/navigation.ts` +- [X] T031 [US3] Reframe footer email and `Subscribe` CTA so it does not imply a newsletter workflow in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/navbar&footer/FooterSection.astro` +- [X] T032 [US3] Reframe static contact form copy and controls so they do not imply backend submission or guaranteed provisioning in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/contact.astro` +- [X] T033 [P] [US3] Verify homepage CTA targets route only to `/contact` or `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T034 [P] [US3] Verify platform CTA targets route only to intentional public routes or anchors in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T035 [P] [US3] Verify pricing CTAs remain contact/demo-oriented in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/pricing.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/pricing.json` +- [X] T036 [P] [US3] Verify trust and docs CTA targets are intentional in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/trust.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/docs` +- [X] T037 [US3] Run fresh-build US3 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 3 is independently shippable once CTAs are honest and resolvable. + +--- + +## Phase 6: User Story 4 - Trust The Claims (Priority: P2) + +**Goal**: A security-conscious buyer sees conservative, proof-safe wording across trust, legal, pricing, product, and docs pages. + +**Independent Test**: Review visible copy and metadata on `/`, `/platform`, `/pricing`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, and docs routes; no unsupported proof claims or fake social proof appear. + +### Tests For User Story 4 + +- [X] T038 [P] [US4] Expand forbidden public claim assertions for visible copy and metadata in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T039 [P] [US4] Add trust, pricing, legal, and docs claim-safety smoke assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` + +### Implementation For User Story 4 + +- [X] T040 [US4] Rewrite trust page copy to avoid unsupported certification, endorsement, uptime, recovery, compliance, and customer adoption claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/trust.astro` +- [X] T041 [P] [US4] Rewrite pricing page and pricing data copy to avoid self-serve subscription, billing, checkout, and entitlement claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/pricing.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/pricing.json` +- [X] T042 [P] [US4] Replace production placeholder wording on `/imprint` with conservative placeholder-safe public language in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/imprint.astro` +- [X] T043 [P] [US4] Review legal, privacy, and terms pages for unsupported claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/legal.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/privacy.astro`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/terms.astro` +- [X] T044 [P] [US4] Review docs copy for unsupported runtime, compliance, recovery, or live-tenant claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/docs` +- [X] T045 [P] [US4] Remove or neutralize any fake testimonial, customer logo, or social proof imports in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/testimonials` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/landing/ClientsSection.astro` +- [X] T046 [US4] Run fresh-build US4 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 4 is independently shippable once public claims are conservative or backed by supplied proof. + +--- + +## Phase 7: User Story 5 - Review Launch Metadata And Route Exposure (Priority: P3) + +**Goal**: A site owner can confirm route exposure, metadata, sitemap, robots, and redirects are intentional after build. + +**Independent Test**: Run static route and metadata checks against the built website output. + +### Tests For User Story 5 + +- [X] T047 [US5] Add route-specific title, description, canonical, Open Graph, and Twitter metadata assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T048 [US5] Add sitemap allowlist assertions for all intentional canonical routes and exclusion assertions for redirect-only aliases in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T049 [US5] Add exact `/sitemap-index.xml` robots assertion in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T050 [US5] Add redirect status and noindex-if-rendered smoke assertions for `/product`, `/products`, `/services`, `/blog`, and `/insights` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` + +### Implementation For User Story 5 + +- [X] T051 [US5] Fix route-specific social URL metadata in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/Meta.astro` +- [X] T052 [US5] Fix social image URL and MIME/type metadata consistency in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/Meta.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/starlight/Head.astro` +- [X] T053 [P] [US5] Confirm sitemap redirect filtering still covers all redirect aliases in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/astro.config.mjs` +- [X] T054 [P] [US5] Confirm `robots.txt` points to `/sitemap-index.xml` for `https://tenantial.com` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/robots.txt.ts` +- [X] T055 [P] [US5] Confirm redirect-only pages use documented redirect status/target behavior or noindex metadata if they render HTML in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/product.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/products/index.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/services.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/blog/index.astro`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/insights/index.astro` +- [X] T056 [US5] Run build validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` +- [X] T057 [US5] Run public smoke validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 5 is independently shippable once route exposure and metadata output are intentional. + +--- + +## Final Phase: Polish And Cross-Cutting Concerns + +**Purpose**: Complete launch handoff, final browser review, and scope validation. + +- [X] T058 [P] Create launch-readiness note documenting route exposure, CTA assumptions, trust/pricing claim posture, sitemap behavior, deployment expectations, and `apps/platform` untouched confirmation in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/launch-readiness.md` +- [X] T059 [P] Perform manual desktop browser review for light and dark modes on public routes from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md` +- [X] T060 [P] Perform manual mobile browser review for light and dark modes on public routes from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md` +- [X] T061 [P] Verify keyboard focus reachability and visible focus states across primary navigation, CTAs, footer links, and visible controls using `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` +- [X] T062 [P] Verify reduced-motion readability and color-independent preview meaning on rendered pages in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages` +- [X] T063 Run final build validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` +- [X] T064 Run final public smoke validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` +- [X] T065 Run final whitespace validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `git diff --check` +- [X] T066 Run final scope validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `git status --short -- apps/platform` +- [X] T067 Run source-wide placeholder validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `! rg "href=\"#\"|href=\\{\"#\"\\}|href=\\{'#'\\}" apps/website/src` +- [X] T068 Add no-JavaScript primary content and link usability smoke coverage in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` +- [X] T069 Review homepage and platform copy for trust/pricing/preview claim safety in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T070 Add German-default and English-prefixed localization helpers, copy, pages, docs, redirects, and route metadata in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src` +- [X] T071 Add localized language picker behavior to primary navigation and verify `Docs` remains visible in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/navbar&footer/Navbar.astro` +- [X] T072 Expand Playwright smoke coverage for German default routes, English `/en/...` routes, localized redirects, metadata, sitemap, and language switching in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke` +- [X] T073 Update Spec 403 artifacts to document German as the default locale and English as the additional `/en/...` locale in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness` + +--- + +## Dependencies + +**Phase Dependencies** + +Setup before Foundational. Foundational before any user story. User Stories 1 and 2 are both P1 and can proceed after Foundational. User Stories 3 and 4 can proceed after the relevant P1 page and CTA surfaces exist. User Story 5 should run after route, copy, CTA, and trust/pricing decisions settle. + +**User Story Dependencies** + +- **US1**: Depends on Phase 2 only. +- **US2**: Depends on Phase 2 only. +- **US3**: Depends on Phase 2 and benefits from US1/US2 CTA copy decisions. +- **US4**: Depends on Phase 2 and can run parallel with US3 after P1 copy direction is stable. +- **US5**: Depends on Phase 2 and final route/metadata decisions from US1-US4. + +**MVP Scope** + +Complete Phase 1, Phase 2, and Phase 3 (US1) for the smallest independently reviewable increment. + +--- + +## Parallel Execution Examples + +**After Phase 2** + +```text +US1 independent files: +- T016 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/faqs.json +- T013 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts + +US2 independent file: +- T024 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/blocks/ReviewComponent.astro +``` + +**CTA And Claim Safety** + +```text +US3 CTA work: +- T031 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/navbar&footer/FooterSection.astro +- T032 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/contact.astro +- T035 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/pricing.astro and /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/pricing.json + +US4 claim work: +- T040 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/trust.astro +- T042 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/imprint.astro +- T044 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/docs +``` + +**Metadata And Route Exposure** + +```text +US5 output work: +- T053 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/astro.config.mjs +- T054 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/robots.txt.ts +- T055 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/product.astro and related redirect page files +``` + +--- + +## Implementation Strategy + +1. Finish the setup and foundational smoke-helper tasks first. +2. Ship the MVP with US1 homepage positioning and smoke assertions. +3. Add US2 `/platform` product clarity before broad CTA review so link labels and product terms are stable. +4. Add US3 CTA integrity and US4 claim safety in parallel where files do not overlap. +5. Finish with US5 metadata, route exposure, sitemap, robots, and final build/smoke validation. +6. Complete the launch-readiness note and final `apps/platform` untouched proof before review. + +## Test Governance Notes + +The declared test purpose is Browser/static website. The affected validation lanes are website build, Playwright public smoke, manual browser review, whitespace validation, and `apps/platform` scope validation. No Pest, Laravel, Filament, Livewire, database, provider, workspace, membership, session, queue, Sail, or heavy-governance setup is introduced. -- 2.45.2