customer-presentation/protected/platform.css
Jonathan Hvid e3439d6c8f deepdive: add Beat-5 'Everything Client-Managed' frame around platform stack
Why: closes the Beat-5 summary with a visible boundary + label so the
"client-managed" point lands visually, not just in copy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:11:05 +02:00

508 lines
15 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* =============================================================
Fenja AI — "Product Deepdive" page
A self-contained top-level page (#page-product-deepdive) reached
via its own dot. Hosts:
"The Question" — full-viewport framing statement (fade-in)
"The Layers" — pinned scrubbed five-beat architecture build
"Choose your Capability" — 4 product cards (final section)
============================================================= */
/* ─── Page scaffold ─────────────────────────────────────────
#page-product-deepdive is `position: fixed; inset: 0;` via the
shared `.page` rule. Its child #product-deepdive-scroll is the
internal scroller — Lenis + ScrollTrigger.scrollerProxy attach
here so the scrub on Section B works without touching the
window scroll. Mirrors #overview-scroll's shape. */
#product-deepdive-scroll {
position: absolute;
inset: 0;
overflow-y: auto;
overflow-x: hidden;
background: var(--background);
scrollbar-width: thin;
scrollbar-color: rgba(56,56,49,0.18) transparent;
}
#product-deepdive-scroll::-webkit-scrollbar { width: 6px; }
#product-deepdive-scroll::-webkit-scrollbar-thumb {
background: rgba(56,56,49,0.18);
border-radius: 3px;
}
#product-deepdive-scroll > section { position: relative; z-index: 2; }
/* The dot-nav-tray paper-fade reads as a soft footer on the timeline
page but would interrupt the architecture pin's scrub on the
deepdive page. Hide it while this page is active, mirroring the
#page-overview rule. */
body:has(#page-product-deepdive.is-active) .dot-nav-tray { opacity: 0; }
/* ─── Section A: Choose your Capability — final section ───────
This is the last section of the Deepdive page. min-height + flex
centring make the cards land vertically centred in the viewport
when the user scrolls to the page end. */
#platform-cards {
position: relative;
width: 100%;
min-height: 100vh;
background: var(--background);
color: var(--on-surface);
padding: clamp(2rem, 6vh, 5rem) clamp(2rem, 5vw, 7rem);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: clamp(2.5rem, 6vh, 4rem);
box-sizing: border-box;
}
.platform-cards-head {
text-align: center;
max-width: var(--content-max);
}
.platform-eyebrow {
font-family: var(--font-sans);
font-weight: 500;
font-size: var(--text-label-md);
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
color: var(--on-surface-variant);
margin: 0 0 var(--space-5) 0;
}
.platform-title {
font-family: var(--font-serif);
font-weight: 400;
font-size: clamp(2.5rem, 5vw, 3.75rem);
letter-spacing: -0.022em;
line-height: 1.1;
color: var(--on-surface);
margin: 0;
}
.platform-title em { font-style: italic; font-weight: 400; }
.platform-card-grid {
width: 100%;
max-width: var(--content-max);
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: var(--space-5);
align-items: stretch;
}
.platform-card {
display: flex;
flex-direction: column;
background: var(--surface-container-lowest);
color: var(--on-surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-ambient);
padding: var(--space-7) var(--space-6);
min-height: 320px;
}
.platform-card.is-dark {
background: var(--secondary);
color: #fffcf7;
}
.platform-card-name {
font-family: var(--font-serif);
font-weight: 400;
font-size: clamp(1.75rem, 2.4vw, 2.125rem);
line-height: 1.05;
margin: 0 0 var(--space-3) 0;
color: inherit;
}
.platform-card-name em {
font-style: italic;
font-weight: 400;
display: block;
}
.platform-card-tier {
font-family: var(--font-sans);
font-weight: 600;
font-size: var(--text-label-sm);
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
color: var(--on-surface-variant);
margin: 0 0 var(--space-5) 0;
}
.platform-card.is-dark .platform-card-tier {
color: rgba(255, 252, 247, 0.65);
}
.platform-card-body {
font-family: var(--font-sans);
font-size: var(--text-body-md);
line-height: var(--leading-relaxed);
color: inherit;
margin: 0 0 var(--space-5) 0;
}
.platform-card-includes {
margin: auto 0 0 0;
font-family: var(--font-serif);
font-style: italic;
font-size: var(--text-body-md);
color: var(--on-surface-variant);
}
.platform-card.is-dark .platform-card-includes {
color: rgba(255, 252, 247, 0.78);
}
/* Narrow desktop fallback — 4 cards become a 2×2 grid before content
gets too cramped. Mobile UAs are routed to a separate page entirely
(see PROJECT.md), so this is the only narrow case to handle. */
@media (max-width: 960px) {
.platform-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* =============================================================
"The Question" intro section — first section of the Deepdive
page. A full-viewport framing statement; fades in on scroll
like the cards stagger. Plain (not pinned, not scrubbed).
============================================================= */
#platform-question {
position: relative;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: clamp(4rem, 8vh, 8rem) clamp(2rem, 5vw, 7rem);
background: var(--background);
box-sizing: border-box;
}
.pq-wrap {
width: 100%;
max-width: var(--reading-max);
}
/* Title — italic serif for the parallel "isn't enough" rejection.
Sized below the previous hero scale so the longer flowing text
reads as a sustained statement rather than a billboard. */
.pq-title {
font-family: var(--font-serif);
font-style: italic;
font-weight: 400;
font-size: clamp(1.75rem, 3.2vw, 2.5rem);
letter-spacing: var(--tracking-snug);
line-height: 1.25;
color: var(--on-surface);
margin: 0 0 var(--space-6) 0;
opacity: 0; /* GSAP animates this; CSS prevents pre-init flash */
}
.pq-title em { font-style: italic; font-weight: 400; }
/* Body — upright serif for the affirmative resolution. Same size
as the title so the two parts read as paired voices, not as
title-and-fineprint. Italic <em> emphasises the key claim
("platform you control"). */
.pq-body {
font-family: var(--font-serif);
font-weight: 400;
font-size: clamp(1.5rem, 2.6vw, 2rem);
letter-spacing: var(--tracking-snug);
line-height: 1.35;
color: var(--on-surface);
margin: 0;
opacity: 0; /* GSAP animates this; CSS prevents pre-init flash */
}
.pq-body em { font-style: italic; font-weight: 400; }
/* =============================================================
"The Layers" entry section — pinned scrubbed five-beat build.
A .pl-pin two-row layout: a static title header and a
two-column body (copy stage + diagram canvas). Pinned for
+=500% so five beats can play out at 100%-viewport-per-beat
(Beats 14 assemble the diagram; Beat 5 is the closing
summary copy panel against the fully assembled diagram). The
card and layer-wrapper visuals are recreated 1:1 from the
design handoff (architecture boxes/.../README.md) — pixel
sizes, letter-spacing, gaps, and radii are taken verbatim.
============================================================= */
/* No fixed height: ScrollTrigger inserts a pin-spacer (~600vh for
the +=500% pin) inside this section. */
#platform-layers {
position: relative;
width: 100%;
background: var(--background);
color: var(--on-surface);
}
/* The pin: a vertical flex stack — header (title), body
(two-column copy + canvas), footer (closing caption). The body
takes remaining height and centres its two columns. */
.pl-pin {
position: relative;
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
/* Top padding sets where the header sits. The body below fills
the remaining height and centres the diagram, so a larger
top padding both lowers the header and tightens the gap to
the boxes (gap shrinks by ~half the padding bump). */
padding: clamp(5rem, 12vh, 9rem) clamp(2rem, 5vw, 7rem) clamp(1rem, 2vh, 2rem);
box-sizing: border-box;
}
.pl-pin-header {
flex: 0 0 auto;
text-align: center;
margin-bottom: clamp(1rem, 2.5vh, 2rem);
}
.pl-pin-title {
font-family: var(--font-serif);
font-weight: 400;
font-size: clamp(1.625rem, 2.6vw, 2.125rem);
letter-spacing: var(--tracking-snug);
line-height: 1.15;
color: var(--on-surface);
margin: 0 0 var(--space-2) 0;
}
.pl-pin-subtitle {
font-family: var(--font-serif);
font-style: italic;
font-weight: 400;
font-size: clamp(0.95rem, 1.4vw, 1.125rem);
color: var(--on-surface-variant);
margin: 0;
}
.pl-pin-body {
flex: 1 1 auto;
min-height: 0;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: clamp(2rem, 5vw, 6rem);
}
/* Copy stage — flex item that takes remaining width up to the
reading max. Each .pl-copy-step is absolutely positioned within
the stage; vertical centring keeps shorter beats (Beats 1, 3)
reading as deliberate panels rather than top-anchored fragments. */
.pl-copy-stage {
position: relative;
flex: 1 1 0;
min-width: 0;
max-width: var(--reading-max);
min-height: 320px;
}
.pl-copy-step {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
opacity: 0; /* GSAP animates this; CSS prevents pre-init flash */
}
/* Diagram canvas — three layer slots in their final vertical
positions from the start, regardless of which beats have fired.
This is the "settled-elements-never-move" guarantee: each layer
reveals in place rather than growing from zero, so neither
Foundation nor Tools is ever pushed to make room. */
.pl-canvas-wrap {
flex: 0 1 auto;
width: clamp(440px, 50vw, 700px);
min-width: 0;
}
.pl-canvas {
position: relative; /* anchors .pl-canvas-frame */
display: flex;
flex-direction: column;
gap: 20px; /* layer-to-layer gap, per design spec */
}
/* Beat-5 frame — a thin outline around the three settled layer
groups with a label sitting above the top-right corner. Initial
opacity 0 so it's invisible during Beats 14; platform.js fades
it in on Beat 5. The negative inset gives breathing room between
the existing card-group surfaces and the outline. */
.pl-canvas-frame {
position: absolute;
inset: -18px;
border: 1px solid var(--outline-variant, rgba(56,56,49,0.22));
border-radius: calc(var(--radius-lg) + 6px);
pointer-events: none;
opacity: 0;
}
.pl-canvas-frame-label {
position: absolute;
bottom: 100%; /* sits above the top edge */
right: 0;
margin-bottom: 8px;
font-family: var(--font-sans);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--on-surface-variant);
white-space: nowrap;
}
.pl-eyebrow {
font-family: var(--font-sans);
font-weight: 500;
font-size: var(--text-label-md);
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
color: var(--on-surface-variant);
margin: 0 0 var(--space-5) 0;
}
.pl-headline {
font-family: var(--font-serif);
font-weight: 400;
font-size: clamp(1.875rem, 3.4vw, 2.75rem);
letter-spacing: -0.02em;
line-height: 1.1;
color: var(--on-surface);
margin: 0 0 var(--space-6) 0;
}
.pl-headline em { font-style: italic; font-weight: 400; }
.pl-body {
font-family: var(--font-sans);
font-size: var(--text-body-md);
line-height: var(--leading-relaxed);
color: var(--on-surface);
margin: 0;
}
.pl-body em { font-style: italic; }
/* ─── Architecture-layer visual (right column) ──────────────
Pixel values below come straight from the design handoff
README. Do not relax them to tokens — the spec is explicit. */
.pl-group {
background: var(--surface-container);
border-radius: var(--radius-lg); /* 20px */
padding: 24px 24px 28px;
}
.pl-group-head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 18px;
padding-left: 4px;
gap: var(--space-4);
}
.pl-group-label {
font-family: var(--font-sans);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--on-surface-variant);
}
.pl-group-caption {
font-family: var(--font-serif);
font-style: italic;
font-size: 14px;
color: var(--on-surface-muted);
}
.pl-cards {
display: grid;
gap: 14px;
}
.pl-cards--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pl-cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pl-cards--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pl-cards--start { align-items: start; }
.pl-cards--stretch { align-items: stretch; }
.pl-card {
background: var(--surface-container-lowest);
border-radius: var(--radius-md); /* 12px */
padding: 18px 20px;
display: flex;
flex-direction: column;
gap: 6px;
min-height: 76px;
box-shadow: 0 1px 0 rgba(56,56,49,0.04), 0 8px 20px -16px rgba(56,56,49,0.18);
}
.pl-card-name {
font-family: var(--font-sans);
font-size: 15px;
font-weight: 600;
letter-spacing: -0.005em;
color: var(--on-surface);
margin: 0 0 2px 0;
}
.pl-card-italic {
font-family: var(--font-serif);
font-style: italic;
font-size: 13px;
line-height: 1.35;
color: var(--on-surface-variant);
margin: 0;
}
.pl-card-mono {
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: 0.04em;
line-height: 1.4;
color: var(--on-surface-muted);
margin: 0;
}
/* Narrow desktop: drop Tools/Agents to a 2-col grid before content
gets too cramped. Same breakpoint .platform-card-grid uses.
Mobile UAs go to a separate page entirely (PROJECT.md), so this
is the only narrow case to handle. */
@media (max-width: 960px) {
.pl-cards--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Reduced motion — release the pin entirely, stack header, all
five text panels, and the fully assembled diagram vertically.
platform.js mirrors this gate. The .pq-* opacity:1 lines belong
to #platform-question (above) but the GSAP fade-in init also
applies there, so they're paired here for the same reason. */
@media (prefers-reduced-motion: reduce) {
.pq-title, .pq-body { opacity: 1; }
#platform-layers .pl-pin {
height: auto;
gap: var(--space-10);
padding: var(--space-12) clamp(2rem, 5vw, 7rem);
}
#platform-layers .pl-pin-body {
flex-direction: column;
gap: var(--space-10);
}
#platform-layers .pl-copy-stage {
position: relative;
min-height: 0;
max-width: none;
display: flex;
flex-direction: column;
gap: var(--space-7);
}
#platform-layers .pl-copy-step {
position: relative;
opacity: 1;
}
#platform-layers .pl-canvas-wrap { width: 100%; }
#platform-layers .pl-group { opacity: 1; }
#platform-layers .pl-card { opacity: 1; transform: none; }
#platform-layers .pl-canvas-frame { opacity: 1; }
}