Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DATABASE template/Design/Details/Style.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
cornerRadius: 12
style: rounded
logoSize: 1
3 changes: 2 additions & 1 deletion DATABASE/Design/Details/Style.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
cornerRadius: 0
style: sharp
style: sharp
logoSize: 1
2 changes: 1 addition & 1 deletion app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function AboutPage() {
const response = await fetch('/api/design');
const designData = await response.json();
setDesign(designData);
document.title = `${designData.companyName} - About`;
document.title = `${designData.companyName || 'Shuttle'} - About`;

const versionResponse = await fetch('/api/version');
const versionData = await versionResponse.json();
Expand Down
1 change: 1 addition & 0 deletions app/cart/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default function CartPage() {
fetch('/api/inventory').then(r => r.ok ? r.json() : []).catch(() => []),
]).then(([designData, inventoryData]) => {
setDesign(designData);
document.title = `${designData.companyName || 'Shuttle'} - Cart`;
const map: Record<string, number> = {};
inventoryData.forEach((item: any) => { map[item.productId] = item.stock; });
setStockMap(map);
Expand Down
5 changes: 4 additions & 1 deletion app/checkout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ export default function CheckoutPage() {

fetch('/api/design')
.then(r => r.json())
.then(setDesign)
.then((designData) => {
setDesign(designData);
document.title = `${designData.companyName || 'Shuttle'} - Checkout`;
})
.catch(console.error);

fetch('/api/presets')
Expand Down
2 changes: 1 addition & 1 deletion app/collections/[collectionId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function CollectionPage({ params }: { params: Promise<{ collectio

const coll = await collectionResponse.json();
setCollection(coll);
document.title = `${designData.companyName} - ${coll.name}`;
document.title = `${designData.companyName || 'Shuttle'} - ${coll.name}`;

// Use showcase photo if available, otherwise fall back to product images
const showcaseImage = designData.collectionShowcaseImages?.[resolvedParams.collectionId];
Expand Down
2 changes: 1 addition & 1 deletion app/collections/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function CollectionsPage() {

setDesign(designData);
setCollections(collectionsData);
document.title = `${designData.companyName} - Collections`;
document.title = `${designData.companyName || 'Shuttle'} - Collections`;
}
loadData();
}, [router]);
Expand Down
7 changes: 6 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import Header from "@/components/Header";
import Footer from "@/components/Footer";
import PasswordProtection from "@/components/PasswordProtection";

// Always render per-request so the title/branding reflect the current
// DATABASE contents instead of whatever was present at build time.
export const dynamic = 'force-dynamic';

export async function generateMetadata(): Promise<Metadata> {
const design = getDesignData();
return {
title: design.companyName,
title: design.companyName || 'Shuttle',
description: design.descriptions.tagline,
icons: {
icon: '/favicon.ico',
Expand Down Expand Up @@ -83,6 +87,7 @@ export default function RootLayout({
titleFont={design.fonts.titleFont}
bodyFont={design.fonts.bodyFont}
cornerRadius={design.style.cornerRadius}
logoSize={design.style.logoSize}
/>
<main className="flex-grow">
{children}
Expand Down
5 changes: 4 additions & 1 deletion app/order-success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ function OrderSuccessContent() {
useEffect(() => {
fetch('/api/design')
.then(r => r.json())
.then(setDesign)
.then((designData) => {
setDesign(designData);
document.title = `${designData.companyName || 'Shuttle'} - Order Confirmation`;
})
.catch(console.error);

try {
Expand Down
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function Home() {

setDesign(designData);
setCollections(collectionsData);
document.title = designData.companyName;
document.title = designData.companyName || 'Shuttle';

// If single collection, flatten products and fetch inventory for stock badges
if (collectionsData.length >= 1 && collectionsData.length <= 1) {
Expand Down Expand Up @@ -95,7 +95,7 @@ export default function Home() {
src={design.logoWhitePath}
alt={design.companyName}
className="w-auto mx-auto mb-4"
style={{ height: '4.5rem', objectFit: 'contain' }}
style={{ height: `${4.5 * (design.style?.logoSize || 1)}rem`, objectFit: 'contain' }}
/>
) : (
<h1
Expand Down
2 changes: 1 addition & 1 deletion app/products/[productId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default function ProductPage({ params }: { params: Promise<{ productId: s
setDesign(designData);
setInventory(inventoryData);
if (productData && designData) {
document.title = `${designData.companyName} - ${productData.name}`;
document.title = `${designData.companyName || 'Shuttle'} - ${productData.name}`;
}
})
.catch(error => {
Expand Down
5 changes: 4 additions & 1 deletion app/secret/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export default function SecretPage() {
useEffect(() => {
fetch('/api/design')
.then(r => r.json())
.then(setDesign)
.then((designData) => {
setDesign(designData);
document.title = designData.companyName || 'Shuttle';
})
.catch(console.error);
}, []);

Expand Down
2 changes: 1 addition & 1 deletion app/shop-all/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function ShopAllPage() {
const collectionsData = await collectionsResponse.json();

setDesign(designData);
document.title = `${designData.companyName} - Shop All`;
document.title = `${designData.companyName || 'Shuttle'} - Shop All`;

// Build stock map from inventory
if (inventoryResponse?.ok) {
Expand Down
32 changes: 28 additions & 4 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,31 @@ interface FooterProps {
bodyFont?: string;
}

/**
* Returns true when a hex color is light enough that black foreground
* elements should be used on top of it. Falls back to "dark" (white
* foreground) when the color can't be parsed.
*/
function isLightColor(hex: string): boolean {
if (!hex) return false;
let h = hex.trim().replace('#', '');
if (h.length === 3) h = h.split('').map((c) => c + c).join('');
if (h.length !== 6) return false;
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
if ([r, g, b].some(isNaN)) return false;
// Perceived luminance (ITU-R BT.601)
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.6;
}

export default function Footer({ footerText, primaryColor, bodyFont = 'Inter' }: FooterProps) {
const version = getVersion();
const lightBg = isLightColor(primaryColor);
const textColor = lightBg ? '#1a1a1a' : '#ffffff';
// LR Paris logo is black artwork on a transparent background:
// keep it black on light footers, invert to white on dark footers.
const logoFilter = lightBg ? 'brightness(0)' : 'brightness(0) invert(1)';

return (
<footer
Expand All @@ -18,9 +41,9 @@ export default function Footer({ footerText, primaryColor, bodyFont = 'Inter' }:
>
<div className="container mx-auto px-4">
<div className="flex flex-col items-center space-y-4">
<p className="text-center text-white text-sm" style={{ fontFamily: bodyFont }}>{footerText}</p>
<p className="text-center text-sm" style={{ fontFamily: bodyFont, color: textColor }}>{footerText}</p>
<div className="flex items-center gap-2">
<p className="text-center text-white text-xs opacity-75" style={{ fontFamily: bodyFont }}>
<p className="text-center text-xs opacity-75" style={{ fontFamily: bodyFont, color: textColor }}>
{version} • Built with LR Paris Shuttle •{' '}
<Link href="/about" className="underline hover:opacity-80">
About
Expand All @@ -41,20 +64,21 @@ export default function Footer({ footerText, primaryColor, bodyFont = 'Inter' }:
/>
</Link>
</div>
{/* LR Paris Logo */}
{/* LR Paris Logo — no background box, color adapts to footer background */}
<a
href="https://lrparis.com"
target="_blank"
rel="noopener noreferrer"
className="block hover:opacity-90 transition-opacity"
style={{ background: 'transparent' }}
>
<Image
src="/lr-paris-logo.svg"
alt="LR Paris"
width={60}
height={60}
className="block"
style={{ filter: 'brightness(0) invert(1)' }}
style={{ filter: logoFilter, background: 'transparent' }}
/>
</a>
</div>
Expand Down
10 changes: 7 additions & 3 deletions components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface HeaderProps {
titleFont?: string;
bodyFont?: string;
cornerRadius?: number;
logoSize?: number;
}

interface Collection {
Expand All @@ -30,7 +31,8 @@ export default function Header({
secondaryColor: initialSecondaryColor,
titleFont: initialTitleFont = 'Inter',
bodyFont: initialBodyFont = 'Inter',
cornerRadius: initialCornerRadius = 12
cornerRadius: initialCornerRadius = 12,
logoSize: initialLogoSize = 1
}: HeaderProps) {
const [cartItemCount, setCartItemCount] = useState(0);
const [collections, setCollections] = useState<Collection[]>([]);
Expand All @@ -46,6 +48,7 @@ export default function Header({
const [titleFont, setTitleFont] = useState(initialTitleFont);
const [bodyFont, setBodyFont] = useState(initialBodyFont);
const [cornerRadius, setCornerRadius] = useState(initialCornerRadius);
const [logoSize, setLogoSize] = useState(initialLogoSize);

const pathname = usePathname();
const isHome = pathname === '/';
Expand Down Expand Up @@ -83,6 +86,7 @@ export default function Header({
setTitleFont(designData.fonts.titleFont);
setBodyFont(designData.fonts.bodyFont);
setCornerRadius(designData.style.cornerRadius);
setLogoSize(designData.style.logoSize || 1);
}
})
.catch(console.error);
Expand All @@ -106,7 +110,7 @@ export default function Header({
<div className="flex justify-center mb-2" style={{ opacity: isHome ? 0 : 1, transition: 'opacity 0.3s ease', pointerEvents: isHome ? 'none' : 'auto' }}>
<Link href="/" className="flex items-center">
{(logoWhitePath || logoPath) ? (
<img src={(logoWhitePath || logoPath)!} alt={companyName} className="h-8 w-auto flex-shrink-0" style={{ objectFit: 'contain' }} />
<img src={(logoWhitePath || logoPath)!} alt={companyName} className="w-auto flex-shrink-0" style={{ objectFit: 'contain', height: `${2 * logoSize}rem` }} />
) : (
<span className="text-xl font-bold text-white" style={{ fontFamily: titleFont }}>{companyName}</span>
)}
Expand Down Expand Up @@ -233,7 +237,7 @@ export default function Header({
<div className="header-desktop hidden md:flex items-center justify-between" style={{ alignItems: 'center', justifyContent: 'space-between' }}>
<Link href="/" className="flex items-center space-x-3" style={{ opacity: isHome ? 0 : 1, transition: 'opacity 0.3s ease', pointerEvents: isHome ? 'none' : 'auto' }}>
{(logoWhitePath || logoPath) ? (
<img src={(logoWhitePath || logoPath)!} alt={companyName} className="h-8 md:h-10 w-auto flex-shrink-0" style={{ objectFit: 'contain' }} />
<img src={(logoWhitePath || logoPath)!} alt={companyName} className="w-auto flex-shrink-0" style={{ objectFit: 'contain', height: `${2.5 * logoSize}rem` }} />
) : (
<span className="text-2xl font-bold text-white" style={{ fontFamily: titleFont }}>{companyName}</span>
)}
Expand Down
9 changes: 9 additions & 0 deletions lib/design.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export interface Fonts {
export interface Style {
cornerRadius: number;
style: string;
/** Logo size multiplier (1 = default). Applies to the header logo and home page hero logo. */
logoSize: number;
}

export interface FAQEntry {
Expand Down Expand Up @@ -168,15 +170,22 @@ export function getStyle(): Style {
const content = fs.readFileSync(stylePath, 'utf-8');
const parsed = parseKeyValueFile(content);

// logoSize is a multiplier (e.g. 1 = default, 1.5 = 50% bigger). Clamped to a sane range.
let logoSize = parseFloat(parsed.logoSize || '1');
if (isNaN(logoSize) || logoSize <= 0) logoSize = 1;
logoSize = Math.min(Math.max(logoSize, 0.25), 4);

return {
cornerRadius: parseInt(parsed.cornerRadius || '12', 10),
style: parsed.style || 'rounded',
logoSize,
};
} catch (error) {
console.error('Error reading style:', error);
return {
cornerRadius: 12,
style: 'rounded',
logoSize: 1,
};
}
}
Expand Down
9 changes: 5 additions & 4 deletions lib/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* - STS-2.6.1 - Stock badges show actual box counts, populated inventory CSV with real stock levels
* - STS-2.6.2 - Checkout inventory re-validation to prevent overselling, stock count on product page, negative stock allowed in CSV, description newline support
* - STS-2.6.3 - Launchpad email integration: order notify webhook, Status/Tracking columns in CSV, AdminEmail.txt support
* - STS-2.6.4 - Fix stale page title (always per-request, Shuttle fallback, set on all pages), logoSize setting in Style.txt, footer LR logo adapts to background color
*
* To increment version:
* 1. Update VERSION constant below
Expand All @@ -36,14 +37,14 @@
* 4. Commit with version number in commit message
*/

export const VERSION = 'STS-2.6.3';
export const VERSION = 'STS-2.6.4';

export const VERSION_INFO = {
name: 'Shop Template System',
version: VERSION,
codename: 'Guardian',
releaseDate: '2026-03-04',
description: 'Launchpad email integration: order notify webhook, Status/Tracking columns in CSV, AdminEmail.txt support',
codename: 'Beacon',
releaseDate: '2026-06-09',
description: 'Stale title fix, logoSize setting, adaptive footer logo color',
attribution: 'Built with LR Paris Shuttle',
};

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shop-template-system",
"version": "2.0.5",
"version": "2.6.4",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down