TenantAtlas/apps/website/src/lib/site.ts
Ahmed Darrazi 54d604ff1e
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m50s
feat: implement website visual foundation
2026-04-19 09:10:29 +02:00

206 lines
7.2 KiB
TypeScript

import type {
CtaLink,
FooterLead,
FooterNavigationGroup,
NavigationItem,
PageDefinition,
PageFamily,
ShellTone,
SiteMetadata,
SitePath,
VisualFoundationContract,
} from '@/types/site';
export const siteMetadata: SiteMetadata = {
siteName: 'TenantAtlas',
siteTagline: 'Governance of record for Microsoft tenant operations.',
siteDescription:
'TenantAtlas helps MSP and enterprise teams keep Microsoft tenant change history observable, reviewable, and safer to operate.',
siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example',
};
export const visualFoundationContract: VisualFoundationContract = {
pageFamilies: ['landing', 'trust', 'content'],
ctaHierarchy: ['primary', 'secondary', 'ghost'],
requiredColorRoles: [
'background',
'foreground',
'muted',
'muted-foreground',
'card',
'card-foreground',
'border',
'input',
'primary',
'primary-foreground',
'secondary',
'secondary-foreground',
'accent',
'accent-foreground',
'success',
'warning',
'destructive',
'info',
],
accessibilityBaseline: [
'focus-visible',
'readable-contrast',
'non-color-only-semantics',
'navigation-vs-cta-differentiation',
'mobile-readable-density',
],
surfaceLayers: ['page-background', 'default-content', 'card', 'elevated', 'muted-inset', 'highlighted'],
};
export const primaryNavigation: NavigationItem[] = [
{ href: '/product', label: 'Product', description: 'Understand the operating model.' },
{ href: '/solutions', label: 'Solutions', description: 'See the fit for MSP and enterprise teams.' },
{ href: '/security-trust', label: 'Security & Trust', description: 'Review the product posture.' },
{ href: '/integrations', label: 'Integrations', description: 'Inspect the real ecosystem fit.' },
{ href: '/contact', label: 'Contact', description: 'Reach the team for a working session.' },
];
export const footerNavigationGroups: FooterNavigationGroup[] = [
{
title: 'Explore',
items: [
{ href: '/', label: 'Home' },
{ href: '/product', label: 'Product' },
{ href: '/solutions', label: 'Solutions' },
{ href: '/security-trust', label: 'Security & Trust' },
{ href: '/integrations', label: 'Integrations' },
],
},
{
title: 'Next step',
items: [
{ href: '/contact', label: 'Contact / Demo' },
{ href: '/legal', label: 'Legal' },
],
},
{
title: 'Legal',
items: [
{ href: '/privacy', label: 'Privacy' },
{ href: '/terms', label: 'Terms' },
],
},
];
export const contactCta: CtaLink = {
href: '/contact',
label: 'Request a working session',
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
variant: 'primary',
};
const footerLeadByFamily: Record<PageFamily, FooterLead> = {
landing: {
eyebrow: 'Keep the next move obvious',
title: 'A calmer public surface should still lead somewhere concrete.',
description:
'Landing pages should move visitors from orientation into a product, trust, or contact path without competing CTAs or dead ends.',
intent: 'conversion',
primaryCta: contactCta,
},
trust: {
eyebrow: 'Trust stays actionable',
title: 'Security, legal, and contact paths should reinforce one another.',
description:
'Trust-oriented pages should keep legal context, product posture, and the working-session route connected instead of turning caution into friction.',
intent: 'guidance',
primaryCta: {
href: '/contact',
label: 'Discuss trust requirements',
helper: 'Bring current review, legal, or rollout questions into one working conversation.',
variant: 'primary',
},
},
content: {
eyebrow: 'Reading should still progress',
title: 'Long-form pages need the same action hierarchy as landing pages.',
description:
'Contact, legal, privacy, and terms routes should feel deliberate and connected to the evaluation path instead of behaving like detached documents.',
intent: 'legal',
primaryCta: {
href: '/contact',
label: 'Continue the evaluation path',
helper: 'Move from the reading surface back into a product or trust conversation.',
variant: 'primary',
},
},
};
const headerCtaByFamily: Record<PageFamily, CtaLink> = {
landing: {
href: '/contact',
label: 'Request a working session',
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
variant: 'secondary',
},
trust: {
href: '/contact',
label: 'Discuss trust requirements',
helper: 'Route trust, legal, or rollout questions into one conversation.',
variant: 'secondary',
},
content: {
href: '/contact',
label: 'Start the working session',
helper: 'Turn the reading path into a concrete next step.',
variant: 'secondary',
},
};
export const pageDefinitions: Record<SitePath, PageDefinition> = {
'/': { path: '/', pageRole: 'home', family: 'landing', shellTone: 'brand' },
'/product': { path: '/product', pageRole: 'product', family: 'landing', shellTone: 'brand' },
'/solutions': { path: '/solutions', pageRole: 'solutions', family: 'landing', shellTone: 'brand' },
'/security-trust': { path: '/security-trust', pageRole: 'trust', family: 'trust', shellTone: 'trust' },
'/integrations': { path: '/integrations', pageRole: 'integrations', family: 'landing', shellTone: 'neutral' },
'/contact': { path: '/contact', pageRole: 'contact', family: 'content', shellTone: 'neutral' },
'/legal': { path: '/legal', pageRole: 'legal', family: 'trust', shellTone: 'trust' },
'/privacy': { path: '/privacy', pageRole: 'privacy', family: 'content', shellTone: 'neutral' },
'/terms': { path: '/terms', pageRole: 'terms', family: 'content', shellTone: 'neutral' },
};
export const coreRoutes = Object.keys(pageDefinitions) as SitePath[];
export function getPageDefinition(path: string): PageDefinition {
return pageDefinitions[path as SitePath] ?? pageDefinitions['/'];
}
export function getPageFamily(path: string): PageFamily {
return getPageDefinition(path).family;
}
export function getShellTone(path: string): ShellTone {
return getPageDefinition(path).shellTone;
}
export function getHeaderCta(path: string): CtaLink {
const definition = getPageDefinition(path);
return {
...headerCtaByFamily[definition.family],
...definition.headerCta,
};
}
export function getFooterLead(path: string): FooterLead {
const definition = getPageDefinition(path);
return {
...footerLeadByFamily[definition.family],
...definition.footerLead,
};
}
export function isActiveNavigationPath(currentPath: string, href: string): boolean {
if (href === '/') {
return currentPath === '/';
}
return currentPath === href || currentPath.startsWith(`${href}/`);
}