diff options
Diffstat (limited to 'src/scenes/birthday_letters.tsx')
-rw-r--r-- | src/scenes/birthday_letters.tsx | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/src/scenes/birthday_letters.tsx b/src/scenes/birthday_letters.tsx new file mode 100644 index 0000000..2a63360 --- /dev/null +++ b/src/scenes/birthday_letters.tsx @@ -0,0 +1,213 @@ +import { Layout, Txt, makeScene2D } from "@motion-canvas/2d"; +import { + Direction, + all, + beginSlide, + createRef, + makeRef, + slideTransition, + waitFor, +} from "@motion-canvas/core"; + +import { FunctionBox } from "../components/function_box"; +import { theme } from "../theme"; +import { PEOPLE, Person, PersonI } from "../components/person"; + +const daysUntilNextBirthday = (birthDate: Date): number => { + const today = new Date(); + + const nextBirthday = new Date( + today.getFullYear(), + birthDate.getMonth(), + birthDate.getDate(), + ); + if (today > nextBirthday) nextBirthday.setFullYear(today.getFullYear() + 1); + + const timeDiff = nextBirthday.getTime() - today.getTime(); + return Math.ceil(timeDiff / (1000 * 3600 * 24)); +}; + +export interface CardI { + message: string; + deliverInDays: number; +} + +export const birthdayCardsFor = (people: PersonI[]): CardI[] => { + const cards: CardI[] = []; + + for (const person of people) { + const age = new Date().getFullYear() - person.birthday.getFullYear(); + const ending = + ({ 1: "st", 2: "nd", 3: "rd" } as Record<number, string>)[ + parseInt(Array.from(age.toString()).at(-1)) + ] ?? "th"; + const message = + `Happy ${age}${ending} birthday, ${person.name}!\n` + + "I can't believe it's already been a year!"; + + const deliverInDays = daysUntilNextBirthday(person.birthday); + cards.push({ deliverInDays, message }); + } + + return cards; +}; + +const birthdayCardsSrc = `export interface CardI { + message: string; + deliverInDays: number; +} + +const birthdayCardsFor = (people: PersonI[]): CardI[] => { + const cards: CardI[] = []; + + for (const person of people) { + const age = new Date().getFullYear() - person.birthday.getFullYear(); + const ageEnding = ( + { 1: "st", 2: "nd", 3: "rd" } as Record<number, string> + )[parseInt(Array.from(age.toString()).at(-1))] ?? "th"; + + const message = + \`Happy \${age}\${ageEnding} birthday, \${person.name}!\\n\` + + "I can't believe it's already been a year!"; + + const deliverInDays = daysUntilNextBirthday(person.birthday); + cards.push({ deliverInDays, message }); + } + + return cards; +};`; + +export default makeScene2D(function* (view) { + const layout = createRef<Layout>(); + const date = createRef<Txt>(); + const functionBox = createRef<FunctionBox>(); + const people: Person[] = []; + const peopleLayout: Layout[] = []; + const peopleText: Txt[] = []; + + view.add( + <FunctionBox + ref={functionBox} + source={birthdayCardsSrc} + fn={birthdayCardsFor} + workingText="📝⚙" + outputFontSize={25} + ></FunctionBox>, + ); + + yield* all(slideTransition(Direction.Left), functionBox().showCode(0.75)); + yield* functionBox().reset(0.1); + + yield* beginSlide("Show code"); + + yield* functionBox().reset(0.1); + yield* all( + functionBox().hideCode(0.8), + functionBox().setInputs( + [ + { + val: PEOPLE, + node: ( + <Layout direction="column" gap={5} layout> + {PEOPLE.map((person) => ( + <Person person={person} /> + ))} + </Layout> + ), + }, + ], + 0.8, + ), + ); + + yield* beginSlide("Show people"); + + yield* functionBox().propogateInput(0.6); + yield* functionBox().propogateOutput(0.6); + yield* beginSlide("Generate birthday cards"); + + yield* functionBox().opacity(0, 0.5); + functionBox().remove(); + + view.add( + <Layout opacity={0} ref={layout} direction="column" gap={15} layout> + <Txt + fontSize={30} + fontFamily={theme.font} + fill={theme.text.hex} + ref={date} + /> + + <Layout direction="row" gap={15}> + {PEOPLE.map((person, i) => ( + <Layout + ref={makeRef(peopleLayout, i)} + alignItems="center" + direction="column" + gap={100} + > + <Person ref={makeRef(people, i)} person={person} /> + <Txt + textAlign="center" + ref={makeRef(peopleText, i)} + fontSize={0} + fontFamily={theme.font} + fill={theme.text.hex} + /> + </Layout> + ))} + </Layout> + </Layout>, + ); + yield* layout().opacity(1, 0.5); + + const cards = birthdayCardsFor(PEOPLE); + + for (let i = 1; i <= 365 + 1; i++) { + const day = new Date(Date.now() + i * 24 * 60 * 60 * 1000); + + yield* waitFor(0.02); + yield date().text(day.toDateString()); + + const peoplesBirthday = cards + .map((x, i) => [x, i] as [CardI, number]) + .filter(([{ deliverInDays }]) => deliverInDays === i) + .map(([_, i]) => i); + + yield* all( + ...peoplesBirthday.map((p) => { + const text = peopleText[p]; + return all( + text.text("🗓️🎂\n" + cards[p].message, 0), + text.fontSize(20, 0.5), + ); + }), + ); + + yield* all( + ...peoplesBirthday.map((p) => { + const layout = peopleLayout[p]; + return layout.gap(0, 0.5); + }), + ); + + yield* all( + ...peoplesBirthday.map((p) => { + const person = people[p]; + const text = peopleText[p]; + return all( + text.opacity(0, 0.5), + text.fontSize(0, 0.5), + person.emit("🥳", 0.8, false), + ); + }), + ); + } + + yield* beginSlide("See their reactions"); + yield* all( + date().fontSize(0, 0.5), + date().opacity(0, 0.65), + ...people.map((person) => person.opacity(0, 0.5)), + ); +}); |