Spaces:
Runtime error
Runtime error
Commit ·
d326c2d
1
Parent(s): aeea10c
LETS GOOOOOOOOOOOOOOOOOOOOOOOOO
Browse files
frontend/src/pages/Landing.tsx
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
| 19 |
|
| 20 |
export const PENDING_SOURCE_KEY = "ns_pending_source";
|
| 21 |
// Bump after re-rendering public/demo.mp4 so browsers and the CDN fetch the new file.
|
| 22 |
-
const DEMO_VERSION =
|
| 23 |
|
| 24 |
const FEATURES = [
|
| 25 |
{
|
|
|
|
| 19 |
|
| 20 |
export const PENDING_SOURCE_KEY = "ns_pending_source";
|
| 21 |
// Bump after re-rendering public/demo.mp4 so browsers and the CDN fetch the new file.
|
| 22 |
+
const DEMO_VERSION = 4;
|
| 23 |
|
| 24 |
const FEATURES = [
|
| 25 |
{
|
renderer/scripts/make_demo_vo.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
The screens show the product; the voice never explains it, it only says how it feels: exploring,
|
| 4 |
excited, mesmerized. Also writes src/compositions/demoVo.ts; scenes stretch to fit their lines.
|
| 5 |
-
|
| 6 |
|
| 7 |
cd backend && PYTHONPATH=. .venv/Scripts/python ../renderer/scripts/make_demo_vo.py
|
| 8 |
"""
|
|
|
|
| 2 |
|
| 3 |
The screens show the product; the voice never explains it, it only says how it feels: exploring,
|
| 4 |
excited, mesmerized. Also writes src/compositions/demoVo.ts; scenes stretch to fit their lines.
|
| 5 |
+
make_demo_music.py writes a 47.5 s score; raise its TOTAL and rerun it if the demo grows past that.
|
| 6 |
|
| 7 |
cd backend && PYTHONPATH=. .venv/Scripts/python ../renderer/scripts/make_demo_vo.py
|
| 8 |
"""
|
renderer/src/compositions/LandingDemo.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import type { CSSProperties, ReactNode } from "react";
|
| 2 |
-
import { AbsoluteFill, Audio as SoundTrack, Easing,
|
| 3 |
import { theme } from "../theme";
|
|
|
|
|
|
|
| 4 |
|
| 5 |
/** Landing page product demo: a trip through the Notestack galaxy. 1920x1080, 30 fps, ~32 s.
|
| 6 |
* Scenes: warp in, paste URL, posts enter orbit, cited research, audio overview, Launch Kit,
|
|
@@ -9,24 +11,25 @@ import { theme } from "../theme";
|
|
| 9 |
export const DEMO_FPS = 30;
|
| 10 |
const S = (seconds: number) => Math.round(seconds * DEMO_FPS);
|
| 11 |
|
| 12 |
-
//
|
| 13 |
-
//
|
| 14 |
-
|
|
|
|
| 15 |
{ id: "warp", len: 5.2, vo: 0.6 },
|
| 16 |
{ id: "paste", len: 9.4, vo: 0.4 },
|
| 17 |
{ id: "orbit", len: 7.6, vo: 0.4 },
|
| 18 |
{ id: "research", len: 5.6, vo: 0.4 },
|
| 19 |
-
{ id: "audio", len: 5.
|
| 20 |
{ id: "launchkit", len: 7.8, vo: 0.4 },
|
| 21 |
{ id: "launchpad", len: 4.6, vo: 0.4 },
|
| 22 |
{ id: "outro", len: 4.2, vo: 0.5 },
|
| 23 |
] as const;
|
| 24 |
const XFADE = 0.5; // seconds of overlap between scenes
|
|
|
|
| 25 |
|
| 26 |
-
// Narration clip lengths
|
| 27 |
-
const VO_SECONDS: Record<(typeof
|
| 28 |
-
|
| 29 |
-
};
|
| 30 |
|
| 31 |
/** Frame ranges where the narrator is speaking, in composition time. */
|
| 32 |
const VO_WINDOWS: [number, number][] = (() => {
|
|
@@ -192,9 +195,9 @@ function Warp() {
|
|
| 192 |
<br />
|
| 193 |
<span style={{ color: theme.white, textShadow: `0 0 30px ${BLUE}, 0 0 80px ${BLUE}` }}>in orbit.</span>
|
| 194 |
</h1>
|
| 195 |
-
<
|
| 196 |
-
|
| 197 |
-
</
|
| 198 |
</AbsoluteFill>
|
| 199 |
<Flare at={50} duration={30} y={46} />
|
| 200 |
</AbsoluteFill>
|
|
@@ -214,7 +217,7 @@ function Paste() {
|
|
| 214 |
return (
|
| 215 |
<AbsoluteFill>
|
| 216 |
<WarpField speed={1.2} />
|
| 217 |
-
<Caption eyebrow="Step 01" title="
|
| 218 |
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center", flexDirection: "column", gap: 44, paddingTop: 120 }}>
|
| 219 |
<div style={{ ...panel, display: "flex", alignItems: "center", gap: 18, padding: 14, width: 1100, borderRadius: 999 }}>
|
| 220 |
<div style={{ flex: 1, fontFamily: theme.body, fontSize: 40, color: url ? theme.white : "rgba(255,255,255,0.4)", paddingLeft: 30 }}>
|
|
@@ -306,7 +309,7 @@ function Orbit() {
|
|
| 306 |
return (
|
| 307 |
<AbsoluteFill>
|
| 308 |
<WarpField speed={0.8} />
|
| 309 |
-
<Caption eyebrow="Step 02" title="
|
| 310 |
<svg width={width} height={height} style={{ position: "absolute" }}>
|
| 311 |
<defs>
|
| 312 |
<radialGradient id="planet" cx="40%" cy="35%" r="70%">
|
|
@@ -373,7 +376,7 @@ function Research() {
|
|
| 373 |
return (
|
| 374 |
<AbsoluteFill>
|
| 375 |
<WarpField speed={0.6} count={260} />
|
| 376 |
-
<Caption eyebrow="Research" title="
|
| 377 |
<div style={{ position: "absolute", left: 140, right: 140, top: 360, display: "grid", gridTemplateColumns: "1.25fr 1fr", gap: 40 }}>
|
| 378 |
<div style={{ ...panel, padding: 36, display: "grid", gap: 22, alignContent: "start" }}>
|
| 379 |
<div style={{ justifySelf: "end", padding: "14px 22px", borderRadius: "18px 18px 4px 18px", background: `${BLUE}22`, border: `1px solid ${BLUE}66`, fontFamily: theme.body, fontSize: 30, color: theme.white }}>
|
|
@@ -426,7 +429,7 @@ function Audio() {
|
|
| 426 |
return (
|
| 427 |
<AbsoluteFill>
|
| 428 |
<WarpField speed={0.7} count={260} />
|
| 429 |
-
<Caption eyebrow="Audio overview" title="
|
| 430 |
<div style={{ position: "absolute", left: 140, right: 140, top: 380, display: "grid", gap: 40 }}>
|
| 431 |
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 10, height: 220 }}>
|
| 432 |
{Array.from({ length: 64 }).map((_, i) => {
|
|
@@ -466,7 +469,7 @@ function LaunchKit() {
|
|
| 466 |
return (
|
| 467 |
<AbsoluteFill>
|
| 468 |
<WarpField speed={0.9} count={260} />
|
| 469 |
-
<Caption eyebrow="Launch Kit" title="
|
| 470 |
<div style={{ position: "absolute", left: 140, right: 140, top: 380, display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 28 }}>
|
| 471 |
{cards.map((c, i) => {
|
| 472 |
const s = spring({ frame: frame - 10 - i * 7, fps, config: { damping: 14 } });
|
|
@@ -498,7 +501,7 @@ function Launchpad() {
|
|
| 498 |
return (
|
| 499 |
<AbsoluteFill>
|
| 500 |
<WarpField speed={0.8} count={260} />
|
| 501 |
-
<Caption eyebrow="Launchpad" title="
|
| 502 |
<div style={{ position: "absolute", left: 140, top: 360, display: "grid", gridTemplateColumns: "repeat(7, 120px)", gap: 12 }}>
|
| 503 |
{Array.from({ length: 28 }).map((_, i) => {
|
| 504 |
const on = lit.includes(i) && frame > 8 + lit.indexOf(i) * 6;
|
|
@@ -564,8 +567,9 @@ function Outro() {
|
|
| 564 |
<WarpField speed={interpolate(frame, [0, 60, 114], [0.5, 2, 12])} streak={interpolate(frame, [70, 114], [0, 1], { extrapolateLeft: "clamp" })} />
|
| 565 |
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center", textAlign: "center", gap: 30 }}>
|
| 566 |
{/* The navbar mark (frontend/public/logo.svg, copied to public/logo.svg). */}
|
| 567 |
-
<
|
| 568 |
-
|
|
|
|
| 569 |
<p style={{ margin: 0, fontFamily: theme.body, fontSize: 40, color: "rgba(255,255,255,0.8)", opacity: cta }}>Your knowledge, in orbit.</p>
|
| 570 |
<p style={{ margin: 0, padding: "18px 40px", borderRadius: 999, background: BLUE, color: theme.white, fontFamily: theme.body, fontWeight: 600, fontSize: 32, opacity: cta, boxShadow: `0 0 40px ${BLUE}` }}>
|
| 571 |
Start free at notestack.ai
|
|
|
|
| 1 |
import type { CSSProperties, ReactNode } from "react";
|
| 2 |
+
import { AbsoluteFill, Audio as SoundTrack, Easing, interpolate, random, Sequence, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
|
| 3 |
import { theme } from "../theme";
|
| 4 |
+
import { DEMO_VO_SECONDS } from "./demoVo";
|
| 5 |
+
import { Lockup } from "./Lockup";
|
| 6 |
|
| 7 |
/** Landing page product demo: a trip through the Notestack galaxy. 1920x1080, 30 fps, ~32 s.
|
| 8 |
* Scenes: warp in, paste URL, posts enter orbit, cited research, audio overview, Launch Kit,
|
|
|
|
| 11 |
export const DEMO_FPS = 30;
|
| 12 |
const S = (seconds: number) => Math.round(seconds * DEMO_FPS);
|
| 13 |
|
| 14 |
+
// Narration (public/demo-vo/*.mp3, voiced by "Nora Vale, Mission Control"): each line starts `vo`
|
| 15 |
+
// seconds into its scene. Scenes keep their minimum length for the animation and stretch when a line
|
| 16 |
+
// needs more room, so it always ends with a breath before the next scene's line.
|
| 17 |
+
const BASE_SCENES = [
|
| 18 |
{ id: "warp", len: 5.2, vo: 0.6 },
|
| 19 |
{ id: "paste", len: 9.4, vo: 0.4 },
|
| 20 |
{ id: "orbit", len: 7.6, vo: 0.4 },
|
| 21 |
{ id: "research", len: 5.6, vo: 0.4 },
|
| 22 |
+
{ id: "audio", len: 5.1, vo: 0.4 },
|
| 23 |
{ id: "launchkit", len: 7.8, vo: 0.4 },
|
| 24 |
{ id: "launchpad", len: 4.6, vo: 0.4 },
|
| 25 |
{ id: "outro", len: 4.2, vo: 0.5 },
|
| 26 |
] as const;
|
| 27 |
const XFADE = 0.5; // seconds of overlap between scenes
|
| 28 |
+
const BREATH = 0.8; // gap between the end of one line and the next scene's line (includes the crossfade)
|
| 29 |
|
| 30 |
+
// Narration clip lengths, written by scripts/make_demo_vo.py; the music ducks under them.
|
| 31 |
+
const VO_SECONDS: Record<(typeof BASE_SCENES)[number]["id"], number> = DEMO_VO_SECONDS;
|
| 32 |
+
const SCENES = BASE_SCENES.map((s) => ({ ...s, len: Math.max(s.len, s.vo + VO_SECONDS[s.id] + BREATH) }));
|
|
|
|
| 33 |
|
| 34 |
/** Frame ranges where the narrator is speaking, in composition time. */
|
| 35 |
const VO_WINDOWS: [number, number][] = (() => {
|
|
|
|
| 195 |
<br />
|
| 196 |
<span style={{ color: theme.white, textShadow: `0 0 30px ${BLUE}, 0 0 80px ${BLUE}` }}>in orbit.</span>
|
| 197 |
</h1>
|
| 198 |
+
<div style={{ marginTop: 44, opacity: sub }}>
|
| 199 |
+
<Lockup size={60} glow={0.7} />
|
| 200 |
+
</div>
|
| 201 |
</AbsoluteFill>
|
| 202 |
<Flare at={50} duration={30} y={46} />
|
| 203 |
</AbsoluteFill>
|
|
|
|
| 217 |
return (
|
| 218 |
<AbsoluteFill>
|
| 219 |
<WarpField speed={1.2} />
|
| 220 |
+
<Caption eyebrow="Step 01" title="Feel the pull of something new" />
|
| 221 |
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center", flexDirection: "column", gap: 44, paddingTop: 120 }}>
|
| 222 |
<div style={{ ...panel, display: "flex", alignItems: "center", gap: 18, padding: 14, width: 1100, borderRadius: 999 }}>
|
| 223 |
<div style={{ flex: 1, fontFamily: theme.body, fontSize: 40, color: url ? theme.white : "rgba(255,255,255,0.4)", paddingLeft: 30 }}>
|
|
|
|
| 309 |
return (
|
| 310 |
<AbsoluteFill>
|
| 311 |
<WarpField speed={0.8} />
|
| 312 |
+
<Caption eyebrow="Step 02" title="Shimmering. Impossible to look away." />
|
| 313 |
<svg width={width} height={height} style={{ position: "absolute" }}>
|
| 314 |
<defs>
|
| 315 |
<radialGradient id="planet" cx="40%" cy="35%" r="70%">
|
|
|
|
| 376 |
return (
|
| 377 |
<AbsoluteFill>
|
| 378 |
<WarpField speed={0.6} count={260} />
|
| 379 |
+
<Caption eyebrow="Research" title="A spark. Suddenly, wide awake." />
|
| 380 |
<div style={{ position: "absolute", left: 140, right: 140, top: 360, display: "grid", gridTemplateColumns: "1.25fr 1fr", gap: 40 }}>
|
| 381 |
<div style={{ ...panel, padding: 36, display: "grid", gap: 22, alignContent: "start" }}>
|
| 382 |
<div style={{ justifySelf: "end", padding: "14px 22px", borderRadius: "18px 18px 4px 18px", background: `${BLUE}22`, border: `1px solid ${BLUE}66`, fontFamily: theme.body, fontSize: 30, color: theme.white }}>
|
|
|
|
| 429 |
return (
|
| 430 |
<AbsoluteFill>
|
| 431 |
<WarpField speed={0.7} count={260} />
|
| 432 |
+
<Caption eyebrow="Audio overview" title="Lean back. Let it wash over you." />
|
| 433 |
<div style={{ position: "absolute", left: 140, right: 140, top: 380, display: "grid", gap: 40 }}>
|
| 434 |
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 10, height: 220 }}>
|
| 435 |
{Array.from({ length: 64 }).map((_, i) => {
|
|
|
|
| 469 |
return (
|
| 470 |
<AbsoluteFill>
|
| 471 |
<WarpField speed={0.9} count={260} />
|
| 472 |
+
<Caption eyebrow="Launch Kit" title="Your pulse quickens." />
|
| 473 |
<div style={{ position: "absolute", left: 140, right: 140, top: 380, display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 28 }}>
|
| 474 |
{cards.map((c, i) => {
|
| 475 |
const s = spring({ frame: frame - 10 - i * 7, fps, config: { damping: 14 } });
|
|
|
|
| 501 |
return (
|
| 502 |
<AbsoluteFill>
|
| 503 |
<WarpField speed={0.8} count={260} />
|
| 504 |
+
<Caption eyebrow="Launchpad" title="Three, two, one. Flying." />
|
| 505 |
<div style={{ position: "absolute", left: 140, top: 360, display: "grid", gridTemplateColumns: "repeat(7, 120px)", gap: 12 }}>
|
| 506 |
{Array.from({ length: 28 }).map((_, i) => {
|
| 507 |
const on = lit.includes(i) && frame > 8 + lit.indexOf(i) * 6;
|
|
|
|
| 567 |
<WarpField speed={interpolate(frame, [0, 60, 114], [0.5, 2, 12])} streak={interpolate(frame, [70, 114], [0, 1], { extrapolateLeft: "clamp" })} />
|
| 568 |
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center", textAlign: "center", gap: 30 }}>
|
| 569 |
{/* The navbar mark (frontend/public/logo.svg, copied to public/logo.svg). */}
|
| 570 |
+
<div style={{ opacity: Math.min(1, s), transform: `scale(${0.85 + 0.15 * s})` }}>
|
| 571 |
+
<Lockup size={150} />
|
| 572 |
+
</div>
|
| 573 |
<p style={{ margin: 0, fontFamily: theme.body, fontSize: 40, color: "rgba(255,255,255,0.8)", opacity: cta }}>Your knowledge, in orbit.</p>
|
| 574 |
<p style={{ margin: 0, padding: "18px 40px", borderRadius: 999, background: BLUE, color: theme.white, fontFamily: theme.body, fontWeight: 600, fontSize: 32, opacity: cta, boxShadow: `0 0 40px ${BLUE}` }}>
|
| 575 |
Start free at notestack.ai
|
renderer/src/compositions/LaunchTrailer.tsx
CHANGED
|
@@ -259,12 +259,16 @@ function Planet({ cx, cy, r }: { cx: number; cy: number; r: number }) {
|
|
| 259 |
<stop offset="90%" stopColor={BLUE} stopOpacity={0.55} />
|
| 260 |
<stop offset="100%" stopColor={BLUE} stopOpacity={0} />
|
| 261 |
</radialGradient>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
<clipPath id="planet-clip">
|
| 263 |
<circle r={r} />
|
| 264 |
</clipPath>
|
| 265 |
</defs>
|
| 266 |
<g transform={`translate(${cx} ${cy})`}>
|
| 267 |
-
<circle r={r * 2.
|
| 268 |
<g transform={`rotate(${tilt})`}>{ring("back")}</g>
|
| 269 |
<circle r={r} fill="url(#planet-body)" />
|
| 270 |
{/* Soft cloud bands, clipped to the sphere, drifting slowly. */}
|
|
|
|
| 259 |
<stop offset="90%" stopColor={BLUE} stopOpacity={0.55} />
|
| 260 |
<stop offset="100%" stopColor={BLUE} stopOpacity={0} />
|
| 261 |
</radialGradient>
|
| 262 |
+
<radialGradient id="planet-halo" cx="50%" cy="50%" r="50%">
|
| 263 |
+
<stop offset="30%" stopColor={BLUE} stopOpacity={0.3} />
|
| 264 |
+
<stop offset="100%" stopColor={BLUE} stopOpacity={0} />
|
| 265 |
+
</radialGradient>
|
| 266 |
<clipPath id="planet-clip">
|
| 267 |
<circle r={r} />
|
| 268 |
</clipPath>
|
| 269 |
</defs>
|
| 270 |
<g transform={`translate(${cx} ${cy})`}>
|
| 271 |
+
<circle r={r * 2.4} fill="url(#planet-halo)" opacity={shimmer} />
|
| 272 |
<g transform={`rotate(${tilt})`}>{ring("back")}</g>
|
| 273 |
<circle r={r} fill="url(#planet-body)" />
|
| 274 |
{/* Soft cloud bands, clipped to the sphere, drifting slowly. */}
|