Skip to content
Merged
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
13 changes: 12 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ src/
│ ├── ui/ # Generic components (Button, Card)
│ └── features/ # Feature-specific components (Team, SocialLinks)
├── content/ # Content collections (blog posts)
├── data-models/ # Shared TypeScript models for data modules
├── layouts/ # Page layouts (BaseLayout, BlogLayout)
├── pages/ # File-based routing
├── styles/ # Global styles and Tailwind config
Expand All @@ -60,8 +61,9 @@ public/ # Static assets served directly

- Keep documentation concise and maintainable
- Prefer linking to the source-of-truth file over duplicating large config examples
- When documenting configuration, show a short quick-start and point to the canonical file (for branding: `src/config/brand.ts`)
- When documenting configuration, show a short quick-start and point to the canonical file (for branding: `src/data/brand.ts`)
- Update docs in the same PR when behavior, APIs, or customization paths change
- Document content architecture decisions (for example, keeping site copy in `src/data/`)

### TypeScript

Expand Down Expand Up @@ -204,6 +206,15 @@ draft: false
---
```

## Site Content Pattern

Place site-specific page copy and labels in `src/data/`, and import them into `.astro` pages.

- Keep `.astro` pages focused on layout, rendering, and wiring content collections.
- Use data modules (`src/data/home.ts`, `src/data/about.ts`, `src/data/blog.ts`, `src/data/resources.ts`) for UI copy.
- Keep shared data interfaces in `src/data-models/` (including `src/data-models/brand.ts`).
- Reuse `src/data/brand.ts` for global identity, SEO defaults, and link metadata.

## Git Commits

Use conventional commits format:
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Open [localhost:4321](http://localhost:4321) in your browser.
src/
├── components/ # Reusable UI components
├── content/ # Blog posts and resources (MDX)
├── data/ # Site-specific page copy and UI content
├── data-models/ # Shared TypeScript models for data modules
├── layouts/ # Page layouts
├── pages/ # Routes (file-based)
Comment thread
georg-schwarz marked this conversation as resolved.
└── styles/ # Global styles
Expand All @@ -49,13 +51,22 @@ src/

## Customize Branding

The site branding now comes from one source of truth: `src/config/brand.ts`.
The site branding now comes from one source of truth: `src/data/brand.ts`.

1. Open `src/config/brand.ts`.
1. Open `src/data/brand.ts`.
2. Update identity, organization, theme, links, SEO, and blog values.
3. Keep paths aligned with your configured `base` path in `astro.config.mjs`.

For the complete typed example and current defaults, use `src/config/brand.ts` directly.
For the complete typed example and current defaults, use `src/data/brand.ts` directly.

## Site Content Pattern

Keep site-specific copy and content in `src/data/`, then import it into `.astro` pages.

- Use `src/data/home.ts`, `src/data/about.ts`, `src/data/blog.ts`, and `src/data/resources.ts` for page-level strings and labels.
- Keep shared data interfaces in `src/data-models/` (including `src/data-models/brand.ts`).
- Keep `.astro` pages focused on layout and rendering logic; avoid hard-coded copy in templates.
- Reuse `src/data/brand.ts` for global identity, SEO defaults, and links.

### Theming Notes

Expand Down
2 changes: 1 addition & 1 deletion src/components/features/SocialLinks.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import type { SocialPlatform } from '@/data/social';
import type { SocialPlatform } from '@/data-models/social';

export interface SocialLink {
platform: SocialPlatform;
Expand Down
7 changes: 1 addition & 6 deletions src/components/features/Team.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
---
export interface TeamMember {
name: string;
role: string;
image?: string;
bio?: string;
}
import type { TeamMember } from "@/data-models/about";

export interface Props {
members: TeamMember[];
Expand Down
25 changes: 25 additions & 0 deletions src/data-models/about.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export interface AboutPageContent {
title: string;
description: string;
hero: AboutHeroContent;
sections: AboutSection[];
teamTitle: string;
socialLabel: string;
}

export interface AboutHeroContent {
title: string;
body: string;
}

export interface AboutSection {
title: string;
body: string;
}

export interface TeamMember {
name: string;
role: string;
image?: string;
bio?: string;
}
4 changes: 4 additions & 0 deletions src/data-models/blog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface BlogPageContent {
title: string;
emptyState: string;
}
79 changes: 79 additions & 0 deletions src/data-models/brand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { SocialPlatform } from "@/data-models/social";

export interface BrandColorScale {
"50": string;
"100": string;
"200": string;
"300": string;
"400": string;
"500": string;
"600": string;
"700": string;
"800": string;
"900": string;
"950": string;
}

export interface BrandAddress {
streetAddress?: string;
addressLocality?: string;
addressRegion?: string;
postalCode?: string;
addressCountry?: string;
}

export interface BrandSocialLink {
platform: SocialPlatform;
url: string;
}

export interface BrandLink {
label: string;
href: string;
}

export interface BrandConfig {
identity: {
siteName: string;
tagline: string;
siteUrl: string;
language: string;
locale: string;
logoPath: string;
faviconPath: string;
appleTouchIconPath: string;
manifestPath: string;
defaultOgImagePath: string;
};
organization: {
name: string;
legalName?: string;
socialProfiles: readonly string[];
address?: BrandAddress;
};
theme: {
primary: BrandColorScale;
secondary: BrandColorScale;
themeColor: string;
fonts?: {
sans: readonly string[];
mono: readonly string[];
};
};
links: {
primaryCtas: readonly BrandLink[];
appLinks: readonly BrandLink[];
social: readonly BrandSocialLink[];
};
seo: {
titleTemplate: string;
defaultDescription: string;
robots: string;
twitterCard: "summary" | "summary_large_image";
twitterSite?: string;
};
blog: {
title: string;
description: string;
};
}
27 changes: 27 additions & 0 deletions src/data-models/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export interface Feature {
title: string;
description: string;
}

export interface HomePageContent {
hero: HomeHeroContent;
sections: HomeSections;
}

export interface HomeHeroContent {
title: string;
highlight: string;
descriptionSuffix: string;
}

export interface HomeSections {
featuresTitle: string;
callToAction: HomeCallToAction;
}

export interface HomeCallToAction {
title: string;
description: string;
buttonLabel: string;
buttonHref: string;
}
8 changes: 8 additions & 0 deletions src/data-models/resources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface ResourcesPageContent {
title: string;
description: string;
emptyState: string;
relatedResourcesTitle: string;
canonicalLabel: string;
updatedLabel: string;
}
File renamed without changes.
37 changes: 21 additions & 16 deletions src/data/about.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
import type { SocialPlatform } from "@/data/social";
import { BRAND_CONFIG } from "@/config/brand";

export interface TeamMember {
name: string;
role: string;
bio: string;
}

export interface SocialLink {
platform: SocialPlatform;
url: string;
}
import type { AboutPageContent, TeamMember } from "@/data-models/about";

export const TEAM_MEMBERS = [
{
Expand All @@ -30,6 +18,23 @@ export const TEAM_MEMBERS = [
},
] satisfies readonly TeamMember[];

export const SOCIAL_LINKS = [
...BRAND_CONFIG.links.social,
] satisfies readonly SocialLink[];
export const ABOUT_PAGE_CONTENT = {
title: "About",
description: "Learn about the Idol or Bust research project and our mission.",
hero: {
title: "About the Project",
body: "Idol or Bust is a research project focused on developing simple website software that helps other research projects understand their user needs.",
},
sections: [
{
title: "Our Mission",
body: "We believe that understanding users is the foundation of successful research projects. Our goal is to provide lightweight, maintainable tools that help researchers gather insights and make data-driven decisions.",
},
{
title: "Our Approach",
body: "We follow a user-centered design process, constantly iterating based on feedback from the research community. Our tools are built with maintainability in mind, ensuring they can evolve with changing needs.",
},
],
teamTitle: "Research Team",
socialLabel: "Follow us:",
} satisfies AboutPageContent;
6 changes: 6 additions & 0 deletions src/data/blog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { BlogPageContent } from "@/data-models/blog";

export const BLOG_PAGE_CONTENT = {
title: "Blog",
emptyState: "No posts yet. Check back soon!",
} satisfies BlogPageContent;
96 changes: 1 addition & 95 deletions src/config/brand.ts → src/data/brand.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,4 @@
import type { SocialPlatform } from "@/data/social";

export interface BrandColorScale {
"50": string;
"100": string;
"200": string;
"300": string;
"400": string;
"500": string;
"600": string;
"700": string;
"800": string;
"900": string;
"950": string;
}

export interface BrandAddress {
streetAddress?: string;
addressLocality?: string;
addressRegion?: string;
postalCode?: string;
addressCountry?: string;
}

export interface BrandSocialLink {
platform: SocialPlatform;
url: string;
}

export interface BrandLink {
label: string;
href: string;
}

export interface BrandConfig {
identity: {
siteName: string;
tagline: string;
siteUrl: string;
language: string;
locale: string;
logoPath: string;
faviconPath: string;
appleTouchIconPath: string;
manifestPath: string;
defaultOgImagePath: string;
};
organization: {
name: string;
legalName?: string;
socialProfiles: readonly string[];
address?: BrandAddress;
};
theme: {
primary: BrandColorScale;
secondary: BrandColorScale;
themeColor: string;
fonts?: {
sans: readonly string[];
mono: readonly string[];
};
};
links: {
primaryCtas: readonly BrandLink[];
appLinks: readonly BrandLink[];
social: readonly BrandSocialLink[];
};
seo: {
titleTemplate: string;
defaultDescription: string;
robots: string;
twitterCard: "summary" | "summary_large_image";
twitterSite?: string;
};
blog: {
title: string;
description: string;
};
}
import type { BrandConfig } from "@/data-models/brand";

export const BRAND_CONFIG: BrandConfig = {
identity: {
Expand Down Expand Up @@ -152,19 +74,3 @@ export const BRAND_CONFIG: BrandConfig = {
"Latest updates and insights from the Idol or Bust research project.",
},
};

export function formatPageTitle(pageTitle: string): string {
const siteName = BRAND_CONFIG.identity.siteName;

if (pageTitle === siteName) {
return pageTitle;
}

const template = BRAND_CONFIG.seo.titleTemplate;

if (!template.includes("%s") && !template.includes("{siteName}")) {
return `${pageTitle} | ${siteName}`;
}

return template.replace(/%s/g, pageTitle).replace(/\{siteName\}/g, siteName);
}
Loading
Loading