146 lines
5.6 KiB
TypeScript
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",
|
|
};
|
|
}
|