SP Souvenir Programme Microsite Documentation

Overview

SP (Souvenir Programme) microsites are specialized Astro-based applications designed for Singapore’s SWA annual events. Each edition (SP/48, SP/49, SP/50) is a self-contained microsite with consistent architecture, reusable components, and dynamic content management through YAML collections.


Table of Contents

  1. Quick Start: Creating a New Edition
  2. Architecture Overview
  3. SP CSS Framework
  4. Component Patterns
  5. Content Collections
  6. Step-by-Step: Creating a New Edition
  7. Image Optimization
  8. British English Standards
  9. SEO & Analytics
  10. Best Practices

Quick Start: Creating a New Edition

Time to set up a new SP edition: ~30 minutes (for experienced developers)

Minimal Setup

# 1. Copy SP/49 folder to new edition (SP/50)
cp -r src/pages/sp/49 src/pages/sp/50
cp -r src/content/sp49 src/content/sp50
cp -r src/components/sp/49 src/components/sp/50
cp src/styles/sp49.css src/styles/sp50.css

# 2. Create new image folder
mkdir -p src/images/sp/50

# 3. Update content.config.ts with new collections
# (See "Content Collections" section)

# 4. Rename CSS framework class references in new files
# sp/50/ components: update sp49.css → sp50.css
# sp/50/ components: update sp49 imports → sp50

# 5. Update component props from sp49 → sp50 theme

Then proceed with detailed setup below.


Architecture Overview

Folder Structure

src/pages/sp/
├── 48/                           # SP/48 Souvenir Programme
│   ├── index.astro              # Main landing page
│   ├── charity-presentation.astro
│   ├── gtw-totals.astro
│   ├── stage-display.astro
│   └── README.md
├── 49/                           # SP/49 Souvenir Programme
│   ├── index.astro
│   ├── charity-presentation.astro
│   ├── gtw-totals.astro
│   └── README.md
└── 50/                           # Future editions follow same pattern
    ├── index.astro
    └── [other pages as needed]

src/components/sp/
├── 48/                           # 42 SP/48-specific components
├── 49/                           # 50 SP/49-specific components
├── 50/                           # Copy from 49 and customize
└── shared/                       # 8 Edition-agnostic components
    ├── SPLayout.astro           # Base layout with theme support
    ├── SPHeader.astro           # Navigation with scroll tracking
    ├── SPFooter.astro
    ├── SPSection.astro          # Reusable section wrapper
    ├── SPSponsorCard.astro
    ├── SPBottomNav.astro
    ├── SPGa4Tracker.astro
    └── SPStageLayout.astro

src/styles/
├── sp48.css                      # SP/48 CSS Framework
├── sp49.css                      # SP/49 CSS Framework (current best practice)
└── sp50.css                      # New editions copy sp49.css and customize

src/content/
├── sp48/
│   └── schedule.md              # Schedule data
├── sp49/                        # 8 content files
│   ├── schedule.md
│   ├── messages.md
│   ├── committee.md
│   ├── contestants.md
│   ├── judges.md
│   ├── acknowledgements.md
│   └── gtw-prizes.md
└── sp49Sponsors/
    └── sponsors.yaml            # Sponsor data by tier

src/images/sp/
├── peony-bg.webp               # Shared background
├── swa-logo.png                # Shared logo
├── 49/                         # Edition-specific images
└── 50/                         # Future editions

Three-Layer Component Architecture

Every SP section follows a 3-layer pattern for consistent visual design:

<section id="section-id" class="sp-section-base">
  <!-- Layer 1: Background (z-0) -->
  <div class="sp-background-layer">
    <Image src={BackgroundImage} alt="..." class="w-full h-full object-cover" />
  </div>

  <!-- Layer 2: Overlay (z-10) - Controls transparency/readability -->
  <div class="sp-overlay-layer"></div>

  <!-- Layer 3: Content (z-20) - Text, buttons, interactive elements -->
  <article class="sp-content-layer">
    <h2 class="sp-title">Section Title</h2>
    <p class="sp-body-text">Your content here</p>
  </article>
</section>

Z-index Strategy:


SP CSS Framework

Overview

The SP CSS Framework provides reusable component classes for consistent styling across all editions. Each edition has its own CSS file (sp48.css, sp49.css, etc.) that can be customized.

Framework Classes Reference

Section Structure

.sp-section-base       /* Full-screen section container */
.sp-background-layer   /* Background image container */
.sp-overlay-layer      /* Dark overlay (transparency configurable) */
.sp-content-layer      /* Content with proper z-index and padding */

Typography System

.sp-title              /* Main headings (responsive, progressive scaling) */
.sp-subtitle           /* Secondary headings */
.sp-body-text          /* Body text with optimal line height */
.sp-accent-text        /* Brand accent color text */

Layout Utilities

.sp-container          /* Content spacing and margins */
.sp-center-content     /* Flex centered layout */
.sp-text-center        /* Text alignment center */
.sp-full-width         /* Full-width responsive container */
.sp-grid-responsive    /* Responsive grid (1 col mobile, 2-3 cols desktop) */

Component-Specific

.sp-timeline-item      /* Schedule/timeline items */
.sp-sponsor-card       /* Sponsor card layout */
.sp-btn-primary        /* Primary action button */
.sp-image-container    /* Image wrapper with styling */
.sp-flex-buttons       /* Button layout container */
.sp-divider            /* Content divider line */

SP49 CSS Specifications (Current Best Practice)

Use this as the baseline for new editions:

Container Widths:

Typography Scaling:

Overlay Opacity:

Content Padding (Progressive):

Customizing CSS for New Editions

When creating SP/50, you’ll customize sp50.css:

Color Changes (primary customization):

.sp-background-layer {
  @apply absolute inset-0 z-0 bg-swa-1;  /* Change to bg-swa-2 or custom color */
}

.sp-overlay-layer {
  background-color: rgba(0, 0, 0, 0.2);  /* Adjust transparency (0.2 = light) */
}

.sp-subtitle {
  @apply font-semibold text-swa-4;  /* Change accent text color */
}

Layout Changes (advanced):

.sp-full-width {
  max-width: 90rem;  /* Make wider if needed */
}

.sp-title {
  font-size: 2.5rem;  /* Larger/smaller titles */
  line-height: 1.1;
}

When to Modify CSS vs Use Inline Styles

Use CSS Framework Classes (90% of cases):

<section class="sp-section-base">
  <div class="sp-overlay-layer"></div>
  <article class="sp-content-layer">
    <h2 class="sp-title">Title</h2>
  </article>
</section>

Inline Classes Only For:

Never Use Inline Styles (except for dynamic values):

<!-- ✗ Avoid -->
<div style="min-height: 100vh; background-color: purple;"></div>

<!-- ✓ Use -->
<div class="sp-section-base"></div>

Component Patterns

Standard SP Component Structure

---
import { Image } from "astro:assets";
import Layout from "../../../components/sp/shared/SPLayout.astro";
import PeonyBg from "../../../images/sp/peony-bg.webp";

interface Props {
  theme?: "48" | "49" | "50";
  variant?: "default" | "alt";
}

const { theme = "50", variant = "default" } = Astro.props;
---

<Layout title="Section Title" theme={theme}>
  <section id="unique-id" class="sp-section-base">
    <!-- Background Layer -->
    <div class="sp-background-layer">
      <Image
        src={PeonyBg}
        alt="Background"
        class="w-full h-full object-cover"
        format="webp"
        quality={20}
      />
    </div>

    <!-- Overlay Layer -->
    <div class="sp-overlay-layer"></div>

    <!-- Content Layer -->
    <article class="sp-content-layer">
      <h2 class="sp-title">Your Section Title</h2>
      <p class="sp-body-text">Content goes here</p>
    </article>
  </section>
</Layout>

Shared Components

SPLayout.astro (Base Layout)

Purpose: Wraps all pages with theme, header, footer, and GA4 tracking

Props:

interface Props {
  title: string;              // Page title
  description?: string;       // Meta description
  url?: string;              // Page URL for SEO
  imageUrl?: string;         // OG image URL
  siteName?: string;         // Site name for schema
  theme: "48" | "49" | "50"; // Edition theme
  edition?: string;          // Edition name (e.g., "SP/50")
}

Usage:

---
import Layout from "../components/sp/shared/SPLayout.astro";
---

<Layout title="Event Name" theme="50" edition="SP/50">
  <!-- Your pages go here -->
</Layout>

SPSection.astro (Reusable Section)

Purpose: Pre-styled section wrapper with consistent structure

Props:

interface Props {
  id: string;                // Section anchor ID
  title: string;            // Section heading
  subtitle?: string;        // Optional subheading
  variant?: "default" | "alt" | "accent"; // Style variant
  theme?: "48" | "49" | "50";
}

Usage:

<SPSection id="schedule" title="Event Schedule" theme="50">
  <!-- Content inside uses sp-* classes -->
</SPSection>

SPHeader.astro (Navigation)

Features:

Scroll Events Tracked:

SPGa4Tracker.astro (Analytics)

Purpose: Event delegation tracking for GA4

Tracked Events:

"sp50_cta_click"     // CTA button clicks
"sp50_sponsor_click" // Sponsor logo clicks
"sp50_donation_click" // Donation buttons

Implementation:

<button
  data-sp-track="true"
  data-sp-cta-type="donation"
  class="sp-btn-primary"
>
  Donate Now
</button>

Creating Custom Components

When creating new components for your edition:

  1. Place in src/components/sp/50/YourComponent.astro
  2. Use theme prop to support multiple editions
  3. Apply CSS Framework classes from sp50.css
  4. Import images with WebP format and quality settings
  5. Add GA4 attributes for tracking user interactions

Example Custom Component:

---
import { Image } from "astro:assets";
import SponsorImage from "../../../images/sp/50/sponsor-logo.webp";

interface Props {
  sponsorName: string;
  sponsorImage: ImageMetadata;
  theme?: "50";
}

const { sponsorName, sponsorImage, theme = "50" } = Astro.props;
---

<article class="sp-sponsor-card">
  <Image
    src={sponsorImage}
    alt={sponsorName}
    quality={90}
  />
  <h3 class="sp-subtitle">{sponsorName}</h3>
</article>

<style>
  article {
    @apply p-6 bg-white/10 rounded-lg;
  }
</style>

Content Collections

Overview

SP editions use YAML content collections for dynamic content management. This allows non-technical team members to update content without modifying code.

Available Collection Types

1. Schedule Collection (sp50 in content.config.ts)

Purpose: Event programme schedule with times and descriptions

File Location: src/content/sp50/schedule.md

Schema:

scheduleItems: Array<{
  time: string;           // Display time (e.g., "6.00pm")
  timeOrder: number;      // Sort order in 24-hour format (1800 = 6:00pm)
  title: string;          // Event title
  subtitle?: string;      // Optional subtitle
  description?: string;   // Optional description
}>

Example:

---
scheduleItems:
  - time: "6.00pm"
    timeOrder: 1800
    title: "Arrival of Guests"

  - time: "6.50pm"
    timeOrder: 1850
    title: "Arrival of Guest-of-Honour"
    subtitle: "Mr Seah Kian Peng"
    description: "Lead Advisor, Marine Parade-Braddell Heights"
---

2. Sponsors Collection (sp50Sponsors)

Purpose: Sponsor data organized by sponsorship tier

File Location: src/content/sp50Sponsors/sponsors.yaml

Schema:

platinum?: Array<{
  name: string;
  website?: string;      // Optional link
  role?: string;        // Role/contribution
  image?: string;       // Logo filename
}>;
gold?: Array<{...}>;
silver?: Array<{...}>;
bronze?: Array<{...}>;
official?: Array<{...}>;
doorGifts?: Array<{...}>;

Example:

platinum:
  - name: "Major Sponsor Ltd"
    website: "https://example.com"
    role: "Title Sponsor"
    image: "sponsor-logo.webp"

gold:
  - name: "Gold Partner Inc"
    image: "gold-partner.webp"

3. Messages Collection (sp50Messages)

Purpose: Welcome/greeting messages from VIPs

File Location: src/content/sp50Messages/messages.yaml

Schema:

messages: Array<{
  name: string;        // Person's name
  title: string;       // Their title
  content: string;     // Message text (can include markdown)
}>

4. Committee Collection (sp50Committee)

Purpose: Organizing and working committee members

Schema:

organisingCommittee?: Array<{
  name: string;
  role: string;
  title?: string;
}>;
workingCommittee?: Array<{
  name: string;
  role: string;
  title?: string;
}>;

5. Contestants Collection (sp50Contestants)

Purpose: MSPI contestant profiles

Schema:

contestants: Array<{
  number: number;        // Contestant number
  name: string;
  image: string;         // Image filename
  imageAlt: string;      // Alt text
  bio?: string;          // Optional biography
}>

6. Judges Collection (sp50Judges)

Purpose: Chief judge and judging panel members

Schema:

judges: {
  chief?: Array<{name: string; title?: string}>;
  table1?: Array<{name: string; title?: string}>;
  table2?: Array<{name: string; title?: string}>;
}

7. Acknowledgements Collection (sp50Acknowledgements)

Purpose: Special thanks and recognition

Schema:

guestOfHonour?: {
  name: string;
  title: string;
};
specialAcknowledgements?: Array<{
  name: string;
  contribution?: string;
}>;
friends?: string[];  // Simple list of names

8. Prizes Collection (sp50Prizes)

Purpose: GTW contest prizes and rewards

Schema:

prizes: Array<{
  rank: number;          // 1st, 2nd, 3rd, etc.
  name: string;          // Prize name
  sponsor?: string;      // Sponsor name
  value?: string;        // Prize value/description
  description?: string;  // Detailed description
  image?: string;        // Prize image
  imageAlt?: string;
  winners?: number;      // Number of winners (default: 1)
}>

Using Collections in Components

Example: Loading and displaying schedule:

---
import { getEntry } from "astro:content";

const scheduleData = await getEntry("sp50", "schedule");
const items = scheduleData.data.scheduleItems.sort(
  (a, b) => a.timeOrder - b.timeOrder
);
---

{items.map((item) => (
  <div class="sp-timeline-item">
    <time class="sp-subtitle">{item.time}</time>
    <div>
      <h3 class="sp-body-text font-semibold">{item.title}</h3>
      {item.description && <p class="sp-body-text text-sm">{item.description}</p>}
    </div>
  </div>
))}

Adding New Collections for New Editions

  1. Create content file: src/content/sp50/new-collection.yaml
  2. Update content.config.ts with new schema
  3. Import in components: getEntry("sp50", "new-collection")

Step-by-Step: Creating a New Edition

Phase 1: Setup (10 minutes)

Step 1.1: Copy Folder Structure

# Pages
cp -r src/pages/sp/49 src/pages/sp/50

# Components
cp -r src/components/sp/49 src/components/sp/50

# Styles
cp src/styles/sp49.css src/styles/sp50.css

# Content collections
cp -r src/content/sp49 src/content/sp50
cp -r src/content/sp49Sponsors src/content/sp50Sponsors
cp -r src/content/sp49Messages src/content/sp50Messages
cp -r src/content/sp49Committee src/content/sp50Committee
cp -r src/content/sp49Contestants src/content/sp50Contestants
cp -r src/content/sp49Judges src/content/sp50Judges
cp -r src/content/sp49Acknowledgements src/content/sp50Acknowledgements
cp -r src/content/sp49Prizes src/content/sp50Prizes

# Images
mkdir -p src/images/sp/50
# Copy edition-specific images to src/images/sp/50/

Step 1.2: Update Configuration Files

In /src/content.config.ts:

Find the SP49 sections and duplicate for SP50:

// Add new SP50 collection definitions
const sp50 = defineCollection({
  loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/sp50" }),
  schema: z.object({
    scheduleItems: z.array(z.object({
      time: z.string(),
      timeOrder: z.number(),
      title: z.string(),
      subtitle: z.string().optional(),
      description: z.string().optional(),
    })),
  }),
});

const sp50Sponsors = defineCollection({
  type: "data",
  schema: z.object({
    platinum: z.array(z.object({
      name: z.string(),
      website: z.string().optional().nullable(),
      role: z.string().optional(),
      image: z.string().optional(),
    })).optional(),
    // ... repeat for gold, silver, bronze, official, doorGifts
  }),
});

// Repeat for: sp50Messages, sp50Committee, sp50Contestants,
// sp50Judges, sp50Acknowledgements, sp50Prizes

// Update exports
export const collections = {
  // ... existing
  sp50: sp50,
  sp50Sponsors: sp50Sponsors,
  sp50Messages: sp50Messages,
  // ... etc for all collections
};

Phase 2: Code Updates (15 minutes)

Step 2.1: Update Page Components

In src/pages/sp/50/index.astro and other pages:

Search and Replace:

Example Update:

---
// OLD
import Layout from "../../../components/sp/shared/SPLayout.astro";
import SPHome from "../../../components/sp/49/SPHome.astro";

// NEW
import Layout from "../../../components/sp/shared/SPLayout.astro";
import SPHome from "../../../components/sp/50/SPHome.astro";
---

<Layout title="SP/50" theme="50" edition="SP/50">
  <SPHome theme="50" />
</Layout>

Step 2.2: Update Component Imports

In src/components/sp/50/ folder, update all component files:

Replace in all files:

// OLD
import { getEntry } from "astro:content";
const data = await getEntry("sp49", "schedule");

// NEW
import { getEntry } from "astro:content";
const data = await getEntry("sp50", "schedule");

Step 2.3: Update CSS Reference

In components, update CSS import:

---
// OLD
import "../../styles/sp49.css";

// NEW
import "../../styles/sp50.css";
---

Phase 3: Customize CSS (5 minutes)

Edit src/styles/sp50.css:

Update colors if desired:

.sp-background-layer {
  @apply absolute inset-0 z-0 bg-swa-2;  /* Changed from bg-swa-1 */
}

.sp-subtitle {
  @apply font-semibold text-swa-3;  /* Changed color */
}

Adjust typography if needed:

.sp-title {
  font-size: 2.5rem;  /* Make titles smaller/larger */
}

.sp-full-width {
  max-width: 85rem;  /* Adjust container width */
}

Phase 4: Content Updates (10 minutes)

Step 4.1: Update Schedule

Edit src/content/sp50/schedule.md:

---
scheduleItems:
  - time: "6.00pm"
    timeOrder: 1800
    title: "[Your event title]"
---

Step 4.2: Update Sponsors

Edit src/content/sp50Sponsors/sponsors.yaml with your sponsor information.

Step 4.3: Update Other Collections

Update all YAML files in src/content/sp50*/ directories with edition-specific content.

Phase 5: Images & Assets (5 minutes)

Step 5.1: Add Edition Images

Place edition-specific images in src/images/sp/50/:

Step 5.2: Optimize Images

Convert all images to WebP:

npm run convert:webp src/images/sp/50/

Quality Settings:

Phase 6: SEO & Metadata (5 minutes)

Step 6.1: Update SPLayout Props

In src/pages/sp/50/index.astro:

<Layout
  title="SWA SP/50 Souvenir Programme"
  description="Join us for the 50th Singapore Women's Achievement Dinner 2026"
  imageUrl="/og-image-sp50.png"
  siteName="SWA"
  edition="SP/50"
  theme="50"
>

Step 6.2: Update Structured Data

Check SPLayout.astro for schema.org structured data generation. Update date, name, and description for your edition.

Phase 7: Testing & Validation (5 minutes)

Step 7.1: Start Dev Server

npm run dev
# Navigate to http://localhost:4321/sp/50/

Step 7.2: Checklist

Step 7.3: Build Test

npm run build
# Check for build errors and warnings

Image Optimization

WebP Conversion

All images in SP microsites should be converted to WebP format for optimal performance.

Quality Settings Guide:

Background images:  quality={20}  # Highly compressed
Contestant photos:  quality={80}  # Balanced
Sponsor logos:      quality={90}  # High quality needed
Logo/icon images:   quality={95}  # Logo clarity

Convert Images:

# For a specific folder
npm run convert:webp src/images/sp/50/

# For entire site
npm run optimize:site

Image Component Pattern

---
import { Image } from "astro:assets";
import SampleImage from "../../../images/sp/50/sample.png";
---

<Image
  src={SampleImage}
  alt="Descriptive alt text for accessibility"
  format="webp"
  quality={80}
  class="w-full h-auto rounded-lg"
/>

Responsive Image Sizing

Use Tailwind classes for responsive sizes:

<!-- Small hero images -->
<Image src={...} class="w-32 h-32 md:w-48 md:h-48 lg:w-64 lg:h-64" />

<!-- Full-width background images -->
<Image src={...} class="w-full h-full object-cover" />

<!-- Sponsor logos (max width to prevent stretching) -->
<Image src={...} class="w-full max-w-xs h-auto mx-auto" />

British English Standards

All content should follow British English conventions for consistency across SWA publications.

Common British English Usage

Spelling:

Date Format:

Time Format:

Numbers:

Terminology:

Example Content

Correct:

The SWA celebrates 50 years of honouring outstanding women across
Singapore. This year's souvenir programme features a full schedule
of events, recognising the contributions of our sponsors and organisers.

Incorrect:

The SWA celebrates 50 years of honoring outstanding women across
Singapore. This year's program features a full schedule of events,
recognizing the contributions of our sponsors and organizers.

SEO & Analytics

SEO Implementation

Meta Tags (Handled by SPLayout)

The SPLayout.astro component automatically generates:

Provide these props to SPLayout:

<Layout
  title="SWA SP/50: 50th Souvenir Programme"
  description="Join us for the 50th Singapore Women's Achievement Dinner"
  imageUrl="/images/og-sp50.png"
  url="https://swa.sg/sp/50"
  siteName="SWA"
/>

Structured Data (Schema.org)

SPLayout generates Event schema markup:

{
  "@context": "https://schema.org",
  "@type": "Event",
  "name": "SWA SP/50",
  "description": "...",
  "url": "https://swa.sg/sp/50",
  "image": "...",
  "startDate": "2026-07-19",
  "endDate": "2026-07-19",
  "organizer": {
    "@type": "Organization",
    "name": "SWA",
    "url": "https://swa.sg"
  },
  "location": {
    "@type": "VirtualLocation",
    "url": "https://swa.sg/sp/50"
  }
}

Accessibility (A11y)

All SP pages include:

Analytics Implementation

GA4 Event Tracking

Events are tracked via SPGa4Tracker.astro:

<!-- Track CTA clicks -->
<button
  data-sp-track="true"
  data-sp-cta-type="donation"
  class="sp-btn-primary"
>
  Donate
</button>

<!-- Track sponsor clicks -->
<a
  href="https://sponsor.com"
  data-sp-track="true"
  data-sp-sponsor="Sponsor Name"
  data-sp-tier="platinum"
>
  Sponsor Logo
</a>

Events Generated:

Custom Events

For edition-specific tracking, add data attributes:

<button
  data-sp-track="true"
  data-sp-cta-type="[custom-type]"
  data-sp-custom-prop="value"
>
  Click Me
</button>

To Process Custom Events: Update SPGa4Tracker.astro to handle new data attributes.


Best Practices

Architecture Patterns

1. Theme Consistency

2. Component Reusability

3. Content Management

Performance Guidelines

1. Image Optimization

2. Build Optimization

# Before production deployment
npm run build

# Check for warnings
# File sizes should be < 2MB per page

3. CSS Framework Usage

Accessibility Requirements

1. Color Contrast

2. Alt Text All images require descriptive alt text:

<Image
  src={...}
  alt="Contestant Jane Doe, number 5, wearing a red dress"
/>

3. Keyboard Navigation

Maintenance

Updating Future Editions (SP/51, SP/52):

  1. Copy sp/50/sp/51/
  2. Copy sp50.csssp51.css
  3. Repeat content updates for edition-specific data
  4. Update content.config.ts with new collections
  5. Test and validate

Keeping CSS Framework Updated:


Edition Checklist

Before Launch

Post-Launch


Troubleshooting

Common Issues

Issue: Images not displaying

Issue: Content collections not loading

Issue: Styling inconsistencies between pages

Issue: GA4 events not firing

Issue: Mobile layout breaks at certain viewport sizes



Version History


This documentation maintains the SP microsite architecture. For questions or updates, refer to component comments and official Astro documentation.