From c55d9d2832a46d9dc265fec36936b1313f5af6f6 Mon Sep 17 00:00:00 2001
From: Elizabeth Hunt <elizabeth.hunt@simponic.xyz>
Date: Sun, 11 Feb 2024 20:44:17 -0700
Subject: birthdays

---
 src/scenes/birthday_letters.tsx | 213 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 213 insertions(+)
 create mode 100644 src/scenes/birthday_letters.tsx

(limited to 'src/scenes/birthday_letters.tsx')

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)),
+  );
+});
-- 
cgit v1.3

