Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ArrowDown, Github } from "lucide-react";

import { PILLARS, tint } from "@/app/[lang]/components/marketing-tokens";

import { type HeroDictionary } from "./i18n/hero.types";
import { type HeroDictionary } from "../home-page-section/hero/i18n/hero.types";

export function HeroOpenSourceChip({ t }: { t: HeroDictionary["openSource"] }) {
return (
Expand Down
58 changes: 58 additions & 0 deletions apps/web/src/app/[lang]/components/hero-shell/hero-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { type ReactNode } from "react";

import { type HeroDictionary } from "../home-page-section/hero/i18n/hero.types";
import { HeroGlassFrame } from "./hero-glass-frame";
import { HeroOpenSourceChip } from "./hero-open-source-chip";

const CONTENT_INSET =
"md:top-[calc(clamp(88px,11.5vh,126px)+14px)] md:right-[calc(clamp(18px,5vw,112px)+14px)] md:bottom-[calc(clamp(28px,5.5vh,76px)+14px)] md:left-[calc(clamp(18px,5vw,112px)+14px)]";

// German compounds need the wider `md` measure to keep the headline off a fourth line.
const TITLE_CLASS =
"m-0 max-w-[19ch] text-[clamp(32px,min(6.6vw,9vh),68px)] leading-[1.1] font-medium tracking-[-0.026em] text-balance text-[#131c46] [text-shadow:0_2px_24px_rgba(255,255,255,0.6)] md:max-w-[21ch]";

const SUBTITLE_CLASS =
"mt-[clamp(16px,2.8vh,26px)] max-w-[440px] text-[clamp(15px,4vw,16.5px)] leading-[1.5] text-pretty text-[#2b3a58] [text-shadow:0_2px_14px_rgba(255,255,255,0.65)]";

export function HeroShell({
alt,
openSource,
title,
subtitle,
children,
}: {
alt: string;
openSource: HeroDictionary["openSource"];
title: ReactNode;
subtitle: ReactNode;
children?: ReactNode;
}) {
return (
<section className="font-display relative z-10 flex min-h-svh flex-col bg-[linear-gradient(180deg,#dbe7f4_0%,#dbe7f4_60%,#fdfdfb_100%)] md:bg-white md:bg-none md:pb-[clamp(20px,3vh,44px)]">
<div
className="pointer-events-none absolute inset-0 hidden bg-[linear-gradient(rgba(15,23,42,0.055)_1px,transparent_1px),linear-gradient(90deg,rgba(15,23,42,0.055)_1px,transparent_1px)] [mask-image:linear-gradient(180deg,rgba(0,0,0,0)_0%,rgba(0,0,0,0.85)_16%,rgba(0,0,0,0.85)_58%,rgba(0,0,0,0)_88%)] bg-[length:68px_68px] bg-[position:center_top] md:block"
aria-hidden
/>
<div
className="pointer-events-none absolute hidden h-[120px] rounded-[50%] bg-[radial-gradient(50%_50%_at_50%_50%,rgba(15,23,42,0.07)_0%,rgba(15,23,42,0)_74%)] blur-[38px] md:right-[calc(clamp(18px,5vw,112px)+26px)] md:bottom-[calc(clamp(28px,5.5vh,76px)-86px)] md:left-[calc(clamp(18px,5vw,112px)+26px)] md:block"
aria-hidden
/>

<HeroGlassFrame alt={alt} />

<div
className={`relative z-[2] flex min-h-0 flex-1 flex-col items-center justify-center px-5 pt-24 pb-9 text-center md:absolute md:px-[clamp(40px,6vw,96px)] md:py-[clamp(24px,4vh,60px)] ${CONTENT_INSET}`}
>
<div className="mb-[clamp(16px,2.6vh,26px)]">
<HeroOpenSourceChip t={openSource} />
</div>

<h1 className={TITLE_CLASS}>{title}</h1>

<p className={SUBTITLE_CLASS}>{subtitle}</p>

{children}
</div>
</section>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useCallback, useState } from "react";

const CALENDLY_CSS = "https://assets.calendly.com/assets/external/widget.css";
const CALENDLY_JS = "https://assets.calendly.com/assets/external/widget.js";
const CALENDLY_DEMO_URL = "https://calendly.com/felix-kuennecke/scibly-demo";
const DEFAULT_DEMO_URL = "https://calendly.com/felix-kuennecke/scibly-demo";

declare global {
interface Window {
Expand All @@ -24,13 +24,13 @@ function ensureCalendlyStylesheet() {
document.head.appendChild(link);
}

export function useCalendlyPopup() {
export function useCalendlyPopup(url: string = DEFAULT_DEMO_URL) {
const [scriptReady, setScriptReady] = useState(false);

const openCalendly = useCallback(() => {
if (!window.Calendly) return;
window.Calendly.initPopupWidget({ url: CALENDLY_DEMO_URL });
}, []);
window.Calendly.initPopupWidget({ url });
}, [url]);

// Stylesheet is injected when the lazyOnload script arrives: after the
// landing page's own bytes, but before any human can click — injecting at
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { type CtaDictionary } from "./i18n/cta.types";

interface CtaSectionProps {
t: CtaDictionary;
calendlyUrl?: string;
}

const FLOATING_CHAPTERS: Array<{
Expand Down Expand Up @@ -54,10 +55,11 @@ const FLOATING_CHAPTERS: Array<{
},
];

export function CtaSection({ t }: CtaSectionProps) {
export function CtaSection({ t, calendlyUrl }: CtaSectionProps) {
const params = useParams();
const locale = typeof params.lang === "string" ? params.lang : "de";
const { openCalendly, scriptReady, calendlyScript } = useCalendlyPopup();
const { openCalendly, scriptReady, calendlyScript } =
useCalendlyPopup(calendlyUrl);

useEffect(() => {
if (window.location.hash !== "#cta-section") return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,34 @@
import { type ReactNode } from "react";

import { HeroGlassFrame } from "./hero-glass-frame";
import { HeroOpenSourceChip } from "./hero-open-source-chip";
import { HeroShell } from "../../hero-shell/hero-shell";
import { type HeroDictionary } from "./i18n/hero.types";

interface HeroProps {
t: HeroDictionary;
children?: ReactNode;
}

/**
* Copy sits inside the frame's own padding, so it clears the bezel without a
* second set of hard-coded offsets.
*/
const CONTENT_INSET =
"md:top-[calc(clamp(88px,11.5vh,126px)+14px)] md:right-[calc(clamp(18px,5vw,112px)+14px)] md:bottom-[calc(clamp(28px,5.5vh,76px)+14px)] md:left-[calc(clamp(18px,5vw,112px)+14px)]";

export function Hero({ t, children }: HeroProps) {
return (
<section className="font-display relative z-10 flex min-h-svh flex-col bg-[linear-gradient(180deg,#dbe7f4_0%,#dbe7f4_60%,#fdfdfb_100%)] md:bg-white md:bg-none md:pb-[clamp(20px,3vh,44px)]">
{/* Faint blueprint grid, faded out top and bottom so it never meets an edge */}
<div
className="pointer-events-none absolute inset-0 hidden bg-[linear-gradient(rgba(15,23,42,0.055)_1px,transparent_1px),linear-gradient(90deg,rgba(15,23,42,0.055)_1px,transparent_1px)] [mask-image:linear-gradient(180deg,rgba(0,0,0,0)_0%,rgba(0,0,0,0.85)_16%,rgba(0,0,0,0.85)_58%,rgba(0,0,0,0)_88%)] bg-[length:68px_68px] bg-[position:center_top] md:block"
aria-hidden
/>
{/* Contact shadow — grounds the frame on the page */}
<div
className="pointer-events-none absolute hidden h-[120px] rounded-[50%] bg-[radial-gradient(50%_50%_at_50%_50%,rgba(15,23,42,0.07)_0%,rgba(15,23,42,0)_74%)] blur-[38px] md:right-[calc(clamp(18px,5vw,112px)+26px)] md:bottom-[calc(clamp(28px,5.5vh,76px)-86px)] md:left-[calc(clamp(18px,5vw,112px)+26px)] md:block"
aria-hidden
/>

<HeroGlassFrame alt={t.productAlt} />

<div
className={`relative z-[2] flex min-h-0 flex-1 flex-col items-center justify-center px-5 pt-24 pb-9 text-center md:absolute md:px-[clamp(40px,6vw,96px)] md:py-[clamp(24px,4vh,60px)] ${CONTENT_INSET}`}
>
<div className="mb-[clamp(16px,2.6vh,26px)]">
<HeroOpenSourceChip t={t.openSource} />
</div>

{/* Measured in characters so the line length holds at every size. German
compounds need the wider measure to keep the headline off a fourth
line; phones stay tighter so it never runs edge to edge. */}
<h1 className="m-0 max-w-[19ch] text-[clamp(32px,min(6.6vw,9vh),68px)] leading-[1.1] font-medium tracking-[-0.026em] text-balance text-[#131c46] [text-shadow:0_2px_24px_rgba(255,255,255,0.6)] md:max-w-[21ch]">
<HeroShell
alt={t.productAlt}
openSource={t.openSource}
title={
<>
{t.title}
{/* The one word the sentence turns on, in the serif accent. */}
<span className="font-serif-accent font-normal tracking-[-0.01em] italic">
{t.titleHighlight}
</span>
{t.titleSuffix}
</h1>

<p className="mt-[clamp(16px,2.8vh,26px)] max-w-[440px] text-[clamp(15px,4vw,16.5px)] leading-[1.5] text-pretty text-[#2b3a58] [text-shadow:0_2px_14px_rgba(255,255,255,0.65)]">
{t.subtitle}
</p>

{children !== undefined ? (
<div className="relative mt-[clamp(24px,4.4vh,44px)] w-full max-w-[592px] text-left">
{children}
</div>
) : null}
</div>
</section>
</>
}
subtitle={t.subtitle}
>
{children !== undefined ? (
<div className="relative mt-[clamp(24px,4.4vh,44px)] w-full max-w-[592px] text-left">
{children}
</div>
) : null}
</HeroShell>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { GitPullRequest, Ticket } from "lucide-react";

import { CORRECT_KEY } from "@/app/[lang]/components/marketing-tokens";
import { BrandLogo } from "@/components/brand-logo";

import { type ArtifactsCopy } from "../../i18n/onboarding-offboarding.types";
import { enter, FAINT, HAIRLINE, MUTED } from "../mock/mock-theme";
import { MockWindow } from "../mock/mock-window";
import { MockWindowBar } from "../mock/mock-window-bar";
import { CapturedChip } from "./captured-chip";
import { CardCanvas } from "./card-canvas";
import { SLACK_TONE, WORK_TONE } from "./knowledge-sources-tones";

export function ArtifactsCanvas({
t,
capturedLabel,
}: {
t: ArtifactsCopy;
capturedLabel: string;
}) {
return (
<CardCanvas tone={WORK_TONE}>
<div className="absolute inset-x-5 top-5 flex flex-col gap-2.5">
<MockWindow data-bento-anim style={enter("sc-rise", 360)}>
<MockWindowBar>
<BrandLogo domain="slack.com" alt="" size={12} />
<span>{t.slackChannel}</span>
<span className="font-medium" style={{ color: FAINT }}>
{t.slackTime}
</span>
<CapturedChip tone={SLACK_TONE} label={capturedLabel} />
</MockWindowBar>
<div className="flex items-start gap-2 px-3 py-2.5">
<span
className="flex size-6 shrink-0 items-center justify-center rounded-[6px] text-[10px] font-bold text-white"
style={{ backgroundColor: SLACK_TONE.accentColor }}
>
{t.slackAuthor.charAt(0)}
</span>
<p
className="m-0 min-w-0 text-[11.5px] leading-[1.45]"
style={{ color: MUTED }}
>
<span className="text-ink font-bold">{t.slackAuthor}</span>{" "}
{t.slackText}
</p>
</div>
</MockWindow>

<MockWindow data-bento-anim style={enter("sc-rise", 480)}>
<MockWindowBar>
<GitPullRequest
size={12}
strokeWidth={2.6}
style={{ color: WORK_TONE.accentColor }}
/>
<span className="font-mono font-medium">{t.codeFile}</span>
<span
className="rounded-[4px] px-1 py-px text-[9.5px] font-bold"
style={{ backgroundColor: HAIRLINE, color: MUTED }}
>
{t.codeReview}
</span>
<CapturedChip tone={WORK_TONE} label={capturedLabel} />
</MockWindowBar>
<div className="font-mono text-[10.5px] leading-[1.7]">
<div className="flex gap-2 bg-[#ffecec] px-3 text-[#b3261e]">
<span className="w-2 select-none">-</span>
<span className="truncate">{t.codeRemoved}</span>
</div>
<div className="flex gap-2 bg-[#e6f8e0] px-3 text-[#2f6a00]">
<span className="w-2 select-none">+</span>
<span className="truncate">{t.codeAdded}</span>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-2">
<span
className="flex size-5 shrink-0 items-center justify-center rounded-full text-[8.5px] font-bold text-white"
style={{ backgroundColor: WORK_TONE.accentColor }}
>
TK
</span>
<p
className="m-0 min-w-0 truncate text-[11.5px]"
style={{ color: MUTED }}
>
{t.codeComment}
</p>
</div>
</MockWindow>

<MockWindow data-bento-anim style={enter("sc-rise", 600)}>
<MockWindowBar>
<Ticket
size={12}
strokeWidth={2.6}
style={{ color: WORK_TONE.accentColor }}
/>
<span className="font-mono font-medium">{t.ticketId}</span>
<span
className="rounded-[4px] px-1.5 py-px text-[9.5px] font-bold"
style={{
backgroundColor: CORRECT_KEY.face,
color: CORRECT_KEY.ink,
}}
>
{t.ticketStatus}
</span>
<CapturedChip tone={WORK_TONE} label={capturedLabel} />
</MockWindowBar>
<div className="flex flex-col gap-0.5 px-3 py-2.5">
<span className="text-ink text-[12px] font-bold">
{t.ticketTitle}
</span>
<span className="text-[11px]" style={{ color: MUTED }}>
{t.ticketCause}
</span>
</div>
</MockWindow>
</div>
</CardCanvas>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Check } from "lucide-react";

import { type Pillar } from "@/app/[lang]/components/marketing-tokens";

export function CapturedChip({ tone, label }: { tone: Pillar; label: string }) {
return (
<span
className="ml-auto inline-flex shrink-0 items-center gap-1 rounded-[5px] px-1.5 py-0.5 text-[9.5px] font-bold tracking-wide uppercase"
style={{ backgroundColor: tone.softColor, color: tone.accentColor }}
>
<Check size={9} strokeWidth={3.5} />
{label}
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type ReactNode } from "react";

import { type Pillar, tint } from "@/app/[lang]/components/marketing-tokens";

import { MockGroundFade } from "../mock/mock-ground-fade";
import { dottedGround } from "../mock/mock-theme";

export function CardCanvas({
tone,
children,
}: {
tone: Pillar;
children: ReactNode;
}) {
const ground = tint(tone.softColor, 42);
return (
<div
className="relative h-[328px] overflow-hidden rounded-[14px]"
style={{ backgroundColor: ground, ...dottedGround(tone) }}
aria-hidden
>
{children}
<MockGroundFade ground={ground} className="h-16" />
</div>
);
}
Loading
Loading