diff --git a/client/src/components/content-card.tsx b/client/src/components/content-card.tsx
index 30ff462..68302da 100644
--- a/client/src/components/content-card.tsx
+++ b/client/src/components/content-card.tsx
@@ -116,7 +116,7 @@ export function ContentCard({
{buttonLabel}
diff --git a/client/src/components/resources-horizontal-card.tsx b/client/src/components/resources-horizontal-card.tsx
new file mode 100644
index 0000000..602fd5f
--- /dev/null
+++ b/client/src/components/resources-horizontal-card.tsx
@@ -0,0 +1,87 @@
+import { ArrowRight, ImageOff } from "lucide-react";
+import Image from "next/image";
+import Link from "next/link";
+
+import { cn } from "@/lib/utils";
+
+export interface HorizontalCardProps {
+ className?: string;
+ imageSrc: string;
+ imageAlt: string;
+ title: string;
+ description: string;
+ href: string;
+ buttonLabel?: string;
+ author?: string;
+ dateTime?: string;
+}
+
+export function HorizontalCard({
+ className,
+ imageSrc,
+ imageAlt,
+ title,
+ description,
+ href,
+ buttonLabel = "View More",
+ author,
+ dateTime,
+}: HorizontalCardProps) {
+ const meta = [author, dateTime].filter(Boolean).join(" • ");
+
+ return (
+
+
+ {imageSrc ? (
+
+ ) : (
+
+
+
+ )}
+
+
+
+ {meta ? (
+
+ {meta}
+
+ ) : null}
+
+
+ {title}
+
+
+
+ {description}
+
+
+
+
{buttonLabel}
+
+
+
+
+ );
+}
diff --git a/client/src/components/savings-card.tsx b/client/src/components/savings-card.tsx
new file mode 100644
index 0000000..415f39b
--- /dev/null
+++ b/client/src/components/savings-card.tsx
@@ -0,0 +1,106 @@
+import styles from "@/styles/components/savings_card.module.css";
+
+export interface SavingCardProp {
+ heading: string;
+ body: string;
+ savings: string;
+ data: Record
>;
+}
+
+// Your theme only defines primary/secondary/muted/accent/destructive — no green or
+// purple token, so those two fall back to plain Tailwind colors. Swap in your own
+// tokens here if you add them later.
+const ROW_DOT_CLASSES = [
+ "bg-emerald-500", // no theme equivalent
+ "bg-destructive",
+ "bg-foreground",
+ "bg-violet-500", // no theme equivalent
+];
+
+export default function SavingHorizontalCard({
+ heading,
+ body,
+ savings,
+ data,
+}: SavingCardProp) {
+ const columns = Object.keys(data);
+ // row labels come from the inner keys — assumes every column has the same set of rows
+ const rows = columns.length > 0 ? Object.keys(data[columns[0]]) : [];
+
+ const columnTotal = (col: string) =>
+ rows.reduce((sum, row) => sum + (data[col][row] ?? 0), 0);
+
+ return (
+
+ {/* left panel */}
+
+
+
+ {heading}
+
+
+ {body}
+
+
+ {/* no "warning"/amber token in the theme, so this uses plain amber rather than a css var */}
+
+
You could save
+
+ ${savings}
+
+
over 15 years
+
+
+
+ {/* right panel — table replaces the bar chart */}
+
+
+
+
+ |
+ {columns.map((col) => (
+
+
+ {col}
+
+
+ ${columnTotal(col).toLocaleString()}
+
+ |
+ ))}
+
+
+
+ {rows.map((row, i) => (
+
+ |
+
+ {row}
+ |
+ {columns.map((col) => (
+
+ ${data[col][row].toLocaleString()}
+ |
+ ))}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/client/src/hooks/resources.ts b/client/src/hooks/resources.ts
new file mode 100644
index 0000000..9b71ea4
--- /dev/null
+++ b/client/src/hooks/resources.ts
@@ -0,0 +1,57 @@
+import { useQuery } from "@tanstack/react-query";
+
+export interface Resource {
+ id: string;
+ date_made: string;
+ author: string;
+ image: string | null;
+ name: string;
+ slug: string;
+ summary: string;
+ type: "page" | "file";
+ body: string;
+ file_url: string;
+ file_name: string;
+}
+
+const MOCK_RESOURCES: Resource[] = [
+ {
+ id: "1",
+ date_made: "2022-02-04",
+ author: "John Doe",
+ image: null,
+ name: "New feature available on Devias",
+ slug: "new-feature",
+ summary:
+ "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.",
+ type: "page",
+ body: "",
+ file_url: "",
+ file_name: "",
+ },
+ {
+ id: "2",
+ date_made: "2023-08-15",
+ author: "Jane Smith",
+ image: null,
+ name: "How to electrify your home",
+ slug: "electrify-your-home",
+ summary: "A practical guide to going all-electric in Western Australia.",
+ type: "page",
+ body: "",
+ file_url: "",
+ file_name: "",
+ },
+];
+
+export function useResources() {
+ return useQuery({
+ queryKey: ["resources"],
+ queryFn: async (): Promise => {
+ // Swap this block for the real fetch once the endpoint lands:
+ // const res = await fetch("/api/resources/");
+ // return res.json();
+ return MOCK_RESOURCES;
+ },
+ });
+}
diff --git a/client/src/styles/components/savings_card.module.css b/client/src/styles/components/savings_card.module.css
new file mode 100644
index 0000000..fdbd381
--- /dev/null
+++ b/client/src/styles/components/savings_card.module.css
@@ -0,0 +1,53 @@
+Savinghorizontalcard.module · CSS .card {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ width: 100%;
+}
+
+.leftPanel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.rightPanel {
+ flex: 1;
+ overflow-x: auto;
+}
+
+.table {
+ width: 100%;
+ border-collapse: collapse;
+ table-layout: fixed;
+}
+
+.rowLabel {
+ display: flex;
+ align-items: center;
+}
+
+.dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 9999px;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+@media (min-width: 640px) {
+ .card {
+ flex-direction: row;
+ gap: 1.5rem;
+ }
+
+ .rightPanel {
+ flex: 1.2;
+ }
+
+ .dot {
+ width: 10px;
+ height: 10px;
+ }
+}