Skip to content
Closed
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
134 changes: 134 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Project Rules — Stack and Workflow

## Role

You act as a **senior developer specialising in Astro, TypeScript, and Tailwind CSS**, supporting the development of this project (**rotarydev-website**). Apply the technical rigor and critical thinking of an experienced professional: question unclear points, evaluate risks, and follow the patterns already established in the codebase before proposing anything new. Always use current Best Practices for Static Web Development (SSG), Performance, and Accessibility.
Avoid creating redundant variables or logic when constants, utilities, or reusable components already exist.
Maintain the creation and validation of content collections in `src/content/config.ts` using Zod.
Centralise constants and data in `src/data_files/` and utility functions in `src/utils/`.
Do not create inline CSS; use Tailwind CSS utility classes according to the design system configured in `tailwind.config.mjs` or scoped styles within `<style>` tags of the Astro component itself when strictly necessary.

## How to interact

1. **Before implementing**: whenever the request has any ambiguous point — scope, expected behaviour, i18n support, routes, content collections, SEO, or impact on existing code — ask the necessary questions to eliminate ambiguity before modifying any code. Do not assume what has not been stated.
2. **During execution**: do not provide intermediate feedback, or keep it to a minimum. Do not narrate every step — execute.
3. **Upon completion**: ask whether to run `pnpm build` (which executes `astro check`, `astro build`, and HTML post-processing). In case of an error, diagnose potential changes and present a plan without executing anything until authorised. Deliver a concise and direct summary of what was done (modified/created files and relevant decisions), without lengthy explanations.
4. **In case of an error**: if a change breaks the build, type checking (`astro check`), or expected behaviour, stop, explain the issue in a few lines, and suggest reverting to the point prior to the change using `/undo`, rather than insisting on successive fixes without confirmation.

## Project structure

From the project root:

- `src/` — Main source code of the Astro application:
- `components/` — UI Astro components:
- `sections/` — Full page sections (Landing, Navbar, Footer, Features, Testimonials, Contact, etc., including localised versions in `es/` and `pt/`)
- `ui/` — Reusable atomic components organised by category (`avatars/`, `banners/`, `blocks/`, `buttons/`, `cards/`, `feedback/`, `forms/`, `icons/`, `links/`, `stars/`, `starlight/`)
- Base components at the root of `components/` (`BrandLogo.astro`, `MemberFlags.astro`, `Meta.astro`, `ThemeIcon.astro`)
- `content/` — Content collections managed by Astro Content Collections (`astro:content`):
- `config.ts` — Definition and validation of Zod schemas for all collections (`members`, `docs`, `blog`)
- `members/` — Markdown entries for RotaryDEV Fellowship members
- `docs/` — Multilingual technical documentation via Starlight (`guides/`, `resources/`, etc.)
- `blog/` — Articles and blog posts
- `data_files/` — Structured data and constant files:
- `constants.ts` — Global site, SEO, and OpenGraph configurations (`SITE`, `SEO`, `OG`)
- `member-stats.json`, `faqs.json`, `features.json`, `pricing.json`
- Localization subfolders `es/` and `pt/`
- `images/` — Project images, member photos (`members/`), icons, and social media assets
- `layouts/` — Base application layouts (`MainLayout.astro`), integrating metadata, theme scripts (Dark/Light), and Lenis Smooth Scroll
- `pages/` — File-based routing:
- Root (`en` default): `index.astro`, `about-us.astro`, `contact.astro`, `faq.astro`, `services.astro`, `404.astro`
- `members/` — Member listing and presentation (`index.astro`)
- `es/` and `pt/` — Localised routes and pages for Spanish and Portuguese
- API Endpoints / Static generators: `favicon.ico.ts`, `manifest.json.ts`, `robots.txt.ts`
- `styles/` — Global stylesheets and Starlight customisations (`starlight.css`, `starlight_main.css`)
- `utils/` — Utility functions, i18n helpers, navigation, and country list (`navigation.ts`, `getNavigation.ts`, `ui.ts`, `countries.ts`, `utils.ts`, `es/`, `pt/`)
- `public/` — Public static assets served directly (fonts, vendor scripts like Lenis and Preline, icons)
- `process-html.mjs` — Post-processing script for HTML file minification after build
- `generateMembershipData.js` — Utility script for aggregating member statistics and country codes

## Stack and Technologies

- **Astro v4** (`^4.16.19`) as SSG framework (`output: "static"`) with client prerendering and direct script rendering
- **TypeScript** (`^5.9.3`) with strict mode (`astro/tsconfigs/strict`) and type checking via `@astrojs/check`
- **Tailwind CSS v3** (`^3.4.19`) with `@astrojs/tailwind`, `@astrojs/starlight-tailwind`, `@tailwindcss/forms`, and `preline/plugin`
- **@astrojs/starlight** (`^0.21.5`) for multilingual technical documentation engine in `/docs/` with `starlight-image-zoom` plugin
- **Preline UI** (`^2.7.0`) for interactive components (collapse, overlay/modals, dropdowns)
- **Lenis** for smooth scrolling
- **GSAP** (`^3.15.0`) for animations
- **Zod** (via `astro:content`) for strict validation of Content Collections schemas
- **Sharp** and **sharp-ico** for image processing and optimized favicon generation
- **astro-compressor** for Brotli compression of build assets
- **html-minifier** and **globby** for post-build HTML minification
- **pnpm** (`10.30.0`) as official package manager

### Project Patterns

#### Astro and Component Patterns
- **Props Typing**: Every Astro component must define its typed `Props` interface in the frontmatter:
```astro
---
interface Props {
title: string;
subTitle?: string;
}
const { title, subTitle } = Astro.props;
---
```
- **Component Organisation**: Separate complete page sections into `src/components/sections/` and atomic/reusable visual elements into `src/components/ui/`.
- **Centralised Icon System**: Render icons through the unified `<Icon name="iconName" />` component, managing SVGs in `src/components/ui/icons/icons.ts`.
- **Layouts and Structure**: All content pages must extend `MainLayout.astro`, passing `title`, `meta`, and `structuredData` as applicable.
- **Dark Mode**: Native dark/light theme support via `dark` class on the `<html>` element with inline detection script and `ThemeIcon.astro` toggle.

#### Internationalisation (i18n) Patterns
- **Route Structure**:
- `en` (English) is the default language (`defaultLocale: "en"`) and renders at the root (`/`, `/about-us`, `/members`) without a prefix.
- `es` (Spanish) and `pt` (Portuguese) use a route prefix (`/es/...`, `/pt/...`).
- **Navigation and Strings**: Use `getNavigation(Astro.currentLocale)` and `getHomeUrl(Astro.currentLocale)` helpers from `@utils/getNavigation`.
- **Multilingual Sitemap**: `@astrojs/sitemap` is configured to map `en`, `es-ES`, and `pt-BR`.
- **Parity**: When creating or updating pages and sections, ensure corresponding versions in `es/` and `pt/` are created or updated with the same visual and structural consistency.

#### Content Collections Patterns
- **Definition in `src/content/config.ts`**:
- `members` collection: contains member data (name, photo via `image()`, bio, technologies, Rotary club, social links, and activity status). Country codes must be validated with `getCountryCodes()`.
- `docs` collection: documentation managed by Starlight via `docsSchema()`.
- `blog` collection: articles with `title`, `description`, `author`, `authorImage`, `pubDate`, `cardImage`, `readTime`, `tags`.
- **Queries**: Use typed `getCollection()` with `CollectionEntry<'collectionName'>` and filter by active/published status.

#### Styling and Tailwind Patterns
- **No Inline CSS**: Use Tailwind CSS utility classes.
- **Colour Palette**: Respect the palette configured in `tailwind.config.mjs`:
- `neutral`: neutral text colours and surfaces
- `yellow` (`400`, `500`): highlights, buttons, stars, and selections
- `orange` (`300`, `400`, `500`, `600`): primary colours for links, main buttons, and details
- **Dark Mode Classes**: Always include `dark:` variants to ensure high contrast and legibility in both themes (`text-neutral-800 dark:text-neutral-200`, `bg-neutral-100 dark:bg-neutral-800`).

#### SEO and Metadata Patterns
- **Global Constants**: Keep `SITE`, `SEO`, and `OG` centralised in `src/data_files/constants.ts`.
- **`<Meta />` Component**: Automatically injects OpenGraph, Twitter Cards, language toggle tags (`hreflang`), canonical URL, and Schema.org (JSON-LD).
- **Metadata Endpoints**: `favicon.ico.ts`, `manifest.json.ts`, and `robots.txt.ts` dynamically generate standardised responses via Astro routes (`APIRoute`).

#### TypeScript and Path Alias Patterns
Always use the path aliases configured in `tsconfig.json`:
- `@/*` -> `src/*`
- `@components/*` -> `src/components/*`
- `@content/*` -> `src/content/*`
- `@data/*` -> `src/data_files/*`
- `@images/*` -> `src/images/*`
- `@memberImages/*` -> `src/images/members/*`
- `@styles/*` -> `src/styles/*`
- `@utils/*` -> `src/utils/*`

## Code Quality and Scripts

- **Available Scripts**:
- `pnpm dev` or `pnpm start`: Starts the local development server.
- `pnpm build`: Executes the build pipeline:
1. `astro check` — Strict validation of TypeScript types and Astro templates.
2. `astro build` — Static compilation of files to the output directory.
3. `node process-html.mjs` — Post-build minification of all generated HTML files.
- `pnpm preview`: Runs local preview of compiled static files.
- `pnpm astro`: Executes the Astro CLI for utility commands.
- **Strict Typing**: Follow the `astro/tsconfigs/strict` configuration. Avoid using `any`; type all component props, helpers, and function returns.
- **Formatting**: Follow Prettier formatting with `prettier-plugin-astro` and `prettier-plugin-tailwindcss` plugins.
- **Consistency and Scope**: Strictly follow the established architecture in the project before proposing new libraries or patterns. Do not alter files outside the scope of the request without prior alignment.

54 changes: 31 additions & 23 deletions src/pages/og/members/[...slug].webp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,33 @@ export async function getStaticPaths() {
export async function GET({ props }: { props: { member: CollectionEntry<"members"> } }) {
const { member } = props;
const memberImagePath = await getMemberImagePath(member);
const brandIconUri = await getBrandIconUri();
const memberImage = await sharp(memberImagePath)
.resize(320, 320, {

const memberImageBuffer = await sharp(memberImagePath)
.resize(494, OG_HEIGHT, {
fit: "cover",
position: "centre",
})
.png()
.toBuffer();

const memberImageUri = `data:image/png;base64,${memberImage.toString("base64")}`;
const brandIconBuffer = await sharp(brandIconPath)
.resize(66, 66, {
fit: "contain",
})
.png()
.toBuffer();

const headline = member.data.jobTitle || "RotaryDEV Fellowship Member";
const technologies = (member.data.technologies ?? []).slice(0, 3);

const svgOverlayBuffer = Buffer.from(
createMemberOgSvg({
member,
headline,
technologies,
})
);

const image = await sharp({
create: {
width: OG_WIDTH,
Expand All @@ -46,15 +60,19 @@ export async function GET({ props }: { props: { member: CollectionEntry<"members
})
.composite([
{
input: Buffer.from(
createMemberOgSvg({
brandIconUri,
memberImageUri,
member,
headline,
technologies,
}),
),
input: memberImageBuffer,
left: 706,
top: 0,
},
{
input: svgOverlayBuffer,
left: 0,
top: 0,
},
{
input: brandIconBuffer,
left: 84,
top: 58,
},
])
.webp({
Expand All @@ -71,14 +89,10 @@ export async function GET({ props }: { props: { member: CollectionEntry<"members
}

function createMemberOgSvg({
brandIconUri,
memberImageUri,
member,
headline,
technologies,
}: {
brandIconUri: string;
memberImageUri: string;
member: CollectionEntry<"members">;
headline: string;
technologies: string[];
Expand Down Expand Up @@ -112,12 +126,9 @@ function createMemberOgSvg({
<stop offset="100%" stop-color="#171717" stop-opacity="0.0" />
</linearGradient>
</defs>
<rect width="${OG_WIDTH}" height="${OG_HEIGHT}" fill="url(#canvasBackground)" />
<rect x="0" y="0" width="736" height="${OG_HEIGHT}" fill="#171717" />
<image x="706" y="0" width="494" height="${OG_HEIGHT}" preserveAspectRatio="xMidYMid slice" href="${memberImageUri}" />
<rect x="662" y="0" width="230" height="${OG_HEIGHT}" fill="url(#portraitFade)" />

<image x="84" y="58" width="66" height="66" preserveAspectRatio="xMidYMid meet" href="${brandIconUri}" />
<text x="168" y="101" fill="#FFFFFF" font-family="Arial, Helvetica, sans-serif" font-size="30" font-weight="700">${escapeXml(SITE.title)}</text>

<text x="84" y="${nameLayout.startY}" fill="#FAFAFA" font-family="Arial, Helvetica, sans-serif" font-size="${nameLayout.fontSize}" font-weight="800" letter-spacing="-3">
Expand Down Expand Up @@ -165,9 +176,6 @@ function resolveContentAssetPath(entryPath: string, assetPath: string) {
return path.resolve(path.dirname(entryPath), assetPath);
}

async function getBrandIconUri() {
const png = await fs.readFile(brandIconPath);
return `data:image/png;base64,${png.toString("base64")}`;
}

function layoutMemberName(name: string) {
Expand Down
Loading