merge: session work for public website launch readiness
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 6m28s

This commit is contained in:
Ahmed Darrazi 2026-05-21 23:32:22 +02:00
commit 50bf170975
80 changed files with 3841 additions and 680 deletions

View File

@ -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)

View File

@ -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(),
],

View File

@ -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 ?? ''}`;
---
<span
{...Astro.props}
{...attrs}
class:list={[
'inline-flex items-center gap-2 text-neutral-800 dark:text-neutral-100',
Astro.props.class,
'tenantial-wordmark inline-block shrink-0 align-middle text-neutral-700 transition-colors duration-300 dark:text-white',
className,
]}
style={logoStyle}
role="img"
aria-label={ariaLabel}
>
<span
class="inline-grid size-9 place-items-center rounded-xl bg-neutral-800 text-sm font-black text-yellow-400 dark:bg-yellow-400 dark:text-neutral-900"
>T</span
>
<span class="text-xl font-bold tracking-tight">Tenantial</span>
<span class="tenantial-wordmark__shape" aria-hidden="true"></span>
</span>
<style>
.tenantial-wordmark {
color: #4b4b4f;
}
:global(.dark) .tenantial-wordmark,
:global([data-theme='dark']) .tenantial-wordmark {
color: #ffffff;
}
.tenantial-wordmark__shape {
display: block;
width: 100%;
aspect-ratio: 898 / 167;
background: currentColor;
mask-image: var(--tenantial-logo-mask);
mask-mode: alpha;
mask-position: center;
mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-image: var(--tenantial-logo-mask);
-webkit-mask-position: center;
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: contain;
}
</style>

View File

@ -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({
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="canonical" href={canonical} />
{
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 <link rel="alternate" hreflang={lang} href={href} />;
})
}
<link
rel="alternate"
hreflang="x-default"
href={new URL(localizedPath(cleanPath, 'de'), Astro.site || Astro.url.origin)
.href}
/>
{/* Facebook Meta Tags */}
<meta
property="og:locale"
content="en_US"
/>
<meta property="og:url" content={siteURL} />
<meta property="og:locale" content={localeOg[locale]} />
<meta property="og:url" content={canonical} />
<meta property="og:type" content="website" />
<meta property="og:title" content={ogTitle} />
<meta property="og:site_name" content={SITE.title} />
@ -117,8 +123,8 @@ const appleTouchIcon = await getImage({
{/* Twitter Meta Tags */}
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content={siteURL} />
<meta property="twitter:url" content={siteURL} />
<meta property="twitter:domain" content={twitterDomain} />
<meta property="twitter:url" content={canonical} />
<meta name="twitter:title" content={ogTitle} />
<meta name="twitter:description" content={ogDescription} />
<meta name="twitter:image" content={socialImage} />

View File

@ -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);
---
<MainLayout
lang={locale}
title={copy.pageTitle}
customDescription={copy.metaDescription}
customOgTitle={copy.pageTitle}
structuredData={{
'@context': 'https://schema.org',
'@type': 'WebPage',
'@id': `${SITE.url}${canonicalPath}`,
url: `${SITE.url}${canonicalPath}`,
name: copy.pageTitle,
description: copy.metaDescription,
isPartOf: {
'@type': 'WebSite',
url: SITE.url,
name: SITE.title,
description: siteDescription,
},
inLanguage: localeHtmlLang[locale],
}}
>
<ContactSection locale={locale} />
</MainLayout>

View File

@ -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],
}));
---
<MainLayout
lang={locale}
title={copy.pageTitle}
customDescription={copy.metaDescription}
customOgTitle={copy.pageTitle}
>
<HeroSection
title={copy.heroTitle}
subTitle={copy.heroSubtitle}
primaryBtn={copy.primaryCta}
primaryBtnURL={localizeHref('/contact', locale)}
secondaryBtn={copy.secondaryCta}
secondaryBtnURL={localizeHref('/platform', locale)}
withReview={false}
src={heroImage}
alt={copy.heroAlt}
/>
<FeaturesGeneral
title={copy.featureTitle}
subTitle={copy.featureSubtitle}
src={featureImage}
alt={copy.featureAlt}
features={featuresByLocale[locale]}
/>
<FeaturesNavs title={copy.workflowTitle} tabs={tabs} />
<PricingSection pricing={pricingByLocale[locale]} locale={locale} />
<FAQ title={copy.faqTitle} faqs={faqsByLocale[locale]} />
</MainLayout>

View File

@ -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;
---
<MainLayout
lang={locale}
title={copy.pageTitle}
customDescription={copy.metaDescription}
customOgTitle={copy.pageTitle}
>
<MainSection title={copy.heading} subTitle={copy.subtitle} />
<section class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8">
<div class="flex flex-wrap gap-3">
<PrimaryCTA title={copy.privacy} url={localizeHref('/privacy', locale)} />
<SecondaryCTA title={copy.terms} url={localizeHref('/terms', locale)} />
<SecondaryCTA
title={copy.imprint}
url={localizeHref('/imprint', locale)}
/>
</div>
</section>
</MainLayout>

View File

@ -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);
---
<MainLayout
lang={locale}
title={copy.pageTitle}
customDescription={copy.metaDescription}
customOgTitle={copy.pageTitle}
structuredData={{
'@context': 'https://schema.org',
'@type': 'WebPage',
'@id': `${SITE.url}${canonicalPath}`,
url: `${SITE.url}${canonicalPath}`,
name: copy.pageTitle,
description: copy.metaDescription,
isPartOf: {
'@type': 'WebSite',
url: SITE.url,
name: SITE.title,
description: siteDescription,
},
inLanguage: localeHtmlLang[locale],
}}
>
<MainSection
title={copy.heading}
subTitle={copy.subtitle}
btnExists={true}
btnTitle={siteCopy[locale].auth.walkthrough}
btnURL={localizeHref('/contact', locale)}
/>
<RightSection
title={copy.backupTitle}
subTitle={copy.backupSubtitle}
single={false}
imgOne={dashboard}
imgOneAlt={copy.dashboardAlt}
imgTwo={evidenceImage}
imgTwoAlt={copy.evidenceAlt}
/>
<LeftSection
title={copy.driftTitle}
subTitle={copy.driftSubtitle}
img={reviewImage}
imgAlt={copy.driftAlt}
btnExists={true}
btnTitle={copy.trustCta}
btnURL={localizeHref('/trust', locale)}
/>
<RightSection
title={copy.restoreTitle}
subTitle={copy.restoreSubtitle}
single={true}
imgOne={restoreImage}
imgOneAlt={copy.restoreAlt}
btnExists={true}
btnTitle={copy.rolloutCta}
btnURL={localizeHref('/contact', locale)}
/>
<FeaturesStats
title={copy.boundaryTitle}
subTitle={copy.boundarySubtitle}
mainStatTitle={copy.mainStatTitle}
mainStatSubTitle={copy.mainStatSubTitle}
stats={copy.stats}
/>
</MainLayout>

View File

@ -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;
---
<MainLayout
lang={locale}
title={copy.pageTitle}
customDescription={copy.metaDescription}
customOgTitle={copy.pageTitle}
>
<MainSection title={copy.heading} subTitle={copy.subtitle} />
<PricingSection pricing={pricingByLocale[locale]} locale={locale} />
</MainLayout>

View File

@ -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];
---
<MainLayout
lang={locale}
title={copy.pageTitle}
customDescription={copy.metaDescription}
customOgTitle={copy.pageTitle}
>
<MainSection title={copy.heading} subTitle={copy.subtitle} />
</MainLayout>

View File

@ -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;
---
<MainLayout
lang={locale}
title={copy.pageTitle}
customDescription={copy.metaDescription}
customOgTitle={copy.pageTitle}
>
<MainSection
title={copy.heading}
subTitle={copy.subtitle}
btnExists={true}
btnTitle={copy.cta}
btnURL={localizeHref('/contact', locale)}
/>
<FeaturesStats
title={copy.statsTitle}
subTitle={copy.statsSubtitle}
mainStatTitle={copy.mainStatTitle}
mainStatSubTitle={copy.mainStatSubTitle}
stats={copy.stats}
/>
</MainLayout>

View File

@ -14,6 +14,10 @@ interface Props {
subTitle?: string;
partners: Partner[];
}
const visiblePartners = partners.filter(
partner => partner.href && partner.icon
);
---
<section
@ -39,8 +43,13 @@ interface Props {
>
{/* Clients Group SVGs */}
{
partners.map(partner => (
<a href={partner.href} target="_blank" rel="noopener noreferrer">
visiblePartners.map(partner => (
<a
href={partner.href}
target="_blank"
rel="noopener noreferrer"
aria-label={partner.name}
>
<div set:html={partner.icon} />
</a>
))

View File

@ -15,7 +15,6 @@ const {
secondaryBtnURL,
withReview,
avatars,
starCount,
rating,
reviews,
src,
@ -32,7 +31,6 @@ interface Props {
secondaryBtnURL?: string;
withReview?: boolean;
avatars?: Array<string>;
starCount?: number;
rating?: string;
reviews?: string;
src?: any;
@ -81,12 +79,7 @@ interface Props {
}
{
withReview ? (
<ReviewComponent
avatars={avatars}
starCount={starCount}
rating={rating}
reviews={reviews}
/>
<ReviewComponent avatars={avatars} rating={rating} reviews={reviews} />
) : (
''
)

View File

@ -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 = `<svg
class="h-4 w-4 shrink-0"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>`;
const copy = siteCopy[locale].auth;
---
<PrimaryCTA title="Request walkthrough" url="/contact" />
<a
href={localizeHref('/contact', locale)}
data-astro-prefetch
class="flex items-center gap-x-2 text-base font-medium text-neutral-600 ring-zinc-500 outline-hidden transition duration-300 hover:text-orange-400 focus-visible:ring-3 md:my-6 md:border-s md:border-neutral-300 md:ps-6 md:text-sm 2xl:text-base dark:border-neutral-700 dark:text-neutral-400 dark:ring-zinc-200 dark:hover:text-orange-300 dark:focus:outline-hidden"
>
<Fragment set:html={userSVG} />
{copy.walkthrough}
</a>

View File

@ -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 =
<h1
class="text-2xl font-bold tracking-tight text-balance text-neutral-800 md:text-4xl md:leading-tight dark:text-neutral-200"
>
{title}
{copy.title}
</h1>
<p class="mt-1 text-pretty text-neutral-600 dark:text-neutral-400">
{subTitle}
{copy.subtitle}
</p>
</div>
@ -36,7 +38,7 @@ const formSubTitle: string =
<h2
class="mb-8 text-xl font-bold text-neutral-700 dark:text-neutral-300"
>
{formTitle}
{copy.formTitle}
</h2>
{
/* Form for user input with various input fields.-->
@ -47,31 +49,31 @@ const formSubTitle: string =
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<TextInput
id="hs-firstname-contacts"
label="First Name"
label={copy.firstName}
name="hs-firstname-contacts"
/>
<TextInput
id="hs-lastname-contacts"
label="Last Name"
label={copy.lastName}
name="hs-lastname-contacts"
/>
</div>
<EmailContactInput id="hs-email-contacts" />
<PhoneInput id="hs-phone-number" />
<EmailContactInput id="hs-email-contacts" label={copy.email} />
<PhoneInput id="hs-phone-number" label={copy.phone} />
<TextAreaInput
id="hs-about-contacts"
label="Details"
label={copy.details}
name="hs-about-contacts"
/>
</div>
<div class="mt-4 grid">
<AuthBtn title="Prepare Email" />
<AuthBtn title={copy.submit} />
</div>
<div class="mt-3 text-center">
<p class="text-sm text-neutral-600 dark:text-neutral-400">
{formSubTitle}
{copy.formSubtitle}
</p>
</div>
</form>
@ -82,35 +84,35 @@ const formSubTitle: string =
}
<div class="divide-y divide-neutral-300 dark:divide-neutral-700">
<ContactIconBlock
heading="Knowledgebase"
content="Read public notes about the Tenantial review model."
heading={copy.blocks.docsHeading}
content={copy.blocks.docsContent}
isLinkVisible={true}
linkTitle="Visit docs"
linkURL="/welcome-to-docs/"
linkTitle={copy.blocks.docsLink}
linkURL={localizeHref('/welcome-to-docs/', locale)}
isArrowVisible={true}
><Icon name="question" />
</ContactIconBlock>
<ContactIconBlock
heading="FAQ"
content="Explore our FAQ for quick, clear answers to common queries."
heading={copy.blocks.faqHeading}
content={copy.blocks.faqContent}
isLinkVisible={true}
linkTitle="Visit FAQ"
linkURL="/#faq"
linkTitle={copy.blocks.faqLink}
linkURL={localizeHref('/#faq', locale)}
isArrowVisible={true}
><Icon name="chatBubble" />
</ContactIconBlock>
<ContactIconBlock
heading="Product preview boundary"
content="The website does not connect to live tenant data or start backend workflows."
heading={copy.blocks.boundaryHeading}
content={copy.blocks.boundaryContent}
isAddressVisible={false}
><Icon name="mapPin" />
</ContactIconBlock>
<ContactIconBlock
heading="Contact us by email"
content="Prefer the written word? Drop us an email at"
heading={copy.blocks.emailHeading}
content={copy.blocks.emailContent}
isLinkVisible={true}
linkTitle="hello@tenantial.com"
linkURL="mailto:hello@tenantial.com"

View File

@ -1,17 +1,18 @@
---
// Import the necessary dependencies
import EmailFooterInput from '@components/ui/forms/input/EmailFooterInput.astro';
import enStrings from '@utils/navigation.ts';
import BrandLogo from '@components/BrandLogo.astro';
import { SITE } from '@data/constants';
import { getLocaleFromPath, localizeHref } from '@/i18n';
import { siteCopy } from '@data/site-copy';
const strings = enStrings;
const locale = getLocaleFromPath(Astro.url.pathname);
const copy = siteCopy[locale].footer;
// Define the variables that will be used in this component
const sectionThreeTitle: string =
'Start a scoped conversation';
const sectionThreeContent: string =
'Use email for business contact context only. Do not send secrets, credentials, or tenant exports through the public website.';
type FooterSection = {
section: string;
links: Array<{ name: string; url: string }>;
};
---
<footer class="w-full bg-neutral-300 dark:bg-neutral-900">
@ -21,21 +22,23 @@ const sectionThreeContent: string =
<div class="grid grid-cols-2 gap-6 md:grid-cols-4 lg:grid-cols-5">
<div class="col-span-full lg:col-span-1">
{/* Brand Logo */}
<BrandLogo class="h-auto w-32" />
<BrandLogo class="h-auto w-40" />
</div>
{/* An array of links for Product and Company sections */}
{
strings.footerLinks.map(section => (
<div class="col-span-1">
<h3 class="font-bold text-neutral-800 dark:text-neutral-200">
copy.sections.map((section: FooterSection) => (
<div class="col-span-1 min-w-0">
<h3
class="break-words font-bold text-neutral-800 dark:text-neutral-200"
>
{section.section}
</h3>
<ul class="mt-3 grid space-y-3">
{section.links.map((link, index) => (
{section.links.map(link => (
<li>
<a
href={link.url}
class="inline-flex gap-x-2 rounded-lg text-neutral-600 ring-zinc-500 outline-hidden transition duration-300 hover:text-neutral-500 focus-visible:ring-3 dark:text-neutral-400 dark:ring-zinc-200 dark:hover:text-neutral-300 dark:focus:outline-hidden"
href={localizeHref(link.url, locale)}
class="inline-flex max-w-full gap-x-2 rounded-lg break-words whitespace-normal text-neutral-600 ring-zinc-500 outline-hidden transition duration-300 hover:text-neutral-500 focus-visible:ring-3 dark:text-neutral-400 dark:ring-zinc-200 dark:hover:text-neutral-300 dark:focus:outline-hidden"
>
{link.name}
</a>
@ -46,17 +49,20 @@ const sectionThreeContent: string =
))
}
<div class="col-span-2">
<h3 class="font-bold text-neutral-800 dark:text-neutral-200">
{sectionThreeTitle}
<div class="col-span-2 min-w-0">
<h3 class="break-words font-bold text-neutral-800 dark:text-neutral-200">
{copy.conversationTitle}
</h3>
<form>
<EmailFooterInput />
<p class="mt-3 text-sm text-neutral-600 dark:text-neutral-400">
{sectionThreeContent}
<div>
<EmailFooterInput
title={copy.contactButton}
url={localizeHref('/contact', locale)}
/>
<p class="mt-3 break-words text-sm text-neutral-600 dark:text-neutral-400">
{copy.conversationContent}
</p>
</form>
</div>
</div>
</div>
@ -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"
>
<div class="flex items-center justify-between">
<p class="text-sm text-neutral-600 dark:text-neutral-400">
<p class="break-words text-sm text-neutral-600 dark:text-neutral-400">
© <span id="current-year"></span>
{SITE.title}. Public website content only.
{SITE.title}. {copy.copyrightSuffix}
</p>
</div>
</div>

View File

@ -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"
>
<BrandLogo class="h-auto w-24" />
<BrandLogo class="h-auto w-36 sm:w-40" />
</a>
{/* Collapse toggle for smaller screens */}
<div class="mr-5 ml-auto md:hidden">
@ -84,12 +91,13 @@ const homeUrl = '/';
>
{/* Navigation links and Authentication component */}
{
strings.navBarLinks.map(link => (
<NavLink url={link.url} name={link.name} />
strings.nav.map((link: NavItem) => (
<NavLink url={localizeHref(link.url, locale)} name={link.name} />
))
}
<Authentication />
<Authentication locale={locale} />
<LanguagePicker />
{/* ThemeIcon component specifically for larger screens */}
<span class="hidden md:inline-block">
<ThemeIcon />

View File

@ -2,9 +2,10 @@
// Import SecondaryCTA component for use in this module
import SecondaryCTA from '@components/ui/buttons/SecondaryCTA.astro';
import Icon from '@components/ui/icons/Icon.astro';
import { localizeHref, type Locale } from '@/i18n';
// Define props from Astro
const { pricing } = Astro.props;
const { pricing, locale = 'de' } = Astro.props;
// Define TypeScript type for products.
type Product = {
@ -18,18 +19,19 @@ type Product = {
purchaseLink: string;
};
interface PricingSectionProps {
type Pricing = {
title: string;
subTitle: string;
badge: string;
thirdOption: string;
btnText: string;
pricing: {
title: string;
subTitle: string;
starterKit: Product;
professionalToolbox: Product;
};
starterKit: Product;
professionalToolbox: Product;
};
interface Props {
pricing: Pricing;
locale?: Locale;
}
---
@ -87,7 +89,7 @@ interface PricingSectionProps {
</ul>
{/* CTA for purchasing the product */}
<a
href={pricing.starterKit.purchaseLink}
href={localizeHref(pricing.starterKit.purchaseLink, locale)}
class="block rounded-lg bg-gray-500 px-8 py-3 text-center text-sm font-bold text-gray-100 ring-indigo-300 outline-hidden transition duration-100 hover:bg-gray-600 focus-visible:ring-3 active:text-gray-300 md:text-base"
>{pricing.starterKit.purchaseBtnTitle}</a
>
@ -141,7 +143,7 @@ interface PricingSectionProps {
</ul>
{/* CTA for purchasing the product */}
<a
href={pricing.professionalToolbox.purchaseLink}
href={localizeHref(pricing.professionalToolbox.purchaseLink, locale)}
class="bg-opacity-50 block rounded-lg bg-orange-200/40 px-8 py-3 text-center text-sm font-bold text-neutral-100 ring-orange-300 outline-hidden transition duration-300 hover:bg-orange-300 focus-visible:ring-3 active:bg-orange-400 md:text-base"
>{pricing.professionalToolbox.purchaseBtnTitle}</a
>
@ -153,6 +155,9 @@ interface PricingSectionProps {
{pricing.thirdOption}
</p>
<SecondaryCTA title={pricing.btnText} url="/contact" />
<SecondaryCTA
title={pricing.btnText}
url={localizeHref('/contact', locale)}
/>
</div>
</section>

View File

@ -27,7 +27,7 @@ interface Props {
<Image
class="h-8 w-8 rounded-full"
src={avatarSrc}
alt="Avatar Description"
alt="Illustrative testimonial avatar"
loading={'eager'}
inferSize
/>

View File

@ -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);
---
<div class="hs-dropdown relative inline-flex">
<button
id="hs-language-dropdown"
type="button"
aria-label="Sprache wechseln"
title="Sprache wechseln"
class="hs-dropdown-toggle inline-flex items-center gap-x-2 rounded-lg px-1.5 py-1.5 text-sm font-medium text-neutral-600 ring-zinc-500 outline-hidden transition duration-300 hover:bg-neutral-200 hover:text-orange-400 dark:border-neutral-700 dark:text-neutral-400 dark:ring-zinc-200 dark:hover:bg-neutral-700 dark:hover:text-orange-300 dark:focus:outline-hidden"
>
<Icon name="earth" />
<span class="sr-only">{localeLabels[currentLocale]}</span>
<svg
class="hs-dropdown-open:rotate-180 size-4"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg
>
</button>
<div
class="hs-dropdown-menu duration hs-dropdown-open:opacity-100 top-[98%]! left-[20%]! mt-2 hidden transform-none! rounded-lg bg-neutral-50 p-2 opacity-0 shadow-md transition-[opacity,margin] before:absolute before:start-0 before:-top-4 before:h-4 before:w-full after:absolute after:start-0 after:-bottom-4 after:h-4 after:w-full md:top-[80%]! md:left-[90%]! dark:divide-neutral-700 dark:border dark:border-neutral-700 dark:bg-neutral-800"
aria-labelledby="hs-language-dropdown"
>
{
Object.entries(localeLabels).map(([locale, label]) => (
<a
class:list={[
'flex items-center gap-x-3.5 rounded-lg px-3 py-2 text-sm hover:bg-neutral-100 focus:bg-neutral-100 focus:outline-hidden dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700',
locale === currentLocale
? 'font-semibold text-orange-500 dark:text-orange-300'
: 'text-neutral-800 dark:text-neutral-400',
]}
href={localizedPath(currentPath, locale as 'de' | 'en')}
aria-current={locale === currentLocale ? 'true' : undefined}
>
{label}
</a>
))
}
</div>
</div>

View File

@ -18,16 +18,16 @@ interface Props {
<section
class="mx-auto mt-10 max-w-[85rem] px-4 py-10 sm:px-6 sm:py-16 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div class="max-w-(--breakpoint-md)">
<div class="min-w-0 max-w-(--breakpoint-md)">
{/* Section title */}
<h1
class="mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
class="mb-4 text-4xl font-extrabold tracking-tight break-words text-balance text-neutral-800 dark:text-neutral-200"
>
{title}
</h1>
{/* Section subtitle */}
<p
class="mb-8 max-w-prose font-normal text-pretty text-neutral-600 sm:text-xl dark:text-neutral-400"
class="mb-8 max-w-prose font-normal break-words text-pretty text-neutral-600 sm:text-xl dark:text-neutral-400"
>
{subTitle}
</p>

View File

@ -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<string>;
starCount?: number;
rating?: string;
reviews?: string;
}
@ -19,12 +16,16 @@ interface Props {
<div class="shrink-0 pb-5 sm:flex sm:pe-5 sm:pb-0">
{/* Avatar Group */}
<div class="flex justify-center -space-x-3">
{avatars?.map(src => <Avatar src={src} alt="Avatar Description" />)}
{
avatars?.map(src => (
<Avatar src={src} alt="Illustrative reviewer avatar" />
))
}
<span
class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-zinc-800 ring-2 ring-white dark:bg-zinc-900 dark:ring-zinc-800"
>
<span class="text-xs leading-none font-medium text-white uppercase"
>7k+</span
>Demo</span
>
</span>
</div>
@ -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"
>
</div>
{/* Review Ratings */}
<div class="flex flex-col items-center sm:items-start">
<div class="flex items-baseline space-x-1 pt-5 sm:ps-5 sm:pt-0">
<div class="flex space-x-1">
{/* Your star ratings */}
{
Array(starCount)
.fill(0)
.map((_, i) => <FullStar key={i} />)
}
{/* Adding additional half-star */}
<HalfStar />
</div>
<p class="text-neutral-800 dark:text-neutral-200">
<Fragment set:html={rating} />
<Fragment set:html={rating || 'Illustrative review preview'} />
</p>
</div>
<div class="text-sm text-neutral-800 sm:ps-5 dark:text-neutral-200">
<p>
<Fragment set:html={reviews} />
<Fragment set:html={reviews || 'Static example only'} />
</p>
</div>
</div>

View File

@ -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}
/>
</div>

View File

@ -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';
---
<div
class="mt-4 flex flex-col items-center gap-2 rounded-lg bg-neutral-200 p-2 sm:flex-row sm:gap-3 dark:bg-neutral-800"
class="mt-4 flex flex-col items-start gap-2 rounded-lg bg-neutral-200 p-2 dark:bg-neutral-800"
>
<div class="w-full">
<label for={id} class="sr-only">{label}</label>
<input
type="text"
id={id}
name="footer-input"
class="block w-full rounded-lg border-transparent bg-neutral-100 px-4 py-3 text-sm text-neutral-600 caret-orange-400 focus:border-orange-400 focus:ring-orange-400 disabled:pointer-events-none disabled:opacity-50 dark:border-transparent dark:bg-neutral-700 dark:text-gray-300 dark:placeholder:text-neutral-300"
placeholder={placeholder}
/>
</div>
<a
class="inline-flex w-full items-center justify-center gap-x-2 rounded-lg border border-transparent bg-orange-400 p-3 text-sm font-bold whitespace-nowrap text-neutral-50 ring-zinc-500 outline-hidden transition duration-300 hover:bg-orange-500 focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 sm:w-auto dark:ring-zinc-200 dark:focus:ring-1 dark:focus:outline-hidden"
href="/contact"
href={url}
>
{title}
</a>

View File

@ -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;
---
<VtbotStarlight {...Astro.props} viewTransitionsFallback="animate">
<StarlightHead {...Astro.props}><slot /></StarlightHead>
<StarlightHead {...Astro.props}>
<slot />
</StarlightHead>
<meta property="og:url" content={canonical} />
<meta property="og:image" content={socialImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="600" />
<meta property="og:image:type" content="image/png" />
<meta property="twitter:url" content={canonical} />
<meta name="twitter:image" content={socialImage} />
<script>
import '@scripts/lenisSmoothScroll.js';
</script>

View File

@ -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);
---
<span class="site-title flex">
<a class="main-logo" href="/" aria-label="Tenantial home">Tenantial</a>
<a class="main-logo" href={home} aria-label="Tenantial home">Tenantial</a>
<a class="docs-logo" href={docs} aria-label="Tenantial docs">Docs</a>
</span>

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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';
<CardGrid stagger>
<Card title="Evaluation Guides" icon="document">
Understand the public product model before planning a rollout conversation.
</Card>
<Card title="Platform Notes" icon="seti:terraform">
Review how Tenantial frames inventory evidence, snapshots, findings, and restore planning.
</Card>
</CardGrid>

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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';
<CardGrid stagger>
<Card title="Evaluation Guides" icon="document">
Understand the public product model before planning a rollout conversation.
<Card title="Evaluierungsleitfäden" icon="document">
Verstehe das öffentliche Produktmodell, bevor ein Rollout-Gespräch geplant wird.
</Card>
<Card title="Platform Notes" icon="seti:terraform">
Review how Tenantial frames inventory evidence, snapshots, findings, and restore planning.
<Card title="Plattform-Notizen" icon="seti:terraform">
Prüfe, wie Tenantial Inventory Evidence, Snapshots, Findings und Restore-Planung rahmt.
</Card>
</CardGrid>

View File

@ -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,
};

View File

@ -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."

View File

@ -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"
}
]

View File

@ -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",

View File

@ -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<Locale, any> = {
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 <span class="text-yellow-500 dark:text-yellow-400">Microsoft-Tenant-Konfiguration</span>',
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 <span class="text-yellow-500 dark:text-yellow-400">Review-Workflow</span> 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<br />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 <span class="text-yellow-500 dark:text-yellow-400">Microsoft tenant configuration</span>',
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 <span class="text-yellow-500 dark:text-yellow-400">review workflow</span> 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<br />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<Locale, Feature[]> = {
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<Locale, Pricing> = {
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<Locale, FaqGroup> = {
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.',
},
],
},
};

75
apps/website/src/i18n.ts Normal file
View File

@ -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<Locale, string> = {
de: 'Deutsch',
en: 'English',
};
export const localeHtmlLang: Record<Locale, string> = {
de: 'de',
en: 'en',
};
export const localeOg: Record<Locale, string> = {
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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -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;
}
---
<html lang={lang} class="scrollbar-hide lenis lenis-smooth scroll-pt-16">
<html lang={htmlLang} class="scrollbar-hide lenis lenis-smooth scroll-pt-16">
<head>
{/* Adding metadata to the HTML document */}
<Meta
meta={meta}
structuredData={structuredData}
locale={locale}
customDescription={customDescription}
customOgTitle={customOgTitle}
/>

View File

@ -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';
---
<MainLayout
title={pageTitle}
customDescription={metaDescription}
customOgTitle={ogTitle}
structuredData={{
'@context': 'https://schema.org',
'@type': 'WebPage',
'@id': 'https://tenantial.com/contact',
url: 'https://tenantial.com/contact',
name: 'Contact | Tenantial',
description: metaDescription,
isPartOf: {
'@type': 'WebSite',
url: 'https://tenantial.com',
name: 'Tenantial',
description: SITE.description,
},
inLanguage: 'en-US',
}}
>
<ContactSection />
</MainLayout>
<ContactPage locale="de" />

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/en/platform', 302);
---

View File

@ -0,0 +1,5 @@
---
import ContactPage from '@components/pages/ContactPage.astro';
---
<ContactPage locale="en" />

View File

@ -0,0 +1,5 @@
---
import TextPage from '@components/pages/TextPage.astro';
---
<TextPage locale="en" page="imprint" />

View File

@ -0,0 +1,5 @@
---
import HomePage from '@components/pages/HomePage.astro';
---
<HomePage locale="en" />

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/en/platform', 302);
---

View File

@ -0,0 +1,5 @@
---
import LegalPage from '@components/pages/LegalPage.astro';
---
<LegalPage locale="en" />

View File

@ -0,0 +1,5 @@
---
import PlatformPage from '@components/pages/PlatformPage.astro';
---
<PlatformPage locale="en" />

View File

@ -0,0 +1,5 @@
---
import PricingPage from '@components/pages/PricingPage.astro';
---
<PricingPage locale="en" />

View File

@ -0,0 +1,5 @@
---
import TextPage from '@components/pages/TextPage.astro';
---
<TextPage locale="en" page="privacy" />

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/en/platform', 301);
---

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/en/platform', 301);
---

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/en/platform', 301);
---

View File

@ -0,0 +1,5 @@
---
import TextPage from '@components/pages/TextPage.astro';
---
<TextPage locale="en" page="terms" />

View File

@ -0,0 +1,5 @@
---
import TrustPage from '@components/pages/TrustPage.astro';
---
<TrustPage locale="en" />

View File

@ -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';
---
<MainLayout
title={`Imprint | ${SITE.title}`}
customDescription="Tenantial public website imprint placeholder."
customOgTitle="Imprint | Tenantial"
>
<MainSection
title="Publication information"
subTitle="Publisher: Tenantial. Contact: hello@tenantial.com. Replace this placeholder with reviewed business registration and responsible-party details before public production use."
/>
</MainLayout>
<TextPage locale="de" page="imprint" />

View File

@ -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';
---
<MainLayout>
<HeroSection
title=`Evidence-first governance for <span class="text-yellow-500 dark:text-yellow-400">Microsoft tenants</span>`
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"
/>
<FeaturesGeneral
title="Review tenant change before execution"
subTitle="Tenantial focuses on evidence, reproducible snapshots, readable drift, and conservative restore planning. The public preview is static and does not connect to live tenant data."
src={featureImage}
alt="Static Tenantial review board preview"
features={features}
/>
<FeaturesNavs
title=`A <span class="text-yellow-500 dark:text-yellow-400">stable website foundation</span> 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',
},
]}
/>
<PricingSection pricing={pricing} />
<FAQ title="Frequently<br />asked questions" faqs={faqs} />
</MainLayout>
<HomePage locale="de" />

View File

@ -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';
---
<MainLayout
title={pageTitle}
customDescription="Tenantial public website legal information."
customOgTitle="Legal | Tenantial"
>
<MainSection
title="Public legal information"
subTitle="These pages provide conservative public website information and should be reviewed before production publication in any regulated context."
/>
<section class="mx-auto max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8">
<div class="flex flex-wrap gap-3">
<PrimaryCTA title="Privacy" url="/privacy" />
<SecondaryCTA title="Terms" url="/terms" />
<SecondaryCTA title="Imprint" url="/imprint" />
</div>
</section>
</MainLayout>
<LegalPage locale="de" />

View File

@ -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';
---
<MainLayout
title={pageTitle}
customDescription={metaDescription}
customOgTitle={ogTitle}
structuredData={{
'@context': 'https://schema.org',
'@type': 'WebPage',
'@id': 'https://tenantial.com/platform',
url: 'https://tenantial.com/platform',
name: 'Platform | Tenantial',
description: metaDescription,
isPartOf: {
'@type': 'WebSite',
url: 'https://tenantial.com',
name: 'Tenantial',
description: SITE.description,
},
inLanguage: 'en-US',
}}
>
<MainSection
title="Platform review model"
subTitle="Tenantial is positioned around inventory evidence, immutable policy snapshots, structured diffs, findings, exceptions, audit trail, governance reviews, and cautious restore workflows for Microsoft tenant administrators."
btnExists={true}
btnTitle="Request walkthrough"
btnURL="/contact"
/>
<RightSection
title="Inventory and snapshot evidence"
subTitle="Administrators need a reproducible record of what was observed before deciding whether a change is safe. Tenantial frames snapshots as review evidence, not as hidden automation."
single={false}
imgOne={dashboard}
imgOneAlt="Static Tenantial inventory dashboard preview"
imgTwo={evidenceImage}
imgTwoAlt="Static Tenantial evidence review panel preview"
/>
<LeftSection
title="Drift, findings, and exceptions"
subTitle="Differences and findings should become readable decision work. Exceptions and review notes keep the reasoning visible without claiming that the public website is connected to live tenant data."
img={reviewImage}
imgAlt="Static Tenantial drift workflow preview"
btnExists={true}
btnTitle="Review trust posture"
btnURL="/trust"
/>
<RightSection
title="Restore planning stays defensive"
subTitle="Restore paths are described as preview-first workflows with validation, conflict awareness, selective scope, and explicit confirmation before any sensitive action."
single={true}
imgOne={restoreImage}
imgOneAlt="Static Tenantial rollout readiness plan preview"
btnExists={true}
btnTitle="Discuss rollout"
btnURL="/contact"
/>
<FeaturesStats
title="Public preview boundaries"
subTitle="The website demonstrates the direction and visual foundation. It does not authenticate visitors, read a Microsoft tenant, execute operations, or store tenant exports."
mainStatTitle="0"
mainStatSubTitle="live tenant records used by this public website"
stats={[
{
stat: 'Static',
description: 'product previews',
},
{
stat: 'Preview',
description: 'before restore execution',
},
{
stat: 'Review',
description: 'before high-impact changes',
},
]}
/>
</MainLayout>
<PlatformPage locale="de" />

View File

@ -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';
---
<MainLayout
title={pageTitle}
customDescription={metaDescription}
customOgTitle="Pricing | Tenantial"
>
<MainSection
title="Pricing stays scoped"
subTitle="Tenantial does not claim self-serve billing, fixed plan entitlements, or automatic tenant connection from the public website."
/>
<PricingSection pricing={pricing} />
</MainLayout>
<PricingPage locale="de" />

View File

@ -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';
---
<MainLayout
title={`Privacy | ${SITE.title}`}
customDescription="Tenantial public website privacy notice."
customOgTitle="Privacy | Tenantial"
>
<MainSection
title="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."
/>
</MainLayout>
<TextPage locale="de" page="privacy" />

View File

@ -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';
---
<MainLayout
title={`Terms | ${SITE.title}`}
customDescription="Tenantial public website terms."
customOgTitle="Terms | Tenantial"
>
<MainSection
title="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."
/>
</MainLayout>
<TextPage locale="de" page="terms" />

View File

@ -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';
---
<MainLayout
title={pageTitle}
customDescription={metaDescription}
customOgTitle="Trust | Tenantial"
>
<MainSection
title="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."
btnExists={true}
btnTitle="Contact Tenantial"
btnURL="/contact"
/>
<FeaturesStats
title="Public website posture"
subTitle="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',
},
]}
/>
</MainLayout>
<TrustPage locale="de" />

View File

@ -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,

View File

@ -1,4 +1,4 @@
export const languages = {
de: 'Deutsch',
en: 'English',
fr: 'Français',
};

View File

@ -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<string>();
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();
});

View File

@ -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);
});

View File

@ -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<void> {
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<void> {
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<void> {
expect(metrics.scrollWidth).toBeLessThanOrEqual(metrics.clientWidth + 1);
}
export async function expectCoreCapabilitiesVisible(page: Page): Promise<void> {
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<void> {
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<void> {
const allowedRoutes = new Set<string>(
[
...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<void> {
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>([^<]+)<\/loc>/g)).map(([, url]) => url);
}
export async function readSitemapUrls(page: Page): Promise<string[]> {
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);
}
}

View File

@ -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
```

View File

@ -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.

View File

@ -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.

View File

@ -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`.

View File

@ -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

View File

@ -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.

View File

@ -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
```

View File

@ -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.