heroes /
Landscape Card Hero
Centered white card hero floating on a full-bleed landscape photograph. Headline supports orange-underlined emphasis on selected words. Mobile-first, no shadcn, no JS.
Preview
Source
tsx
import type { ReactNode } from "react";
export interface LandscapeCardNavItem {
label: string;
href: string;
}
export interface LandscapeCardHeroProps {
brand?: string;
brandIcon?: ReactNode;
navItems?: LandscapeCardNavItem[];
navCta?: { label: string; href: string };
/** Words inside the headline to highlight in orange + underline. Matched case-insensitively. */
emphasizedWords?: string[];
headline?: string;
subhead?: string;
primaryCta?: { label: string; href: string };
/** URL of the full-bleed background image. Default ships with the component at /heroes/landscape-card-bg.webp */
backgroundSrc?: string;
/** Alt text for the background image (decorative by default). */
backgroundAlt?: string;
}
const DefaultBrandIcon = () => (
<svg
aria-hidden
viewBox="0 0 24 24"
className="h-6 w-6"
fill="none"
>
<path
d="M12 2 L21 8 L18 20 L6 20 L3 8 Z"
fill="#f97316"
stroke="#c2410c"
strokeWidth="0.8"
strokeLinejoin="round"
/>
<path d="M12 2 L18 20 M12 2 L6 20 M3 8 L21 8" stroke="#fff" strokeOpacity="0.55" strokeWidth="0.6" />
</svg>
);
function highlightWords(text: string, words: string[]) {
if (!words || words.length === 0) return text;
// Build a regex matching any of the target words, case-insensitive, whole-word.
const escaped = words.map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
const re = new RegExp(`\\b(${escaped.join("|")})\\b`, "gi");
const parts: (string | { match: string })[] = [];
let lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = re.exec(text)) !== null) {
if (m.index > lastIndex) parts.push(text.slice(lastIndex, m.index));
parts.push({ match: m[0] });
lastIndex = re.lastIndex;
}
if (lastIndex < text.length) parts.push(text.slice(lastIndex));
return parts.map((part, i) =>
typeof part === "string" ? (
<span key={i}>{part}</span>
) : (
<span
key={i}
className="text-[#f97316] underline decoration-[3px] underline-offset-[6px] decoration-[#f97316]"
>
{part.match}
</span>
)
);
}
export default function LandscapeCardHero({
brand = "Orelloo",
brandIcon,
navItems = [
{ label: "Home", href: "#" },
{ label: "Service", href: "#service" },
{ label: "Resources", href: "#resources" },
{ label: "About us", href: "#about" },
],
navCta = { label: "Contact us", href: "#contact" },
emphasizedWords = ["Secure", "future"],
headline = "Secure your business, Secure your future",
subhead = "Data analysis software is a type of software tool used for data analysis and reporting. It is designed to help businesses, organizations, and individuals process, visualize.",
primaryCta = { label: "Get Started Free", href: "#start" },
backgroundSrc = "/heroes/landscape-card-bg.webp",
backgroundAlt = "",
}: LandscapeCardHeroProps) {
return (
<section className="relative isolate min-h-[120svh] w-full overflow-hidden bg-slate-900 sm:min-h-[125svh] lg:min-h-[130svh]">
{/* Full-bleed background image */}
<img
src={backgroundSrc}
alt={backgroundAlt}
width={1800}
height={1012}
loading="eager"
fetchPriority="high"
decoding="async"
className="absolute inset-0 -z-10 h-full w-full object-cover"
/>
{/* Full-width white panel that fades to transparent at its bottom edge,
letting the landscape image blend through into the lower viewport. */}
<div
className="w-full bg-gradient-to-b from-white from-0% via-white via-65% to-transparent to-100% pb-40 sm:pb-56 lg:pb-64"
>
<div className="mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8">
{/* Nav */}
<nav className="flex items-center justify-between gap-4 py-4 sm:py-5">
<a href="/" className="flex items-center gap-2">
{brandIcon ?? <DefaultBrandIcon />}
<span className="text-lg font-bold text-slate-900">{brand}</span>
</a>
<div className="hidden items-center gap-7 md:flex">
{navItems.map((item, i) => (
<a
key={item.label}
href={item.href}
className={`text-sm font-medium transition-colors ${
i === 0
? "text-[#f97316] underline decoration-2 underline-offset-4 decoration-[#f97316]"
: "text-slate-600 hover:text-slate-900"
}`}
>
{item.label}
</a>
))}
</div>
<a
href={navCta.href}
className="hidden rounded-full bg-[#f97316] px-5 py-2.5 text-sm font-semibold text-white shadow-md shadow-[#f97316]/25 transition-all hover:-translate-y-0.5 hover:bg-[#ea580c] hover:shadow-lg sm:inline-flex"
>
{navCta.label}
</a>
<button
type="button"
aria-label="Open menu"
className="rounded-md p-2 text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900 md:hidden"
>
<svg viewBox="0 0 24 24" fill="none" className="h-5 w-5">
<path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
</svg>
</button>
</nav>
{/* Headline + subhead + CTA */}
<div className="pb-12 pt-6 text-center sm:pb-16 sm:pt-10 lg:pb-20">
<h1 className="mx-auto max-w-3xl text-4xl font-semibold leading-[1.15] tracking-tight text-slate-900 sm:text-5xl lg:text-[3.5rem]">
{highlightWords(headline, emphasizedWords)}
</h1>
<p className="mx-auto mt-6 max-w-xl text-sm leading-relaxed text-slate-500 sm:text-base">
{subhead}
</p>
<div className="mt-8 flex justify-center">
<a
href={primaryCta.href}
className="inline-flex items-center justify-center rounded-full bg-[#f97316] px-8 py-3.5 text-sm font-semibold text-white shadow-lg shadow-[#f97316]/30 transition-all hover:-translate-y-0.5 hover:bg-[#ea580c] hover:shadow-xl hover:shadow-[#f97316]/40"
>
{primaryCta.label}
</a>
</div>
</div>
</div>
</div>
</section>
);
} Claude Code Instructions
CLI Install
npx innovations add landscape-cardWhere to use it
Drop this at the top of a landing page where the brand wants a moody, premium, photographic feel.
In Astro (src/pages/index.astro):
---
import LandscapeCardHero from '../components/innovations/heroes/landscape-card';
---
<LandscapeCardHero />
No client:* directive needed — ships zero JS by default.
In Next.js (app/page.tsx):
import LandscapeCardHero from '@/components/innovations/heroes/landscape-card';
// Drop at the top of the page return.
ASSETS:
The default background image is served from /heroes/landscape-card-bg.webp.
When installing into a target project, also copy public/heroes/landscape-card-bg.webp into that project's public/ directory (or pass your own backgroundSrc prop).
The image is a misty forest landscape generated for this hero. Swap to your own photo via:
<LandscapeCardHero backgroundSrc="/your-image.webp" />
Aim for a wide image (1600-2000px wide), JPEG or WebP, around 80-120 KB. The composition should have visual interest at the bottom (foreground) and softer / lighter content at the top so the white card edges blend in.
CUSTOMIZATION:
Highlight ANY words in the headline by passing emphasizedWords:
<LandscapeCardHero
headline="Build something brilliant for tomorrow"
emphasizedWords={["brilliant", "tomorrow"]}
/>
The matcher is case-insensitive and whole-word.
ACCENT COLOR (orange #f97316 / #ea580c) is hard-coded for visual fidelity. To rebrand, search/replace those two hex values across the file.
NAV STRIP: included inside the white card. If you already have a global navbar, delete the <nav> block — the card collapses cleanly.
PERFORMANCE NOTES:
- The background <img> uses loading="eager" + fetchPriority="high" because it's above the fold.
- Section uses min-h-[100svh] (small-viewport height unit) so iOS Safari address-bar UI doesn't add extra scroll on first paint.