Files
kudinDmitriyUp 0823a5dec7 Initial commit
2026-05-31 20:08:17 +00:00

12 KiB

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

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 (
    <StyleProvider
      siteBackground="aurora"
      heroBackground="cornerGlow"
      buttonVariant="stagger"
    >
      <NavbarInline {...navProps} />

      <HeroBillboard {...heroProps} />

      {/* More sections... */}

      <FooterSimple {...footerProps} />
    </StyleProvider>
  );
}

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

import HeroBillboard from "@/components/sections/hero/HeroBillboard";
import FeaturesMediaCards from "@/components/sections/features/FeaturesMediaCards";

<HeroBillboard
  tag="Welcome"
  title="Your Title"
  description="Description text"
  primaryButton={{ text: "Get Started", href: "#contact" }}
  imageSrc="https://..."
/>

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:

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 aria-label="Section label" className="py-20">
      {/* Section content */}
    </section>
  );
};

export default ComponentName;

theme.css

Each template's theme.css defines the visual identity.

Required Structure

/* [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):

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:

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:

// 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:

const duplicated = [...items, ...items, ...items, ...items];

<div className="overflow-hidden mask-fade-x">
  <div
    className="flex w-max animate-marquee-horizontal"
    style={{ animationDuration: "60s" }}
  >
    {duplicated.map((item, i) => (
      <div key={i} className="shrink-0 mr-3 md:mr-5">
        {/* Item content */}
      </div>
    ))}
  </div>
</div>

Section Header Pattern

Consistent header for sections:

<div className="flex flex-col items-center gap-2 mb-8">
  <div className="px-3 py-1 mb-1 text-sm card rounded w-fit">
    <p>{tag}</p>
  </div>

  <TextAnimation
    text={title}
    variant="slide-up"
    gradientText={true}
    tag="h2"
    className="text-6xl font-medium text-center text-balance"
  />

  <TextAnimation
    text={description}
    variant="slide-up"
    gradientText={false}
    tag="p"
    className="md:max-w-6/10 text-lg leading-snug text-center"
  />

  {(primaryButton || secondaryButton) && (
    <div className="flex flex-wrap justify-center gap-3 mt-3">
      {primaryButton && <Button text={primaryButton.text} href={primaryButton.href} variant="primary" />}
      {secondaryButton && <Button text={secondaryButton.text} href={secondaryButton.href} variant="secondary" animationDelay={0.1} />}
    </div>
  )}
</div>

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