feat: use real blog cover images
Replace the SVG placeholder illustrations with blog1/blog2 covers and remove the unused illustrations component. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
BIN
public/blog1.png
Normal file
BIN
public/blog1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
public/blog2.png
Normal file
BIN
public/blog2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -3,7 +3,7 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { Select } from "@/components/ui/select";
|
import { Select } from "@/components/ui/select";
|
||||||
import { Telescope, Target } from "@/components/ui/illustrations";
|
import Image from "next/image";
|
||||||
import { Reveal } from "@/components/ui/reveal";
|
import { Reveal } from "@/components/ui/reveal";
|
||||||
import { categories, posts, type Post } from "@/data/posts";
|
import { categories, posts, type Post } from "@/data/posts";
|
||||||
|
|
||||||
@@ -25,10 +25,11 @@ function boundary({ y, m, d }: DateParts, end: boolean) {
|
|||||||
|
|
||||||
const dateFormat = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
|
const dateFormat = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
|
||||||
|
|
||||||
const themes = {
|
// The orange cover is the blue telescope image hue-shifted.
|
||||||
navy: "bg-[#11192d]",
|
const covers = {
|
||||||
blue: "bg-linear-to-br from-[#0160d4] to-[#0a8bff]",
|
dart: { src: "/blog1.png", className: "" },
|
||||||
orange: "bg-linear-to-br from-[#f3610c] to-[#ffb020]",
|
telescope: { src: "/blog2.png", className: "" },
|
||||||
|
"telescope-orange": { src: "/blog2.png", className: "hue-rotate-[188deg] saturate-125" },
|
||||||
};
|
};
|
||||||
|
|
||||||
function CalendarIcon() {
|
function CalendarIcon() {
|
||||||
@@ -41,12 +42,11 @@ function CalendarIcon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PostCard({ post }: { post: Post }) {
|
function PostCard({ post }: { post: Post }) {
|
||||||
const Art = post.art === "telescope" ? Telescope : Target;
|
const cover = covers[post.cover];
|
||||||
return (
|
return (
|
||||||
<article className="flex flex-col gap-3">
|
<article className="flex flex-col gap-3">
|
||||||
<div className={`relative aspect-[16/9] overflow-hidden rounded-2xl ${themes[post.theme]}`}>
|
<div className="relative aspect-[361/208] overflow-hidden rounded-2xl">
|
||||||
<div aria-hidden className="absolute -right-10 top-6 size-56 rounded-full border-20 border-white/10" />
|
<Image src={cover.src} alt="" fill sizes="(min-width: 640px) 25rem, 100vw" className={`object-cover ${cover.className}`} />
|
||||||
<Art className="absolute -left-6 top-0 h-[115%] w-auto drop-shadow-xl" />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 px-2">
|
<div className="flex flex-col gap-1 px-2">
|
||||||
<h3 className="text-sm font-bold text-[#11192d]">{post.title}</h3>
|
<h3 className="text-sm font-bold text-[#11192d]">{post.title}</h3>
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
/** Placeholder 3D-style illustrations. Swap for the real artwork when available. */
|
|
||||||
|
|
||||||
function Defs() {
|
|
||||||
return (
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="ill-body" x1="0" y1="0" x2="1" y2="1">
|
|
||||||
<stop offset="0" stopColor="#ffffff" />
|
|
||||||
<stop offset="1" stopColor="#c9d0dd" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Telescope({ className = "" }: { className?: string }) {
|
|
||||||
return (
|
|
||||||
<svg viewBox="0 0 200 200" aria-hidden className={className}>
|
|
||||||
<Defs />
|
|
||||||
<ellipse cx="100" cy="182" rx="38" ry="7" fill="#000" opacity="0.12" />
|
|
||||||
<path d="M90 112 70 178h60l-20-62Z" fill="url(#ill-body)" />
|
|
||||||
<circle cx="100" cy="112" r="9" fill="#e6e9ef" />
|
|
||||||
<g transform="rotate(-35 100 100)">
|
|
||||||
<rect x="44" y="82" width="96" height="38" rx="16" fill="url(#ill-body)" />
|
|
||||||
<rect x="126" y="76" width="22" height="50" rx="9" fill="#f4f6fa" />
|
|
||||||
<ellipse cx="146" cy="101" rx="8" ry="19" fill="#aeb6c6" />
|
|
||||||
<rect x="60" y="68" width="40" height="12" rx="6" fill="url(#ill-body)" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Target({ className = "" }: { className?: string }) {
|
|
||||||
return (
|
|
||||||
<svg viewBox="0 0 200 200" aria-hidden className={className}>
|
|
||||||
<Defs />
|
|
||||||
<ellipse cx="100" cy="176" rx="52" ry="8" fill="#000" opacity="0.12" />
|
|
||||||
<circle cx="96" cy="108" r="62" fill="url(#ill-body)" />
|
|
||||||
<circle cx="96" cy="108" r="46" fill="#dfe3ec" />
|
|
||||||
<circle cx="96" cy="108" r="31" fill="url(#ill-body)" />
|
|
||||||
<circle cx="96" cy="108" r="15" fill="#c2cadb" />
|
|
||||||
<path d="M96 108 168 38" stroke="#d5dbe7" strokeWidth="6" strokeLinecap="round" />
|
|
||||||
<path d="m150 38 22-6-6 22-10-4-6-12Z" fill="url(#ill-body)" />
|
|
||||||
<path d="m132 54 12 4-18 18-6-10 12-12Z" fill="#f4f6fa" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5,23 +5,22 @@ export type Post = {
|
|||||||
category: string;
|
category: string;
|
||||||
/** ISO date (YYYY-MM-DD) */
|
/** ISO date (YYYY-MM-DD) */
|
||||||
date: string;
|
date: string;
|
||||||
theme: "navy" | "blue" | "orange";
|
cover: "dart" | "telescope" | "telescope-orange";
|
||||||
art: "telescope" | "target";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const categories = ["Industry Insights", "Guides", "Product Updates", "Customer Stories"];
|
export const categories = ["Industry Insights", "Guides", "Product Updates", "Customer Stories"];
|
||||||
|
|
||||||
export const posts: Post[] = [
|
export const posts: Post[] = [
|
||||||
{ slug: "choosing-tour-booking-software", title: "How to choose the right tour booking software", excerpt: "A practical checklist for comparing booking platforms for tours, transfers and rentals.", category: "Guides", date: "2026-09-30", theme: "navy", art: "target" },
|
{ slug: "choosing-tour-booking-software", title: "How to choose the right tour booking software", excerpt: "A practical checklist for comparing booking platforms for tours, transfers and rentals.", category: "Guides", date: "2026-09-30", cover: "dart" },
|
||||||
{ slug: "ai-in-tour-operations", title: "How AI is changing tour operations", excerpt: "From dynamic pricing to automated customer replies, where AI already saves operators time.", category: "Industry Insights", date: "2026-09-27", theme: "blue", art: "telescope" },
|
{ slug: "ai-in-tour-operations", title: "How AI is changing tour operations", excerpt: "From dynamic pricing to automated customer replies, where AI already saves operators time.", category: "Industry Insights", date: "2026-09-27", cover: "telescope" },
|
||||||
{ slug: "ota-connectivity-explained", title: "OTA connectivity explained", excerpt: "How to distribute inventory to marketplaces without double bookings or manual updates.", category: "Guides", date: "2026-09-21", theme: "orange", art: "telescope" },
|
{ slug: "ota-connectivity-explained", title: "OTA connectivity explained", excerpt: "How to distribute inventory to marketplaces without double bookings or manual updates.", category: "Guides", date: "2026-09-21", cover: "telescope-orange" },
|
||||||
{ slug: "voyager-fleet-management", title: "New: fleet and staff scheduling", excerpt: "Assign vehicles and drivers to departures in a few clicks, with conflicts flagged for you.", category: "Product Updates", date: "2026-09-14", theme: "navy", art: "target" },
|
{ slug: "voyager-fleet-management", title: "New: fleet and staff scheduling", excerpt: "Assign vehicles and drivers to departures in a few clicks, with conflicts flagged for you.", category: "Product Updates", date: "2026-09-14", cover: "dart" },
|
||||||
{ slug: "direct-bookings-playbook", title: "A playbook for more direct bookings", excerpt: "Lower your OTA commissions by turning your own website into your best sales channel.", category: "Industry Insights", date: "2026-08-29", theme: "navy", art: "target" },
|
{ slug: "direct-bookings-playbook", title: "A playbook for more direct bookings", excerpt: "Lower your OTA commissions by turning your own website into your best sales channel.", category: "Industry Insights", date: "2026-08-29", cover: "dart" },
|
||||||
{ slug: "city-tours-case-study", title: "How a city tour operator doubled online sales", excerpt: "A look at the workflows and tools behind a year of steady growth.", category: "Customer Stories", date: "2026-08-17", theme: "blue", art: "telescope" },
|
{ slug: "city-tours-case-study", title: "How a city tour operator doubled online sales", excerpt: "A look at the workflows and tools behind a year of steady growth.", category: "Customer Stories", date: "2026-08-17", cover: "telescope" },
|
||||||
{ slug: "pricing-your-tours", title: "Fixed vs. dynamic pricing for tours", excerpt: "When to keep prices simple and when demand-based pricing pays off.", category: "Guides", date: "2026-07-22", theme: "orange", art: "telescope" },
|
{ slug: "pricing-your-tours", title: "Fixed vs. dynamic pricing for tours", excerpt: "When to keep prices simple and when demand-based pricing pays off.", category: "Guides", date: "2026-07-22", cover: "telescope-orange" },
|
||||||
{ slug: "payments-update", title: "Faster payouts and simpler reconciliation", excerpt: "What changed in payments, and how it affects your month-end reporting.", category: "Product Updates", date: "2026-07-03", theme: "navy", art: "target" },
|
{ slug: "payments-update", title: "Faster payouts and simpler reconciliation", excerpt: "What changed in payments, and how it affects your month-end reporting.", category: "Product Updates", date: "2026-07-03", cover: "dart" },
|
||||||
{ slug: "rental-business-growth", title: "Adding rentals to a tour business", excerpt: "Why many operators expand into vehicle and equipment rentals, and how to start.", category: "Industry Insights", date: "2026-06-12", theme: "navy", art: "target" },
|
{ slug: "rental-business-growth", title: "Adding rentals to a tour business", excerpt: "Why many operators expand into vehicle and equipment rentals, and how to start.", category: "Industry Insights", date: "2026-06-12", cover: "dart" },
|
||||||
{ slug: "transfer-company-story", title: "A transfer company goes paperless", excerpt: "Replacing spreadsheets and phone calls with one connected booking system.", category: "Customer Stories", date: "2026-05-20", theme: "blue", art: "telescope" },
|
{ slug: "transfer-company-story", title: "A transfer company goes paperless", excerpt: "Replacing spreadsheets and phone calls with one connected booking system.", category: "Customer Stories", date: "2026-05-20", cover: "telescope" },
|
||||||
{ slug: "crm-for-operators", title: "Why tour operators need a CRM", excerpt: "Keep every guest, enquiry and repeat booking in one place.", category: "Guides", date: "2026-04-08", theme: "orange", art: "telescope" },
|
{ slug: "crm-for-operators", title: "Why tour operators need a CRM", excerpt: "Keep every guest, enquiry and repeat booking in one place.", category: "Guides", date: "2026-04-08", cover: "telescope-orange" },
|
||||||
{ slug: "api-and-integrations", title: "API and integrations: what is new", excerpt: "New endpoints and partner integrations for connecting Voyager to your stack.", category: "Product Updates", date: "2026-03-15", theme: "navy", art: "target" },
|
{ slug: "api-and-integrations", title: "API and integrations: what is new", excerpt: "New endpoints and partner integrations for connecting Voyager to your stack.", category: "Product Updates", date: "2026-03-15", cover: "dart" },
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user