tight UI-first bundle that matches your screenshots and rules:

Top-tier industries (FIG, TMT, Healthcare/Pharma, General)

Sub-categories (the list you gave) as filters

Gallery cards (price $14.99, “Bundle” chip, formats text)

Click a card → zoom lightbox showing Cover + 3 Divider previews (same interaction vibe as stock photos)

Remove “Custom Design” CTA

Contributors can upload covers/dividers the same way they upload stock/mockups (goes into approval → monetized)

Below are drop-in changes (frontend + API + schema). Keep Stripe wired from earlier but we’ll focus on UI working perfectly; checkout button can stay pointed at /checkout.

1) Schema (minimal additions)
-- add fields for industry & previews
ALTER TABLE cover_templates
  ADD COLUMN IF NOT EXISTS top_tier TEXT CHECK (top_tier IN ('FIG','TMT','Healthcare/Pharma','General')) DEFAULT 'General',
  ADD COLUMN IF NOT EXISTS subcategories TEXT[] DEFAULT '{}',
  ADD COLUMN IF NOT EXISTS cover_preview_url TEXT,
  ADD COLUMN IF NOT EXISTS divider1_preview_url TEXT,
  ADD COLUMN IF NOT EXISTS divider2_preview_url TEXT,
  ADD COLUMN IF NOT EXISTS divider3_preview_url TEXT;

-- optional: approval like other creator assets
ALTER TABLE cover_templates
  ADD COLUMN IF NOT EXISTS approval_status TEXT DEFAULT 'pending'; -- pending|approved|rejected


Keep preview_image_url for the card thumbnail. The four new preview URLs feed the lightbox.

2) API (filters + lightbox data)

server/storage/coverTemplates.ts – extend list filter:

export async function listCoverTemplates(opts: {
  q?: string; category?: string; min?: number; max?: number;
  top_tier?: 'FIG'|'TMT'|'Healthcare/Pharma'|'General';
  subcat?: string; // single subcategory filter
} = {}) {
  let qy = db("cover_templates").where({ is_active: true }).andWhere({ approval_status: 'approved' });
  if (opts.q) qy = qy.andWhereILike("title", `%${opts.q}%`);
  if (opts.category) qy = qy.andWhere("category", opts.category);
  if (opts.top_tier) qy = qy.andWhere("top_tier", opts.top_tier);
  if (opts.subcat) qy = qy.andWhereRaw("? = ANY (subcategories)", [opts.subcat]);
  if (opts.min != null) qy = qy.andWhere("price_cents", ">=", opts.min);
  if (opts.max != null) qy = qy.andWhere("price_cents", "<=", opts.max);
  return qy.orderBy("created_at", "desc");
}


server/routes.coverTemplates.ts – accept new filters and return all preview URLs:

router.get("/api/cover-templates", async (req, res) => {
  const rows = await listCoverTemplates({
    q: req.query.q as string | undefined,
    category: req.query.category as string | undefined,
    top_tier: req.query.toptier as any,
    subcat: req.query.subcat as string | undefined,
    min: req.query.min ? parseInt(req.query.min as string,10) : undefined,
    max: req.query.max ? parseInt(req.query.max as string,10) : undefined,
  });
  res.json(rows);
});

router.get("/api/cover-templates/:id", async (req, res) => {
  const tpl = await getCoverTemplate(req.params.id);
  if (!tpl || !tpl.is_active || tpl.approval_status !== 'approved') return res.status(404).json({ error: "Not found" });
  res.json(tpl); // includes cover_preview_url + divider1..3
});


Contributor upload: extend payload to include tier/subcats + 4 previews:

// /api/creator/cover-templates (body additions)
const { top_tier, subcategories, cover_preview_url, divider1_preview_url, divider2_preview_url, divider3_preview_url } = req.body || {};
// …store them and keep is_active=false, approval_status='pending'

3) Frontend — Gallery page (filters + lightbox of 4 images)

Replace your current page CoverDividerTemplates.tsx with this version (UI only; Stripe call remains as before). This matches your screenshots: hero, filters, cards with price/“Bundle”, no custom-design block, click → zoom/lightbox with Cover + 3 Divider previews.

import React, { useEffect, useMemo, useState } from "react";
import { DashboardTemplatePage } from "@/components/DashboardTemplatePage";
import { useQuery } from "@tanstack/react-query";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Dialog } from "@/components/ui/dialog";

type CT = {
  id: string; title: string; category: string; price_cents: number; currency: string;
  top_tier: 'FIG'|'TMT'|'Healthcare/Pharma'|'General';
  subcategories: string[];
  preview_image_url: string;              // card image
  cover_preview_url?: string | null;      // lightbox image 1
  divider1_preview_url?: string | null;   // lightbox image 2
  divider2_preview_url?: string | null;   // lightbox image 3
  divider3_preview_url?: string | null;   // lightbox image 4
};

const fmt = (c: number) => `$${(c/100).toFixed(2)}`;

const TOP_TIERS = ['FIG','TMT','Healthcare/Pharma','General'] as const;
const SUBCATS = [
  "Manufacturing","Healthcare","Finance & Insurance","Retail Trade","Information Technology (IT) / Information",
  "Professional, Scientific, & Technical Services","Construction","Real Estate","Accommodation & Food Services",
  "Transportation & Warehousing","Utilities","Agriculture, Forestry, Fishing, & Hunting",
  "Public Administration","Entertainment"
];

export default function CoverDividerTemplates() {
  // filters
  const [qPending, setQPending] = useState("");
  const [q, setQ] = useState("");
  const [tier, setTier] = useState<string>("all");
  const [category, setCategory] = useState<string>("all");
  const [subcat, setSubcat] = useState<string>("all");
  const [min, setMin] = useState(""); const [max, setMax] = useState("");

  useEffect(()=>{ const id = setTimeout(()=>setQ(qPending), 300); return ()=>clearTimeout(id); }, [qPending]);

  const { data, isLoading, refetch } = useQuery({
    queryKey: ["cover-templates", q, tier, category, subcat, min, max],
    queryFn: async () => {
      const p = new URLSearchParams();
      if (q) p.set("q", q);
      if (tier !== "all") p.set("toptier", tier);
      if (category !== "all") p.set("category", category);
      if (subcat !== "all") p.set("subcat", subcat);
      if (min) p.set("min", String(parseInt(min)*100));
      if (max) p.set("max", String(parseInt(max)*100));
      const r = await fetch(`/api/cover-templates?${p.toString()}`);
      if (!r.ok) throw new Error("load");
      return r.json() as Promise<CT[]>;
    }
  });

  // lightbox
  const [open, setOpen] = useState(false);
  const [sel, setSel] = useState<CT | null>(null);
  const [idx, setIdx] = useState(0);
  function openLightbox(t: CT) {
    setSel(t);
    setIdx(0);
    setOpen(true);
  }

  const slides = useMemo(()=> {
    if (!sel) return [];
    const list = [
      sel.cover_preview_url || sel.preview_image_url,
      sel.divider1_preview_url,
      sel.divider2_preview_url,
      sel.divider3_preview_url
    ].filter(Boolean) as string[];
    return list;
  }, [sel]);

  return (
    <DashboardTemplatePage title="Cover & Divider Templates">
      <div className="space-y-6">
        {/* HERO */}
        <div className="rounded-xl border bg-gradient-to-br from-emerald-50 to-sky-50 dark:from-muted dark:to-muted p-6">
          <h1 className="text-2xl font-bold">Professional Cover Templates</h1>
          <p className="text-muted-foreground">
            High-quality, editable cover designs for presentations, documents, and social media. Available in PowerPoint, Keynote, and Google Slides.
          </p>
          <div className="mt-2 text-xs text-muted-foreground flex gap-3">
            <span>✨ Instant Download</span>
            <span>🧩 Fully Customizable</span>
            <span>🗂️ Multi-Platform</span>
          </div>
        </div>

        {/* FILTERS */}
        <div className="rounded-xl border p-4 grid grid-cols-1 lg:grid-cols-5 gap-3 items-end">
          <div className="lg:col-span-2">
            <Input placeholder="Search templates…" value={qPending} onChange={(e)=>setQPending(e.target.value)} />
          </div>
          <Select value={tier} onValueChange={setTier}>
            <SelectTrigger><SelectValue placeholder="Top tier" /></SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All Top Tiers</SelectItem>
              {TOP_TIERS.map(t=> <SelectItem key={t} value={t}>{t}</SelectItem>)}
            </SelectContent>
          </Select>
          <Select value={category} onValueChange={setCategory}>
            <SelectTrigger><SelectValue placeholder="Category" /></SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All Categories</SelectItem>
              <SelectItem value="business">Business</SelectItem>
              <SelectItem value="professional">Professional</SelectItem>
              <SelectItem value="creative">Creative</SelectItem>
              <SelectItem value="modern">Modern</SelectItem>
            </SelectContent>
          </Select>
          <Select value={subcat} onValueChange={setSubcat}>
            <SelectTrigger><SelectValue placeholder="Sector" /></SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All Sectors</SelectItem>
              {SUBCATS.map(s=> <SelectItem key={s} value={s}>{s}</SelectItem>)}
            </SelectContent>
          </Select>
          <div className="flex gap-2">
            <Input placeholder="Min $" value={min} onChange={e=>setMin(e.target.value)} />
            <Input placeholder="Max $" value={max} onChange={e=>setMax(e.target.value)} />
            <Button onClick={()=>refetch()} disabled={isLoading}>Apply Filters</Button>
          </div>
        </div>

        {/* GALLERY */}
        <section>
          {isLoading ? (
            <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-6">
              {Array.from({length:6}).map((_,i)=>(
                <div key={i} className="rounded-lg border overflow-hidden animate-pulse">
                  <div className="aspect-[16/10] bg-muted" />
                  <div className="p-4">
                    <div className="h-4 w-2/3 bg-muted rounded mb-2" />
                    <div className="h-3 w-1/3 bg-muted rounded" />
                  </div>
                </div>
              ))}
            </div>
          ) : (data && data.length) ? (
            <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-6">
              {data!.map(t=>(
                <div key={t.id} className="rounded-lg border overflow-hidden hover:shadow transition">
                  <button onClick={()=>openLightbox(t)} className="relative w-full">
                    <img src={t.preview_image_url} alt={t.title} className="w-full h-full aspect-[16/10] object-cover" loading="lazy" />
                    {/* watermark overlay */}
                    <div className="absolute inset-0 grid place-items-center pointer-events-none opacity-60 mix-blend-multiply">
                      <div className="wm-bg text-xs sm:text-sm">IBrandBiz • Preview</div>
                    </div>
                    <div className="absolute top-2 left-2 text-[11px] bg-black/70 text-white px-2 py-1 rounded"> {t.top_tier} </div>
                  </button>
                  <div className="p-4">
                    <div className="font-semibold">{t.title}</div>
                    <div className="text-xs text-muted-foreground">Available formats: PPTX, Keynote, Google Slides</div>
                    <div className="mt-3 flex items-center justify-between">
                      <div className="text-emerald-600 font-bold">{fmt(t.price_cents)}</div>
                      <Button onClick={()=>openLightbox(t)}>Buy Now</Button>
                    </div>
                    <div className="mt-2 text-[11px] inline-block border rounded px-2 py-0.5">Bundle</div>
                  </div>
                </div>
              ))}
            </div>
          ) : (
            <div className="rounded-lg border p-8 text-center text-muted-foreground">No templates match your filters.</div>
          )}
        </section>

        {/* LIGHTBOX (Cover + 3 Dividers) */}
        <Dialog open={open} onOpenChange={setOpen}>
          {sel && (
            <div className="p-0 md:p-6 max-w-5xl w-full">
              <div className="grid md:grid-cols-[1fr,320px] gap-6">
                <div className="rounded-lg border overflow-hidden relative">
                  <img src={slides[idx]} className="w-full h-full object-contain bg-black/5 aspect-[16/9]" />
                  <div className="absolute inset-0 grid place-items-center pointer-events-none opacity-60 mix-blend-multiply">
                    <div className="wm-bg text-xs md:text-sm">IBrandBiz • Preview</div>
                  </div>
                </div>
                <aside className="space-y-3">
                  <div>
                    <div className="text-lg font-semibold">{sel.title}</div>
                    <div className="text-xs text-muted-foreground">{sel.top_tier}{sel.subcategories?.length?` • ${sel.subcategories[0]}`:''}</div>
                    <div className="mt-1 text-xl font-bold">{fmt(sel.price_cents)}</div>
                    <div className="text-xs text-muted-foreground">Includes editable PPTX, Keynote, or Google Slides.</div>
                  </div>
                  <div className="grid grid-cols-4 gap-2">
                    {slides.map((s, i)=>(
                      <button key={i} onClick={()=>setIdx(i)} className={`border rounded overflow-hidden ${i===idx?'ring-2 ring-primary':''}`}>
                        <img src={s} className="aspect-video object-cover w-full" />
                      </button>
                    ))}
                  </div>
                  <Button className="w-full" onClick={()=>window.location.href=`/api/cover-templates/${sel.id}/checkout`}>Buy Now</Button>
                </aside>
              </div>
            </div>
          )}
        </Dialog>

        {/* watermark CSS */}
        <style>{`
          .wm-bg {
            font-weight: 700;
            letter-spacing: .5px;
            color: rgba(0,0,0,.25);
            background-image: repeating-linear-gradient(45deg, rgba(255,255,255,.25) 0 20px, rgba(255,255,255,.15) 20px 40px);
            padding: .4rem .8rem; border-radius: .5rem; transform: rotate(-18deg); user-select: none; pointer-events: none;
          }
        `}</style>
      </div>
    </DashboardTemplatePage>
  );
}


Notes

The Buy Now inside the lightbox uses the same checkout route; for now it can be a simple redirect (we’ll keep Stripe wiring ready).

No “Custom Design” section anywhere.

4) Contributor upload (same as stock/mockups)

Update your creator upload form to include the extra fields:

Top tier (select: FIG, TMT, Healthcare/Pharma, General)

Sub-categories (multi-select from your list)

Card thumbnail (preview_image_url)

Lightbox previews: cover_preview_url, divider1_preview_url, divider2_preview_url, divider3_preview_url

Formats URLs: at least one of pptx_url, keynote_url, gslides_url (or a bundle zip)

Submit to /api/creator/cover-templates (we already added this earlier). It lands pending, then you approve/activate.