Skip to content

Commit a46fb93

Browse files
committed
feat(mascot): integrate 57-path articulated AJ SVG rig across site & add /mascot-lab
1 parent d7fd66e commit a46fb93

31 files changed

Lines changed: 4645 additions & 177 deletions

‎apps/site/src/components/AjAnimatedMascot.astro‎

Lines changed: 948 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 38 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
---
22
/**
3-
* AJ the CodingCat.dev Mascot - Canonical 2D Upright Bipedal Vector Edition.
3+
* AJ the CodingCat.dev Mascot (`AjMascot.astro`)
44
*
5-
* Styled for both Chalkboard Paper Light Mode (`8d1319903d504a79ae56110486455e17`)
6-
* and Obsidian Chalkboard Dark Mode (`b9e00f99c0d34e9884bf7766a014577b`).
5+
* Delegates directly to `AjAnimatedMascot.astro` so every existing caller
6+
* across Courses, Podcasts, Blog Posts, and Diagrams automatically gets the
7+
* 57-path articulated 2D vector bone rig.
78
*/
8-
import AjHeadphones from "@/components/icons/AjHeadphones.astro";
9-
import AjPrimaryAlt from "@/components/icons/AjPrimaryAlt.astro";
9+
import AjAnimatedMascot, {
10+
type AjAnimationState,
11+
type AjMascotVariant,
12+
type AjMouthViseme,
13+
type AjTurnaroundAngle,
14+
} from "@/components/AjAnimatedMascot.astro";
1015
1116
interface Props {
1217
pose?: "yarn-chase" | "code-peek" | "lever-pull" | "headphones";
18+
state?: AjAnimationState;
19+
variant?: AjMascotVariant;
20+
angle?: AjTurnaroundAngle;
21+
mouth?: AjMouthViseme;
1322
caption?: string;
1423
cls?: string;
1524
showYarnBall?: boolean;
@@ -18,85 +27,34 @@ interface Props {
1827
1928
const {
2029
pose = "yarn-chase",
30+
state,
31+
variant,
32+
angle = "three-quarter-right",
33+
mouth,
2134
caption,
2235
cls = "w-44 h-auto",
2336
showYarnBall = true,
2437
fullBody = true,
2538
} = Astro.props;
26-
---
2739
28-
<div class="relative inline-flex flex-col items-center select-none group">
29-
{
30-
caption && (
31-
<div class="mb-2.5 -rotate-2 rounded-2xl border-2 border-[#2A1155] bg-[#FEF08A] px-3.5 py-1.5 font-hand text-lg font-bold text-[#713F12] transition-transform group-hover:rotate-1 dark:border-yellow-500 shadow-sketch-sm">
32-
{caption}
33-
</div>
34-
)
35-
}
36-
<div class="relative flex items-center justify-center">
37-
{
38-
fullBody ? (
39-
<picture>
40-
<source srcset="/images/aj-bipedal-2d.webp" type="image/webp" />
41-
<img
42-
src="/images/aj-bipedal-2d.png"
43-
alt="AJ the CodingCat.dev mascot standing upright wearing purple podcast headphones and a hexagonal AJ collar tag"
44-
width="280"
45-
height="515"
46-
loading="lazy"
47-
decoding="async"
48-
class={`${cls} object-contain drop-shadow-[0_10px_20px_rgba(42,17,85,0.4)] transition-transform duration-300 group-hover:scale-105`}
49-
/>
50-
</picture>
51-
) : pose === "code-peek" || pose === "lever-pull" ? (
52-
<AjPrimaryAlt
53-
cls={`${cls} transition-transform duration-300 group-hover:scale-105`}
54-
/>
55-
) : (
56-
<AjHeadphones
57-
cls={`${cls} transition-transform duration-300 group-hover:scale-105`}
58-
/>
59-
)
60-
}
40+
const poseToState: Record<NonNullable<Props["pose"]>, AjAnimationState> = {
41+
"yarn-chase": "idle-wave",
42+
"code-peek": "point-diagram",
43+
"lever-pull": "point-diagram",
44+
headphones: "podcast-groove",
45+
};
46+
47+
const resolvedState: AjAnimationState = state ?? poseToState[pose] ?? "idle-wave";
48+
const resolvedVariant: AjMascotVariant =
49+
variant ?? (fullBody ? "full-body" : "head");
50+
---
6151

62-
{
63-
showYarnBall && (
64-
<svg
65-
class="yarn-glow absolute -bottom-2 -right-3 size-14 transition-transform duration-300 group-hover:rotate-12 group-hover:scale-110"
66-
viewBox="0 0 80 80"
67-
fill="none"
68-
xmlns="http://www.w3.org/2000/svg"
69-
aria-hidden="true"
70-
>
71-
{/* CodingCat Royal Purple & Fuchsia Yarn Ball (matches Stitch 8d131990 & b9e00f99) */}
72-
<circle
73-
cx="40"
74-
cy="40"
75-
r="24"
76-
fill="#7C3AED"
77-
stroke="#2A1155"
78-
stroke-width="3"
79-
/>
80-
<path
81-
d="M22 32C30 24 48 24 58 34M20 42C32 34 48 36 60 46M26 52C36 44 48 46 54 54"
82-
stroke="#EDE9FE"
83-
stroke-width="2.5"
84-
stroke-linecap="round"
85-
/>
86-
<path
87-
d="M32 18C26 30 28 48 38 60M44 18C38 32 40 48 50 60"
88-
stroke="#D946EF"
89-
stroke-width="2.5"
90-
stroke-linecap="round"
91-
/>
92-
<path
93-
d="M58 52C66 58 72 64 76 74"
94-
stroke="#C084FC"
95-
stroke-width="4"
96-
stroke-linecap="round"
97-
/>
98-
</svg>
99-
)
100-
}
101-
</div>
102-
</div>
52+
<AjAnimatedMascot
53+
state={resolvedState}
54+
variant={resolvedVariant}
55+
angle={angle}
56+
mouth={mouth}
57+
caption={caption}
58+
cls={cls}
59+
showYarnBall={showYarnBall}
60+
/>

‎apps/site/src/components/AnimatedHero.astro‎

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*
99
* Uses canonical terminology: Courses, Podcasts, Blog, and Diagrams.
1010
*/
11+
import AjAnimatedMascot from "@/components/AjAnimatedMascot.astro";
1112
---
1213

1314
<section class="mx-auto max-w-7xl px-4 pb-16 pt-6 sm:px-6 md:pb-24 md:pt-10">
@@ -253,23 +254,16 @@
253254
</span>
254255
</div>
255256

256-
{/* Bobbling AJ Mascot Character in Bottom-Right Corner */}
257-
<div class="animate-bobble mt-4 flex flex-col items-center sm:absolute sm:-bottom-6 sm:-right-4 sm:mt-0">
258-
<div class="mb-1.5 rotate-3 rounded-2xl border-2 border-[#2A1155] bg-white px-3 py-1 font-hand text-base font-bold text-[#2A1155] shadow-sketch-sm dark:border-purple-300 dark:bg-[#171F33] dark:text-[#FEF08A]">
259-
&ldquo;Purrfect code!&rdquo; 🐾
260-
</div>
261-
<picture>
262-
<source srcset="/images/aj-bipedal-2d.webp" type="image/webp" />
263-
<img
264-
src="/images/aj-bipedal-2d.png"
265-
alt="AJ the CodingCat.dev mascot standing upright wearing purple podcast headphones"
266-
width="140"
267-
height="257"
268-
loading="eager"
269-
decoding="async"
270-
class="h-auto w-24 object-contain drop-shadow-[0_10px_20px_rgba(42,17,85,0.35)] sm:w-28"
271-
/>
272-
</picture>
257+
{/* Articulated Interactive 2D Vector AJ Mascot in Bottom-Right Corner */}
258+
<div class="mt-4 flex flex-col items-center sm:absolute sm:-bottom-6 sm:-right-4 sm:mt-0">
259+
<AjAnimatedMascot
260+
id="hero-aj"
261+
variant="full-body"
262+
state="idle-wave"
263+
angle="three-quarter-left"
264+
caption="Purrfect code! 🐾"
265+
cls="h-auto w-28 sm:w-32"
266+
/>
273267
</div>
274268
</div>
275269
</div>

‎apps/site/src/components/ChatModal.astro‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
2-
// ChatModal.astro — Persistent Right-Side Resizable "Ask Agent" Drawer (Survives across page changes via soft-navigation + pre-paint storage sync)
2+
// ChatModal.astro | Persistent Right-Side Resizable "Ask Agent" Drawer (Survives across page changes via soft-navigation + pre-paint storage sync)
3+
import AjAnimatedMascot from "@/components/AjAnimatedMascot.astro";
34
---
45

56
<aside
@@ -70,7 +71,17 @@
7071
<!-- Welcome / Initial State -->
7172
<div id="ai-chat-welcome" class="flex flex-col gap-4 my-auto py-6">
7273
<div class="flex items-center gap-2.5">
73-
<img src="/icon.svg" alt="Agent AJ" class="size-8 rounded-lg object-contain bg-primary/10 p-1" width="32" height="32" />
74+
<div class="rounded-lg bg-primary/10 p-1 flex items-center justify-center">
75+
<AjAnimatedMascot
76+
id="agent-aj-avatar"
77+
variant="head"
78+
state="idle-wave"
79+
mouth="smile-open"
80+
cls="size-10"
81+
showYarnBall={false}
82+
interactive={false}
83+
/>
84+
</div>
7485
<div>
7586
<div class="font-semibold text-sm text-foreground">Agent AJ</div>
7687
<p class="text-[11px] text-muted-foreground">Sanity Content Lake • Cloudflare Clairic</p>
@@ -904,6 +915,9 @@
904915
if (input) input.value = "";
905916
isStreaming = true;
906917
if (sendBtn) sendBtn.disabled = true;
918+
document
919+
.getElementById("agent-aj-avatar-aj-mouth")
920+
?.setAttribute("data-active-viseme", "talking");
907921

908922
recordRecentAsk(prompt);
909923
storedMessages.push({
@@ -1001,6 +1015,9 @@
10011015
} finally {
10021016
isStreaming = false;
10031017
if (sendBtn) sendBtn.disabled = false;
1018+
document
1019+
.getElementById("agent-aj-avatar-aj-mouth")
1020+
?.setAttribute("data-active-viseme", "smile-open");
10041021
input?.focus();
10051022
}
10061023
}

‎apps/site/src/components/ExcalidrawBlueprintCard.astro‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,11 @@ const edges = [
8282
</div>
8383
<div class="hidden shrink-0 sm:block">
8484
<AjMascot
85-
pose="code-peek"
86-
cls="w-24 h-auto"
85+
variant="head"
86+
state="point-diagram"
87+
angle="three-quarter-left"
88+
showYarnBall={false}
89+
cls="w-20 h-20"
8790
caption="Let’s build it!"
8891
/>
8992
</div>

‎apps/site/src/components/Footer.astro‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ const primaryLinks = [
1414
1515
const communityLinks = [
1616
{ href: "/about", label: "About" },
17-
{ href: "/guests/page/1", label: "Podcast Guests" },
18-
{ href: "/authors/page/1", label: "Authors" },
17+
{ href: "/guests", label: "Podcast Guests" },
18+
{ href: "/authors", label: "Authors" },
19+
{ href: "/sponsors", label: "Sponsors" },
20+
{ href: "/mascot-lab", label: "AJ Mascot Lab" },
1921
{ href: "/book", label: "Book Podcast Session" },
2022
{ href: "/sponsorships", label: "Sponsor CodingCat.dev" },
2123
{ href: "/contact", label: "Contact" },

0 commit comments

Comments
 (0)