-
- {emoji}
-
+ {/* Whole header + body is a single link into the detail page */}
+
+ {/* Header */}
+
+

-
-
- {department.name}
-
+
+
+ {department.name}
+
-
- {department.short}
-
+
{department.short}
+
-
- {/* Description */}
-
- {department.description}
-
+ {/* Description */}
+
+ {department.description}
+
+
{/* Footer */}
-
-
- Mascot:{" "}
- {name}
-
+
+
+ Mascot:{" "}
+
+ {name}
+
+
{department.easterEgg && (
-
- {department.easterEgg}
-
+
+ {department.easterEgg}
+
)}
diff --git a/src/components/ewu/EmotionalWeatherCard.tsx b/src/components/ewu/EmotionalWeatherCard.tsx
index 77e5c3a..5e6d624 100644
--- a/src/components/ewu/EmotionalWeatherCard.tsx
+++ b/src/components/ewu/EmotionalWeatherCard.tsx
@@ -22,7 +22,7 @@
import * as React from "react";
import type { EmotionalWeatherSignal } from "@/lib/emotionalWeather";
-import { EMOTIONAL_WEATHER_STATIC } from "@/lib/emotionalWeather";
+import { EMOTIONAL_WEATHER_LATEST } from "@/lib/weatherReportLoader";
import {
Thermometer,
Gauge,
@@ -57,7 +57,7 @@ function TrendGlyph({ trend }: { trend: EmotionalWeatherSignal["temperature"]["t
}
export function EmotionalWeatherCard(props: EmotionalWeatherProps) {
- const { id, title = "Emotional Weather", className, density = "normal", signal = EMOTIONAL_WEATHER_STATIC } = props;
+ const { id, title = "Emotional Weather", className, density = "normal", signal = EMOTIONAL_WEATHER_LATEST } = props;
const isCompact = density === "compact";
const headingId = id ? `${id}__heading` : "emotional-weather__heading";
const descId = id ? `${id}__desc` : "emotional-weather__desc";
diff --git a/src/components/home/FeaturedVideo.tsx b/src/components/home/FeaturedVideo.tsx
index 79a428d..0d04f40 100644
--- a/src/components/home/FeaturedVideo.tsx
+++ b/src/components/home/FeaturedVideo.tsx
@@ -97,22 +97,24 @@ export function FeaturedVideo() {
{/* VIDEO DETAILS */}
-
- {featured.category}
-
+ {featured.category && (
+
+ {featured.category}
+
+ )}
{featured.title}
- {featured.description}
+ {featured.description}
-
- Duration:{" "}
-
- {featured.duration}
-
-
+ {featured.duration && (
+
+ Duration:{" "}
+ {featured.duration}
+
+ )}
diff --git a/src/components/home/RecentLabNotesPreview.module.css b/src/components/home/RecentLabNotesPreview.module.css
deleted file mode 100644
index 7a4828f..0000000
--- a/src/components/home/RecentLabNotesPreview.module.css
+++ /dev/null
@@ -1,199 +0,0 @@
-/* Wrapper */
-.recentLabNotes {
- margin-block: 3rem;
-}
-
-/* Header row */
-.recentLabNotesHeader {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
- gap: 1rem;
- margin-bottom: 1.5rem;
-}
-
-.recentLabNotesTitle {
- font-size: 1.4rem;
- font-weight: 650;
-}
-
-.recentLabNotesSubtitle {
- font-size: 0.95rem;
- opacity: 0.8;
-}
-
-/* "View all notes →" */
-.recentLabNotesLink {
- font-size: 0.9rem;
- font-weight: 500;
- text-decoration: none;
- color: var(--ifm-color-primary);
- display: inline-flex;
- align-items: center;
- gap: 0.25rem;
- transition: transform 150ms ease, opacity 150ms ease;
-}
-
-.recentLabNotesLink:hover {
- transform: translateX(2px);
- opacity: 0.9;
-}
-
-/* Card grid */
-.recentLabNotesGrid {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 1.25rem;
-}
-
-@media (max-width: 992px) {
- .recentLabNotesGrid {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-}
-
-@media (max-width: 700px) {
- .recentLabNotesGrid {
- grid-template-columns: minmax(0, 1fr);
- }
-}
-
-/* Individual card */
-.recentLabNotesCard {
- position: relative;
- padding: 1.4rem 1.5rem 1.2rem;
- border-radius: 1.25rem;
- background: radial-gradient(circle at top left, rgba(90, 230, 255, 0.08), transparent 55%),
- radial-gradient(circle at bottom right, rgba(186, 104, 255, 0.08), transparent 55%),
- rgba(4, 10, 25, 0.98);
- border: 1px solid rgba(255, 255, 255, 0.06);
- box-shadow:
- 0 18px 45px rgba(0, 0, 0, 0.75),
- 0 0 0 1px rgba(255, 255, 255, 0.02);
- backdrop-filter: blur(12px);
- transition:
- transform 160ms ease-out,
- box-shadow 160ms ease-out,
- border-color 160ms ease-out,
- background 200ms ease-out;
- overflow: hidden;
-}
-
-/* Neon edge glow */
-.recentLabNotesCard::before {
- content: "";
- position: absolute;
- inset: 0;
- border-radius: inherit;
- background: linear-gradient(
- 135deg,
- rgba(0, 255, 200, 0.18),
- rgba(120, 130, 255, 0.08),
- rgba(255, 120, 220, 0.16)
- );
- opacity: 0;
- pointer-events: none;
- mix-blend-mode: screen;
- transition: opacity 200ms ease-out;
-}
-
-.recentLabNotesCard:hover {
- transform: translateY(-4px);
- border-color: rgba(0, 255, 200, 0.45);
- box-shadow:
- 0 22px 55px rgba(0, 0, 0, 0.85),
- 0 0 25px rgba(0, 255, 200, 0.25);
-}
-
-.recentLabNotesCard:hover::before {
- opacity: 0.7;
-}
-
-/* Top row: tag + date */
-.recentLabNotesMetaRow {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 0.9rem;
-}
-
-/* Category pill */
-.recentLabNotesTag {
- font-size: 0.7rem;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- padding: 0.25rem 0.7rem;
- border-radius: 999px;
- border: 1px solid rgba(0, 255, 200, 0.4);
- background: radial-gradient(circle at top left, rgba(0, 255, 200, 0.24), transparent 60%);
- color: rgba(210, 255, 245, 0.94);
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
- white-space: nowrap;
-}
-
-/* You can add small icons someday if you want */
-/* .recentLabNotesTagIcon { font-size: 0.9em; } */
-
-.recentLabNotesDate {
- font-size: 0.75rem;
- opacity: 0.7;
-}
-
-/* Title + summary */
-.recentLabNotesCardTitle {
- font-size: 1.05rem;
- font-weight: 640;
- margin-bottom: 0.4rem;
-}
-
-.recentLabNotesCardExcerpt {
- font-size: 0.9rem;
- line-height: 1.5;
- opacity: 0.85;
- margin-bottom: 1rem;
-}
-
-/* Divider */
-.recentLabNotesDivider {
- height: 1px;
- width: 100%;
- margin-bottom: 0.75rem;
- background: linear-gradient(
- 90deg,
- transparent,
- rgba(255, 255, 255, 0.18),
- transparent
- );
-}
-
-.recentLabNotesFooter {
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 0.8rem;
- color: rgba(255, 255, 255, 0.7);
-}
-
-.recentLabNotesReadTime {
- opacity: 0.8;
-}
-
-/* "Read note →" */
-.recentLabNotesReadLink {
- display: inline-flex;
- align-items: center;
- gap: 0.25rem;
- text-decoration: none;
- font-weight: 500;
- font-size: 0.82rem;
- color: var(--ifm-color-primary);
- transition: transform 160ms ease, color 160ms ease, opacity 160ms ease;
-}
-
-.recentLabNotesReadLink:hover {
- transform: translateX(3px);
- color: #5bffe3; /* or keep var(--ifm-color-primary) if you prefer */
- opacity: 0.95;
-}
diff --git a/src/components/home/RecentLabNotesPreview.tsx b/src/components/home/RecentLabNotesPreview.tsx
deleted file mode 100644
index db905db..0000000
--- a/src/components/home/RecentLabNotesPreview.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-// src/components/home/RecentLabNotesPreview.tsx
-import React, { useEffect, useState } from "react";
-import { Link } from "react-router-dom";
-import type { LabNote } from "@/lib/labNotes";
-import { getNotesIndex } from "@/lib/notesIndex";
-import { RecentLabNotesSkeleton } from "@/components/home/RecentLabNotesSkeleton";
-
-export function RecentLabNotesPreview() {
- const [recent, setRecent] = useState
([]);
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- const controller = new AbortController();
- let alive = true;
-
- (async () => {
- setLoading(true);
- setError(null);
-
- try {
- const all = await getNotesIndex("en", controller.signal);
- if (!alive) return;
- setRecent(all.slice(0, 3));
- } catch (e: any) {
- if (!alive) return;
-
- // Abort shouldn't be treated as an error, but it MUST end loading.
- if (e?.name === "AbortError") {
- setLoading(false);
- return;
- }
-
- setError(e?.message ?? "Failed to load notes.");
- setRecent([]);
- } finally {
- if (alive) setLoading(false);
- }
- })();
-
- return () => {
- alive = false;
- controller.abort();
- };
- }, []);
-
- return (
-
-
- {loading ? (
-
- ) : (
-
- {recent.map((note) => {
- const category = note.tags?.[0]; // currently [] in your data
- const date = note.published ? new Date(note.published) : null;
- const formatted = date
- ? date.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "2-digit" })
- : "";
-
- const excerpt = (note.summary || note.subtitle || "").trim();
-
- return (
-
- {/* subtle corner glow */}
-
-
-
-
- {category ? (
-
- {category}
-
- ) : (
-
- Lab Note
-
- )}
-
- {formatted && {formatted}}
-
-
-
- {note.title}
-
-
- {excerpt && (
-
- {excerpt}
-
- )}
-
-
-
- {note.readingTime ?? "—"} min read
-
-
- Read note ↗
-
-
-
- );
- })}
-
-
- {error ? (
-
Preview failed: {error}
- ) : null}
-
- )}
-
- );
-}
diff --git a/src/components/home/RecentLabNotesSkeleton.tsx b/src/components/home/RecentLabNotesSkeleton.tsx
deleted file mode 100644
index 0abf410..0000000
--- a/src/components/home/RecentLabNotesSkeleton.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-export function RecentLabNotesSkeleton({ count = 3 }: { count?: number }) {
- return (
-
- {Array.from({ length: count }).map((_, i) => (
-
- {/* subtle corner glow */}
-
-
- {/* shimmer */}
-
-
-
-
-
-
- ))}
-
- {/* Tailwind keyframes anchor */}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/labnotes/LabNoteCard.tsx b/src/components/labnotes/LabNoteCard.tsx
deleted file mode 100644
index e157ff1..0000000
--- a/src/components/labnotes/LabNoteCard.tsx
+++ /dev/null
@@ -1,285 +0,0 @@
-/* ===========================================================
- 🌌 HUMAN PATTERN LAB — SOURCE FILE METADATA
- -----------------------------------------------------------
- Author: Ada (Founder, The Human Pattern Lab)
- Assistant: Lyric (AI Lab Companion)
- File: LabNoteCard.tsx
- Lab Unit: SCMS — Systems & Code Management Suite
- Purpose: Renders a single Lab Note preview card for the Lab
- Notes index grid, with department styling, glow,
- staggered reveal, and hover-driven excerpt reveal.
- =========================================================== */
-
-// src/components/labnotes/LabNoteCard.tsx
-import { Link } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import type { LabNote } from "@/lib/labNotes";
-import {
- cx,
- DEPT_STYLES,
- getDeptKey,
- GUEST_STYLES,
- getGuestKey,
-} from "@/components/labnotes/labNoteCard.styles";
-
-type Props = {
- note: LabNote;
- index: number;
-};
-
-function normalizeBlurb(s?: string | null) {
- return String(s ?? "")
- .replace(/\s+/g, " ")
- .replace(/[—–-]\s*$/, "")
- .trim();
-}
-
-function looksDuplicate(a?: string | null, b?: string | null) {
- const A = normalizeBlurb(a).toLowerCase();
- const B = normalizeBlurb(b).toLowerCase();
- if (!A || !B) return false;
-
- if (A === B) return true;
- if (A.includes(B) && B.length > 40) return true;
- if (B.includes(A) && A.length > 40) return true;
-
- return false;
-}
-
-function getPrimaryBlurb(note: any) {
- return (
- normalizeBlurb(note.subtitle) ||
- normalizeBlurb(note.summary) ||
- normalizeBlurb(note.excerpt) ||
- null
- );
-}
-
-// Hover whisper: only show if it adds value (no duplication)
-function getHoverBlurb(note: { subtitle?: string | null; summary?: string | null; excerpt?: string | null }) {
- const subtitle = normalizeBlurb(note.subtitle);
- const summary = normalizeBlurb(note.summary);
- const excerpt = normalizeBlurb(note.excerpt);
-
- const candidate = subtitle || summary || excerpt;
- if (!candidate) return null;
-
- // If it's basically the same as the primary visible blurb, skip it
- const primary = subtitle || summary || excerpt;
- if (looksDuplicate(candidate, primary)) return null;
-
- // Extra guard: avoid “subtitle mirrors summary” cases
- if (!subtitle && looksDuplicate(candidate, summary)) return null;
-
- return candidate;
-}
-
-export function LabNoteCard({ note, index }: Props) {
- const { t, i18n } = useTranslation("labNotesPage");
- const locale = i18n.language || "en";
-
- const hoverBlurb = getHoverBlurb(note as any);
- const primaryBlurb = getPrimaryBlurb(note as any);
-
- const guestKey = getGuestKey(note as any);
- const guest = guestKey ? (GUEST_STYLES[guestKey] ?? GUEST_STYLES.copilot) : null;
-
- const styleKey = note.card_style
- ? String(note.card_style).toLowerCase()
- : getDeptKey(note.dept ?? note.department_id);
-
- const styles = DEPT_STYLES[styleKey] ?? DEPT_STYLES.scms;
-
-
- const tag = note.tags?.[0] || (note.type ? String(note.type).toUpperCase() : "NOTE");
- const shadow = Math.max(0, Math.min(10, Math.round(note.shadow_density ?? 0)));
-
- return (
-
- {/* Glow layer */}
-
-
- {/* Guest “visiting signal” rail (does not replace dept identity) */}
- {guest ? (
-
- ) : null}
-
-
- {/* Pills */}
-
-
- {tag}
-
-
- {guestKey && guest ? (
-
- Guest · {guestKey}
-
- ) : null}
-
-
- {/* Title ↔ hover whisper */}
-
-
- {note.title}
-
-
- {hoverBlurb ? (
-
- {hoverBlurb}
-
- ) : null}
-
-
- {/* Body: always show best available blurb; hover shows concept panel */}
- {primaryBlurb ? (
-
-
- {primaryBlurb}
-
-
-
- {/* Hover: concept signals */}
-
-
-
- Concept Load
-
-
- {note.safer_landing ? (
-
- Safer ✓
-
- ) : null}
-
-
- {guestKey && guest ? (
-
- In conversation with: {guestKey}
-
- ) : null}
-
- {/* Shadow density bars */}
-
- {Array.from({ length: 10 }).map((_, i) => {
- const active = i < shadow;
- return (
-
- );
- })}
-
-
- {/* Tags */}
-
- {(note.tags ?? []).slice(0, 3).map((tagItem) => (
-
- {tagItem}
-
- ))}
-
-
-
-
- ) : (
-
- )}
-
-
- {/* Footer */}
-
-
- {note.readingTime ?? 3} MIN READ
-
-
-
- {t("readMore", { defaultValue: "Open Note" })} →
-
-
-
- );
-}
diff --git a/src/components/labnotes/LabNoteCardSkeleton.tsx b/src/components/labnotes/LabNoteCardSkeleton.tsx
deleted file mode 100644
index fda2dc6..0000000
--- a/src/components/labnotes/LabNoteCardSkeleton.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-// src/components/labnotes/LabNoteCardSkeleton.tsx
-import React from "react";
-import { Skeleton, ScanlineWrap } from "@/components/ui/Skeleton";
-
-export function LabNoteCardSkeleton() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/labnotes/LabNotesGridSkeleton.tsx b/src/components/labnotes/LabNotesGridSkeleton.tsx
deleted file mode 100644
index 2d60715..0000000
--- a/src/components/labnotes/LabNotesGridSkeleton.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-// src/components/labnotes/LabNotesGridSkeleton.tsx
-import React from "react";
-import { LabNoteCardSkeleton } from "@/components/labnotes/LabNoteCardSkeleton";
-import type { LabNote } from "@/lib/labNotes";
-import { LabNoteCard } from "@/components/labnotes/LabNoteCard";
-
-export function LabNotesGridSkeleton({ count = 6 }: { count?: number }) {
- return (
-
- {Array.from({ length: count }).map((_, i) => (
-
- ))}
-
- );
-}
-
-export function LabNotesGrid({ notes }: { notes: LabNote[] }) {
- return (
-
- {notes.map((n, i) => (
-
- ))}
-
- );
-}
diff --git a/src/components/labnotes/labNoteCard.styles.ts b/src/components/labnotes/labNoteCard.styles.ts
deleted file mode 100644
index aed4041..0000000
--- a/src/components/labnotes/labNoteCard.styles.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-// src/components/labnotes/labNoteCard.styles.ts
-
-export interface DeptStyle {
- border: string;
- text: string;
- glow: string;
- shadow: string;
-
- // Optional: used for hover bars / accents (lets Vesper not “own” everything)
- accentBarActive?: string;
- accentBarInactive?: string;
- accentBarShadow?: string;
-}
-
-// Central “design tokens” per department.
-// Keep these subtle: designer > neon arcade.
-export const DEPT_STYLES: Record = {
- coda: {
- border: "hover:!border-coda",
- text: "!text-coda",
- glow: "from-coda/20",
- shadow: "hover:!shadow-[0_0_20px_rgba(255,184,0,0.35)]",
- accentBarActive: "bg-coda",
- accentBarInactive: "bg-slate-800",
- accentBarShadow: "shadow-[0_0_8px_rgba(255,184,0,0.45)]",
- },
- vesper: {
- border: "hover:!border-vesper",
- text: "!text-vesper",
- glow: "from-vesper/20",
- shadow: "hover:!shadow-[0_0_20px_rgba(110,0,255,0.25)]",
- accentBarActive: "bg-vesper",
- accentBarInactive: "bg-slate-800",
- accentBarShadow: "shadow-[0_0_8px_rgba(110,0,255,0.45)]",
- },
- lyric: {
- border: "hover:!border-lyric",
- text: "!text-lyric",
- glow: "from-lyric/20",
- shadow: "hover:!shadow-[0_0_20px_rgba(0,255,133,0.22)]",
- accentBarActive: "bg-lyric",
- accentBarInactive: "bg-slate-800",
- accentBarShadow: "shadow-[0_0_8px_rgba(0,255,133,0.35)]",
- },
-
- // Default / SCMS stays quiet and structured
- scms: {
- border: "hover:!border-slate-500",
- text: "!text-slate-300",
- glow: "from-slate-500/10",
- shadow: "hover:!shadow-none",
- accentBarActive: "bg-slate-500",
- accentBarInactive: "bg-slate-800",
- accentBarShadow: "shadow-none",
- },
-
- // 🧠 Sage: calm authority, not loud — emerald/graphite vibe
- sage: {
- border: "hover:!border-emerald-500/40",
- text: "!text-emerald-200",
- glow: "from-emerald-400/14",
- shadow: "hover:!shadow-[0_0_22px_rgba(52,211,153,0.18)]",
- accentBarActive: "bg-emerald-400",
- accentBarInactive: "bg-slate-800",
- accentBarShadow: "shadow-[0_0_8px_rgba(52,211,153,0.32)]",
- },
-};
-
-export interface GuestStyle {
- badge: string;
- rail: string;
- railMask?: string;
- wash?: string;
- text?: string;
-}
-
-export const GUEST_STYLES: Record = {
- copilot: {
- badge: "bg-slate-900/40 text-slate-300 border-slate-700/70",
- rail: "bg-gradient-to-b from-slate-400/35 via-slate-400/15 to-transparent",
- railMask: "[mask-image:linear-gradient(to_bottom,black_70%,transparent)]",
- wash: "from-slate-400/10 via-transparent to-transparent",
- text: "text-slate-400",
- },
- // future-proof examples
- gemini: {
- badge: "bg-slate-900/40 text-amber-200 border-amber-300/25",
- rail: "bg-gradient-to-b from-amber-300/25 via-amber-300/10 to-transparent",
- railMask: "[mask-image:linear-gradient(to_bottom,black_70%,transparent)]",
- wash: "from-amber-300/10 via-transparent to-transparent",
- text: "text-amber-200/70",
- },
- grok: {
- badge: "bg-slate-900/40 text-fuchsia-200 border-fuchsia-300/25",
- rail: "bg-gradient-to-b from-fuchsia-300/25 via-fuchsia-300/10 to-transparent",
- railMask: "[mask-image:linear-gradient(to_bottom,black_70%,transparent)]",
- wash: "from-fuchsia-300/10 via-transparent to-transparent",
- text: "text-fuchsia-200/70",
- },
- claude: {
- badge: "bg-slate-900/40 text-emerald-200 border-emerald-300/25",
- rail: "bg-gradient-to-b from-emerald-300/25 via-emerald-300/10 to-transparent",
- railMask: "[mask-image:linear-gradient(to_bottom,black_70%,transparent)]",
- wash: "from-emerald-300/10 via-transparent to-transparent",
- text: "text-emerald-200/70",
- },
-};
-
-export function getGuestKey(note: any): string | null {
- const raw = (note?.guest ?? note?.voice ?? "").toString().trim().toLowerCase();
- return raw || null;
-}
-
-// 🧬 Explicitly map potential YAML names to our style keys
-export function getDeptKey(id?: string): keyof typeof DEPT_STYLES {
- const normalized = (id || "scms").toLowerCase();
-
- // Your existing aliases
- if (normalized === "alignment" || normalized === "coda") return "coda";
- if (normalized === "shadow" || normalized === "vesper") return "vesper";
- if (normalized === "structure" || normalized === "lyric") return "lyric";
- if (normalized === "systems" || normalized === "scms") return "scms";
-
- // New: Sage aliases
- if (normalized === "sage") return "sage";
- if (normalized === "analysis" || normalized === "philosophy") return "sage";
-
- return "scms";
-}
-
-// Tiny class joiner (avoids importing clsx just for this)
-export function cx(...parts: Array) {
- return parts.filter(Boolean).join(" ");
-}
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
index 902ba63..b149059 100644
--- a/src/components/layout/Footer.tsx
+++ b/src/components/layout/Footer.tsx
@@ -20,6 +20,8 @@
* and Lab identity messaging.
*/
+import { ExternalLink } from "lucide-react";
+
export function Footer() {
return (