Files
spsays-training/components/social/post-card-shared.tsx
T

146 lines
5.6 KiB
TypeScript

"use client";
/**
* Helpers shared by PostGridCard and PostScrollCard.
*
* • postSourceHref — synthesizes `?hl=` if the stored URL lacks one
* • themeGradientStyle — picks one of four palette-aware gradients
* deterministically from a seed (post id) so a
* feed of image-less posts looks varied without
* shifting between renders.
*/
import type { Post } from "@/lib/social";
import { appendHighlight, hasHighlight } from "@/lib/text-highlight";
export function postSourceHref(post: Post): string {
return hasHighlight(post.source_url) || !post.caption
? post.source_url
: appendHighlight(post.source_url, post.caption);
}
// Quote-card gradients.
//
// Four CSS custom properties (`--quote-gradient-1` … 4) are defined per
// palette in `app/globals.css`. Each palette ships its own hand-tuned
// gradient set so the cards swap colour with the active theme:
//
// • Aged Parchment → mahogany binding + temple gold
// • Tulasī Garden → forest depths + amber edge
// • Yamunā Mist → river depths + dusk ochre
// • Ancient Manuscript → midnight leather + saffron
// • Nīlamaṇi → Kṛṣṇa's sapphire hour (deep blues)
// • Vraja Twilight → deep wine + rose dusk
//
// All variants are dark-leaning so `text-white` reads cleanly. The card
// picks one of the four deterministically from `post.id`.
const GRADIENT_VARS = [
"var(--quote-gradient-1)",
"var(--quote-gradient-2)",
"var(--quote-gradient-3)",
"var(--quote-gradient-4)",
];
/** Returns an inline-style object that applies the chosen gradient. The
* style approach (vs a className) lets us reference CSS custom properties
* that vary per palette — Tailwind can't generate utility classes for
* values that change at runtime via `:root` selectors. */
export function themeGradientStyle(seed: string): React.CSSProperties {
// Cheap, stable hash — same seed always picks the same gradient.
let h = 0;
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
return { backgroundImage: GRADIENT_VARS[h % GRADIENT_VARS.length] };
}
/** Flat background for post cards without an image: pure white on every
* light palette, palette-specific darker contrast on dark palettes.
* Defined as `--quote-card-bg` per `:root` / `.dark` block in
* globals.css. Combine with `text-foreground` for legibility — both
* sides of the theme switch reverse together. */
export function quoteCardBgStyle(): React.CSSProperties {
return { backgroundColor: "var(--quote-card-bg)" };
}
// ───────────────────────────────────────────────────────────────────────────
// Variable quote sizing
// ───────────────────────────────────────────────────────────────────────────
//
// A pithy two-word quote and a full paragraph need very different
// treatments inside a fixed-size card. We pick a tier from the caption's
// character count:
//
// tier 1 (very short) — biggest font, dead-centre, no truncation needed
// tier 2 (short) — large font, centred, ~5-line clamp
// tier 3 (medium) — medium font, centred, ~6/7-line clamp
// tier 4 (long) — smallest font, anchored bottom-LEFT and left-
// aligned (a centred wall of small text would
// cover the entire card and look claustrophobic;
// bottom-left mimics the image variant's caption
// placement and breathes better).
//
// Two scales: "grid" for the small square card, "scroll" for the larger
// Instagram-style card.
export type QuoteSize = {
/** Tailwind font-size classes, e.g. "text-xl sm:text-2xl" */
fontClass: string;
/** Tailwind line-clamp class limiting overflow */
clampClass: string;
/** Where to anchor the text block inside its container */
alignment: "center" | "bottom-left";
};
export function quoteSize(text: string, variant: "grid" | "scroll"): QuoteSize {
const len = (text ?? "").trim().length;
if (variant === "grid") {
if (len <= 50)
return {
fontClass: "text-xl sm:text-2xl",
clampClass: "line-clamp-3",
alignment: "center",
};
if (len <= 150)
return {
fontClass: "text-base sm:text-lg",
clampClass: "line-clamp-5",
alignment: "center",
};
if (len <= 300)
return {
fontClass: "text-sm sm:text-base",
clampClass: "line-clamp-[7]",
alignment: "center",
};
return {
fontClass: "text-xs sm:text-sm",
clampClass: "line-clamp-[9]",
alignment: "bottom-left",
};
}
// scroll — bigger card, larger fonts at every tier, higher thresholds
// so paragraph-length quotes still get centred at a comfortable size.
if (len <= 60)
return {
fontClass: "text-3xl sm:text-4xl",
clampClass: "line-clamp-3",
alignment: "center",
};
if (len <= 180)
return {
fontClass: "text-2xl sm:text-3xl",
clampClass: "line-clamp-5",
alignment: "center",
};
if (len <= 380)
return {
fontClass: "text-lg sm:text-xl",
clampClass: "line-clamp-[8]",
alignment: "center",
};
return {
fontClass: "text-base sm:text-lg",
clampClass: "line-clamp-[10]",
alignment: "bottom-left",
};
}