diff --git a/.opencode/agent/code-reviewer.md b/.opencode/agent/code-reviewer.md index d429d36..9abb13f 100644 --- a/.opencode/agent/code-reviewer.md +++ b/.opencode/agent/code-reviewer.md @@ -26,6 +26,8 @@ Review all code changes for correctness, standards compliance, and quality. You - Check for deprecated API usage. - Verify error handling follows project conventions. - Ensure code follows the project's structure and naming conventions. +- Flag any hardcoded text strings in `.astro` templates — all user-facing strings must use the i18n system (`t()`). This is a blocking issue. +- Verify new translation keys are properly registered in `src/i18n/ui.ts`. ## Output Format diff --git a/.opencode/agent/frontend-engineer.md b/.opencode/agent/frontend-engineer.md index b0aa701..f430701 100644 --- a/.opencode/agent/frontend-engineer.md +++ b/.opencode/agent/frontend-engineer.md @@ -25,10 +25,15 @@ Implement the user-facing parts of the application — interfaces, presentation - Build output formatting and display logic. - Handle user input validation and feedback. - Ensure consistent user experience across the application. +- Use the project's i18n system (`useTranslations`, `t()`) for ALL user-facing text. +- Place new translation keys in `src/i18n/ui.ts` under the appropriate page namespace. +- Never hardcode Portuguese or any language strings in templates. ## Guidelines - Study existing code patterns before writing new code. - Follow the project's established conventions and style. - Keep interface logic thin — delegate business logic to internal modules. +- Load the `astro-i18n` skill before writing any `.astro` file. +- Before writing a new page, first check `src/i18n/ui.ts` for existing keys that match the content you need. - Validate and verify your changes build and pass tests before reporting done. diff --git a/.opencode/learnings/registry.md b/.opencode/learnings/registry.md index 79ffde9..f0fa2dc 100644 --- a/.opencode/learnings/registry.md +++ b/.opencode/learnings/registry.md @@ -15,6 +15,7 @@ All captured learnings from the AI Diamond Chain are registered here. ```markdown ## [Date] - [Learning Title] + - **Type:** skill/agent/workflow/reference - **Source:** Which diamond/event produced this - **Summary:** 1-2 sentence description @@ -25,6 +26,7 @@ All captured learnings from the AI Diamond Chain are registered here. --- ## 2026-04-17 - Continuous Learning System + - **Type:** skill - **Source:** User request to create learning rules - **Summary:** Created continuous-learning skill and supporting docs to ensure all learnings are captured, condensed for reuse, and documented for long-term reference @@ -34,6 +36,7 @@ All captured learnings from the AI Diamond Chain are registered here. --- ## 2026-04-17 - Tmux Automation Skill + - **Type:** skill - **Source:** User request for tmux skill for detached session control - **Summary:** Created tmux-automation skill with commands for creating detached sessions, sending keys programmatically, capturing output, and session management @@ -43,8 +46,19 @@ All captured learnings from the AI Diamond Chain are registered here. --- ## 2026-04-18 - DaisyUI v5 Custom Theme Configuration + - **Type:** skill - **Source:** Implementation Diamond - Custom theme creation for PodCodar brand - **Summary:** Learned DaisyUI v5's new `@plugin "daisyui/theme"` syntax with OKLCH color format, created light/dark theme pair with PodCodar brand colors (tech blues + warm amber accents), disabled default themes for clean implementation - **Location:** `.opencode/skills/daisyui-v5-themes/SKILL.md`, `src/styles/global.css` -- **Confidence:** 🟢 High \ No newline at end of file +- **Confidence:** 🟢 High + +--- + +## 2026-04-26 - i18n Text Convention: No Hardcoded Strings + +- **Type:** skill/agent/workflow +- **Source:** Refactoring transparency layout to /about, /join-us, /contact — discovered massive duplication of hardcoded strings across pages +- **Summary:** Established mandatory convention: ALL user-visible text in `.astro` templates must use the i18n system (`t()`). Updated `astro-i18n` skill with text convention rule, updated `frontend-engineer` and `code-reviewer` agents to enforce it, and documented in `AGENTS.md`. Data-driven content from `src/data/*.ts` files (member names, metric values, etc.) is exempted. +- **Location:** `.opencode/skills/astro-i18n/SKILL.md`, `.opencode/agent/frontend-engineer.md`, `.opencode/agent/code-reviewer.md`, `AGENTS.md` +- **Confidence:** 🟢 High diff --git a/.opencode/skills/astro-i18n/SKILL.md b/.opencode/skills/astro-i18n/SKILL.md index b8f4fbd..7935ca9 100644 --- a/.opencode/skills/astro-i18n/SKILL.md +++ b/.opencode/skills/astro-i18n/SKILL.md @@ -12,6 +12,7 @@ compatibility: opencode ## Quick Start ### 1. Configure astro.config.mjs + ```js i18n: { locales: ['pt-br', 'en'], @@ -23,12 +24,14 @@ i18n: { ``` ### 2. Create Translation Files + ``` src/i18n/ui.ts # Translation strings src/i18n/utils.ts # Helper functions ``` ### 3. Organize Pages + ``` src/pages/ ├── index.astro # default locale (pt-br) @@ -46,53 +49,57 @@ src/pages/ ## Core Concepts ### Locale Detection Priority + 1. **Cookie** (user selected) - highest priority 2. **Browser Accept-Language header** - middleware (SSR required) 3. **Default locale** - fallback ### URL Structure + | prefixDefaultLocale | default locale URL | other locale URL | -|---------------------|-------------------|-----------------| -| false | `/` | `/en/` | -| true | `/pt-br/` | `/en/` | +| ------------------- | ------------------ | ---------------- | +| false | `/` | `/en/` | +| true | `/pt-br/` | `/en/` | --- ## File Reference ### src/i18n/ui.ts + ```typescript export const languages = { - en: 'English', - 'pt-br': 'Português (Brasil)', + en: "English", + "pt-br": "Português (Brasil)", } as const; -export const defaultLang = 'pt-br'; +export const defaultLang = "pt-br"; export const ui = { en: { - 'nav.home': 'Home', - 'nav.blog': 'Blog', - 'nav.about': 'About', - 'nav.contact': 'Contact', - 'footer.copyright': 'All rights reserved.', + "nav.home": "Home", + "nav.blog": "Blog", + "nav.about": "About", + "nav.contact": "Contact", + "footer.copyright": "All rights reserved.", }, - 'pt-br': { - 'nav.home': 'Início', - 'nav.blog': 'Blog', - 'nav.about': 'Sobre', - 'nav.contact': 'Contato', - 'footer.copyright': 'Todos os direitos reservados.', + "pt-br": { + "nav.home": "Início", + "nav.blog": "Blog", + "nav.about": "Sobre", + "nav.contact": "Contato", + "footer.copyright": "Todos os direitos reservados.", }, } as const; ``` ### src/i18n/utils.ts + ```typescript -import { ui, defaultLang } from './ui'; +import { ui, defaultLang } from "./ui"; export function getLangFromUrl(url: URL): keyof typeof ui { - const segments = url.pathname.split('/').filter(Boolean); + const segments = url.pathname.split("/").filter(Boolean); const firstSegment = segments[0]; if (firstSegment && firstSegment in ui) { return firstSegment as keyof typeof ui; @@ -146,6 +153,7 @@ const t = useTranslations(lang); ## Language Picker with Cookie Persistence ### src/components/LanguagePicker.astro + ```astro --- import { getRelativeLocaleUrl } from 'astro:i18n'; @@ -203,15 +211,76 @@ const currentPath = getPathWithoutLocale(pathname, currentLang); --- +## Text Convention (MANDATORY) + +ALL user-visible text in `.astro` files MUST use the i18n system. Hardcoded +strings in templates are forbidden. + +### Correct + +```astro +--- +import { getLangFromUrl, useTranslations } from '@/i18n/utils'; +const lang = getLangFromUrl(Astro.url); +const t = useTranslations(lang); +--- + + +

{t('contributing.donations.body')}

+``` + +### Wrong + +```astro + + +

Se quiser contribuir financeiramente...

+``` + +### Exception + +Data-driven content from `src/data/*.ts` files (board member names, metric +values, channel names, project names) is iterable data, not display strings. +These may be used directly: + +```astro + +{projects.map((project) =>

{project.name}

)} + + +

Projetos e repositórios

+ +

{t('about.projects.title')}

+``` + +### Adding New Keys + +When adding a new key to `src/i18n/ui.ts`, follow the namespace pattern: + +``` +{page}.{section}.{field} +``` + +Examples: + +- `about.hero.eyebrow` — About page, hero section, eyebrow badge +- `joinUs.steps.01.title` — Join Us page, steps section, step 1 title +- `contributing.volunteering.body` — Contributing page, volunteering section, body text + +--- + ## Common Pitfalls ### 1. Hardcoded lang Attribute + **WRONG**: + ```html - + ``` **CORRECT**: + ```astro --- import { getLangFromUrl } from '../i18n/utils'; @@ -221,12 +290,15 @@ const lang = getLangFromUrl(Astro.url); ``` ### 2. Static Links Without getRelativeLocaleUrl + **WRONG**: + ```html - + ``` **CORRECT**: + ```astro --- import { getRelativeLocaleUrl } from 'astro:i18n'; @@ -236,13 +308,16 @@ const lang = getLangFromUrl(Astro.url); ``` ### 3. Wrong Default Locale + **WRONG** (default must be explicitly set): + ```js locales: ['en', 'pt-br'], defaultLocale: 'en', ``` **CORRECT**: + ```js locales: ['pt-br', 'en'], defaultLocale: 'pt-br', @@ -278,6 +353,7 @@ defaultLocale: 'pt-br', ## When Server-Side Redirect is Needed For server-side redirect based on browser language, add SSR adapter: + 1. Install `@astrojs/node` 2. Set `output: 'server'` 3. Create `src/middleware.ts` for server-side detection @@ -311,4 +387,4 @@ src/ │ └── LanguagePicker.astro └── layouts/ └── BlogPost.astro -``` \ No newline at end of file +``` diff --git a/AGENTS.md b/AGENTS.md index 10185b0..ae88015 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -163,6 +163,8 @@ This squad uses 9 specialized agents: - **Configured in**: `astro.config.ts` - **Skill available**: `astro-i18n` for adding English support - Default locale does not use URL prefix (`prefixDefaultLocale: false`) +- **Convention**: ALL user-visible text in `.astro` templates must go through the i18n system (`t()`). Hardcoded strings are not allowed. See the `astro-i18n` skill for the full rule. +- **How**: Import `useTranslations` from `@/i18n/utils`, call `t('key')`. Add keys to `src/i18n/ui.ts` under the relevant page namespace (e.g., `about.hero.title`). --- diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fecb135 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## Upcoming Release + +- Consistent Layout across pages (#254) +- Mobile-Friendly navbar (#252) diff --git a/e2e/site.spec.ts b/e2e/site.spec.ts index 5b494a5..ec3cabb 100644 --- a/e2e/site.spec.ts +++ b/e2e/site.spec.ts @@ -135,3 +135,214 @@ test.describe('Contact page', () => { await expect(page.getByRole('heading', { name: /contato|entre em contato/i })).toBeVisible(); }); }); + +// ────────────────────────────────────────────────────────────────────────────── +// About page (/about) +// ────────────────────────────────────────────────────────────────────────────── + +test.describe('About page', () => { + test('has hero section with gradient background and eyebrow badge', async ({ page }) => { + await page.goto('/about'); + + // Eyebrow badge is visible + const eyebrowBadge = page.locator('section').first().getByText('PodCodar'); + await expect(eyebrowBadge).toBeVisible(); + + // Hero heading is visible + await expect(page.getByRole('heading', { name: /sobre nós/i, level: 1 })).toBeVisible(); + + // Gradient background section exists + const heroSection = page.locator('section').first(); + await expect(heroSection).toHaveClass(/gradient/); + }); + + test('has mission section with icon header', async ({ page }) => { + await page.goto('/about'); + + // Mission section with icon + await expect(page.getByRole('heading', { name: /^missão$/i, level: 2 })).toBeVisible(); + }); + + test('has values section with 3 value cards', async ({ page }) => { + await page.goto('/about'); + + // Values section heading + await expect(page.getByRole('heading', { name: /^valores$/i, level: 2 })).toBeVisible(); + + // Three value cards: Inclusão, Colaboração, Qualidade de ensino + const valuesSection = page.locator('section:has-text("Valores")'); + await expect(valuesSection.getByText('Inclusão')).toBeVisible(); + await expect(valuesSection.getByText('Colaboração')).toBeVisible(); + await expect(valuesSection.getByText('Qualidade de ensino')).toBeVisible(); + }); + + test('has communication channels section with 3 channel cards', async ({ page }) => { + await page.goto('/about'); + + // Communication channels heading + await expect(page.getByRole('heading', { name: /onde conversamos/i })).toBeVisible(); + + // Three channel cards: WhatsApp, Discord, Google Meet + const channelsSection = page.locator('section:has-text("Onde conversamos")'); + await expect(channelsSection.getByText('WhatsApp')).toBeVisible(); + await expect(channelsSection.getByText('Discord')).toBeVisible(); + await expect(channelsSection.getByText('Google Meet')).toBeVisible(); + }); + + test('has projects section with project cards linking to GitHub', async ({ page }) => { + await page.goto('/about'); + + // Projects section heading + await expect(page.getByRole('heading', { name: /projetos e repositórios/i })).toBeVisible(); + + // Project cards have links to GitHub + const projectsSection = page.locator('section:has-text("Projetos e repositórios")'); + await expect(projectsSection.getByRole('link', { name: /abrir link/i })).toHaveCount(2); + + // Check GitHub links are correct + const githubLinks = projectsSection.getByRole('link', { name: /abrir link/i }); + await expect(githubLinks.nth(0)).toHaveAttribute('href', 'https://github.com/podcodar/webapp'); + await expect(githubLinks.nth(1)).toHaveAttribute('href', 'https://github.com/podcodar'); + }); +}); + +// ────────────────────────────────────────────────────────────────────────────── +// Join Us page (/join-us) +// ────────────────────────────────────────────────────────────────────────────── + +test.describe('Join Us page', () => { + test('has hero section with stats (3 channels, 300+ members, weekly encounters)', async ({ + page, + }) => { + await page.goto('/join-us'); + + // Hero heading is visible + await expect(page.getByRole('heading', { name: /faça parte/i, level: 1 })).toBeVisible(); + + // Stats are displayed - use more specific selectors + await expect(page.locator('text=Canais principais').first()).toBeVisible(); + await expect(page.locator('text=Membros ativos').first()).toBeVisible(); + await expect(page.locator('text=Encontros').first()).toBeVisible(); + }); + + test('has channels section with 3 channel cards', async ({ page }) => { + await page.goto('/join-us'); + + // Channels section heading + await expect(page.getByRole('heading', { name: /onde a comunidade vive/i })).toBeVisible(); + + // Three channel cards - use section-specific locators + const channelsSection = page.locator('section:has-text("Onde a comunidade vive")'); + await expect(channelsSection.getByRole('heading', { name: 'WhatsApp' })).toBeVisible(); + await expect(channelsSection.getByRole('heading', { name: 'Discord' })).toBeVisible(); + await expect(channelsSection.getByRole('heading', { name: 'Google Meet' })).toBeVisible(); + }); + + test('has steps section with 5 numbered steps', async ({ page }) => { + await page.goto('/join-us'); + + // Steps section heading + await expect(page.getByRole('heading', { name: /primeiros passos/i })).toBeVisible(); + + // Five steps: Imersão, Escolha, Engajamento, Colaboração, Crescimento + await expect(page.getByText('Imersão')).toBeVisible(); + await expect(page.getByText('Escolha')).toBeVisible(); + await expect(page.getByText('Engajamento')).toBeVisible(); + await expect(page.getByText('Colaboração')).toBeVisible(); + await expect(page.getByText('Crescimento')).toBeVisible(); + }); + + test('has GitHub section with CTA', async ({ page }) => { + await page.goto('/join-us'); + + // GitHub section + await expect(page.getByRole('heading', { name: /^github$/i, level: 2 })).toBeVisible(); + await expect(page.getByText(/github.com\/podcodar/i)).toBeVisible(); + + // GitHub CTA button + await expect(page.getByRole('link', { name: /ver repositórios/i })).toHaveAttribute( + 'href', + 'https://github.com/podcodar' + ); + }); + + test('has contact section with link to /contact', async ({ page }) => { + await page.goto('/join-us'); + + // Contact section + await expect(page.getByRole('heading', { name: /^contato$/i, level: 2 })).toBeVisible(); + + // Link to contact page + await expect(page.getByRole('link', { name: /envie uma mensagem/i })).toHaveAttribute( + 'href', + '/contact' + ); + }); +}); + +// ────────────────────────────────────────────────────────────────────────────── +// Contact page (/contact) +// ────────────────────────────────────────────────────────────────────────────── + +test.describe('Contact page', () => { + test('has hero section with response stats', async ({ page }) => { + await page.goto('/contact'); + + // Hero heading + await expect(page.getByRole('heading', { name: /fale conosco/i, level: 1 })).toBeVisible(); + + // Response stats are displayed - use actual translation values + await expect(page.getByText('1-2 dias')).toBeVisible(); + await expect(page.getByText('Comunidade ativa')).toBeVisible(); + await expect(page.getByText('Suporte dedicado')).toBeVisible(); + }); + + test('has contact methods section with email link', async ({ page }) => { + await page.goto('/contact'); + + // Contact methods heading - using actual translation + await expect(page.getByRole('heading', { name: /canais de comunicação/i })).toBeVisible(); + + // Email link with mailto - use first() since there are multiple + const emailLink = page.locator('a[href^="mailto:"]').first(); + await expect(emailLink).toBeVisible(); + }); + + test('has inquiries section with 5 inquiry type cards', async ({ page }) => { + await page.goto('/contact'); + + // Inquiries section heading - using actual translation + await expect( + page.getByRole('heading', { name: /sobre o que você pode entrar em contato/i }) + ).toBeVisible(); + + // Six inquiry type cards - using actual translation titles + await expect(page.getByText('Precisando contratar?')).toBeVisible(); + await expect(page.getByText('Workshops Patrocinados')).toBeVisible(); + await expect(page.getByText('Mentoria e carreira')).toBeVisible(); + await expect(page.getByText('Parcerias e colaborações')).toBeVisible(); + await expect(page.getByText('Doações, apoio e voluntariado')).toBeVisible(); + await expect(page.getByText('Outros assuntos')).toBeVisible(); + }); + + test('has social links section', async ({ page }) => { + await page.goto('/contact'); + + // Social links section heading - using actual translation + await expect(page.getByRole('heading', { name: /nos acompanhe nas redes/i })).toBeVisible(); + + // Social links are present - use first() to avoid strict mode violation + await expect(page.getByRole('link', { name: /podcodar no github/i }).first()).toBeVisible(); + await expect(page.getByRole('link', { name: /podcodar no linkedin/i }).first()).toBeVisible(); + await expect(page.getByRole('link', { name: /podcodar no instagram/i }).first()).toBeVisible(); + await expect(page.getByRole('link', { name: /podcodar no youtube/i }).first()).toBeVisible(); + }); + + test('has CTA section with mailto link', async ({ page }) => { + await page.goto('/contact'); + + // CTA section with email button + const mailtoLink = page.locator('a[href^="mailto:"]').last(); + await expect(mailtoLink).toBeVisible(); + }); +}); diff --git a/src/components/SocialLinks.astro b/src/components/SocialLinks.astro index f9daad5..9994578 100644 --- a/src/components/SocialLinks.astro +++ b/src/components/SocialLinks.astro @@ -1,6 +1,6 @@ --- import { Icon } from 'astro-icon/components'; -import { socialIconify, socialLinks } from '@/data/social-links'; +import { socialLinks } from '@/data/social-links'; ---
@@ -13,7 +13,7 @@ import { socialIconify, socialLinks } from '@/data/social-links'; class="text-base-content/70 hover:text-primary transition-colors" > {link.label} -
-

{item.title}

-

{item.description}

+

{t(item.titleKey)}

+

{t(item.descKey)}

)) diff --git a/src/components/marketing/Hero.astro b/src/components/marketing/Hero.astro index 910f9bc..d04350b 100644 --- a/src/components/marketing/Hero.astro +++ b/src/components/marketing/Hero.astro @@ -1,9 +1,14 @@ --- import Logo from '@/components/Logo.astro'; -import type { HeroContent } from '@/data/marketing'; interface Props { - content: HeroContent; + content: { + eyebrow: string; + headline: string; + subhead: string; + primaryCta?: { label: string; href: string }; + secondaryCta?: { label: string; href: string }; + }; } const { content } = Astro.props; @@ -29,14 +34,20 @@ const { content } = Astro.props;

{content.subhead}

- + {(content.primaryCta ?? content.secondaryCta) && ( +
+ {content.primaryCta && ( + + {content.primaryCta.label} + + )} + {content.secondaryCta && ( + + {content.secondaryCta.label} + + )} +
+ )} diff --git a/src/components/marketing/HowToHelp.astro b/src/components/marketing/HowToHelp.astro index 35cca62..8ee4923 100644 --- a/src/components/marketing/HowToHelp.astro +++ b/src/components/marketing/HowToHelp.astro @@ -1,11 +1,13 @@ --- import type { HelpPath } from '@/data/marketing'; +import { useTranslations } from '@/i18n/utils'; interface Props { items: readonly HelpPath[]; } const { items } = Astro.props; +const t = useTranslations(); ---
@@ -13,11 +15,11 @@ const { items } = Astro.props; items.map((item) => (
-

{item.title}

-

{item.description}

+

{t(item.titleKey)}

+

{t(item.descKey)}

diff --git a/src/components/marketing/TestimonialGrid.astro b/src/components/marketing/TestimonialGrid.astro index 8308148..9f6bbff 100644 --- a/src/components/marketing/TestimonialGrid.astro +++ b/src/components/marketing/TestimonialGrid.astro @@ -1,29 +1,31 @@ --- import type { Testimonial } from '@/data/marketing'; +import { useTranslations } from '@/i18n/utils'; interface Props { items: readonly Testimonial[]; } const { items } = Astro.props; +const t = useTranslations(); ---
{ - items.map((t) => ( + items.map((item) => (
-

{t.quote}

+

{t(item.quoteKey)}

- {t.name} - {t.role &&

{t.role}

} + {t(item.nameKey)} + {item.role &&

{item.role}

}
diff --git a/src/components/ui/CallToAction.astro b/src/components/ui/CallToAction.astro new file mode 100644 index 0000000..dc5b662 --- /dev/null +++ b/src/components/ui/CallToAction.astro @@ -0,0 +1,29 @@ +--- +import { Icon } from 'astro-icon/components'; + +interface Props { + icon: string; + title: string; + description: string; + class?: string; +} + +const { icon, title, description, class: className = '' } = Astro.props; +--- + +
+
+
+ +
+
+
+ +
+

{title}

+

{description}

+ +
+ +
+
diff --git a/src/components/ui/ChannelCard.astro b/src/components/ui/ChannelCard.astro new file mode 100644 index 0000000..92261fa --- /dev/null +++ b/src/components/ui/ChannelCard.astro @@ -0,0 +1,48 @@ +--- +import { Icon } from 'astro-icon/components'; + +type CardColor = 'violet' | 'emerald' | 'blue'; + +interface Props { + icon: string; + title: string; + subtitle: string; + color: CardColor; +} + +const { icon, title, subtitle, color } = Astro.props; + +const COLORS: Record = { + emerald: { + from: 'from-emerald-500/5', + to: 'to-emerald-600/10', + text: 'text-emerald-500', + border: 'hover:border-emerald-500/30', + }, + violet: { + from: 'from-violet-500/5', + to: 'to-violet-600/10', + text: 'text-violet-500', + border: 'hover:border-violet-500/30', + }, + blue: { + from: 'from-blue-500/5', + to: 'to-blue-600/10', + text: 'text-blue-500', + border: 'hover:border-blue-500/30', + }, +}; + +const c = COLORS[color]; +--- + +
  • +
    +
    +
    + +
    +

    {title}

    +

    {subtitle}

    +
    +
  • diff --git a/src/components/ui/HeroSection.astro b/src/components/ui/HeroSection.astro new file mode 100644 index 0000000..fc36906 --- /dev/null +++ b/src/components/ui/HeroSection.astro @@ -0,0 +1,75 @@ +--- +import { Icon } from 'astro-icon/components'; + +interface Stat { + icon: string; + value: string | number; + label: string; + color?: 'primary' | 'violet' | 'emerald' | 'blue'; +} + +interface Props { + eyebrow?: string; + title: string; + subtitle?: string; + stats?: Stat[]; + class?: string; +} + +const { eyebrow, title, subtitle, stats, class: className = '' } = Astro.props; +--- + +
    + +
    +
    +
    +
    +
    + +
    + {eyebrow && ( +
    + + {eyebrow} +
    + )} + +

    + {title} +

    + + {subtitle && ( +

    + {subtitle} +

    + )} + + {stats && stats.length > 0 && ( +
    + {stats.map((stat) => { + const colorClasses = { + primary: 'bg-primary/10 text-primary', + violet: 'bg-violet-500/10 text-violet-500', + emerald: 'bg-emerald-500/10 text-emerald-500', + blue: 'bg-blue-500/10 text-blue-500', + }; + const color = stat.color ?? 'blue'; + return ( +
    +
    + +
    +
    + {stat.value} + {stat.label} +
    +
    + ); + })} +
    + )} + + +
    +
    diff --git a/src/components/ui/IconCard.astro b/src/components/ui/IconCard.astro new file mode 100644 index 0000000..4a2556b --- /dev/null +++ b/src/components/ui/IconCard.astro @@ -0,0 +1,61 @@ +--- +interface Props { + value: string; + label: string; + color?: 'primary' | 'violet' | 'emerald' | 'blue'; + class?: string; +} + +const { value, label, color = 'emerald', class: className = '' } = Astro.props; + +const colorPairs = { + primary: { + from: 'from-primary/5', + to: 'to-primary/10', + text: 'text-primary', + gradientFrom: 'from-primary', + gradientTo: 'to-primary/70', + hoverBorder: 'hover:border-primary/30', + hoverShadow: 'hover:shadow-primary/5', + }, + violet: { + from: 'from-violet-500/5', + to: 'to-violet-600/10', + text: 'text-violet-500', + gradientFrom: 'from-violet-500', + gradientTo: 'to-violet-600/70', + hoverBorder: 'hover:border-violet-500/30', + hoverShadow: 'hover:shadow-violet-500/5', + }, + emerald: { + from: 'from-emerald-500/5', + to: 'to-emerald-600/10', + text: 'text-emerald-500', + gradientFrom: 'from-emerald-500', + gradientTo: 'to-emerald-600/70', + hoverBorder: 'hover:border-emerald-500/30', + hoverShadow: 'hover:shadow-emerald-500/5', + }, + blue: { + from: 'from-blue-500/5', + to: 'to-blue-600/10', + text: 'text-blue-500', + gradientFrom: 'from-blue-500', + gradientTo: 'to-blue-600/70', + hoverBorder: 'hover:border-blue-500/30', + hoverShadow: 'hover:shadow-blue-500/5', + }, +}; + +const colors = colorPairs[color]; +--- + +
  • +
    +
    +
    + {value} +
    +
    {label}
    +
    +
  • diff --git a/src/components/ui/InquiryCard.astro b/src/components/ui/InquiryCard.astro new file mode 100644 index 0000000..adf2ff9 --- /dev/null +++ b/src/components/ui/InquiryCard.astro @@ -0,0 +1,24 @@ +--- +import { Icon } from 'astro-icon/components'; + +interface Props { + icon: string; + title: string; + description: string; + bg: string; + border: string; + gradient: string; +} + +const { icon, title, description, bg, border, gradient } = Astro.props; +--- + +
  • +
    +
    + +
    +

    {title}

    +

    {description}

    +
    +
  • diff --git a/src/components/ui/SectionHeader.astro b/src/components/ui/SectionHeader.astro new file mode 100644 index 0000000..b392233 --- /dev/null +++ b/src/components/ui/SectionHeader.astro @@ -0,0 +1,30 @@ +--- +import { Icon } from 'astro-icon/components'; + +interface Props { + icon: string; + title: string; + subtitle?: string; + color?: 'primary' | 'violet' | 'emerald' | 'blue'; + class?: string; +} + +const { icon, title, subtitle, color = 'primary', class: className = '' } = Astro.props; + +const colorGradients = { + primary: 'from-primary to-primary/70 shadow-primary/20', + violet: 'from-violet-500 to-violet-600 shadow-violet-500/20', + emerald: 'from-emerald-500 to-emerald-600 shadow-emerald-500/20', + blue: 'from-blue-500 to-blue-600 shadow-blue-500/20', +}; +--- + +
    +
    + +
    +
    +

    {title}

    + {subtitle &&

    {subtitle}

    } +
    +
    diff --git a/src/components/ui/StepItem.astro b/src/components/ui/StepItem.astro new file mode 100644 index 0000000..726d366 --- /dev/null +++ b/src/components/ui/StepItem.astro @@ -0,0 +1,54 @@ +--- +import { Icon } from 'astro-icon/components'; + +type StepColor = 'primary' | 'violet' | 'emerald' | 'blue'; + +interface Props { + step: string; + icon: string; + title: string; + desc: string; + color: StepColor; +} + +const { step, icon, title, desc, color } = Astro.props; + +const COLORS: Record = { + violet: { + text: 'text-violet-500', + gradient: 'from-violet-500/10 to-violet-500/20', + gradientHover: 'group-hover:from-violet-500/20', + }, + emerald: { + text: 'text-emerald-500', + gradient: 'from-emerald-500/10 to-emerald-500/20', + gradientHover: 'group-hover:from-emerald-500/20', + }, + blue: { + text: 'text-blue-500', + gradient: 'from-blue-500/10 to-blue-500/20', + gradientHover: 'group-hover:from-blue-500/20', + }, + primary: { + text: 'text-primary', + gradient: 'from-primary/10 to-primary/20', + gradientHover: 'group-hover:from-primary/20', + }, +}; + +const c = COLORS[color]; +--- + +
  • +
    + +
    +
    +
    +

    + {step} + {title} +

    +

    {desc}

    +
    +
  • diff --git a/src/data/marketing.ts b/src/data/marketing.ts index 0fd3905..b56f17c 100644 --- a/src/data/marketing.ts +++ b/src/data/marketing.ts @@ -1,207 +1,204 @@ -/** User-facing copy for the landing and institutional pages (pt-BR strings). */ - -export const hero = { - eyebrow: 'PodCodar', - headline: 'Educação em tecnologia, feita em comunidade', - subhead: - 'Somos uma comunidade e organização sem fins lucrativos focada em transformar a vida de brasileiros por meio da educação profissionalizante em tecnologia — com mentoria, estudos em grupo e projetos reais.', - /** Short line in the hero side card */ - cardTagline: 'Democratizar o acesso ao conhecimento. Estudar junto. Crescer com propósito.', - primaryCta: { label: 'Faça parte', href: '/join-us' }, - secondaryCta: { label: 'Como posso ajudar?', href: '/contributing' }, -} as const; - -export type HeroContent = typeof hero; +// ── Mission ────────────────────────────────────────────────────────────────── export const mission = { - title: 'Missão', - body: [ - 'A PodCodar existe para democratizar o acesso à educação profissionalizante nas áreas de tecnologia. Acreditamos que qualificação e acesso ao conhecimento digital são motores de mudança de vida — e que, no Brasil, essa educação ainda costuma ser elitizada, limitada e cara.', - 'Por isso guiamos e damos acesso a quem deseja se profissionalizar: em comunidade, com escuta e responsabilidade social.', - 'Nosso foco de ensino inclui, entre outras frentes: software (front-end e back-end), infraestrutura, inteligência artificial, dados (incluindo engenharia e ciência de dados) e design (UI e UX).', - ], + titleKey: 'marketing.mission.title', + bodyKeys: ['marketing.mission.body.0', 'marketing.mission.body.1', 'marketing.mission.body.2'], } as const; +// ── Activities ─────────────────────────────────────────────────────────────── + export type Activity = { - title: string; - description: string; - icon: 'interview' | 'career' | 'groups' | 'project' | 'cafe'; + titleKey: string; + descKey: string; + icon: 'interview' | 'career' | 'groups' | 'project' | 'cafe' | 'workshop'; }; export const activities: Activity[] = [ { - title: 'Entrevistas simuladas', - description: - 'Pratique processos seletivos com apoio da comunidade e feedback para ganhar confiança antes da entrevista de verdade.', icon: 'interview', + titleKey: 'marketing.activities.interview.title', + descKey: 'marketing.activities.interview.desc', }, { - title: 'Mentoria de carreira', - description: - 'Conversas e orientação para transição, currículo, portfólio e próximos passos — do primeiro estágio à troca de área.', icon: 'career', + titleKey: 'marketing.activities.career.title', + descKey: 'marketing.activities.career.desc', }, { - title: 'Grupos de estudo', - description: - 'Turmas e canais no WhatsApp e no Discord para tirar dúvidas, compartilhar materiais e manter o ritmo de estudo coletivo.', icon: 'groups', + titleKey: 'marketing.activities.groups.title', + descKey: 'marketing.activities.groups.desc', }, { - title: 'Assistência em projetos', - description: - 'Mentoria em projetos práticos — da ideia ao repositório — com apoio de quem já passou por desafios parecidos.', icon: 'project', + titleKey: 'marketing.activities.project.title', + descKey: 'marketing.activities.project.desc', }, { - title: 'Café com Código', - description: - 'Encontros para trocar experiência, apresentar o que você está construindo e conhecer a comunidade com calma (e café).', icon: 'cafe', + titleKey: 'marketing.activities.cafe.title', + descKey: 'marketing.activities.cafe.desc', + }, + { + icon: 'workshop', + titleKey: 'marketing.activities.workshop.title', + descKey: 'marketing.activities.workshop.desc', }, ] as const; +// ── Testimonials ───────────────────────────────────────────────────────────── + export type Testimonial = { id: number; - name: string; + nameKey: string; role?: string; avatarUrl: string; profileUrl: string; - quote: string; + quoteKey: string; }; export const testimonials: Testimonial[] = [ { id: 1, - name: 'Giovanna Neves Damasceno', + nameKey: 'marketing.testimonials.1.name', + quoteKey: 'marketing.testimonials.1.quote', avatarUrl: 'https://avatars.githubusercontent.com/u/18710340?v=4', profileUrl: 'https://github.com/giovannand', - quote: - 'A PodCodar juntou minha trajetória em tecnologia com o desejo de trabalhar com pessoas e ajudá-las a se desenvolver. É um projeto com propósito claro — amo fazer parte.', }, { id: 2, - name: 'Gilberto Ferreira Borges Júnior', + nameKey: 'marketing.testimonials.2.name', + quoteKey: 'marketing.testimonials.2.quote', avatarUrl: 'https://avatars.githubusercontent.com/u/57193296?v=4', profileUrl: 'https://github.com/borgesgfj', - quote: - 'Com o apoio da comunidade fiz a transição da pesquisa e do ensino em física para engenharia de software. Hoje contribuir de volta é tão gratificante quanto aprender aqui.', }, { id: 3, - name: 'Filipe Barbosa', + nameKey: 'marketing.testimonials.3.name', + quoteKey: 'marketing.testimonials.3.quote', avatarUrl: 'https://avatars.githubusercontent.com/u/65319425?v=4', profileUrl: 'https://github.com/Filipe-barbosa', - quote: - 'Pensei que programar não era pra mim — entrei na comunidade e, com o tempo, a confiança veio. Hoje sigo construindo carreira com a rede ao lado.', }, { id: 4, - name: 'Guilherme Barbosa', + nameKey: 'marketing.testimonials.4.name', + quoteKey: 'marketing.testimonials.4.quote', avatarUrl: 'https://avatars.githubusercontent.com/u/73261443?v=4', profileUrl: 'https://github.com/Guilherme-BS', - quote: - 'A PodCodar mudou minha perspectiva: novas conversas, novos aprendizados e um lugar onde me sinto em casa na tecnologia.', }, ] as const; +// ── How to Help ────────────────────────────────────────────────────────────── + export type HelpPath = { - title: string; - description: string; + titleKey: string; + descKey: string; href: string; - cta: string; + ctaKey: string; }; export const howToHelp: HelpPath[] = [ { - title: 'Doações', - description: - 'Apoio financeiro de pessoas físicas e patrocínios ajudam a manter a PodCodar sustentável e a ampliar impacto — plataforma de ensino, oficinas e equipe.', + titleKey: 'marketing.help.donations.title', + descKey: 'marketing.help.donations.desc', href: '/contact', - cta: 'Falar sobre doação', + ctaKey: 'marketing.help.donations.cta', }, { - title: 'Voluntariado', - description: - 'Tempo e habilidades: mentoria, facilitação de estudos e eventos, revisão de código, design e comunicação — há espaço para o seu jeito de contribuir.', + titleKey: 'marketing.help.volunteering.title', + descKey: 'marketing.help.volunteering.desc', href: '/contributing', - cta: 'Ver voluntariado', + ctaKey: 'marketing.help.volunteering.cta', }, { - title: 'Parcerias', - description: - 'Empresas e fundos podem apoiar diversidade e educação em tecnologia — inclusive parcerias para contratação de pessoas qualificadas pela comunidade.', + titleKey: 'marketing.help.partnerships.title', + descKey: 'marketing.help.partnerships.desc', href: '/contact', - cta: 'Propor parceria', + ctaKey: 'marketing.help.partnerships.cta', }, ] as const; -/** Core values (from the onboarding guide). */ -export const coreValues: { title: string; text: string }[] = [ - { - title: 'Inclusão', - text: 'Garantir que a educação tecnológica seja acessível a todos, independentemente de origem, renda ou localização. Somos um espaço seguro e acolhedor.', - }, - { - title: 'Colaboração', - text: 'Estudar junto é mais prazeroso e eficiente. Valorizamos o compartilhamento de conhecimento, discussões abertas e o apoio mútuo em projetos e estudos.', - }, +// ── Core Values ────────────────────────────────────────────────────────────── + +export const coreValues = [ + { titleKey: 'marketing.values.inclusion.title', textKey: 'marketing.values.inclusion.text' }, { - title: 'Qualidade de ensino', - text: 'Foco em mentoria e conteúdo que prepare de fato a próxima geração de profissionais digitais para o mercado de trabalho.', + titleKey: 'marketing.values.collaboration.title', + textKey: 'marketing.values.collaboration.text', }, -]; + { titleKey: 'marketing.values.quality.title', textKey: 'marketing.values.quality.text' }, +] as const; + +// ── About Community ────────────────────────────────────────────────────────── export const aboutCommunity = { - title: 'Cultura e organização', - lead: 'A comunidade se organiza para multiplicar impacto: núcleo pedagógico, guildas por área e iniciativas práticas dentro de cada uma.', - points: [ - 'Núcleo Pedagógico: centraliza visão, prioridades e alocação de pessoas e recursos para as iniciativas.', - 'Guildas: núcleos por área de interesse ou entrega (projetos, eventos, design e outras frentes).', - 'Iniciativas: programas concretos — mentorias, incubadora, Café com Código, meetups, workshops, Chá com Design e mais.', + titleKey: 'marketing.community.title', + leadKey: 'marketing.community.lead', + pointKeys: [ + 'marketing.community.point.1', + 'marketing.community.point.2', + 'marketing.community.point.3', ], } as const; +// ── Projects ───────────────────────────────────────────────────────────────── + export type ProjectItem = { - name: string; - description: string; + nameKey: string; + descKey: string; href: string; }; export const projects: ProjectItem[] = [ { - name: 'Site e materiais PodCodar', - description: 'Este site e recursos da comunidade em evolução — contribuições são bem-vindas.', + nameKey: 'marketing.projects.site.name', + descKey: 'marketing.projects.site.desc', href: 'https://github.com/podcodar/webapp', }, { - name: 'Organização no GitHub', - description: 'Repositórios abertos da PodCodar — issues e PRs são um ótimo primeiro passo.', + nameKey: 'marketing.projects.github.name', + descKey: 'marketing.projects.github.desc', href: 'https://github.com/podcodar', }, ] as const; +// ── Events ─────────────────────────────────────────────────────────────────── + export const eventsBlock = { - title: 'Eventos', - body: 'Café com Código, meetups e workshops são alguns dos formatos em que a comunidade se encontra ao vivo (muitas vezes no Google Meet). Novidades e convites circulam nos grupos e no Discord.', - externalLabel: 'Ver organização no GitHub', + titleKey: 'marketing.events.title', + bodyKey: 'marketing.events.body', + ctaKey: 'marketing.events.cta', externalHref: 'https://github.com/podcodar', } as const; -/** Communication channels (onboarding guide). */ -export const communicationChannels: { channel: string; description: string }[] = [ +// ── Communication Channels ─────────────────────────────────────────────────── + +export type ChannelColor = 'emerald' | 'violet' | 'blue'; + +export type CommunicationChannel = { + nameKey: string; + descKey: string; + icon: string; + color: ChannelColor; +}; + +export const communicationChannels: CommunicationChannel[] = [ { - channel: 'WhatsApp', - description: - 'Comunicação do dia a dia: avisos rápidos, interação social e coordenação com a turma.', + nameKey: 'marketing.channels.whatsapp.name', + descKey: 'marketing.channels.whatsapp.desc', + icon: 'lucide:message-circle', + color: 'emerald', }, { - channel: 'Discord', - description: - 'Grupos de estudo, canais técnicos, mentorias e discussões — é o “quartel-general” assíncrono da PodCodar.', + nameKey: 'marketing.channels.discord.name', + descKey: 'marketing.channels.discord.desc', + icon: 'simple-icons:discord', + color: 'violet', }, { - channel: 'Google Meet', - description: 'Reuniões do núcleo pedagógico, workshops e encontros ao vivo com a comunidade.', + nameKey: 'marketing.channels.meet.name', + descKey: 'marketing.channels.meet.desc', + icon: 'lucide:video', + color: 'blue', }, -]; +] as const; diff --git a/src/data/social-links.ts b/src/data/social-links.ts index 2a7d8d4..9028456 100644 --- a/src/data/social-links.ts +++ b/src/data/social-links.ts @@ -9,35 +9,33 @@ export type SocialLink = { href: string; /** Screen reader label (pt-BR). */ label: string; + /** Iconify id for astro-icon. */ + icon: string; network: SocialNetwork; }; -/** Iconify ids for `astro-icon` + `@iconify-json/simple-icons`. */ -export const socialIconify: Record = { - github: 'simple-icons:github', - linkedin: 'simple-icons:linkedin', - instagram: 'simple-icons:instagram', - youtube: 'simple-icons:youtube', -}; - export const socialLinks: SocialLink[] = [ { network: 'github', + icon: 'simple-icons:github', href: 'https://github.com/podcodar/', label: 'PodCodar no GitHub', }, { network: 'linkedin', + icon: 'simple-icons:linkedin', href: 'https://www.linkedin.com/company/podcodar/', label: 'PodCodar no LinkedIn', }, { network: 'instagram', + icon: 'simple-icons:instagram', href: 'https://www.instagram.com/podcodar/', label: 'PodCodar no Instagram', }, { network: 'youtube', + icon: 'simple-icons:youtube', href: 'https://www.youtube.com/@podcodar5070/', label: 'PodCodar no YouTube', }, diff --git a/src/i18n/ui.ts b/src/i18n/ui.ts index 94e91e1..f9c7ec0 100644 --- a/src/i18n/ui.ts +++ b/src/i18n/ui.ts @@ -2,6 +2,7 @@ export const defaultLang = 'pt-br' as const; export const ui = { 'pt-br': { + // ── Navigation ──────────────────────────────────────────────────────────── 'nav.home': 'Início', 'nav.blog': 'Blog', 'nav.about': 'Sobre', @@ -11,14 +12,223 @@ export const ui = { 'nav.transparency': 'Transparência', 'nav.menu': 'Menu', 'nav.close_menu': 'Fechar menu', + + // ── Marketing data ──────────────────────────────────────────────────────── + 'marketing.mission.title': 'Missão', + 'marketing.mission.body.0': + 'A PodCodar existe para democratizar o acesso à educação profissionalizante nas áreas de tecnologia. Acreditamos que qualificação e acesso ao conhecimento digital são motores de mudança de vida — e que, no Brasil, essa educação ainda costuma ser elitizada, limitada e cara.', + 'marketing.mission.body.1': + 'Por isso guiamos e damos acesso a quem deseja se profissionalizar: em comunidade, com escuta e responsabilidade social.', + 'marketing.mission.body.2': + 'Nosso foco de ensino inclui, entre outras frentes: software (front-end e back-end), infraestrutura, inteligência artificial, dados (incluindo engenharia e ciência de dados) e design (UI e UX).', + 'marketing.activities.interview.title': 'Entrevistas simuladas', + 'marketing.activities.interview.desc': + 'Pratique processos seletivos com apoio da comunidade e feedback para ganhar confiança antes da entrevista de verdade.', + 'marketing.activities.career.title': 'Mentoria de carreira', + 'marketing.activities.career.desc': + 'Conversas e orientação para transição, currículo, portfólio e próximos passos — do primeiro estágio à troca de área.', + 'marketing.activities.groups.title': 'Grupos de estudo', + 'marketing.activities.groups.desc': + 'Turmas e canais no WhatsApp e no Discord para tirar dúvidas, compartilhar materiais e manter o ritmo de estudo coletivo.', + 'marketing.activities.project.title': 'Assistência em projetos', + 'marketing.activities.project.desc': + 'Mentoria em projetos práticos — da ideia ao repositório — com apoio de quem já passou por desafios parecidos.', + 'marketing.activities.cafe.title': 'Café com Código', + 'marketing.activities.cafe.desc': + 'Encontros para trocar experiência, apresentar o que você está construindo e conhecer a comunidade com calma (e café).', + 'marketing.activities.workshop.title': 'Workshops', + 'marketing.activities.workshop.desc': + 'Oficinas práticas sobre tecnologia, carreira e ferramentas — do básico ao avançado, abertas para toda a comunidade.', + 'marketing.help.donations.title': 'Doações', + 'marketing.help.donations.desc': + 'Apoio financeiro de pessoas físicas e patrocínios ajudam a manter a PodCodar sustentável e a ampliar impacto — plataforma de ensino, oficinas e equipe.', + 'marketing.help.donations.cta': 'Falar sobre doação', + 'marketing.help.volunteering.title': 'Voluntariado', + 'marketing.help.volunteering.desc': + 'Tempo e habilidades: mentoria, facilitação de estudos e eventos, revisão de código, design e comunicação — há espaço para o seu jeito de contribuir.', + 'marketing.help.volunteering.cta': 'Ver voluntariado', + 'marketing.help.partnerships.title': 'Parcerias', + 'marketing.help.partnerships.desc': + 'Empresas e fundos podem apoiar diversidade e educação em tecnologia — inclusive parcerias para contratação de pessoas qualificadas pela comunidade.', + 'marketing.help.partnerships.cta': 'Propor parceria', + 'marketing.values.inclusion.title': 'Inclusão', + 'marketing.values.inclusion.text': + 'Garantir que a educação tecnológica seja acessível a todos, independentemente de origem, renda ou localização. Somos um espaço seguro e acolhedor.', + 'marketing.values.collaboration.title': 'Colaboração', + 'marketing.values.collaboration.text': + 'Estudar junto é mais prazeroso e eficiente. Valorizamos o compartilhamento de conhecimento, discussões abertas e o apoio mútuo em projetos e estudos.', + 'marketing.values.quality.title': 'Qualidade de ensino', + 'marketing.values.quality.text': + 'Foco em mentoria e conteúdo que prepare de fato a próxima geração de profissionais digitais para o mercado de trabalho.', + 'marketing.community.title': 'Cultura e organização', + 'marketing.community.lead': + 'A comunidade se organiza para multiplicar impacto: núcleo pedagógico, guildas por área e iniciativas práticas dentro de cada uma.', + 'marketing.community.point.1': + 'Núcleo Pedagógico: centraliza visão, prioridades e alocação de pessoas e recursos para as iniciativas.', + 'marketing.community.point.2': + 'Guildas: núcleos por área de interesse ou entrega (projetos, eventos, design e outras frentes).', + 'marketing.community.point.3': + 'Iniciativas: programas concretos — mentorias, incubadora, Café com Código, meetups, workshops, Chá com Design e mais.', + 'marketing.projects.site.name': 'Site e materiais PodCodar', + 'marketing.projects.site.desc': + 'Este site e recursos da comunidade em evolução — contribuições são bem-vindas.', + 'marketing.projects.github.name': 'Organização no GitHub', + 'marketing.projects.github.desc': + 'Repositórios abertos da PodCodar — issues e PRs são um ótimo primeiro passo.', + 'marketing.events.title': 'Eventos', + 'marketing.events.body': + 'Café com Código, meetups e workshops são alguns dos formatos em que a comunidade se encontra ao vivo (muitas vezes no Google Meet). Novidades e convites circulam nos grupos e no Discord.', + 'marketing.events.cta': 'Ver organização no GitHub', + 'marketing.channels.whatsapp.name': 'WhatsApp', + 'marketing.channels.whatsapp.desc': + 'Comunicação do dia a dia: avisos rápidos, interação social e coordenação com a turma.', + 'marketing.channels.discord.name': 'Discord', + 'marketing.channels.discord.desc': + 'Grupos de estudo, canais técnicos, mentorias e discussões — é o "quartel-general" assíncrono da PodCodar.', + 'marketing.channels.meet.name': 'Google Meet', + 'marketing.channels.meet.desc': + 'Reuniões do núcleo pedagógico, workshops e encontros ao vivo com a comunidade.', + 'marketing.testimonials.1.name': 'Giovanna Neves Damasceno', + 'marketing.testimonials.1.quote': + 'A PodCodar juntou minha trajetória em tecnologia com o desejo de trabalhar com pessoas e ajudá-las a se desenvolver. É um projeto com propósito claro — amo fazer parte.', + 'marketing.testimonials.2.name': 'Gilberto Ferreira Borges Júnior', + 'marketing.testimonials.2.quote': + 'Com o apoio da comunidade fiz a transição da pesquisa e do ensino em física para engenharia de software. Hoje contribuir de volta é tão gratificante quanto aprender aqui.', + 'marketing.testimonials.3.name': 'Filipe Barbosa', + 'marketing.testimonials.3.quote': + 'Pensei que programar não era pra mim — entrei na comunidade e, com o tempo, a confiança veio. Hoje sigo construindo carreira com a rede ao lado.', + 'marketing.testimonials.4.name': 'Guilherme Barbosa', + 'marketing.testimonials.4.quote': + 'A PodCodar mudou minha perspectiva: novas conversas, novos aprendizados e um lugar onde me sinto em casa na tecnologia.', + + // ── Layout ──────────────────────────────────────────────────────────────── 'footer.copyright': 'Todos os direitos reservados.', - // Transparency page + 'layout.skipToContent': 'Pular para o conteúdo', + + // ── Home page ───────────────────────────────────────────────────────────── + 'home.hero.eyebrow': 'PodCodar', + 'home.hero.headline': 'Educação em tecnologia, feita em comunidade', + 'home.hero.subhead': + 'Somos uma comunidade e organização sem fins lucrativos focada em transformar a vida de brasileiros por meio da educação profissionalizante em tecnologia — com mentoria, estudos em grupo e projetos reais.', + 'home.mission.title': 'Missão', + 'home.activities.eyebrow': 'Comunidade', + 'home.activities.title': 'Atividades', + 'home.activities.subtitle': + 'Da mentoria à roda de conversa — conheça algumas formas de participar da PodCodar.', + 'home.testimonials.eyebrow': 'Vozes', + 'home.testimonials.title': 'Depoimentos', + 'home.testimonials.subtitle': + 'Quem passou por aqui compartilha um pouco da experiência. Textos podem ser atualizados junto às pessoas citadas.', + 'home.howToHelp.eyebrow': 'Participe', + 'home.howToHelp.title': 'Como posso ajudar?', + 'home.howToHelp.subtitle': 'Três caminhos comuns — escolha o que combina com você hoje.', + 'home.community.title': 'Sobre a comunidade', + 'home.community.cta': 'Conheça mais no Sobre', + + // ── About page ──────────────────────────────────────────────────────────── + 'about.hero.eyebrow': 'PodCodar', + 'about.hero.title': 'Sobre nós', + 'about.hero.subtitle': + 'Somos uma comunidade e organização sem fins lucrativos focada em transformar vidas por meio da educação profissionalizante em tecnologia — com inclusão, colaboração e qualidade de ensino.', + 'about.hero.stats.community.value': 'Comunidade', + 'about.hero.stats.community.label': 'Inclusiva e acolhedora', + 'about.hero.stats.education.value': 'Educação', + 'about.hero.stats.education.label': 'Profissionalizante', + 'about.hero.stats.impact.value': 'Impacto', + 'about.hero.stats.impact.label': 'Social e comunitário', + 'about.mission.title': 'Missão', + 'about.mission.subtitle': 'O que nos move', + 'about.values.title': 'Valores', + 'about.values.subtitle': 'Três princípios que guiam a cultura da comunidade', + 'about.channels.title': 'Onde conversamos', + 'about.channels.subtitle': 'Cada canal tem um papel — escolha o ritmo que combina com você', + 'about.projects.title': 'Projetos e repositórios', + 'about.projects.subtitle': + 'A comunidade também constrói em código aberto — além das iniciativas dentro das guildas', + 'about.projects.linkText': 'Abrir link', + + // ── Join Us page ────────────────────────────────────────────────────────── + 'joinUs.hero.eyebrow': 'Junte-se à comunidade', + 'joinUs.hero.title': 'Faça parte', + 'joinUs.hero.subtitle': + 'Seja para aprender, ensinar ou colaborar com a comunidade, o caminho começa nos canais e nas guildas — espaços onde organizamos estudos, mentorias e projetos.', + 'joinUs.hero.stats.channels': 'Canais principais', + 'joinUs.hero.stats.members': 'Membros ativos', + 'joinUs.hero.stats.meetups': 'Encontros', + 'joinUs.channels.title': 'Onde a comunidade vive', + 'joinUs.channels.subtitle': + 'Comunicação diária no WhatsApp; estudos e conteúdo no Discord; reuniões e eventos ao vivo no Meet.', + 'joinUs.steps.title': 'Primeiros passos', + 'joinUs.steps.subtitle': 'Sugestão de roteiro para quem está chegando — adapte ao seu tempo.', + 'joinUs.steps.s1.title': 'Imersão', + 'joinUs.steps.s1.desc': + 'Entre nos grupos no WhatsApp e nos canais do Discord; leia as regras e se apresente.', + 'joinUs.steps.s2.title': 'Escolha', + 'joinUs.steps.s2.desc': + 'Participe das guildas e iniciativas que mais fazem sentido para você (projetos, eventos, design, etc.).', + 'joinUs.steps.s3.title': 'Engajamento', + 'joinUs.steps.s3.desc': + 'Participe de grupos de estudo e de mentorias em projetos — como mentorado ou mentor.', + 'joinUs.steps.s4.title': 'Colaboração', + 'joinUs.steps.s4.desc': + 'Entre em discussões, junte-se a projetos e compartilhe o que está aprendendo.', + 'joinUs.steps.s5.title': 'Crescimento', + 'joinUs.steps.s5.desc': + 'Peça feedback, peça sugestões de estudo e proponha ideias novas para as guildas.', + 'joinUs.github.title': 'GitHub', + 'joinUs.github.desc': 'Código aberto, transparência e boas primeiras contribuições.', + 'joinUs.github.linkText': 'Acessar github.com/podcodar', + 'joinUs.github.cta': 'Ver repositórios', + 'joinUs.contact.title': 'Contato', + 'joinUs.contact.subtitle': 'Prefere uma mensagem antes de entrar nos grupos? Entre em contato!', + 'joinUs.contact.sendTitle': 'Envie uma mensagem', + 'joinUs.contact.sendDesc': 'Fale diretamente com a gente pelo formulário de contato.', + 'joinUs.contact.discordTitle': 'Conversar no Discord', + 'joinUs.contact.discordDesc': 'Participe das discussões e conheça a comunidade.', + 'joinUs.contact.tagFast': 'Resposta rápida', + 'joinUs.contact.tagCommunity': 'Comunidade acolhedora', + 'joinUs.contact.tagSafe': 'Espaço seguro', + + // ── Contributing page ───────────────────────────────────────────────────── + 'contributing.hero.eyebrow': 'Participe', + 'contributing.hero.title': 'Como posso ajudar?', + 'contributing.hero.subtitle': + 'Como organização sem fins lucrativos, a PodCodar depende de pessoas e organizações que acreditam na democratização da educação em tecnologia. Você pode apoiar com recursos, tempo ou parcerias estratégicas.', + 'contributing.donations.title': 'Doações', + 'contributing.donations.subtitle': + 'Doações de pessoas físicas e patrocínios ajudam a financiar sustentabilidade, impacto e crescimento — plataforma de ensino, oficinas e fortalecimento do time.', + 'contributing.donations.body': + 'Se quiser contribuir financeiramente ou combinar outras formas de apoio, fale com a gente — assim direcionamos sua contribuição de acordo com as necessidades atuais da comunidade.', + 'contributing.donations.cta': 'Falar sobre doação', + 'contributing.volunteering.title': 'Voluntariado', + 'contributing.volunteering.subtitle': + 'Tempo e talento: mentoria, facilitação de estudos e eventos, revisão de código, design, comunicação e muito mais.', + 'contributing.volunteering.body.1': + 'Você não precisa ser "nível sênior" para ajudar — quem está um passo à frente já pode apoiar quem vem atrás. Também há espaço para quem quer organizar encontros, revisar materiais ou participar de projetos nas guildas.', + 'contributing.volunteering.body.2': + 'Repositórios abertos e issues no GitHub são outra porta de entrada para contribuir com código e documentação.', + 'contributing.volunteering.cta1': 'Primeiros passos na comunidade', + 'contributing.volunteering.cta2': 'GitHub da organização', + 'contributing.partnerships.title': 'Parcerias', + 'contributing.partnerships.subtitle': + 'Empresas e fundos podem apoiar diversidade e educação em tecnologia — inclusive parcerias para contratação de pessoas qualificadas pela PodCodar.', + 'contributing.partnerships.body': + 'Se sua organização quer patrocinar iniciativas, bancar oficinas ou conectar-se a talentos da comunidade, entre em contato para alinharmos expectativas e formato de parceria.', + 'contributing.partnerships.cta': 'Propor parceria', + + // ── Transparency page ───────────────────────────────────────────────────── 'transparency.title': 'Transparência', 'transparency.subtitle': 'Compromisso com a transparência e prestação de contas', + 'transparency.hero.eyebrow': 'Compromisso com a Transparência', 'transparency.intro': 'A PodCodar acredita que a transparência é fundamental para construir confiança com nossa comunidade, doadores e parceiros. Estamos comprometidos em prestar contas de nossas atividades, finanças e governança.', + 'transparency.hero.stats.documents': 'Documentos', + 'transparency.hero.stats.members': 'Membros', 'transparency.documents.title': 'Documentos', 'transparency.documents.subtitle': 'Acesse nossos documentos institucionais e financeiros.', + 'transparency.documents.empty': 'Nenhum documento disponível no momento.', + 'transparency.documents.back': 'Voltar para todos os documentos', + 'transparency.documents.available': 'Documento disponível para download', 'transparency.board.title': 'Conselho Administrativo', 'transparency.board.subtitle': 'Conheça a diretoria eleita para o biênio 2026-2028.', 'transparency.metrics.title': 'Impacto e Resultados', @@ -33,6 +243,52 @@ export const ui = { 'transparency.category.financeiro': 'Financeiro', 'transparency.category.fiscal': 'Fiscal', 'transparency.cnpj': 'CNPJ', + + // ── Contact page ────────────────────────────────────────────────────────── + 'contact.title': 'Fale Conosco', + 'contact.subtitle': + 'Estamos aqui para ajudar, colaborar e crescer juntos. Entre em contato com a comunidade PodCodar.', + 'contact.eyebrow': 'Entre em contato', + 'contact.intro': + 'A PodCodar é uma comunidade aberta e acolhedora. Não hesite em entrar em contato para tirar dúvidas, propor parcerias ou simplesmente trocar uma ideia.', + 'contact.methods.title': 'Canais de Comunicação', + 'contact.methods.subtitle': 'Escolha o canal que preferir para entrar em contato.', + 'contact.email.label': 'E-mail', + 'contact.email.value': 'contato@podcodar.org', + 'contact.discord.label': 'Discord', + 'contact.discord.value': 'Comunidade Discord', + 'contact.events.label': 'Eventos', + 'contact.events.value': 'Participe de eventos', + 'contact.inquiries.title': 'Sobre o que você pode entrar em contato?', + 'contact.inquiries.subtitle': 'Estamos abertos para diversos tipos de interação.', + 'contact.inquiries.hiring.title': 'Precisando contratar?', + 'contact.inquiries.hiring.desc': + 'A PodCodar é um parceiro valioso para empresas que buscam profissionais qualificados. Nossa comunidade forma talentos prontos para atuar no mercado — entre em contato para encontrar o candidato ideal para a sua vaga.', + 'contact.inquiries.workshops.title': 'Workshops Patrocinados', + 'contact.inquiries.workshops.desc': + 'Criamos workshops de treinamento focados em preparar candidatos para vagas específicas da sua empresa. Uma forma de conectar seu processo seletivo a profissionais já em formação.', + 'contact.inquiries.mentorship.title': 'Mentoria e carreira', + 'contact.inquiries.mentorship.desc': + 'Dúvidas sobre mentoring, carreira em tech ou orientação profissional.', + 'contact.inquiries.partnerships.title': 'Parcerias e colaborações', + 'contact.inquiries.partnerships.desc': + 'Propostas de parcerias, eventos conjuntos ou Apoiadores.', + 'contact.inquiries.donations.title': 'Doações, apoio e voluntariado', + 'contact.inquiries.donations.desc': + 'Apoio financeiro, institucional ou contribuir com tempo e habilidades como voluntário na comunidade.', + 'contact.inquiries.general.title': 'Outros assuntos', + 'contact.inquiries.general.desc': 'Qualquer outra dúvida ou sugestão.', + 'contact.cta.title': 'Tem alguma pergunta?', + 'contact.cta.text': 'Envie um e-mail e nossa equipe entrará em contato o mais rápido possível.', + 'contact.cta.button': 'Enviar E-mail', + 'contact.social.title': 'Nos acompanhe nas redes', + 'contact.social.subtitle': 'Fique por dentro das novidades e atualizações.', + 'response.fast': '1-2 dias', + 'response.fast.desc': 'Tempo médio de resposta por e-mail', + 'response.community': 'Comunidade ativa', + 'response.community.desc': 'Membros disponíveis para ajudar', + 'response.support': 'Suporte dedicado', + 'response.support.desc': 'Equipe focada em ajudar você', }, } as const; diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts index 6def732..3fd3aa8 100644 --- a/src/i18n/utils.ts +++ b/src/i18n/utils.ts @@ -2,15 +2,18 @@ import { defaultLang, ui } from '@/i18n/ui'; export type Lang = keyof typeof ui; -/** - * Get language from URL pathname. - */ -export function getLangFromUrl(_url: URL): Lang { - return defaultLang; +export function getLangFromUrl(url: URL): Lang { + const lang = url.pathname.split('/')[1]; + return lang && ui[lang as Lang] ? (lang as Lang) : defaultLang; } -export function useTranslations(lang: Lang) { - return function t(key: keyof (typeof ui)[typeof defaultLang]) { - return ui[lang][key]; +export function useTranslations(lang?: Lang) { + return function t(key: string): string { + const activeLang = lang ?? defaultLang; + return ( + (ui[activeLang] as Record)[key] ?? + (ui[defaultLang] as Record)[key] ?? + key + ); }; } diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index b986389..b38c5ad 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -4,7 +4,8 @@ import ClientRouter from 'astro/components/ClientRouter.astro'; import BaseHead from '@/components/BaseHead.astro'; import Footer from '@/components/Footer.astro'; import Header from '@/components/Header.astro'; -import { getLangFromUrl } from '@/i18n/utils'; +import type { Lang } from '@/i18n/utils'; +import { getLangFromUrl, useTranslations } from '@/i18n/utils'; interface Props { title: string; @@ -26,6 +27,7 @@ const { const rawLang = langOverride ?? getLangFromUrl(Astro.url); const documentLang = rawLang === 'pt-br' ? 'pt-BR' : rawLang; +const t = useTranslations(rawLang as Lang); --- @@ -39,7 +41,7 @@ const documentLang = rawLang === 'pt-br' ? 'pt-BR' : rawLang; href="#main-content" class="bg-primary text-primary-content focus:ring-primary fixed top-0 left-0 z-[100] -translate-y-full px-4 py-2 focus-visible:translate-y-0 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none" > - Pular para o conteúdo + {t('layout.skipToContent')}
    diff --git a/src/pages/about.astro b/src/pages/about.astro index c87e566..0eed378 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -1,5 +1,9 @@ --- -import Section from '@/components/marketing/Section.astro'; +import { Icon } from 'astro-icon/components'; +import CallToAction from '@/components/ui/CallToAction.astro'; +import ChannelCard from '@/components/ui/ChannelCard.astro'; +import HeroSection from '@/components/ui/HeroSection.astro'; +import SectionHeader from '@/components/ui/SectionHeader.astro'; import { LAYOUT_MAIN_FULL_WIDTH, SITE_TITLE } from '@/consts'; import { aboutCommunity, @@ -10,113 +14,168 @@ import { projects, } from '@/data/marketing'; import ogDefaultImage from '@/data/og-default'; +import { useTranslations } from '@/i18n/utils'; import Layout from '@/layouts/Layout.astro'; -const title = `Sobre — ${SITE_TITLE}`; -const description = - 'Missão, valores e como a PodCodar se organiza: núcleo pedagógico, guildas, iniciativas e canais de comunicação.'; +const t = useTranslations(); +const title = `${t('nav.about')} — ${SITE_TITLE}`; +const description = t('about.hero.subtitle'); --- -
    -
    -

    PodCodar

    -

    Sobre nós

    -

    - Somos uma comunidade e organização sem fins lucrativos focada em transformar vidas por meio da educação - profissionalizante em tecnologia — com inclusão, colaboração e qualidade de ensino. -

    + + + + + + +
    +
    + + +
    + {mission.bodyKeys.map((key) => ( +

    {t(key)}

    + ))} +
    -
    + + + +
    +
    + -
    -
    - {mission.body.map((paragraph) => ( -

    {paragraph}

    - ))} +
      + {coreValues.map((value, index) => { + const cards = [ + { from: 'from-blue-500/5', to: 'to-blue-600/10', border: 'hover:border-blue-500/30', gradient: 'from-blue-500 to-blue-600', icon: 'lucide:users' }, + { from: 'from-emerald-500/5', to: 'to-emerald-600/10', border: 'hover:border-emerald-500/30', gradient: 'from-emerald-500 to-emerald-600', icon: 'lucide:handshake' }, + { from: 'from-violet-500/5', to: 'to-violet-600/10', border: 'hover:border-violet-500/30', gradient: 'from-violet-500 to-violet-600', icon: 'lucide:award' }, + ]; + const c = cards[index]; + return ( +
    • +
      +
      +
      + +
      +

      {t(value.titleKey)}

      +

      {t(value.textKey)}

      +
      +
    • + ); + })} +
    -
    +
    -
    -
      - { - coreValues.map((v) => ( -
    • -

      {v.title}

      -

      {v.text}

      -
    • - )) - } -
    -
    + +
    +
    + -
    -
      - { - aboutCommunity.points.map((point) => ( -
    • - - {point} -
    • - )) - } -
    -
    +
      + {aboutCommunity.pointKeys.map((key, index) => { + const icons = ['lucide:book-marked', 'lucide:git-branch', 'lucide:rocket']; + return ( +
    • +
      + +
      + {t(key)} +
    • + ); + })} +
    +
    +
    -
    -
      - { - communicationChannels.map((ch) => ( -
    • - {ch.channel} - {ch.description} -
    • - )) - } -
    -
    + +
    +
    +
    -
    -
      - { - projects.map((project) => ( -
    • -

      {project.name}

      -

      {project.description}

      +
      + + +
        + {communicationChannels.map((ch) => ( + + ))} +
      +
      +
    + + +
    +
    + + + -
    + ))} + +
    + -
    -

    - - {eventsBlock.externalLabel} - -

    -
    + + + + + {t(eventsBlock.ctaKey)} + + diff --git a/src/pages/contact.astro b/src/pages/contact.astro index edf3765..e8defdd 100644 --- a/src/pages/contact.astro +++ b/src/pages/contact.astro @@ -1,16 +1,185 @@ --- -import { SITE_DESCRIPTION, SITE_TITLE } from '@/consts'; -import ogDefaultImage from '@/data/og-default'; +import { Icon } from 'astro-icon/components'; +import CallToAction from '@/components/ui/CallToAction.astro'; +import HeroSection from '@/components/ui/HeroSection.astro'; +import InquiryCard from '@/components/ui/InquiryCard.astro'; +import SectionHeader from '@/components/ui/SectionHeader.astro'; +import { SITE_TITLE } from '@/consts'; +import { socialLinks } from '@/data/social-links'; +import { CONTACT_EMAIL } from '@/data/transparency'; +import { useTranslations } from '@/i18n/utils'; import Layout from '@/layouts/Layout.astro'; + +const t = useTranslations(); + +const inquiries = [ + { + icon: 'lucide:briefcase', + bg: 'bg-primary/5', + border: 'border-primary/20', + gradient: 'from-primary to-primary/70', + key: 'hiring' as const, + }, + { + icon: 'lucide:presentation', + bg: 'bg-emerald-500/5', + border: 'border-emerald-500/20', + gradient: 'from-emerald-500 to-emerald-600', + key: 'workshops' as const, + }, + { + icon: 'lucide:graduation-cap', + bg: 'bg-blue-500/5', + border: 'border-blue-500/20', + gradient: 'from-blue-500 to-blue-600', + key: 'mentorship' as const, + }, + { + icon: 'lucide:handshake', + bg: 'bg-violet-500/5', + border: 'border-violet-500/20', + gradient: 'from-violet-500 to-violet-600', + key: 'partnerships' as const, + }, + { + icon: 'lucide:heart', + bg: 'bg-emerald-500/5', + border: 'border-emerald-500/20', + gradient: 'from-emerald-500 to-emerald-600', + key: 'donations' as const, + }, + { + icon: 'lucide:help-circle', + bg: 'bg-blue-500/5', + border: 'border-blue-500/20', + gradient: 'from-blue-500 to-blue-600', + key: 'general' as const, + }, +]; --- - -
    -

    Entre em contato

    -

    Sinta-se livre para entrar em contato!

    -

    - E-mail:{' '} - contato@podcodar.org -

    + + + + + + + +
    +
    + + +
    +
    + + +
      + {inquiries.map((card) => ( + + ))} +
    +
    +
    + + +
    +
    + + + +
    +
    + + + + + + {t('contact.cta.button')} + +
    diff --git a/src/pages/contributing.astro b/src/pages/contributing.astro index d3bab2a..9dd1459 100644 --- a/src/pages/contributing.astro +++ b/src/pages/contributing.astro @@ -1,77 +1,105 @@ --- -import Section from '@/components/marketing/Section.astro'; +import { Icon } from 'astro-icon/components'; +import HeroSection from '@/components/ui/HeroSection.astro'; +import SectionHeader from '@/components/ui/SectionHeader.astro'; import { LAYOUT_MAIN_FULL_WIDTH, SITE_TITLE } from '@/consts'; import ogDefaultImage from '@/data/og-default'; +import { useTranslations } from '@/i18n/utils'; import Layout from '@/layouts/Layout.astro'; -const title = `Como posso ajudar? — ${SITE_TITLE}`; -const description = 'Doações, voluntariado e parcerias: formas de apoiar a missão da PodCodar.'; +const t = useTranslations(); +const title = `${t('nav.contributing')} — ${SITE_TITLE}`; +const description = t('contributing.hero.subtitle'); --- -
    -
    -

    Como posso ajudar?

    -

    - Como organização sem fins lucrativos, a PodCodar depende de pessoas e organizações que acreditam na - democratização da educação em tecnologia. Você pode apoiar com recursos, tempo ou parcerias estratégicas. -

    + + + + + + +
    +
    + + +
    +

    + {t('contributing.donations.body')} +

    +

    + + {t('contributing.donations.cta')} + +

    +
    -
    +
    -
    -

    - Se quiser contribuir financeiramente ou combinar outras formas de apoio, fale com a gente — assim - direcionamos sua contribuição de acordo com as necessidades atuais da comunidade. -

    -

    - - Falar sobre doação - -

    -
    + +
    +
    + -
    -

    - Você não precisa ser “nível sênior” para ajudar — quem está um passo à frente já pode apoiar quem vem atrás. - Também há espaço para quem quer organizar encontros, revisar materiais ou participar de projetos nas - guildas. -

    -

    - Repositórios abertos e issues no GitHub são outra porta de entrada para contribuir com código e documentação. -

    -

    - - Primeiros passos na comunidade - - - GitHub da organização - -

    -
    +
    +

    + {t('contributing.volunteering.body.1')} +

    +

    + {t('contributing.volunteering.body.2')} +

    +

    + + {t('contributing.volunteering.cta1')} + + + {t('contributing.volunteering.cta2')} + +

    +
    +
    +
    -
    -

    - Se sua organização quer patrocinar iniciativas, bancar oficinas ou conectar-se a talentos da comunidade, - entre em contato para alinharmos expectativas e formato de parceria. -

    -

    - Propor parceria -

    -
    + +
    +
    + + +
    +

    + {t('contributing.partnerships.body')} +

    +

    + {t('contributing.partnerships.cta')} +

    +
    +
    +
    diff --git a/src/pages/index.astro b/src/pages/index.astro index 56fb4ff..99afa6b 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -5,66 +5,70 @@ import HowToHelp from '@/components/marketing/HowToHelp.astro'; import Section from '@/components/marketing/Section.astro'; import TestimonialGrid from '@/components/marketing/TestimonialGrid.astro'; import { LAYOUT_MAIN_FULL_WIDTH, SITE_DESCRIPTION, SITE_TITLE } from '@/consts'; -import { - aboutCommunity, - activities, - hero, - howToHelp, - mission, - testimonials, -} from '@/data/marketing'; +import { aboutCommunity, activities, howToHelp, mission, testimonials } from '@/data/marketing'; import ogDefaultImage from '@/data/og-default'; +import { useTranslations } from '@/i18n/utils'; import Layout from '@/layouts/Layout.astro'; + +const t = useTranslations(); + +const heroContent = { + eyebrow: t('home.hero.eyebrow'), + headline: t('home.hero.headline'), + subhead: t('home.hero.subhead'), + primaryCta: { label: t('nav.join_us'), href: '/join-us' }, + secondaryCta: { label: t('nav.contributing'), href: '/contributing' }, +} as const; --- - -
    + +
    - {mission.body.map((paragraph) =>

    {paragraph}

    )} + {mission.bodyKeys.map((key) =>

    {t(key)}

    )}
    -
    +
      { - aboutCommunity.points.map((point) => ( + aboutCommunity.pointKeys.map((key) => (
    • - {point} + {t(key)}
    • )) }

    - Conheça mais no Sobre + {t('home.community.cta')}

    diff --git a/src/pages/join-us.astro b/src/pages/join-us.astro index 7c8806d..e339629 100644 --- a/src/pages/join-us.astro +++ b/src/pages/join-us.astro @@ -1,87 +1,174 @@ --- -import Section from '@/components/marketing/Section.astro'; +import { Icon } from 'astro-icon/components'; +import ChannelCard from '@/components/ui/ChannelCard.astro'; +import HeroSection from '@/components/ui/HeroSection.astro'; +import SectionHeader from '@/components/ui/SectionHeader.astro'; +import StepItem from '@/components/ui/StepItem.astro'; import { LAYOUT_MAIN_FULL_WIDTH, SITE_TITLE } from '@/consts'; import { communicationChannels } from '@/data/marketing'; import ogDefaultImage from '@/data/og-default'; +import { useTranslations } from '@/i18n/utils'; import Layout from '@/layouts/Layout.astro'; -const title = `Faça parte — ${SITE_TITLE}`; -const description = - 'Entre na PodCodar: WhatsApp, Discord, Google Meet — e um plano de ação para começar a colaborar.'; +const t = useTranslations(); +const title = `${t('nav.join_us')} — ${SITE_TITLE}`; +const description = t('joinUs.hero.subtitle'); +const channelStats = communicationChannels.length; --- -
    -
    -

    Faça parte

    -

    - Seja para aprender, ensinar ou colaborar com a comunidade, o caminho começa nos canais e nas guildas — - espaços onde organizamos estudos, mentorias e projetos. -

    + + + + +
    +
    + + +
      + {communicationChannels.map((ch) => ( + + ))} +
    +
    +
    + + +
    +
    + + +
      + + + + + +
    -
    +
    -
    -
      - { - communicationChannels.map((ch) => ( -
    • - {ch.channel} - {ch.description} -
    • - )) - } -
    -
    + +
    +
    +
    +
    +
    -
    -
      -
    1. - Imersão. Entre nos grupos no WhatsApp e nos canais do Discord; - leia as regras e se apresente. -
    2. -
    3. - Escolha. Participe das guildas e iniciativas que mais fazem - sentido para você (projetos, eventos, design, etc.). -
    4. -
    5. - Engajamento. Participe de grupos de estudo e de mentorias em - projetos — como mentorado ou mentor. -
    6. -
    7. - Colaboração. Entre em discussões, junte-se a projetos e - compartilhe o que está aprendendo. -
    8. -
    9. - Crescimento. Peça feedback, peça sugestões de estudo e proponha - ideias novas para as guildas. -
    10. -
    -
    +
    +
    + +
    +
    +

    {t('joinUs.github.title')}

    +

    {t('joinUs.github.desc')}

    + + {t('joinUs.github.linkText')} + + +
    + + {t('joinUs.github.cta')} + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    -
    -

    - - Acessar github.com/podcodar - -

    -
    + + +
    + + + {t('joinUs.contact.tagFast')} + + + + + {t('joinUs.contact.tagCommunity')} + + + + + {t('joinUs.contact.tagSafe')} + +
    +
    +
    +
    diff --git a/src/pages/transparency/[slug].astro b/src/pages/transparency/[slug].astro index 5d5ce5a..7759a14 100644 --- a/src/pages/transparency/[slug].astro +++ b/src/pages/transparency/[slug].astro @@ -15,7 +15,7 @@ export async function getStaticPaths() { const { doc } = Astro.props; -const t = useTranslations('pt-br'); +const t = useTranslations(); const { Content } = await render(doc); @@ -115,7 +115,7 @@ const colors = categoryColors[doc.data.category] || {
    - Voltar para todos os documentos + {t('transparency.documents.back')}
    diff --git a/src/pages/transparency/index.astro b/src/pages/transparency/index.astro index b6727ad..219d8a9 100644 --- a/src/pages/transparency/index.astro +++ b/src/pages/transparency/index.astro @@ -3,11 +3,15 @@ import { getCollection } from 'astro:content'; import { Icon } from 'astro-icon/components'; import FormattedDate from '@/components/FormattedDate.astro'; import DocumentCard from '@/components/transparency/DocumentCard.astro'; +import CallToAction from '@/components/ui/CallToAction.astro'; +import HeroSection from '@/components/ui/HeroSection.astro'; +import IconCard from '@/components/ui/IconCard.astro'; +import SectionHeader from '@/components/ui/SectionHeader.astro'; import { BOARD_MEMBERS, CNPJ, CONTACT_EMAIL, METRICS } from '@/data/transparency'; import { useTranslations } from '@/i18n/utils'; import Layout from '@/layouts/Layout.astro'; -const t = useTranslations('pt-br'); +const t = useTranslations(); const documents = (await getCollection('transparency')).sort( (a, b) => b.data.date.valueOf() - a.data.date.valueOf() @@ -18,6 +22,11 @@ const financialDocs = documents.filter((d) => d.data.category === 'financeiro'); const fiscalDocs = documents.filter((d) => d.data.category === 'fiscal'); const lastUpdated = documents[0]?.data.date; +const formattedLastUpdated = lastUpdated?.toLocaleDateString('pt-br', { + day: '2-digit', + month: 'short', + year: 'numeric', +}); --- -
    - -
    -
    -
    -
    -
    - -
    - -
    - - Compromisso com a Transparência -
    - -

    - {t('transparency.title')} -

    - -

    - {t('transparency.intro')} -

    - - -
    -
    -
    - -
    -
    - {documents.length} - Documentos -
    -
    -
    -
    - -
    -
    - {BOARD_MEMBERS.length} - Membros -
    -
    - {lastUpdated && ( -
    -
    - -
    -
    - Atualizado - -
    -
    - )} -
    -
    -
    + + +
    -
    -
    - -
    -
    -

    {t('transparency.documents.title')}

    -

    {t('transparency.documents.subtitle')}

    -
    -
    - + + {institutionalDocs.length > 0 && (

    @@ -156,20 +118,18 @@ const lastUpdated = documents[0]?.data.date;
    -
    -
    - -
    -
    -

    {t('transparency.board.title')}

    -

    {t('transparency.board.subtitle')}

    -
    -
    - + +
      {BOARD_MEMBERS.map((member, index) => ( -
    • -
      +
    • +
      {member.name.charAt(0)} @@ -194,69 +154,45 @@ const lastUpdated = documents[0]?.data.date;
      -
      -
      - -
      -
      -

      {t('transparency.metrics.title')}

      -

      {t('transparency.metrics.subtitle')}

      -
      -
      - -
        + + +
          {METRICS.map((metric) => ( -
        • -
          -
          -
          - {metric.value} -
          -
          {metric.label}
          -
          -
        • + ))}
      -
      - -
      -
      - -
      -
      -
      - -
      -

      {t('transparency.contact.title')}

      -

      {t('transparency.contact.text')}

      - - - - {CONTACT_EMAIL} - - -
      -
      - - - {t('transparency.cnpj')}: {CNPJ} - - {lastUpdated && ( - - - {t('transparency.lastUpdated')}: - - )} -
      -
      + + + + {CONTACT_EMAIL} + + +
      +
      + + + {t('transparency.cnpj')}: {CNPJ} + + {lastUpdated && ( + + + {t('transparency.lastUpdated')}: + + )}
      -
      +