Skip to content
121 changes: 121 additions & 0 deletions app/home-poc/_components/Ledger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use client';

import { useEffect, useRef, useState } from 'react';
import { useInView, useReducedMotion } from 'framer-motion';

/**
* Terminal-style framework ledger: a monospaced data table that reads like
* a compliance trading desk. The aggregate score counts up on view; each
* row carries a coverage bar and a live tag. Replaces the usual "trust
* strip + posture card" with something data-forward and brutal.
*/

type Row = {
framework: string;
controls: string;
coverage: number; // %
score: number; // %
};

const ROWS: Row[] = [
{ framework: 'ISO 27001', controls: '114 / 114', coverage: 100, score: 100 },
{ framework: 'SOC 2 Type II', controls: '61 / 64', coverage: 95, score: 97 },
{ framework: 'NDIS Practice', controls: '88 / 92', coverage: 96, score: 96 },
{ framework: 'HIPAA', controls: '54 / 58', coverage: 93, score: 94 },
{ framework: 'GDPR', controls: '41 / 43', coverage: 95, score: 95 },
];

function useCountUp(target: number, run: boolean) {
const reduce = useReducedMotion();
const [v, setV] = useState(reduce ? target : 0);
useEffect(() => {
if (!run || reduce) {
if (reduce) setV(target);
return;
}
let raf = 0;
let start: number | null = null;
const dur = 1100;
const tick = (t: number) => {
if (start === null) start = t;
const p = Math.min((t - start) / dur, 1);
setV(Math.round((1 - Math.pow(1 - p, 3)) * target));
if (p < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [run, reduce, target]);
return v;
}

export function Ledger() {
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref, { once: true, amount: 0.4 });
const agg = useCountUp(96, inView);

return (
<div ref={ref}>
{/* aggregate readout */}
<div
style={{
display: 'flex',
alignItems: 'flex-end',
justifyContent: 'space-between',
flexWrap: 'wrap',
gap: 16,
marginBottom: 18,
}}
>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 14 }}>
<span
className="bru-display"
style={{ fontSize: 'clamp(3rem, 7vw, 5.5rem)', lineHeight: 0.8 }}
>
{agg}
<span style={{ color: 'var(--red)' }}>%</span>
</span>
<span className="bru-mono" style={{ fontSize: 12, color: 'var(--ink-dim)' }}>
AGGREGATE
<br />
POSTURE
</span>
</div>
<span className="bru-tag bru-tag-live">
<span className="bru-live-dot" style={{ display: 'inline-block', marginRight: 6 }} />
LIVE · ALL FRAMEWORKS
</span>
</div>

<table className="bru-ledger">
<thead>
<tr>
<th>Framework</th>
<th>Controls</th>
<th className="hidden sm:table-cell">Coverage</th>
<th>Score</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{ROWS.map((r) => (
<tr key={r.framework}>
<td className="bru-fw">{r.framework}</td>
<td style={{ color: 'var(--ink-dim)' }}>{r.controls}</td>
<td className="hidden sm:table-cell">
<span className="bru-bar-track">
<span className="bru-bar" style={{ width: `${r.coverage}%` }} />
</span>
</td>
<td className="bru-score">{r.score}%</td>
<td>
<span className={`bru-tag ${r.score === 100 ? 'bru-tag-live' : ''}`}>
{r.score === 100 ? 'SEALED' : 'TRACKED'}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
26 changes: 26 additions & 0 deletions app/home-poc/_components/Reveal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { CSSProperties, ReactNode } from 'react';

/**
* Layout passthrough. The brutalist hero/manifesto read best instant (no
* fade-in), and entrance motion was hiding content when the Intersection
* observer raced on above-the-fold mount. The page's real motion lives in
* the self-contained islands (ledger count-up, schematic draw-on-scroll).
* Kept as a named wrapper so call sites stay declarative and we can
* reintroduce a robust reveal later in one place.
*/
export function Reveal({
children,
className,
style,
}: {
children: ReactNode;
delay?: number;
className?: string;
style?: CSSProperties;
}) {
return (
<div className={className} style={style}>
{children}
</div>
);
}
148 changes: 148 additions & 0 deletions app/home-poc/_components/Schematic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'use client';

import { motion, useReducedMotion } from 'framer-motion';

/**
* The compliance graph drawn as an engineering schematic / patent figure:
* boxed nodes, orthogonal (right-angle) bus wiring, callout numbers, crop
* marks and coordinate ticks on the frame. Wires draw on scroll. The
* evidence run carries the red signal. Deliberately technical, not floaty.
*/

type Box = {
id: string;
x: number;
y: number;
w: number;
h: number;
n: string;
label: string;
sub: string;
accent?: boolean;
};

const BOXES: Box[] = [
{ id: 'obl', x: 40, y: 70, w: 210, h: 72, n: '01', label: 'OBLIGATION', sub: 'NDIS Std 4.2' },
{ id: 'ctl', x: 375, y: 40, w: 230, h: 130, n: '02', label: 'CONTROL', sub: 'Enforced · scored' },
{ id: 'own', x: 730, y: 28, w: 210, h: 64, n: '03', label: 'OWNER', sub: 'Named · accountable' },
{ id: 'evd', x: 730, y: 120, w: 210, h: 64, n: '04', label: 'EVIDENCE', sub: 'Immutable chain', accent: true },
{ id: 'aud', x: 375, y: 270, w: 230, h: 64, n: '05', label: 'AUDIT', sub: 'Continuous readout' },
];

// orthogonal wire (right-angle) from box A right-edge to box B left-edge
function busPath(a: Box, b: Box) {
const ax = a.x + a.w;
const ay = a.y + a.h / 2;
const bx = b.x;
const by = b.y + b.h / 2;
const midx = ax + (bx - ax) / 2;
return `M ${ax} ${ay} H ${midx} V ${by} H ${bx}`;
}
// vertical drop from control bottom to audit
function dropPath(a: Box, b: Box) {
const ax = a.x + a.w / 2;
const ay = a.y + a.h;
const by = b.y;
return `M ${ax} ${ay} V ${by}`;
}

const WIRES = [
{ d: busPath(BOXES[0], BOXES[1]), accent: false },
{ d: busPath(BOXES[1], BOXES[2]), accent: false },
{ d: busPath(BOXES[1], BOXES[3]), accent: true },
{ d: dropPath(BOXES[1], BOXES[4]), accent: false },
];

export function Schematic() {
const reduce = useReducedMotion();

return (
<svg
viewBox="0 0 980 370"
width="100%"
role="img"
aria-label="Schematic: an obligation wires into an enforced control, which wires to its owner, its evidence chain and a continuous audit readout."
style={{ display: 'block' }}
>
{/* frame + coordinate ticks */}
<rect x="6" y="6" width="968" height="358" fill="none" stroke="var(--line)" strokeWidth="1" />
{['A', 'B', 'C', 'D'].map((c, i) => (
<text key={c} x={6 + 240 * i + 120} y="22" textAnchor="middle" className="bru-schem-sub">
{c}
</text>
))}
{[1, 2, 3].map((r, i) => (
<text key={r} x="20" y={70 + 110 * i} className="bru-schem-sub">
{r}
</text>
))}

{/* wiring */}
{WIRES.map((w, i) => (
<motion.path
key={`w-${i}`}
d={w.d}
fill="none"
stroke={w.accent ? 'var(--red)' : 'var(--line-2)'}
strokeWidth={w.accent ? 2 : 1.25}
initial={reduce ? false : { pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true, amount: 0.4 }}
transition={{ duration: 0.9, delay: 0.3 + i * 0.16, ease: [0.65, 0, 0.35, 1] }}
/>
))}

{/* signal pulse along the evidence wire */}
{!reduce && (
<circle r="3.5" fill="var(--red)">
<animateMotion dur="1.3s" begin="1.2s" repeatCount="1" fill="freeze" path={WIRES[2].d} />
</circle>
)}

{/* boxes */}
{BOXES.map((b, i) => (
<motion.g
key={b.id}
initial={reduce ? false : { opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, amount: 0.4 }}
transition={{ duration: 0.35, delay: 0.15 + i * 0.12 }}
>
{/* crop ticks at corners */}
{[
[b.x, b.y, 1, 1],
[b.x + b.w, b.y, -1, 1],
[b.x, b.y + b.h, 1, -1],
[b.x + b.w, b.y + b.h, -1, -1],
].map(([cx, cy, dx, dy], k) => (
<path
key={k}
d={`M ${cx} ${cy + (dy as number) * 7} V ${cy} H ${cx + (dx as number) * 7}`}
stroke={b.accent ? 'var(--red)' : 'var(--line-2)'}
strokeWidth="1.25"
fill="none"
/>
))}
<rect
x={b.x}
y={b.y}
width={b.w}
height={b.h}
fill={b.id === 'ctl' ? '#141416' : 'transparent'}
stroke={b.accent ? 'var(--red)' : 'var(--line-2)'}
strokeWidth={b.id === 'ctl' ? 1.5 : 1}
/>
<text x={b.x + 14} y={b.y + 26} className="bru-schem-sub" fill="var(--ink-faint)">
FIG.{b.n}
</text>
<text x={b.x + 14} y={b.y + (b.h > 90 ? 64 : 44)} className="bru-schem-label">
{b.label}
</text>
<text x={b.x + 14} y={b.y + (b.h > 90 ? 84 : 60)} className="bru-schem-sub">
{b.sub}
</text>
</motion.g>
))}
</svg>
);
}
25 changes: 25 additions & 0 deletions app/home-poc/_components/StickyCTA.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import { useEffect, useState } from 'react';

/** Mobile-only sticky action bar: split red/ghost, appears past the hero. */
export function StickyCTA() {
const [show, setShow] = useState(false);
useEffect(() => {
const onScroll = () => setShow(window.scrollY > 600);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
if (!show) return null;
return (
<div className="bru-sticky lg:hidden">
<a href="#" style={{ background: 'var(--red)', color: '#fff' }}>
Book a walkthrough →
</a>
<a href="#" style={{ color: 'var(--ink)', borderLeft: '1.5px solid var(--line-2)' }}>
Assess
</a>
</div>
);
}
47 changes: 47 additions & 0 deletions app/home-poc/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Metadata } from 'next';
import { Archivo, Spline_Sans_Mono } from 'next/font/google';
import './poc.css';

/**
* Brutalist-editorial type system.
* — Archivo (variable, incl. width axis) carries the whole grotesque voice:
* pushed to Black + Expanded it gives monumental poster headlines without
* the Inter/Sora/Fraunces "AI default" tell.
* — Spline Sans Mono is the technical metadata voice (mastheads, ledgers,
* coordinates, callouts).
*
* PRODUCTION UPGRADE: swap --bru-sans for a licensed grotesque (GT America /
* Söhne / ABC Diatype) and the display face for Druk Wide — one var change in
* poc.css. Stand-ins here are the closest self-hostable equivalents.
*/
const archivo = Archivo({
subsets: ['latin'],
display: 'swap',
variable: '--bru-sans',
weight: 'variable',
axes: ['wdth'],
});

const mono = Spline_Sans_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--bru-mono',
weight: ['400', '500', '700'],
});

export const metadata: Metadata = {
title: 'FormaOS — Homepage POC (Brutalist-Editorial)',
robots: { index: false, follow: false },
};

export default function HomePocLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className={`${archivo.variable} ${mono.variable} bru-root`}>
{children}
</div>
);
}
Loading
Loading