Build compliance portal home page sections

Add the frameworks (Compliance), security commitments, trusted-by, and
recent updates sections to the home page, plus a site-wide "Powered by
Probo" footer. Frameworks, references, and updates are relay-driven; the
security commitments section uses placeholder POJO data until a backend
exists.

Keep generic primitives in the v2 kit (ProboLogo, a faint Text tone, and
a soft Card variant with padding="none") and place the product-specific
cards under the app's components folder, composing the base Card. Resolve
each framework's themed logo in the graph via Framework.themedLogoUrl,
mirroring the trust center logo resolver.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-06-29 09:15:50 +02:00
parent 6bcb7461be
commit 930c6bab45
40 changed files with 1490 additions and 24 deletions

View File

@@ -24,10 +24,10 @@ export type CardProps = ComponentProps<"div"> & VariantProps<typeof card>;
// clickable card set `interactive` and wrap the content in an anchor/link. Use
// CardInset for content that should bleed to the card's edges.
export function Card(props: CardProps) {
const { size = 1, variant, interactive, className, children, ...rest } = props;
const { size = 1, padding = size, variant, interactive, className, children, ...rest } = props;
return (
<div className={card({ size, variant, interactive, className })} {...rest}>
<div className={card({ size, padding, variant, interactive, className })} {...rest}>
<CardProvider value={size}>{children}</CardProvider>
</div>
);

View File

@@ -19,17 +19,29 @@ import { tv } from "tailwind-variants/lite";
export const card = tv({
base: "block overflow-hidden",
variants: {
// Padding scales with size; radius scales with it too (per the Figma).
// Radius scales with size (per the Figma).
size: {
1: "rounded-4 p-3",
2: "rounded-4 p-4",
3: "rounded-5 p-5",
4: "rounded-5 p-6",
5: "rounded-6 p-8",
1: "rounded-4",
2: "rounded-4",
3: "rounded-5",
4: "rounded-5",
5: "rounded-6",
},
// Padding defaults to match `size` (set in Card.tsx); pass `padding="none"`
// for cards whose regions own their own padding (e.g. full-bleed media).
padding: {
1: "p-3",
2: "p-4",
3: "p-5",
4: "p-6",
5: "p-8",
none: "p-0",
},
variant: {
surface: "border border-sand-6 bg-sand-2",
classic: "border border-sand-6 bg-sand-2 shadow-2",
// Soft surface: translucent neutral border on the app background.
soft: "border border-sand-a3 bg-sand-1",
ghost: "",
},
// Hover/active affordance for clickable cards. This tunes look only; wrap
@@ -40,7 +52,7 @@ export const card = tv({
},
},
compoundVariants: [
{ variant: ["surface", "classic"], interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" },
{ variant: ["surface", "classic", "soft"], interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" },
{ variant: "ghost", interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" },
],
defaultVariants: {

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { Meta, StoryObj } from "@storybook/react";
import { ProboLogo } from "./ProboLogo";
export default {
title: "v2/ProboLogo",
component: ProboLogo,
render: () => <ProboLogo className="h-6 w-auto text-sand-12" />,
} satisfies Meta<typeof ProboLogo>;
type Story = StoryObj<typeof ProboLogo>;
export const Playground: Story = {};
export const Sizes: Story = {
render: () => (
<div className="flex items-center gap-6 text-sand-11">
<ProboLogo className="h-4 w-auto" />
<ProboLogo className="h-6 w-auto" />
<ProboLogo className="h-10 w-auto" />
</div>
),
};

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import type { ComponentProps } from "react";
import { useId } from "react";
export type ProboLogoProps = ComponentProps<"svg">;
// The Probo brand logo (picto + wordmark). Rendered in `currentColor`; the
// picto's mark is a transparent cutout (via mask) so the underlying surface
// shows through on any background. Size it with a height class (e.g. `h-6`).
export function ProboLogo(props: ProboLogoProps) {
const maskId = useId();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 90 24"
role="img"
{...props}
>
<mask id={maskId} maskUnits="userSpaceOnUse">
<rect width="24" height="24" x=".85" fill="#fff" rx="5.14" />
<path
stroke="#000"
strokeWidth="3.53"
d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66"
/>
<path
fill="#000"
d="M19.07 7.1a1.77 1.77 0 0 0 3-1.86l-1.5.93-1.5.94ZM8.82 25.18l1.71-.41c-2.3-9.67.01-14.66 2.54-16.83A6.25 6.25 0 0 1 17 6.33c1.22-.01 1.87.44 2.08.78l1.5-.94 1.5-.93c-1.1-1.74-3.16-2.46-5.13-2.44-2.03.03-4.26.81-6.17 2.45-3.9 3.35-6.16 9.92-3.67 20.33l1.72-.41Z"
/>
</mask>
<rect width="24" height="24" x=".85" fill="currentColor" rx="5.14" mask={`url(#${maskId})`} />
<path
fill="currentColor"
d="M66.87 0v7.32a4.84 4.84 0 0 1 1.48-.74 5.63 5.63 0 0 1 1.76-.28c1.63 0 3 .55 4.1 1.68a5.5 5.5 0 0 1 1.67 4.06c0 1.6-.56 2.93-1.69 4.06a5.49 5.49 0 0 1-4.08 1.69 5.8 5.8 0 0 1-4.06-1.56 5.48 5.48 0 0 1-1.71-4.27V0h2.53Zm3.24 8.72a3.2 3.2 0 0 0-3.24 3.32 3.18 3.18 0 0 0 3.24 3.32 3.2 3.2 0 0 0 3.24-3.32c0-1.9-1.43-3.32-3.24-3.32ZM52.75 7.98a5.53 5.53 0 0 1 4.08-1.68c1.61 0 2.93.55 4.06 1.68a5.51 5.51 0 0 1 1.69 4.06c0 1.58-.55 2.95-1.69 4.09a5.59 5.59 0 0 1-4.06 1.66 5.64 5.64 0 0 1-4.08-1.66 5.53 5.53 0 0 1-1.69-4.09c0-1.6.56-2.92 1.7-4.06Zm.84 4.06c0 .98.32 1.77.93 2.4.63.6 1.4.92 2.31.92.92 0 1.7-.31 2.3-.92.63-.64.94-1.42.94-2.4 0-.97-.31-1.76-.94-2.37a3.03 3.03 0 0 0-2.3-.95 3.22 3.22 0 0 0-3.24 3.32ZM79.32 7.98A5.53 5.53 0 0 1 83.4 6.3c1.6 0 2.93.55 4.06 1.68a5.51 5.51 0 0 1 1.69 4.06c0 1.58-.56 2.95-1.7 4.09a5.59 5.59 0 0 1-4.05 1.66 5.64 5.64 0 0 1-4.08-1.66 5.53 5.53 0 0 1-1.69-4.09c0-1.6.55-2.92 1.69-4.06Zm.84 4.06c0 .98.32 1.77.92 2.4.64.6 1.4.92 2.32.92.92 0 1.69-.31 2.3-.92.63-.64.94-1.42.94-2.4 0-.97-.31-1.76-.95-2.37a3.03 3.03 0 0 0-2.29-.95 3.22 3.22 0 0 0-3.24 3.32ZM34.24 16.76V24h-2.53V12.12c0-1.82.58-3.24 1.71-4.27a5.87 5.87 0 0 1 4.06-1.55c1.56 0 2.98.55 4.08 1.68a5.51 5.51 0 0 1 1.7 4.06 5.66 5.66 0 0 1-5.77 5.75 5.43 5.43 0 0 1-2.88-.77l-.36-.26Zm3.24-8.04a3.2 3.2 0 0 0-3.24 3.32c0 1.9 1.42 3.32 3.24 3.32a3.2 3.2 0 0 0 3.24-3.32 3.2 3.2 0 0 0-3.24-3.32ZM47.4 17.65h-2.5v-6.7c0-3.23 1.82-4.52 4.27-4.52h.6v2.42h-.47c-1.26 0-1.9.71-1.9 2.1v6.7Z"
/>
</svg>
);
}

View File

@@ -46,8 +46,10 @@ const typographyVariants = {
},
// Hue only; the resolved text step comes from the color × highContrast
// compound variants below (step 11 low-contrast, step 12 high-contrast).
// `faint` is a de-emphasized neutral (sand alpha 8) for fine-print metadata.
color: {
neutral: "",
faint: "",
gold: "",
red: "",
green: "",
@@ -63,6 +65,9 @@ const typographyVariants = {
const colorCompoundVariants = [
{ color: "neutral", highContrast: false, class: "text-sand-11" },
{ color: "neutral", highContrast: true, class: "text-sand-12" },
// Faint metadata; a single de-emphasized step regardless of highContrast.
{ color: "faint", highContrast: false, class: "text-sand-a8" },
{ color: "faint", highContrast: true, class: "text-sand-a8" },
{ color: "gold", highContrast: false, class: "text-gold-11" },
{ color: "gold", highContrast: true, class: "text-gold-12" },
{ color: "red", highContrast: false, class: "text-red-11" },