# Templates Templates are complete website starting points with customizable theming, pre-built sections, and consistent patterns. --- ## Directory Structure ``` src/ ├── templates/ # Template Pages (full websites) │ ├── hotel/ │ │ ├── page.tsx # Page component with sections │ │ └── theme.css # Colors, buttons, cards │ ├── saas/ │ └── ... │ └── components/ └── templates/ # Template Components (reusable sections) ├── ResultsComparison.tsx └── HeroOverlayMarquee.tsx ``` --- ## Template Pages Located in `/src/templates/[name]/`, each template contains: - `page.tsx` - Full page with StyleProvider wrapper and sections - `theme.css` - CSS variables for colors, typography, buttons, cards ### page.tsx Structure ```tsx import { StyleProvider } from "@/components/ui/StyleProvider"; import NavbarInline from "@/components/ui/NavbarInline"; import HeroBillboard from "@/components/sections/hero/HeroBillboard"; import FooterSimple from "@/components/sections/footer/FooterSimple"; import "./theme.css"; export default function TemplateName() { return ( {/* More sections... */} ); } ``` ### StyleProvider Options | Prop | Options | |------|---------| | `siteBackground` | `"aurora"`, `"cornerGlow"`, `"lightRaysCenter"`, `"lightRaysCorner"`, `"floatingGradient"`, `"gradientBars"`, `"horizonGlow"`, `"none"` | | `heroBackground` | `"cornerGlow"`, `"lightRaysCenter"`, `"lightRaysCorner"`, `"horizonGlow"`, `"gradientBars"`, `"none"` | | `buttonVariant` | `"stagger"`, `"arrow"`, `"expand"`, `"elastic"`, `"shift"`, `"magnetic"`, `"default"` | ### Asset Handling Use Google Cloud Storage CDN for all media: ``` https://storage.googleapis.com/webild/default/templates/[template-name]/[asset].webp https://storage.googleapis.com/webild/default/templates/[template-name]/[asset].mp4 ``` --- ## Adding Sections to a Template ### Decision Flow ``` 1. Need a section for your template ↓ 2. Check /src/components/sections/[category]/ ↓ ┌───────┴───────┐ ↓ ↓ EXISTS? DOESN'T EXIST? ↓ ↓ Import & Create template component use it in /src/components/templates/ ``` ### Step 1: Check Existing Sections Look in `/src/components/sections/` by category: | Category | Path | Examples | |----------|------|----------| | Hero | `hero/` | HeroBillboard, HeroSplit, HeroOverlay | | Features | `features/` | FeaturesBento, FeaturesMediaCards | | Testimonial | `testimonial/` | TestimonialTrustCard, TestimonialRatingCards | | Pricing | `pricing/` | PricingCards, PricingComparison | | Footer | `footer/` | FooterSimple, FooterSimpleCard | | Contact | `contact/` | ContactSplitForm, ContactCenter | | FAQ | `faq/` | FaqSimple, FaqTwoColumn | | Team | `team/` | TeamCards, TeamGrid | | Blog | `blog/` | BlogSimpleCards | | About | `about/` | AboutTextSplit | | Metrics | `metrics/` | MetricsCards | ### Step 2a: If Section Exists → Use It ```tsx import HeroBillboard from "@/components/sections/hero/HeroBillboard"; import FeaturesMediaCards from "@/components/sections/features/FeaturesMediaCards"; ``` ### Step 2b: If Section Doesn't Exist → Create Template Component 1. **First, study similar sections** in `/src/components/sections/[category]/` - Read 2-3 sections in the same category - Understand their props structure - Note which UI components they use - Observe animation and layout patterns 2. **Create your component** in `/src/components/templates/[Name].tsx` - Follow the same patterns you observed - Use consistent prop naming (tag, title, description, items) - Import the same UI components (Button, TextAnimation, etc.) --- ## Template Components Located in `/src/components/templates/`, these are reusable section-level components for templates when no standard section fits. ### When to Create Create when no suitable section exists in `/src/components/sections/` (see "Adding Sections" above). ### Current Components | Component | Purpose | |-----------|---------| | `ResultsComparison` | Before/after comparison marquee with treatment cards | | `HeroOverlayMarquee` | Full-screen hero with media background and bottom marquee | ### Structure Template components follow the same patterns as UI components: ```tsx import Button from "@/components/ui/Button"; import TextAnimation from "@/components/ui/TextAnimation"; import ImageOrVideo from "@/components/ui/ImageOrVideo"; import ScrollReveal from "@/components/ui/ScrollReveal"; import { cls } from "@/lib/utils"; type ItemType = { // item properties }; interface ComponentProps { tag: string; title: string; description: string; primaryButton?: { text: string; href: string }; secondaryButton?: { text: string; href: string }; items: ItemType[]; } const ComponentName = ({ tag, title, description, primaryButton, secondaryButton, items }: ComponentProps) => { return (
{/* Section content */}
); }; export default ComponentName; ``` --- ## theme.css Each template's theme.css defines the visual identity. ### Required Structure ```css /* [Template Name] - [Theme Description] */ @import "tailwindcss"; @import "../../styles/masks.css"; @import "../../styles/animations.css"; :root { /* Colors (9 required) */ --background: #ffffff; --card: #f5f5f5; --foreground: #171717; --primary-cta: #171717; --primary-cta-text: #ffffff; --secondary-cta: #f5f5f5; --secondary-cta-text: #171717; --accent: #171717; --background-accent: #171717; /* Layout */ --radius: 1.5rem; --width-content-width: clamp(40rem, 72.5vw, 100rem); /* Carousel */ --vw-1_5: 1.35rem; --width-carousel-padding: calc((100vw - var(--width-content-width)) / 2 + 1px - 1rem); --width-carousel-padding-controls: calc((100vw - var(--width-content-width)) / 2 + 1px); --width-carousel-item-2: calc(var(--width-content-width) / 2 - var(--vw-1_5) / 2); --width-carousel-item-3: calc(var(--width-content-width) / 3 - var(--vw-1_5) / 3 * 2); --width-carousel-item-4: calc(var(--width-content-width) / 4 - var(--vw-1_5) / 4 * 3); /* Typography */ --text-2xs: 0.62rem; --text-xs: 0.72rem; --text-sm: 0.82rem; --text-base: 0.92rem; --text-lg: 1rem; --text-xl: 1.1rem; --text-2xl: 1.3rem; --text-3xl: 1.6rem; --text-4xl: 2rem; --text-5xl: 2.75rem; --text-6xl: 3.3rem; --text-7xl: 4rem; --text-8xl: 4.5rem; --text-9xl: 7rem; } /* Mobile typography */ @media (max-width: 768px) { :root { --text-2xs: 2.5vw; /* ... mobile sizes ... */ --width-content-width: 85vw; } } @theme inline { /* Tailwind color mappings */ --color-background: var(--background); --color-card: var(--card); --color-foreground: var(--foreground); /* ... */ } /* Base styles */ * { scrollbar-width: thin; } html { overscroll-behavior: none; } body { background-color: var(--background); color: var(--foreground); } /* WEBILD_CARD_STYLE */ .card { background: var(--color-card); border: 1px solid rgba(0, 0, 0, 0.06); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); } /* WEBILD_PRIMARY_BUTTON */ .primary-button { background: linear-gradient(180deg, #1e1e1e 0%, #0c0c0c 100%); box-shadow: /* complex shadow */; } /* WEBILD_SECONDARY_BUTTON */ .secondary-button { background: var(--color-secondary-cta); border: 1px solid rgba(0, 0, 0, 0.08); } ``` --- ## Props & Naming Conventions ### Standard Props | Prop | Usage | |------|-------| | `tag` | Small badge/label text | | `title` | Main heading (h1/h2) | | `description` | Subtitle/description text | | `primaryButton` | `{ text: string; href: string }` | | `secondaryButton` | `{ text: string; href: string }` | | `items` / `features` | Array of data items | | `imageSrc` / `videoSrc` | Media sources (exclusive) | | `className` | Main wrapper element | | `[element]ClassName` | Specific element (titleClassName, etc.) | ### Discriminated Union for Media When a component accepts either image OR video (not both): ```typescript type Props = { tag: string; title: string; // ...other props } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); ``` --- ## Code Patterns ### cls() Utility Always use for conditional classNames: ```tsx import { cls } from "@/lib/utils"; className={cls( "base-classes px-4 py-2", isActive && "bg-primary", variant === "large" ? "text-xl" : "text-base" )} ``` ### Responsive Padding Use consistent responsive spacing: ```tsx // Container padding className="p-3 xl:p-4 2xl:p-5" // Gaps className="gap-3 xl:gap-4 2xl:gap-5" // Margins className="mb-3 xl:mb-4 2xl:mb-5" ``` ### UI Components Import from `/src/components/ui/`: | Component | Purpose | |-----------|---------| | `Button` | CTA buttons with variant prop | | `TextAnimation` | Animated headings with gradientText option | | `ImageOrVideo` | Handles both img and video display | | `ScrollReveal` | Scroll-triggered animation wrapper | | `AvatarGroup` | Avatar displays with overflow and label | | `Accordion` | Expandable content sections | ### Marquee Pattern For infinite horizontal scrolling: ```tsx const duplicated = [...items, ...items, ...items, ...items];
{duplicated.map((item, i) => (
{/* Item content */}
))}
``` ### Section Header Pattern Consistent header for sections: ```tsx

{tag}

{(primaryButton || secondaryButton) && (
{primaryButton &&
)}
``` --- ## Template Catalog | Template | Theme | Navbar | siteBackground | |----------|-------|--------|----------------| | landscaping | Light Green | NavbarCentered | aurora | | luxury-travel-agency | Light Beige | NavbarInline | cornerGlow | | hvac | Light Blue | NavbarInline | lightRaysCenter | | plumber | Dark Blue | NavbarInline | lightRaysCorner | | roofing | Dark Orange | NavbarCentered | cornerGlow | | saas | Dark Purple | NavbarInline | aurora | | skincare | Light Sand | NavbarFloating | floatingGradient | | dentist | Light Blue | NavbarInline | lightRaysCenter | | detailing | Dark Orange | NavbarCentered | cornerGlow | | real-estate | Light Elegant | NavbarFloating | gradientBars | | skincare-luxury | Light Rose | NavbarDropdown | floatingGradient | | med-spa | Light Mauve | NavbarCentered | horizonGlow | | hotel | Dark Luxury | NavbarInline | none | --- ## Creating a Template 1. Create `/src/templates/[name]/page.tsx` and `theme.css` 2. Set up StyleProvider with appropriate background/button variants 3. Import sections from `/src/components/sections/` or template components 4. Configure theme.css with colors, buttons, and card styles 5. Use CDN URLs for all media assets 6. Add route in App.tsx