spb/airiskindex Public
The most methodologically rigorous, fully transparent AI job-exposure index.
TypeScript 88%
Python 6.1%
SQL 2.7%
CSS 1.2%
JavaScript 0.9%
Shell 0.8%
1// File: layout.tsx2// Path: apps/web/app/layout.tsx3// Project: AI Risk Index — airiskindex.io4// Author: Simon-Pierre Boucher5// Contact: contact@spboucher.ai6// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.7//8// Description: Root layout: sticky header, mobile drawer menu, preview banner, footer.910import type { Metadata, Viewport } from "next";11import Link from "next/link";12import type { ReactNode } from "react";13import { prisma } from "@airiskindex/db";14import { INDEX_VERSION } from "@airiskindex/scoring";15import { LogoMark } from "@/components/logo";16import { MobileMenu } from "@/components/mobile-menu";17import "./globals.css";1819export const metadata: Metadata = {20 metadataBase: new URL("https://www.airiskindex.io"),21 title: "AI Risk Index — task-based AI exposure scores for every occupation",22 description:23 "Transparent, versioned, task-based scores of how occupations are exposed to AI — with separate exposure, substitution and augmentation sub-scores and confidence intervals. Adaptation guidance, not doom.",24 openGraph: {25 title: "AI Risk Index",26 description:27 "How is your occupation exposed to AI — task by task? Three sub-scores, confidence intervals, fully open methodology.",28 url: "https://www.airiskindex.io",29 siteName: "AI Risk Index",30 },31};3233export const viewport: Viewport = {34 width: "device-width",35 initialScale: 1,36 themeColor: [37 { media: "(prefers-color-scheme: light)", color: "#f9f9f7" },38 { media: "(prefers-color-scheme: dark)", color: "#0d0d0d" },39 ],40};4142async function isDemoRun(): Promise<boolean> {43 try {44 const run = await prisma.scoreRun.findFirst({ orderBy: { createdAt: "desc" } });45 return run ? run.raterModels.some((model) => model.startsWith("demo-")) : true;46 } catch {47 return true;48 }49}5051const NAV = [52 ["/ranking", "Full index"],53 ["/insights", "Insights"],54 ["/occupations", "Browse"],55 ["/methodology", "Methodology"],56 ["/api/v1/methodology", "API"],57] as const;5859const MOBILE_NAV = [60 ...NAV,61 ["/terms", "Terms of Service"],62 ["/privacy", "Privacy Policy"],63] as const;6465export default async function RootLayout({66 children,67}: {68 children: ReactNode;69}): Promise<JSX.Element> {70 const demo = await isDemoRun();71 return (72 <html lang="en">73 <body className="min-h-screen antialiased">74 {demo && (75 <div className="bg-[var(--seq)] px-4 py-2 text-center text-xs font-medium text-white">76 Preview — scores are computed from a demonstration dataset while the first published77 index run ({INDEX_VERSION.replace("-draft.1", "")}) is in progress.78 </div>79 )}80 <header className="sticky top-0 z-40 border-b border-[var(--border)] backdrop-blur-md [background:var(--header)]">81 <nav className="relative mx-auto flex max-w-5xl items-center justify-between gap-3 px-4 py-3 sm:px-6">82 <Link href="/" className="flex shrink-0 items-center gap-2.5 font-semibold tracking-tight">83 <LogoMark size={24} />84 AI Risk Index85 </Link>86 <div className="hidden items-center gap-6 text-sm text-[var(--ink-2)] sm:flex">87 {NAV.map(([href, label]) => (88 <Link89 key={href}90 href={href}91 className="transition-colors hover:text-[var(--ink)]"92 >93 {label}94 </Link>95 ))}96 </div>97 <MobileMenu items={MOBILE_NAV} />98 </nav>99 </header>100 {children}101 <footer className="mt-20 border-t border-[var(--border)] bg-[var(--surface-1)]">102 <div className="mx-auto grid max-w-5xl gap-8 px-4 py-10 sm:grid-cols-3 sm:px-6">103 <div>104 <p className="flex items-center gap-2.5 font-semibold tracking-tight">105 <LogoMark size={20} />106 AI Risk Index107 </p>108 <p className="mt-2 text-xs leading-relaxed text-[var(--muted)]">109 The transparent, task-based AI job-exposure index. Methodology {INDEX_VERSION} —110 every score traces to a stored, immutable computation run.111 </p>112 </div>113 <div className="text-sm">114 <p className="text-xs font-semibold uppercase tracking-wide text-[var(--muted)]">115 Explore116 </p>117 <ul className="mt-2 space-y-1.5 text-[var(--ink-2)]">118 <li>119 <Link href="/ranking" className="hover:text-[var(--ink)] hover:underline">120 Full index121 </Link>122 </li>123 <li>124 <Link href="/insights" className="hover:text-[var(--ink)] hover:underline">125 Insights126 </Link>127 </li>128 <li>129 <Link href="/occupations" className="hover:text-[var(--ink)] hover:underline">130 All occupations131 </Link>132 </li>133 <li>134 <Link href="/methodology" className="hover:text-[var(--ink)] hover:underline">135 Methodology136 </Link>137 </li>138 <li>139 <Link140 href="/api/v1/methodology"141 className="hover:text-[var(--ink)] hover:underline"142 >143 Public API144 </Link>145 </li>146 </ul>147 </div>148 <div className="text-sm">149 <p className="text-xs font-semibold uppercase tracking-wide text-[var(--muted)]">150 Legal & contact151 </p>152 <ul className="mt-2 space-y-1.5 text-[var(--ink-2)]">153 <li>154 <Link href="/terms" className="hover:text-[var(--ink)] hover:underline">155 Terms of Service156 </Link>157 </li>158 <li>159 <Link href="/privacy" className="hover:text-[var(--ink)] hover:underline">160 Privacy Policy161 </Link>162 </li>163 <li>164 <a165 href="mailto:contact@spboucher.ai"166 className="hover:text-[var(--ink)] hover:underline"167 >168 contact@spboucher.ai169 </a>170 </li>171 </ul>172 </div>173 </div>174 <div className="border-t border-[var(--grid)]">175 <p className="mx-auto max-w-5xl px-4 py-4 text-xs leading-relaxed text-[var(--muted)] sm:px-6">176 Occupation and task data from O*NET (CC BY 4.0, U.S. Department of Labor) · wages177 from BLS OEWS. This index measures task-level pressure, not certainty of job loss —178 read it as adaptation guidance. © 2026 Simon-Pierre Boucher.179 </p>180 </div>181 </footer>182 </body>183 </html>184 );185}186