/* =============================================================
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)); }
}
/* =============================================================
IMPLEMENTATION ROADMAP — #platform-roadmap
Four stage cards in a horizontal row, with chevrons between
each pair, then a continuous cross-cutting band below.
Mirrors #platform-cards section framing (.platform-cards-head,
.platform-eyebrow, .platform-title) and pl-card type density
(sans name + serif italic subtitle + mono meta).
Cards use --secondary (walnut) — the deck's primary brand
accent. The cross-cutting band uses --surface-container so it
reads as neutral infrastructure, not a fifth card.
============================================================= */
#platform-roadmap {
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(2rem, 5vh, 3.5rem);
box-sizing: border-box;
}
/* Stage row — four equal cards with a chevron between each pair.
The chevron lives on .rm-card::after; the last card suppresses
it so we never trail off with an arrow pointing into the void. */
.rm-row {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
max-width: var(--content-max);
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: var(--space-5);
align-items: stretch;
}
.rm-card {
position: relative;
background: var(--secondary);
color: var(--on-secondary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-ambient);
padding: var(--space-6) var(--space-5);
display: flex;
flex-direction: column;
justify-content: center;
gap: var(--space-2);
min-height: 132px;
/* Clickable. Hover and focus-visible lift the card so it's
obvious the surface is interactive; the focus outline uses
the brand accent and the same offset the rest of the deck
uses for focus rings. */
cursor: pointer;
user-select: none;
transition: transform 200ms cubic-bezier(0.2, 0, 0, 1),
box-shadow 200ms cubic-bezier(0.2, 0, 0, 1);
}
.rm-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-float);
}
.rm-card:focus-visible {
outline: 2px solid var(--secondary);
outline-offset: 4px;
transform: translateY(-3px);
box-shadow: var(--shadow-float);
}
@media (prefers-reduced-motion: reduce) {
.rm-card { transition: none; }
.rm-card:hover,
.rm-card:focus-visible { transform: none; }
}
/* Chevron between cards. SVG-as-background lives in the gutter
between this card's right edge and the next card's left edge,
centered horizontally in that gap. */
.rm-card::after {
content: "";
position: absolute;
top: 50%;
left: 100%;
width: 14px;
height: 24px;
/* Translate right by half the gutter so the chevron's centerline
lands at the gutter midpoint; vertical translate -50% centers
it on the card's vertical midline. */
transform: translate(calc((var(--space-5) - 14px) * 0.5), -50%);
background-image: url("data:image/svg+xml;utf8,");
background-repeat: no-repeat;
background-position: center;
background-size: 14px 24px;
pointer-events: none;
}
.rm-card:last-child::after { content: none; }
.rm-name {
font-family: var(--font-sans);
font-size: 18px;
font-weight: 600;
letter-spacing: -0.005em;
color: inherit;
margin: 0;
}
.rm-italic {
font-family: var(--font-serif);
font-style: italic;
font-weight: 400;
font-size: var(--text-body-md);
line-height: 1.35;
color: rgba(255, 252, 247, 0.86);
margin: 0;
}
/* Cross-cutting band — sits underneath the row, spans the same
width. Paper surface (not walnut) so the eye reads "continuous
infrastructure" rather than "fifth step". */
.rm-band {
width: 100%;
max-width: var(--content-max);
background: var(--surface-container);
color: var(--on-surface);
border-radius: var(--radius-lg);
padding: var(--space-5) var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-2);
box-shadow: var(--shadow-ambient);
}
.rm-band-name {
font-family: var(--font-sans);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--on-surface-variant);
margin: 0;
}
.rm-band-italic {
font-family: var(--font-serif);
font-style: italic;
font-weight: 400;
font-size: var(--text-body-md);
line-height: 1.4;
color: var(--on-surface);
margin: 0;
}
.rm-foot {
width: 100%;
max-width: var(--content-max);
font-family: var(--font-serif);
font-style: italic;
font-weight: 400;
font-size: var(--text-body-sm);
line-height: 1.4;
color: var(--on-surface-muted);
text-align: center;
margin: 0;
}
/* Narrow desktop — drop to a 2×2 grid of stage cards, same
breakpoint .platform-card-grid uses. The horizontal chevron
between columns no longer makes sense in 2×2, so suppress it;
the band + footer keep their full-width layout. */
@media (max-width: 960px) {
.rm-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rm-card::after { content: none; }
}
/* =============================================================
ROADMAP CARD MORPH — same-element expansion (no modal)
The clicked card IS the expanded panel — same DOM element,
same walnut surface, same border-radius. On expand the row
grid flips from 4-up × 1 row to 6-up × 2 rows: the clicked
card spans columns 2–5 of row 1 (≈66% of the row's content
width); the other three cards drop to row 2, each spanning
2 columns, so they remain visible below. The transition is
FLIP-driven from initRoadmap in platform.js.
============================================================= */
/* Expanded row layout — six logical columns, two rows.
Active when one card has .is-expanded; off otherwise. */
.rm-row.has-expanded {
grid-template-columns: repeat(6, minmax(0, 1fr));
grid-template-rows: auto auto;
row-gap: var(--space-6);
}
.rm-row.has-expanded .rm-card.is-expanded {
grid-column: 2 / span 4;
grid-row: 1;
}
.rm-row.has-expanded .rm-card:not(.is-expanded) {
grid-column: span 2;
grid-row: 2;
/* Subtle recede — readable but visually secondary. */
opacity: 0.7;
}
/* Default body / close-button states (collapsed). The body sits
in the DOM but is removed from layout via display:none so the
card's collapsed face stays compact. */
.rm-card-body { display: none; }
.rm-card-close { display: none; }
/* Expanded card — visual treatment. Same brown surface, same
radius. The walnut accent does not change; only the size, the
content arrangement, and a stronger drop-shadow change. */
.rm-card.is-expanded {
/* Walnut surface stays. Stronger shadow reinforces "floating
above the row of cards below". */
box-shadow:
0 24px 48px -16px rgba(56, 56, 49, 0.20),
0 6px 16px -6px rgba(56, 56, 49, 0.10);
padding: var(--space-7) clamp(2rem, 4vw, 3rem);
cursor: default;
z-index: 2;
}
/* Suppress the inter-card chevron on the expanded card and any
neighbour in row 2 — the visual sequence is interrupted while
one card is featured. */
.rm-row.has-expanded .rm-card::after { content: none; }
/* Expanded body — visible, normal flow inside the card. */
.rm-card.is-expanded .rm-card-body {
display: block;
margin-top: var(--space-5);
}
/* Title + subtitle sit at the top of the expanded panel. Subtitle
gets a small bottom margin to separate it from the intro. */
.rm-card.is-expanded .rm-name {
font-size: clamp(1.5rem, 2.4vw, 2rem);
margin: 0;
}
.rm-card.is-expanded .rm-italic {
font-size: var(--text-body-lg);
margin: 0 0 var(--space-2) 0;
color: rgba(255, 252, 247, 0.86);
}
/* Intro paragraph — serif, lightly literary. Inside the walnut
card the colour is the card's on-secondary tone, slightly
softened. */
.rm-card-intro {
font-family: var(--font-serif);
font-weight: 400;
font-size: var(--text-body-lg);
line-height: var(--leading-relaxed);
color: rgba(255, 252, 247, 0.92);
margin: 0 0 var(--space-5) 0;
}
.rm-card-section-label {
font-family: var(--font-sans);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: rgba(255, 252, 247, 0.62);
margin: 0 0 var(--space-3) 0;
}
.rm-card-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.rm-card-list li {
font-family: var(--font-sans);
font-size: var(--text-body-md);
line-height: 1.45;
color: rgba(255, 252, 247, 0.94);
padding-left: 18px;
position: relative;
}
/* Cream bullet against the walnut background. */
.rm-card-list li::before {
content: "";
position: absolute;
left: 4px;
top: 0.55em;
width: 5px;
height: 5px;
border-radius: 50%;
background: rgba(255, 252, 247, 0.78);
}
/* Close (×) button — top-right of the expanded card. Same
visual idiom as the rest of the deck's circular hit targets. */
.rm-card.is-expanded .rm-card-close {
display: inline-flex;
position: absolute;
top: var(--space-4);
right: var(--space-4);
width: 28px;
height: 28px;
align-items: center;
justify-content: center;
background: transparent;
border: 0;
border-radius: 999px;
color: rgba(255, 252, 247, 0.70);
cursor: pointer;
transition: background 180ms ease, color 180ms ease;
}
.rm-card.is-expanded .rm-card-close:hover,
.rm-card.is-expanded .rm-card-close:focus-visible {
background: rgba(255, 252, 247, 0.10);
color: rgba(255, 252, 247, 0.95);
}
.rm-card.is-expanded .rm-card-close:focus-visible {
outline: 2px solid rgba(255, 252, 247, 0.55);
outline-offset: 2px;
}
/* Reduced motion — let the layout change happen without a FLIP
transform animation. Cards still toggle states; just no
translate/scale tweening between them. */
@media (prefers-reduced-motion: reduce) {
.rm-card { transition: opacity 200ms ease !important; }
.rm-card.is-expanded,
.rm-row.has-expanded .rm-card { transform: none !important; }
}
/* =============================================================
"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 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 1–4 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 1–4; 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)); }
}
/* =============================================================
WIKI DEEP-DIVE — #wiki-deepdive
Pinned scrubbed five-beat section, mirroring #platform-layers's
structure: a .pl-pin-style shell (.wd-pin) with the reused
.pl-pin-header for title/subtitle, and a three-column .wd-body
below. platform.js (initWiki) drives the beats.
Visual contract:
• LEFT (scatter) — muted, jittered, "messy"
• MIDDLE (compiler) — walnut/secondary accent (only "active"
element on the slide; reads as configurable engine)
• RIGHT (wiki mock) — calm, neutral; inline citations
use the same walnut accent so the trust thread (Beat 4)
has a colour to follow back to the source
============================================================= */
#wiki-deepdive {
position: relative;
width: 100%;
background: var(--background);
color: var(--on-surface);
}
/* Pin shell — vertical stack: header at top, action band centred
vertically below. The band has explicit height (58vh) so all
three zones share a top edge, a bottom edge, and a centreline.
Header gets its own row via grid; body row takes the rest and
centres the action band inside it. */
.wd-pin {
position: relative;
width: 100%;
height: 100vh;
display: grid;
grid-template-rows: auto 1fr;
align-items: start;
padding: clamp(5rem, 12vh, 9rem) clamp(2rem, 5vw, 7rem) clamp(1.5rem, 3vh, 2.5rem);
box-sizing: border-box;
}
.wd-pin .pl-pin-header { grid-row: 1; justify-self: center; }
.wd-pin .pl-pin-title em { font-style: italic; font-weight: 400; }
/* Beat-0 anchor — the architecture grid's Wiki card, scaled up
and centered over the action band. platform.js holds it at
opacity 1 / scale 1.6 during Beat 0 and fades it down as Beat 1
reveals the band. The pl-card visual is identical to the one
in #platform-layers; same DOM class, same tokens. */
.wd-anchor {
position: absolute;
top: 50%;
left: 50%;
width: clamp(220px, 22vw, 320px);
transform: translate(-50%, -50%) scale(1);
pointer-events: none;
z-index: 4;
opacity: 0; /* GSAP fades this in for Beat 0 */
}
.wd-anchor .pl-card { box-shadow: var(--shadow-float); }
/* Action band — the shared 55–60vh strip. Five-column grid:
three zones with two small chevron columns sitting in the
gaps. The chevrons take their natural width; outer gap is
wider than before so the compiler reads as an equal-weight
peer of the cluster and stack, with real whitespace on both
sides instead of crowding either neighbour. */
.wd-body {
grid-row: 2;
align-self: center;
justify-self: center;
width: 100%;
max-width: var(--content-max);
height: clamp(360px, 58vh, 620px);
display: grid;
grid-template-columns:
minmax(0, 1.15fr) auto
minmax(0, 0.78fr) auto
minmax(0, 1.15fr);
/* +20% horizontal whitespace between zones from the previous
`clamp(2.25rem, 5vw, 5rem)`. Wider gutters reinforce the
three zones as distinct movements rather than a continuous
band, and give the chevrons more room to breathe between
them. */
gap: clamp(2.7rem, 6vw, 6rem);
align-items: stretch;
position: relative;
z-index: 2;
}
/* Chevron between zones — same geometric mark used in
#platform-roadmap. Vertically centred via align-self; sized
below the body text so it reads as a hint, not a sign. */
.wd-chevron {
align-self: center;
width: 14px;
height: 24px;
background-image: url("data:image/svg+xml;utf8,");
background-repeat: no-repeat;
background-position: center;
background-size: 14px 24px;
opacity: 0.55;
}
/* Zone column. Top section (labels) is auto-height; the visual
below fills remaining height. A consistent .wd-zone-head wrapper
keeps the eyebrow/name lines aligned across all three columns. */
.wd-zone {
display: flex;
flex-direction: column;
min-height: 0;
gap: var(--space-3);
}
/* Shared top-label block. Reserves enough height for an optional
sub-caption line (used by the Fenja Wiki zone) so all three
columns hand off to their visual at the same Y baseline. */
.wd-zone-eyebrow {
font-family: var(--font-sans);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--on-surface-variant);
margin: 0;
}
.wd-zone-name {
font-family: var(--font-sans);
font-size: 15px;
font-weight: 600;
letter-spacing: -0.005em;
color: var(--on-surface);
margin: 2px 0 0 0;
}
/* Sub-caption directly under the zone name (used by Fenja Wiki
and Compiler). Italic serif, muted — matches the rest of the
deck's secondary captions. Reserved baseline-height block so
zones without a sub-caption keep the same handoff Y. */
.wd-zone-sub {
font-family: var(--font-serif);
font-style: italic;
font-size: 12.5px;
line-height: 1.4;
color: var(--on-surface-muted);
margin: 4px 0 0 0;
min-height: 1em;
}
/* ─── LEFT: Scattered knowledge ───────────────────────────── */
.wd-scatter {
position: relative;
flex: 1 1 auto;
margin-top: var(--space-3);
overflow: hidden; /* bound the cluster strictly inside */
}
/* Icons absolutely positioned via inline --tx / --ty / --r / --s.
--tx/--ty are top-left offsets in % of the scatter zone — the
cluster stays inside the action band rather than spreading
floor-to-ceiling like the previous arrangement. */
.wd-doc {
position: absolute;
left: var(--tx, 0);
top: var(--ty, 0);
transform: rotate(var(--r, 0deg)) scale(var(--s, 1));
transform-origin: top left;
color: var(--on-surface-muted);
display: block;
width: clamp(46px, 4.6vw, 66px);
/* --o is the per-icon opacity target (1 = foreground, ~0.45
= background pile). Read by initWiki at reveal-time so the
stagger fades each icon to its own opacity, not all to 1. */
opacity: var(--o, 1);
filter: drop-shadow(0 4px 6px rgba(56, 56, 49, 0.05));
transition: color 280ms ease, filter 280ms ease;
}
.wd-doc svg { display: block; width: 100%; height: auto; }
.wd-doc--slide { width: clamp(60px, 6vw, 86px); }
.wd-doc--tacit { width: clamp(58px, 5.8vw, 84px); }
.wd-doc--note { width: clamp(42px, 4.2vw, 58px); }
.wd-doc--mail { width: clamp(54px, 5.4vw, 76px); }
/* Trust-beat source-tint: the originating document on the left
subtly lifts toward charcoal + a tiny upscale. platform.js
toggles .is-source on the matching .wd-doc at Beat 4. */
.wd-doc.is-source {
color: var(--on-surface);
filter: drop-shadow(0 6px 10px rgba(56, 56, 49, 0.10));
}
/* ─── MIDDLE: Fenja AI Compiler ───────────────────────────── */
/* The compiler zone overrides the default flex layout used by
the other zones so the card can be locked to the zone's exact
vertical centre (matching the chevron midline on either side).
The labels are grouped in .wd-compiler-head and anchored just
above the card so their spacing follows it. */
.wd-zone--compiler {
position: relative;
display: block;
text-align: center;
}
.wd-zone--compiler .wd-zone-eyebrow,
.wd-zone--compiler .wd-zone-name,
.wd-zone--compiler .wd-zone-sub {
text-align: center;
}
/* Label group anchored at the TOP of the zone so the title sits
on the same horizontal baseline as the eyebrows in the other
two zones (Scattered knowledge / Structured output). The card
below is still locked to zone-centre via absolute positioning
— the resulting gap between the title block and the card is
intentional.
Flex + gap match the other zones' .wd-zone container so the
internal rhythm between eyebrow → name → sub is the same as
the left and right columns. */
.wd-compiler-head {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 100%;
max-width: 280px;
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.wd-compiler-head .wd-zone-eyebrow { margin: 0; }
.wd-compiler-head .wd-zone-name { margin: 2px 0 0 0; }
.wd-compiler-head .wd-zone-sub { margin: 4px 0 0 0; min-height: 1em; }
/* Rules card — outline + paper fill. Locked to the zone's
vertical centre via absolute positioning so its midline
aligns exactly with the chevrons in the gutters on either
side (both centred in the same grid row). */
.wd-compiler {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 100%;
max-width: 240px;
margin: 0;
padding: var(--space-4) var(--space-5);
background: var(--surface-container-lowest);
color: var(--on-surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-ambient);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--space-3);
}
.wd-compiler-label {
font-family: var(--font-sans);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--on-surface-variant);
}
.wd-compiler-rules {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 11px;
width: 100%;
flex: 0 0 auto;
}
.wd-compiler-rules li {
display: flex;
align-items: center;
gap: 10px;
}
.wd-rule-toggle {
width: 22px;
height: 12px;
border-radius: 999px;
background: var(--surface-container);
border: 1px solid var(--outline-variant);
position: relative;
flex-shrink: 0;
}
.wd-rule-toggle::after {
content: "";
position: absolute;
top: 1px;
left: 1px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--on-surface-muted);
transition: left 200ms ease, background 200ms ease;
}
.wd-rule-toggle.is-on {
background: var(--surface-container-high);
}
.wd-rule-toggle.is-on::after {
left: 12px;
background: var(--on-surface);
}
.wd-rule-line {
flex: 1 1 auto;
height: 1px;
background: var(--outline-variant);
}
/* ─── RIGHT: Abstract layered page stack ───────────────────
Three page-shaped cards stacked with depth. Each subsequent
card is offset toward the TOP-RIGHT of the one behind (≈60%
overlap), so the eye reads back → mid → front. The frontmost
card is sharp; cards behind are progressively blurred — as if
each layer in front fogs the cards beneath. */
.wd-stack {
position: relative;
flex: 1 1 auto;
margin-top: var(--space-3);
/* Card sizing reflects two reductions from the previous
pass:
Step A — internal whitespace between body and source list
is halved (no `margin-top: auto` on the source
list; tighter divider margins), giving a ~20%
height reduction without changing block sizes.
Step B — the whole stack is then reduced by a further
~30% in both dimensions.
Combined: roughly 0.8 × 0.7 = 0.56× the previous size. */
--wd-card-w: clamp(140px, 12vw, 200px);
--wd-card-h: clamp(160px, 20vh, 225px);
/* Per-card shift tightened to ~22% (≈78% overlap) so the
three cards read as one thing with depth rather than three
drifting pages. Same top-right offset direction; the back-
blur depth effect is unchanged. */
--wd-card-step-x: 22%;
--wd-card-step-y: 22%;
}
.wd-stack-card {
position: absolute;
width: var(--wd-card-w);
height: var(--wd-card-h);
background: var(--surface-container-lowest);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
box-shadow:
0 6px 14px -8px rgba(56, 56, 49, 0.10),
0 2px 6px -3px rgba(56, 56, 49, 0.06);
padding: 11px 11px 11px 22px;
display: flex;
flex-direction: column;
}
.wd-stack-content {
display: flex;
flex-direction: column;
gap: 5px;
flex: 1 1 auto;
min-height: 0;
}
/* Per-depth offsets. The back card sits in the bottom-left, the
middle card shifts ~36% right & up, the front card another
~36% right & up — total stack runs upper-right. */
.wd-stack-card[data-depth="back"] {
left: 0;
top: calc(var(--wd-card-step-y) * 2);
z-index: 1;
}
.wd-stack-card[data-depth="mid"] {
left: var(--wd-card-step-x);
top: var(--wd-card-step-y);
z-index: 2;
}
.wd-stack-card[data-depth="front"] {
left: calc(var(--wd-card-step-x) * 2);
top: 0;
z-index: 3;
}
/* Thin vertical rail on the left edge — abstract sidebar/nav,
not labelled tabs. Three short ticks sit on the rail. */
.wd-stack-rail {
position: absolute;
top: 13px;
bottom: 13px;
left: 11px;
width: 1px;
background: var(--outline-variant);
}
.wd-stack-rail::before,
.wd-stack-rail::after {
content: "";
position: absolute;
left: -3px;
width: 7px;
height: 1.2px;
background: var(--on-surface-muted);
opacity: 0.55;
}
.wd-stack-rail::before { top: 12px; }
.wd-stack-rail::after { top: 30px; }
/* Title bar — short underline-style block at the top of each
page card. Thicker and darker than body blocks below. */
.wd-stack-title-bar {
display: block;
height: 5px;
width: 58%;
background: var(--on-surface);
border-radius: 2px;
opacity: 0.55;
margin-bottom: 2px;
}
/* Subtitle — shorter darker block between the two body
sections. Smaller than the title bar; reads as an H2. */
.wd-stack-subhead {
display: block;
height: 4px;
width: 34%;
background: var(--on-surface);
border-radius: 2px;
opacity: 0.42;
margin-top: 2px;
margin-bottom: 1px;
}
/* Body line — a flex row of inline pieces (blocks, brackets,
citations). Keeps everything on a baseline; spacing between
pieces is the gap. */
.wd-stack-line {
display: flex;
align-items: center;
gap: 4px;
position: relative;
}
/* Body block — one inline run of "text". Width comes from a
per-instance --w custom prop so each line reads as varying
prose. The default keeps a fallback if --w isn't set. */
.wd-stack-block {
display: block;
height: 3.5px;
width: var(--w, 100%);
background: var(--surface-container-high);
border-radius: 999px;
flex: 0 0 auto;
}
.wd-stack-block--short { width: 64%; }
/* Wiki-style internal-link marker — a proper square-bracket
pair enclosing a short horizontal block. Renders as `[ ── ]`,
reading as an inline link to another subject. The bracket
characters come from ::before / ::after; the inner block is
a real child element so per-instance --bw varies its width. */
.wd-stack-bracket {
display: inline-flex;
align-items: center;
gap: 2px;
flex: 0 0 auto;
font-family: var(--font-mono);
font-size: 10px;
line-height: 1;
color: var(--on-surface-muted);
padding: 0 1px;
}
.wd-stack-bracket::before { content: "["; opacity: 0.75; }
.wd-stack-bracket::after { content: "]"; opacity: 0.75; }
.wd-stack-bracket > span {
display: inline-block;
height: 3.5px;
width: var(--bw, 14px);
background: var(--surface-container-high);
border-radius: 999px;
}
/* Citation markers sit inline at the trailing edge of the
block/segment they cite. Walnut so they thread the trust
beat. */
.wd-cite {
font-family: var(--font-mono);
font-size: 7.5px;
font-weight: 600;
color: var(--secondary);
line-height: 1;
padding: 0 1px;
border-radius: 3px;
transition: color 220ms ease, transform 220ms ease;
display: inline-block;
margin-left: 1px;
position: relative;
top: -2px;
}
.wd-cite.is-lit {
/* Soft walnut pill behind the lit marker. Subtle by design —
pairs with the scale pulse + source highlight + arc draw
to register without dominating. */
background: rgba(120, 95, 83, 0.16);
padding: 1px 3px;
}
/* Thin divider — separates the body from the source list at
the bottom of each card. Step A from the size-reduction pass
halved the whitespace here, bringing the body and source
list into close contact (modest gap on either side of the
divider, no auto-margin pushing the sources to the bottom). */
.wd-stack-divider {
border: 0;
height: 1px;
background: var(--outline-variant);
margin: 3px 0 2px 0;
width: 100%;
}
/* Source list — three numbered horizontal lines below the
divider. Sits directly under the divider (no auto top
margin) so the bottom of the card is tight rather than
reserving empty space. data-source on each entry pairs it
with an in-text citation so Beat 4 highlights the matching
bottom entry alongside the lit inline marker. */
.wd-stack-sources {
display: flex;
flex-direction: column;
gap: 2px;
}
.wd-stack-source {
display: flex;
align-items: center;
gap: 4px;
transition: opacity 220ms ease;
}
.wd-stack-source sup {
font-family: var(--font-mono);
font-size: 7px;
font-weight: 600;
color: var(--on-surface-muted);
line-height: 1;
min-width: 5px;
}
.wd-stack-source-line {
flex: 1 1 auto;
height: 1px;
background: var(--surface-container-high);
border-radius: 999px;
max-width: 70%;
}
/* Beat-4 highlight on the source entry that pairs with the
lit citation. Subtle tint — kept low-key so the lit marker
above the divider stays the focus. */
.wd-stack-source.is-paired sup {
color: var(--secondary);
}
.wd-stack-source.is-paired .wd-stack-source-line {
background: var(--secondary);
opacity: 0.55;
}
/* Narrow desktop — single-column stack so the left → middle →
right narrative direction is preserved. Same breakpoint other
sections use. Action band height + anchor are disabled in
this mode, and the compiler's absolute positioning (used to
lock to the chevron midline on wider screens) collapses back
to natural flow. */
@media (max-width: 960px) {
.wd-body {
grid-template-columns: minmax(0, 1fr);
gap: var(--space-8);
height: auto;
}
.wd-chevron { display: none; }
.wd-anchor { width: 200px; }
.wd-scatter { min-height: 220px; }
.wd-stack { min-height: 280px; }
.wd-zone--compiler { display: flex; flex-direction: column; }
.wd-compiler-head {
position: static;
transform: none;
max-width: none;
}
.wd-compiler {
position: static;
transform: none;
margin: var(--space-4) auto 0;
}
}
/* 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; }
/* Wiki deep-dive: release the pin, stack columns, show all
content + flow lines in their final composed state. Back-
blur on the stack is also disabled here per the brief:
reduced motion gets the final composed state, no staged
blur animation. */
#wiki-deepdive .wd-pin {
height: auto;
padding: var(--space-12) clamp(2rem, 5vw, 7rem);
gap: var(--space-8);
display: flex;
flex-direction: column;
}
#wiki-deepdive .wd-anchor { display: none; }
#wiki-deepdive .wd-body {
grid-template-columns: minmax(0, 1fr);
gap: var(--space-8);
height: auto;
}
#wiki-deepdive .wd-cite { color: var(--secondary); }
#wiki-deepdive .wd-stack-card { filter: none !important; }
/* Release the compiler's absolute positioning so labels and
the card stack naturally below each other when the pin is
released. */
#wiki-deepdive .wd-zone--compiler {
display: flex;
flex-direction: column;
}
#wiki-deepdive .wd-compiler-head {
position: static;
transform: none;
max-width: none;
}
#wiki-deepdive .wd-compiler {
position: static;
transform: none;
margin: var(--space-4) auto 0;
}
}