Add Tailwind CSS configuration, integrate lucide-react icons, and set up initial components for the Kampüs Cadıları website. Update package.json and pnpm-lock.yaml to include new dependencies. Remove outdated assets and implement new logo images.

This commit is contained in:
Nicholai 2025-12-28 15:29:13 -07:00
parent de410bbe04
commit a4b83dbdd4
105 changed files with 5569 additions and 1830 deletions

28
.cursor/debug.log Normal file
View File

@ -0,0 +1,28 @@
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"white","size":"medium","className":"w-16 h-16 md:w-24 md:h-24"},"timestamp":1766959674202,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_white_medium","fallbackPath":"/media/logo_white.png","processedPath":"/media/logos/logo_white_medium@1x.png"},"timestamp":1766959674202,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"black","size":"small","className":"w-12 h-12"},"timestamp":1766959682790,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"white","size":"medium","className":"w-16 h-16 md:w-24 md:h-24"},"timestamp":1766959682805,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_black_small","fallbackPath":"/media/logo_black.png","processedPath":"/media/logos/logo_black_small@1x.png"},"timestamp":1766959682790,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"white","size":"medium","className":"w-16 h-16 md:w-24 md:h-24"},"timestamp":1766959682790,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_white_medium","fallbackPath":"/media/logo_white.png","processedPath":"/media/logos/logo_white_medium@1x.png"},"timestamp":1766959682790,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_white_medium","fallbackPath":"/media/logo_white.png","processedPath":"/media/logos/logo_white_medium@1x.png"},"timestamp":1766959682805,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"black","size":"small","className":"w-12 h-12"},"timestamp":1766959682796,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_black_small","fallbackPath":"/media/logo_black.png","processedPath":"/media/logos/logo_black_small@1x.png"},"timestamp":1766959682796,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"black","size":"small","className":"w-12 h-12"},"timestamp":1766959683932,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_black_small","fallbackPath":"/media/logo_black.png","processedPath":"/media/logos/logo_black_small@1x.png"},"timestamp":1766959683933,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"white","size":"medium","className":"w-16 h-16 md:w-24 md:h-24"},"timestamp":1766959683934,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_white_medium","fallbackPath":"/media/logo_white.png","processedPath":"/media/logos/logo_white_medium@1x.png"},"timestamp":1766959683934,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"black","size":"small","className":"w-12 h-12"},"timestamp":1766959684064,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_black_small","fallbackPath":"/media/logo_black.png","processedPath":"/media/logos/logo_black_small@1x.png"},"timestamp":1766959684064,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"white","size":"medium","className":"w-16 h-16 md:w-24 md:h-24"},"timestamp":1766959684073,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_white_medium","fallbackPath":"/media/logo_white.png","processedPath":"/media/logos/logo_white_medium@1x.png"},"timestamp":1766959684073,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"black","size":"small","className":"w-12 h-12"},"timestamp":1766959685074,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_black_small","fallbackPath":"/media/logo_black.png","processedPath":"/media/logos/logo_black_small@1x.png"},"timestamp":1766959685074,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"white","size":"medium","className":"w-16 h-16 md:w-24 md:h-24"},"timestamp":1766959685074,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_white_medium","fallbackPath":"/media/logo_white.png","processedPath":"/media/logos/logo_white_medium@1x.png"},"timestamp":1766959685075,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:35","message":"Logo image load error","data":{"variant":"black","size":"small","attemptedPath":"/media/logo_black.png"},"timestamp":1766959685082,"sessionId":"debug-session","runId":"run1","hypothesisId":"H"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"black","size":"small","className":"w-12 h-12"},"timestamp":1766959685083,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_black_small","fallbackPath":"/media/logo_black.png","processedPath":"/media/logos/logo_black_small@1x.png"},"timestamp":1766959685084,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:15","message":"Logo component render","data":{"variant":"white","size":"medium","className":"w-16 h-16 md:w-24 md:h-24"},"timestamp":1766959685084,"sessionId":"debug-session","runId":"run1","hypothesisId":"E"}
{"location":"Logo.tsx:22","message":"Logo path construction","data":{"basePath":"/media/logos/logo_white_medium","fallbackPath":"/media/logo_white.png","processedPath":"/media/logos/logo_white_medium@1x.png"},"timestamp":1766959685084,"sessionId":"debug-session","runId":"run1","hypothesisId":"F"}
{"location":"Logo.tsx:35","message":"Logo image load error","data":{"variant":"white","size":"medium","attemptedPath":"/media/logo_white.png"},"timestamp":1766959685090,"sessionId":"debug-session","runId":"run1","hypothesisId":"H"}

View File

@ -0,0 +1,326 @@
---
name: Convert Vite React App to Astro
overview: ""
todos: []
---
# Convert Vite React App to Astro
Convert the Vite React SPA in `dev/generated/` to an Astro application following Astro conventions, preserving functionality and design while leveraging Astro's file-based routing and island architecture.
## Architecture Overview
The conversion will transform the single-page React app into an Astro multi-page application with:
- File-based routing: `/` (home) and `/publications` (publications page)
- Astro layouts for shared structure (header, footer)
- React islands for interactive components (language switcher, filters, scroll animations)
- Client-side language switching preserved
- Design system integrated via TailwindCSS configuration
**Key Principle: Reuse Existing Code**
- Copy React components from `dev/generated/components/` with minimal modifications
- Extract header/footer logic from `App.tsx` rather than rewriting
- Preserve all scroll animations, state management, and interactivity as-is
- Only adapt what's necessary for Astro integration (imports, props passing)
## Implementation Steps
### 1. Setup and Configuration
**Files to create/modify:**
- `tailwind.config.mjs` - Configure TailwindCSS with brand colors, fonts, and animations from `design.json`
- `src/styles/global.css` - Update with brand colors, fonts, and custom animations
- `astro.config.mjs` - Ensure React integration is configured (already present)
**Key changes:**
- Extract Tailwind config from `index.html` to proper Tailwind config file
- Add brand color palette, font families (Inter, Oswald, Permanent Marker), and custom animations (blob, float, marquee)
- Configure font loading in BaseLayout
### 2. Content Structure
**Files to copy (reuse as-is):**
- `src/content/content.ts` - **Copy** `dev/generated/content.ts` here (bilingual content structure)
- `src/types.ts` - **Copy** `dev/generated/types.ts` here (TypeScript types)
**Key changes:**
- Keep content structure as TypeScript module (not content collections) since it's structured data, not markdown
- Preserve bilingual `CONTENT` object structure exactly as it exists
- No modifications needed - these are pure data/type files
### 2.5. Logo Image Processing
**Files to create:**
- `src/utils/process-logos.js` - Script to process logo images with ffmpeg
**Process logo images:**
- Source images: `public/media/logo_black.png`, `public/media/logo_col.png`, `public/media/logo_white.png` (786x1334px)
- Preserve aspect ratio: ~0.589 (786/1334)
- Generate multiple sizes for different use cases:
- Small (header): 28x48px (1x), 56x96px (2x)
- Medium (footer): 57x96px (1x), 114x192px (2x)
- Large (hero/featured): 118x200px (1x), 236x400px (2x)
- Formats: WebP (modern), AVIF (best compression), PNG (fallback)
- Output location: `public/media/logos/` with naming: `logo_{variant}_{size}@{density}.{format}`
**ffmpeg commands:**
- Resize preserving aspect ratio
- Convert to WebP and AVIF formats
- Maintain quality while optimizing file size
### 3. Component Conversion
**Strategy: Copy and Adapt (Minimal Changes)Pure Astro components (new, replacing SVG):**
- `src/components/Logo.astro` - **NEW** Astro component using processed logo images
- Accept props: `variant` ('black' | 'col' | 'white'), `size` ('small' | 'medium' | 'large'), `className`
- Use Astro's `<Image>` component for optimization
- Provide srcset for responsive images (1x, 2x)
- Fallback to PNG if WebP/AVIF not supported
- **Replaces** `dev/generated/components/Logo.tsx` (SVG version)
**React Components - Copy from `dev/generated/components/` with minimal changes:**
- `src/components/Reveal.tsx` - **COPY** `dev/generated/components/Reveal.tsx` as-is
- No changes needed - pure React component with Intersection Observer
- Use as React island (client:load)
- `src/components/Button.tsx` - **COPY** `dev/generated/components/Button.tsx` as-is
- No changes needed - reusable button component
- Use as React island (client:load)
- `src/components/ArticleCard.tsx` - **COPY** `dev/generated/components/ArticleCard.tsx` as-is
- No changes needed - article card component
- Use as React island (client:load) if interactive
- `src/components/Home.tsx` - **COPY** `dev/generated/components/Home.tsx` with minimal adaptions
- Keep all scroll animations, parallax effects, and state management exactly as-is
- Only change: Update Logo import to use new Logo.astro (or pass as prop)
- Update import paths if needed (content.ts, types.ts)
- Use as React island (client:load)
- `src/components/Publications.tsx` - **COPY** `dev/generated/components/Publications.tsx` with minimal adaptions
- Keep all filter logic, state management exactly as-is
- Only change: Update import paths if needed (types.ts, Reveal.tsx)
- Use as React island (client:load)
**Header/Footer - Extract from App.tsx:**
- `src/components/Header.tsx` - **EXTRACT** header JSX from `App.tsx` (lines 54-130)
- Extract the `<nav>` section from App.tsx
- Keep all state management (isNavOpen, lang, currentView) - adapt for Astro context
- Keep all styling and interactions exactly as-is
- Update Logo usage to new Logo.astro component
- Use as React island (client:load)
- `src/components/Footer.tsx` - **EXTRACT** footer JSX from `App.tsx` (lines 22-48)
- Extract the `<footer>` section from App.tsx
- Keep all styling and structure exactly as-is
- Update Logo usage to new Logo.astro component
- Update navigation to use Astro links instead of onClick handlers
- Use as React island (client:load)
**Key principle:** Preserve all existing logic, animations, and state management. Only adapt imports and component boundaries for Astro.
### 4. Layout Structure
**Files to create/modify:**
- `src/layouts/BaseLayout.astro` - Update existing layout to include:
- Header component (React island - extracted from App.tsx)
- Footer component (React island - extracted from App.tsx)
- Language context provider (if needed for shared state)
- Global styles and fonts (extract from index.html)
- Noise overlay (extract from index.html)
**Key changes:**
- **Reuse** header/footer JSX from `App.tsx` (extract, don't rewrite)
- Add proper HTML structure with head management
- Include TailwindCSS and font loading (extract config from `dev/generated/index.html`)
- Preserve all styling and structure from original App.tsx wrapper
### 5. Page Routes
**Files to create:**
- `src/pages/index.astro` - Home page:
- Import and render `Home` React component as island
- Pass content via props
- Handle language state (client-side)
- `src/pages/publications.astro` - Publications page:
- Import and render `Publications` React component as island
- Pass content via props
- Handle language state (client-side)
**Key changes:**
- Convert view-based routing to file-based routing
- Use Astro's `<ClientOnly>` or direct React island imports
- Pass content and language state to React components
### 6. Language Management
**Strategy: Reuse existing pattern from App.tsxFiles to create:**
- `src/utils/language.ts` - Language state management utility (if needed)
- Extract language state logic from App.tsx
- LocalStorage persistence (if not already in App.tsx)
- Default to 'TR' (as in App.tsx)
**Implementation:**
- **Reuse** the language state pattern from `App.tsx` (line 13: `const [lang, setLang] = useState<'TR' | 'EN'>('TR')`)
- Create React context for language state (shared across islands) OR pass via props
- Language switcher in Header (extracted from App.tsx) updates state
- Consider: Keep simple useState pattern if props passing works, or create context if needed for multiple islands
### 7. Styling and Assets
**Files to modify:**
- `src/styles/global.css` - **Extract and adapt** from `dev/generated/index.html`:
- Brand color CSS variables (extract from Tailwind config in index.html)
- Font imports (Google Fonts - copy from index.html lines 9-13)
- Custom animations (blob, float, marquee - copy from index.html lines 38-58)
- Noise overlay styles (copy from index.html lines 80-91)
- Base typography styles (copy from index.html lines 64-72)
- `tailwind.config.mjs` - **Extract** Tailwind config from `dev/generated/index.html` (lines 15-61)
- Copy entire theme.extend configuration
- Preserve all brand colors, fonts, animations exactly as defined
**Assets:**
- **Reuse** font loading from `dev/generated/index.html` (Google Fonts link)
- **Preserve** all design system tokens from `design.json` (reference, don't rewrite)
- Logo images processed and optimized in `public/media/logos/`
- Use appropriate variant based on background (black on light, white on dark, col for featured)
- Use appropriate size based on context (small for header, medium for footer, large for hero)
### 8. Scroll Animations and Interactivity
**Preserve EXACTLY as-is in copied React components:**
- Parallax scrolling in Home component - **Keep all code from Home.tsx** (lines 31-84)
- Horizontal scroll section - **Keep all code from Home.tsx** (lines 164-198)
- Intersection Observer animations (Reveal component) - **Keep all code from Reveal.tsx**
- All scroll-based transforms - **No changes needed**
- All useEffect hooks, refs, and state management - **Copy exactly**
**Key considerations:**
- **Don't modify** the animation logic - it already respects `prefers-reduced-motion` (Home.tsx line 32)
- **Don't modify** the `requestAnimationFrame` implementation (Home.tsx lines 35-71)
- Test scroll behavior in Astro context - should work identically since components are React islands
### 9. TypeScript Configuration
**Files to check:**
- `tsconfig.json` - Ensure path aliases if needed
- Type definitions for Astro props
### 10. Testing and Validation
**Checklist:**
- [ ] Home page renders correctly
- [ ] Publications page with filters works
- [ ] Language switching persists across pages
- [ ] Scroll animations function properly
- [ ] Responsive design maintained
- [ ] All brand colors and fonts applied correctly
- [ ] Footer fixed positioning works
- [ ] Navigation between pages works
## File Structure After Conversion
```javascript
src/
├── components/
│ ├── Logo.astro (uses processed logo images)
│ ├── Header.tsx (client:load)
│ ├── Footer.tsx (client:load)
│ ├── Home.tsx (client:load)
│ ├── Publications.tsx (client:load)
│ ├── Reveal.tsx (client:load)
│ ├── Button.tsx (client:load)
│ └── ArticleCard.tsx (client:load)
├── layouts/
│ └── BaseLayout.astro (updated)
├── pages/
│ ├── index.astro (home)
│ └── publications.astro
├── content/
│ └── content.ts (moved from generated/)
├── styles/
│ └── global.css (updated with design system)
├── utils/
│ ├── language.ts (new)
│ └── process-logos.js (new - logo processing script)
├── types.ts (moved from generated/)
└── consts.ts (update with site info)
public/
└── media/
├── logos/ (processed logo images)
│ ├── logo_black_small@1x.webp
│ ├── logo_black_small@2x.webp
│ ├── logo_black_medium@1x.webp
│ ├── logo_black_medium@2x.webp
│ ├── logo_black_large@1x.webp
│ ├── logo_black_large@2x.webp
│ ├── logo_col_*.{webp,avif,png}
│ ├── logo_white_*.{webp,avif,png}
│ └── (PNG fallbacks and AVIF variants)
├── logo_black.png (original)
├── logo_col.png (original)
└── logo_white.png (original)
tailwind.config.mjs (new)
```
## Migration Notes
**Core Principle: Maximum Code Reuse**
- **Copy, don't rewrite**: All React components should be copied from `dev/generated/components/` with minimal changes
- **Extract, don't recreate**: Header/footer should be extracted from `App.tsx`, not rewritten
- **Preserve all logic**: Keep all state management, animations, effects, and interactions exactly as they exist
- **Minimal adaptations**: Only change what's necessary for Astro (imports, component boundaries, Logo usage)
**Specific Reuse Strategy:**
- `Home.tsx`: Copy entire file, only update Logo import and content/types import paths
- `Publications.tsx`: Copy entire file, only update import paths
- `Reveal.tsx`, `Button.tsx`, `ArticleCard.tsx`: Copy as-is, no changes needed
- Header/Footer: Extract JSX from `App.tsx` (lines 22-48 for footer, 54-130 for header)
- Content/Types: Copy `content.ts` and `types.ts` exactly as-is
- Styling: Extract Tailwind config and styles from `index.html`, don't recreate
**Design System:**
- Preserve all design system values from `design.json` (reference, don't rewrite)
- Maintain exact visual appearance and interactions
- Extract Tailwind configuration from `index.html` to `tailwind.config.mjs`
**Astro Integration:**
- Use Astro islands strategically - only for components needing client-side interactivity
- Keep content structure as TypeScript (not content collections) for flexibility
- Language state should persist across page navigations (localStorage) - reuse pattern from App.tsx
- All scroll animations and parallax effects must be preserved - they're already in the copied components
**Logo Images:**
- Replace SVG Logo component with image-based Logo.astro component (only new component)
- Process original 786x1334px images to multiple sizes preserving 0.589 aspect ratio
- Generate WebP, AVIF, and PNG formats for optimal browser support
- Use appropriate variant (black/col/white) based on background context
- Use appropriate size (small/medium/large) based on usage context

View File

@ -1,559 +0,0 @@
# Kampüs Cadıları Website Design Brief
## Turkish Feminist Organization - Site Rebuild 2025
---
## Project Overview
### Organization
**Kampüs Cadıları** (Campus Witches) is a Turkish feminist organization focused on campus activism, gender equality, and creating safe spaces for women and LGBTQ+ individuals in academic settings. The organization publishes feminist content, translations, and daily feminist news while building community through their platform.
### Project Goals
- Rebuild the organization's web presence with a modern, accessible, and culturally resonant design
- Create a platform that empowers, informs, and mobilizes the feminist community
- Blend bold activism aesthetics with professional credibility and readability
- Support both Turkish and international audiences with multilingual content
- Facilitate content sharing, event coordination, and community building through compelling visual storytelling
### Target Audience
- **Primary**: Turkish university students (18-25) interested in feminism and social justice
- **Secondary**: Activists, academics, and allies engaged in feminist discourse
- **Tertiary**: International audiences seeking Turkish feminist perspectives and translated content
---
## Brand Identity
### Logo & Symbol
The Kampüs Cadıları logo features a stylized witch figure positioned on a female symbol (♀) with a star accent, representing empowerment, magic, and feminist identity. The witch imagery reclaims historical persecution narratives while celebrating women's power.
### Color Palette
#### Primary Brand Colors
**Purple (Pantone 526 C)**
- CMYK: C67 / M98 / Y6 / K0
- RGB: ~84, 25, 139
- Hex: #54198B (approximate)
- Usage: Primary brand color, navigation, CTAs, headers
**Red (Pantone 485 C)**
- CMYK: C5 / M100 / Y100 / K0
- RGB: ~237, 28, 36
- Hex: #ED1C24 (approximate)
- Usage: Accent color, alerts, important CTAs, emphasis elements
#### Extended Palette
Based on existing style system, augmented with brand colors:
**Neutrals**
- Background: `#0a0a0a` (near-black for hero sections)
- Surface: `#ffffff` (white for content areas)
- Text Primary: `#171717` (near-black)
- Text Secondary: `#737373` (gray)
- Border: `#e5e5e5` (light gray)
**Purple Spectrum** (derived from Pantone 526 C)
- Primary: `#54198B`
- Light: `#8B4FB8`
- Lighter: `#B88BD4`
- Lightest: `#E8D4F0`
**Red Spectrum** (derived from Pantone 485 C)
- Primary: `#ED1C24`
- Light: `#F25C61`
- Lighter: `#F89195`
**Functional Colors**
- Success: `#22c55e`
- Warning: `#f59e0b`
- Error: `#ef4444`
- Info: `#3b82f6`
### Typography
#### Font Stack
**Primary**: Inter (body text, UI elements)
- Clean, modern, highly readable
- Excellent multilingual support for Turkish characters (ç, ğ, ı, ö, ş, ü)
- Variable font for performance
**Display**: Oswald (headings, impact statements)
- Bold, condensed aesthetic for activist messaging
- Strong presence for hero sections and key statements
- Use sparingly for maximum impact
**Backup Stack**: system-ui, -apple-system, sans-serif
#### Type Scale
- Hero/H1: 56px / 3.5rem (Oswald, uppercase for impact)
- H2: 40px / 2.5rem (Oswald or Inter Bold)
- H3: 32px / 2rem (Inter Bold)
- H4: 24px / 1.5rem (Inter SemiBold)
- Body Large: 18px / 1.125rem (Inter Regular)
- Body: 16px / 1rem (Inter Regular)
- Body Small: 14px / 0.875rem (Inter Regular)
- Caption: 12px / 0.75rem (Inter Regular)
#### Line Heights
- Headings: 1.2
- Body text: 1.6 (for readability in article content)
- UI elements: 1.5
---
## Current Site Structure & Content DNA
### Navigation Architecture
Based on current site analysis:
**Primary Navigation**
1. **ANASAYFA** (Homepage) - Hero carousel + featured content
2. **CADI YAYINLARI** (Witch Publications) - Original content by the organization
3. **ÇEVİRİ** (Translations) - Translated feminist texts and articles
4. **FEMİNİST GÜNDEM** (Feminist Agenda) - Daily news and commentary
5. **TARIHÇEMIZ** (Our History) - About the organization and its journey
**Utility Navigation**
- Search functionality (icon in header)
- Social media links (Instagram, Facebook, TikTok, YouTube)
- Language toggle (TR/EN consideration for rebuild)
### Content Types
#### Hero Section
- Large, impactful visual storytelling
- Rotating slider with multiple featured stories
- Dark background with high contrast text
- "KISA Bİ HİKAYE" (Short Story) as content marker
- Writer attribution with photo
- Strong CTA ("Read more")
#### Article Cards
Current format to maintain:
- Article title
- Category tag (color-coded by content type)
- Author name ("By Kampüs Cadıları")
- Publication date
- Excerpt/preview text
- "Read More" CTA button (blue in current, to be adapted to purple)
#### Content Categories
- **Kadınlardan Gelenler** (From Women) - Personal stories and testimonials
- **Feminist Gündem** (Feminist Agenda) - News and commentary
- **Çeviri** (Translations) - Translated works
- Custom categories as needed
---
## Design System
### Layout System
#### Grid Structure
12-column grid system with Tailwind CSS spacing:
- Desktop: 1440px max-width container
- Tablet: 768px breakpoint
- Mobile: 375px base, full-width content
#### Spacing Scale
```
xs: 0.25rem / 4px
sm: 0.5rem / 8px
md: 1rem / 16px
lg: 1.5rem / 24px
xl: 2rem / 32px
2xl: 3rem / 48px
3xl: 4rem / 64px
4xl: 6rem / 96px
```
#### Section Spacing
- Section padding: `py-16 md:py-24` (64px mobile, 96px desktop)
- Component spacing: `space-y-8 md:space-y-12`
- Container padding: `px-4 md:px-6 lg:px-8`
### Components
#### Cards
```css
.article-card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.article-card:hover {
box-shadow: 0 10px 40px rgba(84, 25, 139, 0.15);
transform: translateY(-4px);
}
```
#### Buttons
**Primary CTA** (Purple brand color)
```css
.btn-primary {
background: #54198B;
color: white;
padding: 12px 32px;
border-radius: 999px; /* pill shape */
font-weight: 600;
transition: all 0.3s ease;
}
.btn-primary:hover {
background: #3D1265;
transform: scale(1.05);
}
```
**Secondary CTA** (Outlined)
```css
.btn-secondary {
background: transparent;
color: #54198B;
border: 2px solid #54198B;
padding: 12px 32px;
border-radius: 999px;
font-weight: 600;
}
.btn-secondary:hover {
background: #54198B;
color: white;
}
```
**Load More** (Purple pill button)
```css
.btn-load-more {
background: #54198B;
color: white;
padding: 14px 40px;
border-radius: 999px;
font-weight: 600;
text-align: center;
display: inline-block;
}
```
#### Navigation
**Header**
```css
.site-header {
background: white;
position: sticky;
top: 0;
z-index: 50;
border-bottom: 1px solid #e5e5e5;
backdrop-filter: blur(12px);
background: rgba(255,255,255,0.95);
}
.nav-links {
display: flex;
gap: 32px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 14px;
}
.nav-link {
color: #171717;
transition: color 0.3s ease;
}
.nav-link:hover,
.nav-link.active {
color: #54198B;
}
```
#### Hero Section
**Full-bleed dark hero**
```css
.hero-section {
background: #0a0a0a;
min-height: 80vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
position: relative;
overflow: hidden;
}
.hero-title {
font-family: 'Oswald', sans-serif;
font-size: clamp(32px, 5vw, 56px);
color: white;
text-transform: lowercase; /* matching "kelime kelime feminist bi kelime" */
line-height: 1.2;
margin-bottom: 24px;
}
.hero-subtitle {
font-size: 14px;
color: rgba(255,255,255,0.8);
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 16px;
}
```
#### Category Tags
```css
.category-tag {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.category-tag.feminist-gundem {
background: #ED1C24;
color: white;
}
.category-tag.ceviri {
background: #54198B;
color: white;
}
.category-tag.kadinlardan-gelenler {
background: #B88BD4;
color: #3D1265;
}
```
### Animations
#### Fade In
```css
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation: fadeIn 0.6s ease-out;
}
```
#### Slide In
```css
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
animation: slideIn 0.8s ease-out;
}
```
#### Stagger Children
```css
.stagger-children > * {
opacity: 0;
animation: slideIn 0.8s ease-out forwards;
}
.stagger-children > *:nth-child(1) { animation-delay: 0.1s; }
.stagger-children > *:nth-child(2) { animation-delay: 0.2s; }
.stagger-children > *:nth-child(3) { animation-delay: 0.3s; }
```
### Visual Effects
#### Shadows
```css
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
--shadow-xl: 0 20px 25px rgba(0,0,0,0.15);
--shadow-purple: 0 10px 40px rgba(84, 25, 139, 0.15); /* brand shadow */
```
#### Backdrop Blur
```css
.backdrop-blur {
backdrop-filter: blur(12px);
background: rgba(255,255,255,0.95);
}
.backdrop-blur-dark {
backdrop-filter: blur(12px);
background: rgba(10,10,10,0.9);
}
```
---
## Page Templates
### Homepage
1. **Sticky Header** - Logo, navigation, search
2. **Hero Carousel** - Full-width rotating featured stories with dark background
3. **Welcome Section** - "KIZKARDEŞİM, SİTEMİZDE SENİ BEKLEYENLER..." (Sister, waiting for you on our site)
4. **Featured Articles Grid** - 3-column card layout
5. **Load More Button** - Purple pill CTA
6. **Footer** - Social links, copyright, contact info
### Article/Blog Template
1. **Header** - Category tag, title, author, date
2. **Featured Image** - Full-width hero
3. **Article Content** - Rich text with proper typography
4. **Share Tools** - Social sharing buttons
5. **Related Articles** - Card grid of similar content
6. **Comments/Discussion** (if applicable)
### Category/Archive Template
1. **Category Hero** - Brief description of category purpose
2. **Filter/Sort Options** - Date, relevance, author
3. **Article Grid** - Masonry or standard grid layout
4. **Pagination** - Load more or numbered pagination
### About/History Page
1. **Hero Section** - Mission statement
2. **Timeline** - Visual history of the organization
3. **Team/Contributors** (if applicable)
4. **Values & Principles** - What Kampüs Cadıları stands for
5. **Get Involved** - CTA for participation
---
## Content Strategy
### Voice & Tone
- **Empowering** - Lift up voices, celebrate strength
- **Direct** - Clear, accessible language avoiding academic jargon where possible
- **Activist** - Unapologetically feminist, calling for action and change
- **Inclusive** - Welcoming to all who support gender equality
- **Authentic** - Genuine stories and perspectives from the community
### Content Pillars
1. **Original Commentary** (Cadı Yayınları) - Organization's voice on current issues
2. **Translation** (Çeviri) - Making global feminist discourse accessible in Turkish
3. **Daily News** (Feminist Gündem) - Curated news with feminist analysis
4. **Community Stories** (Kadınlardan Gelenler) - Personal narratives from the community
5. **Educational Resources** - Guides, explainers, and learning materials
### Multilingual Considerations
- **Primary Language**: Turkish
- **Secondary Language**: English (for select articles and UI)
- Language toggle in header
- Ensure proper Turkish character support (ç, ğ, ı, ö, ş, ü, Ç, Ğ, İ, Ö, Ş, Ü)
- RTL text support not needed (Turkish is LTR)
---
## Accessibility Requirements
### WCAG 2.1 AA Compliance
- Color contrast ratios minimum 4.5:1 for normal text
- All interactive elements keyboard accessible
- Proper heading hierarchy (h1 → h2 → h3, etc.)
- Alt text for all images
- ARIA labels for icon-only buttons
- Focus indicators visible and clear
### Performance
- Core Web Vitals optimization
- LCP (Largest Contentful Paint) < 2.5s
- FID (First Input Delay) < 100ms
- CLS (Cumulative Layout Shift) < 0.1
- Image optimization (WebP, lazy loading)
- Minimal JavaScript for fast page loads
- Progressive enhancement approach
### Responsive Design
- Mobile-first approach
- Breakpoints: 375px (mobile), 768px (tablet), 1024px (desktop), 1440px (wide)
- Touch-friendly targets (minimum 44x44px)
- Readable text at all sizes (minimum 16px body text on mobile)
---
### SEO & Analytics
- Semantic HTML structure
- Open Graph tags for social sharing
- JSON-LD structured data
- Google Analytics or privacy-focused alternative (Plausible)
- Search Console integration
- Sitemap generation
---
## Design Principles
### 1. Bold but Readable
Strike a balance between activist impact and content accessibility. Use dark hero sections for visual drama, but ensure article content is highly readable with excellent typography.
### 2. Color as Meaning
Use the purple/red brand colors strategically:
- Purple for navigation, primary CTAs, and organizational identity
- Red for urgent actions, alerts, and emphasis
- Neutrals for reading comfort and professionalism
### 3. Progressive Enhancement
Build a fast, accessible baseline that works everywhere, then enhance with animations and interactions for capable browsers.
### 4. Content-First
The design should elevate and showcase the powerful stories and analysis the organization produces. Never let design obscure or compete with content.
### 5. Community-Centered
Create spaces for community voices, user-generated content, and participation. The site should feel like a collective project, not a one-way broadcast.
---
## Success Metrics
### Engagement
- Time on page (target: 3+ minutes for articles)
- Pages per session (target: 3+)
- Bounce rate (target: <60%)
- Returning visitor rate (target: 30%+)
### Growth
- Monthly unique visitors
- Social shares per article
- Newsletter signups
- Community contributions (if applicable)
### Performance
- Mobile page load time (target: <3s)
- Desktop page load time (target: <2s)
- Core Web Vitals passing scores
- 95%+ accessibility audit score
---
## Brand Essence
Kampüs Cadıları reclaims the witch as a symbol of feminine power, wisdom, and resistance. The design should embody this spirit: bold, unapologetic, magical, and transformative. The purple witch on the female symbol isn't just a logo—it's a statement of identity, a rallying symbol for feminist activism on Turkish campuses and beyond.
The website should feel like a gathering place, a library of resistance, and a megaphone for change—all while being beautiful, accessible, and technically excellent.
---
**Design Brief Version**: 1.0
**Last Updated**: December 27, 2025

View File

@ -1,84 +0,0 @@
# Continuity Log
Development log for tracking changes, decisions, and next steps.
## Entry Template
```markdown
## YYYY-MM-DD - Brief Description
### Changes
- What changed
- Why it changed
### Decisions
- Key decisions made
- Trade-offs considered
### Next Steps
- [ ] Follow-up items
```
---
## 2024-12-27 - Stripped to Barebones Template
### Changes
- **Removed all elaborate features and styling** to create truly minimal template
- Removed fancy components: CustomCursor, SearchDialog, GridOverlay, ThemeToggle, ThemePreferenceDialog, Navigation, Footer, section components, blog components (RelatedPosts, PostNavigation, BlogFilters, ReadingProgress, TableOfContents)
- Removed all portfolio/section content (hero, experience, skills, featured-project)
- Removed pages content collection
- Simplified all layouts to bare minimum HTML structure
- Simplified all pages to basic content with no styling
- Stripped global.css from 800 lines to ~70 lines (basic typography + simple prose styles)
- Simplified BaseHead component to just essential meta tags (removed elaborate structured data, font loading, etc.)
- Simplified BlogCard to just title, date, description, link
- Updated content.config.ts to only include blog collection
### Current Structure
**Pages:**
- `/` - Homepage with title, description, and basic nav links
- `/blog` - Simple list of blog posts
- `/blog/[slug]` - Basic blog post with header, content, footer
- `/contact` - Basic contact form (non-functional, just markup)
- `/404` - Simple 404 page
**Components (only 3):**
- BaseHead.astro - Essential SEO metadata
- BlogCard.astro - Minimal blog card
- FormattedDate.astro - Date formatting
**Layouts (only 2):**
- BaseLayout.astro - Basic HTML wrapper
- BlogPost.astro - Simple blog post layout
**Content:**
- Single example blog post showing MDX structure
- Blog schema: title, description, pubDate, updatedDate, heroImage, category, tags
### Decisions
- Chose absolute minimalism over feature-rich starter
- Template is meant to be a clean foundation, not a portfolio showcase
- Removed all theming, animations, and visual flourishes
- Kept only essential blog functionality
- Removed all complex data flow (related posts, next/prev navigation, featured posts, filtering)
- Kept utility scripts (AVIF conversion, AI commits, reading time)
- Kept Cloudflare Pages deployment setup
### Stack
- Astro 5 + React 19 + Tailwind CSS 4 (minimal usage)
- TypeScript
- MDX content collections
- Cloudflare Pages deployment
- pnpm package manager
### Next Steps
- [ ] Clone this template when starting new Astro projects
- [ ] Add only the components and features you actually need
- [ ] Replace placeholder content in src/consts.ts
- [ ] Add your own blog posts
- [ ] Customize styling as needed
---
Add new entries below...

View File

@ -1,11 +1,218 @@
{
"_readme": "Design system documentation. Add your design tokens, color palette, typography scale, and component patterns here as your project grows.",
"design_system": {
"name": "Your Design System",
"meta": {
"name": "Kampüs Cadıları Design System",
"version": "1.0.0",
"colors": {},
"typography": {},
"spacing": {},
"components": {}
"lastUpdated": "2025-05-19"
},
"brand": {
"identity": {
"name": "Kampüs Cadıları",
"tagline": "Feminist Bi' Dünya",
"description": "A feminist university student organization fighting for equality, freedom, and secularism.",
"voice": {
"tone": ["Bold", "Rebellious", "Inclusive", "Energetic", "Unapologetic"],
"keywords": ["Solidarity", "Freedom", "Struggle", "Sisterhood"]
}
},
"assets": {
"logo": {
"path": "components/Logo.tsx",
"aspectRatio": "1:1",
"usage": "Use 'brand.deep' color on light backgrounds, 'brand.lilac' on dark backgrounds."
}
}
},
"colors": {
"palette": {
"lilac": {
"base": "#EADDFA",
"dark": "#D0B5EA",
"description": "Primary background color, resembling poster paper."
},
"purple": {
"base": "#6B2C91",
"description": "Primary brand color for accents and highlights."
},
"deep": {
"base": "#2D0F41",
"description": "Used for text, borders, and high contrast elements (replaces black)."
},
"red": {
"base": "#ED1C24",
"description": "Action color, used for calls to action and emphasis."
},
"accent": {
"base": "#9D4EDD",
"yellow": "#FFC107"
},
"surface": {
"paper": "#FFFCF9",
"white": "#FFFFFF"
}
},
"semantic": {
"text": {
"primary": "#2D0F41",
"inverse": "#FFFFFF",
"muted": "#6B2C91"
},
"background": {
"default": "#EADDFA",
"paper": "#FFFCF9",
"card": "#FFFFFF",
"inverse": "#2D0F41"
},
"border": {
"default": "#2D0F41",
"focus": "#6B2C91"
},
"status": {
"success": "#2E7D32",
"error": "#ED1C24",
"warning": "#FFC107",
"info": "#29B6F6"
}
}
},
"typography": {
"families": {
"primary": {
"name": "Inter",
"fallback": "sans-serif",
"weights": [400, 500, 600, 700],
"usage": "Body text, UI elements, long-form content."
},
"display": {
"name": "Oswald",
"fallback": "sans-serif",
"weights": [400, 500, 600, 700],
"usage": "Headings, navigation, buttons, impact text."
},
"marker": {
"name": "Permanent Marker",
"fallback": "cursive",
"weights": [400],
"usage": "Stickers, badges, handwritten notes, emphasis."
}
},
"scale": {
"xs": { "size": "0.75rem", "lineHeight": "1rem" },
"sm": { "size": "0.875rem", "lineHeight": "1.25rem" },
"base": { "size": "1rem", "lineHeight": "1.5rem" },
"lg": { "size": "1.125rem", "lineHeight": "1.75rem" },
"xl": { "size": "1.25rem", "lineHeight": "1.75rem" },
"2xl": { "size": "1.5rem", "lineHeight": "2rem" },
"3xl": { "size": "1.875rem", "lineHeight": "2.25rem" },
"4xl": { "size": "2.25rem", "lineHeight": "2.5rem" },
"5xl": { "size": "3rem", "lineHeight": "1" },
"6xl": { "size": "3.75rem", "lineHeight": "1" },
"7xl": { "size": "4.5rem", "lineHeight": "1" },
"8xl": { "size": "6rem", "lineHeight": "1" },
"9xl": { "size": "8rem", "lineHeight": "1" }
}
},
"layout": {
"spacing": {
"baseUnit": 4,
"scale": {
"1": "4px",
"2": "8px",
"3": "12px",
"4": "16px",
"6": "24px",
"8": "32px",
"12": "48px",
"16": "64px",
"24": "96px",
"32": "128px"
}
},
"grid": {
"columns": 12,
"gutter": "24px",
"maxWidth": "1280px"
},
"breakpoints": {
"sm": "640px",
"md": "768px",
"lg": "1024px",
"xl": "1280px",
"2xl": "1536px"
}
},
"components": {
"button": {
"base": {
"fontFamily": "Oswald",
"fontWeight": "700",
"textTransform": "uppercase",
"letterSpacing": "wider",
"borderRadius": "0.5rem", // rounded-lg
"borderWidth": "2px",
"transition": "all 0.3s ease"
},
"variants": {
"primary": {
"backgroundColor": "#6B2C91",
"color": "#FFFFFF",
"borderColor": "#2D0F41",
"boxShadow": "4px 4px 0px #2D0F41"
},
"secondary": {
"backgroundColor": "transparent",
"color": "#2D0F41",
"borderColor": "#2D0F41"
}
}
},
"card": {
"base": {
"backgroundColor": "#FFFFFF",
"borderWidth": "4px",
"borderColor": "#2D0F41",
"borderRadius": "1.5rem", // rounded-2xl or rounded-3xl
"boxShadow": "12px 12px 0px rgba(45,15,65,0.2)"
},
"hover": {
"transform": "translateY(-4px)",
"boxShadow": "16px 16px 0px #6B2C91"
}
},
"sticker": {
"transform": "rotate(-2deg)",
"borderWidth": "2px",
"borderColor": "#2D0F41",
"boxShadow": "4px 4px 0px #2D0F41"
}
},
"animation": {
"duration": {
"fast": "200ms",
"normal": "300ms",
"slow": "500ms",
"long": "1000ms"
},
"easing": {
"default": "ease-in-out",
"bounce": "cubic-bezier(0.175, 0.885, 0.32, 1.275)"
},
"custom": {
"marquee": "linear infinite 25s",
"float": "ease-in-out infinite 6s",
"blob": "infinite 7s"
}
},
"media": {
"textures": {
"noise": "url('https://www.transparenttextures.com/patterns/noise-lines.png')",
"paper": "url('https://www.transparenttextures.com/patterns/cream-paper.png')",
"dust": "url('https://www.transparenttextures.com/patterns/dust.png')",
"halftone": "radial-gradient(circle, #2D0F41 1px, transparent 1px)"
},
"effects": {
"blendMode": "multiply",
"grayscale": "grayscale(100%)",
"duotone": "mix-blend-mode: luminosity"
}
}
}

24
dev/generated/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

141
dev/generated/App.tsx Normal file
View File

@ -0,0 +1,141 @@
import React, { useState } from 'react';
import { Menu, Search, Instagram, Twitter, Facebook, Youtube } from 'lucide-react';
import { Logo } from './components/Logo';
import { Home } from './components/Home';
import { Publications } from './components/Publications';
import { CONTENT } from './content';
type View = 'HOME' | 'PUBLICATIONS';
const App: React.FC = () => {
const [isNavOpen, setIsNavOpen] = useState(false);
const [lang, setLang] = useState<'TR' | 'EN'>('TR');
const [currentView, setCurrentView] = useState<View>('HOME');
const t = CONTENT[lang];
return (
<div className="bg-brand-lilac text-brand-deep antialiased font-sans w-full selection:bg-brand-purple selection:text-white">
{/* --- FOOTER (FIXED BEHIND CONTENT) --- */}
<footer className="fixed bottom-0 left-0 w-full h-[600px] bg-brand-deep flex flex-col justify-between p-8 md:p-12 z-0">
<div className="flex justify-between items-start">
<Logo className="w-16 h-16 md:w-24 md:h-24 text-brand-lilac" />
<div className="text-right">
<h2 className="text-[15vw] font-display font-bold text-brand-purple leading-none select-none opacity-50">{t.footer.feminist}</h2>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-white font-display font-bold tracking-wider text-sm md:text-lg">
<button onClick={() => setCurrentView('HOME')} className="text-left hover:text-brand-lilac transition-colors">{t.footer.about}</button>
<button onClick={() => setCurrentView('PUBLICATIONS')} className="text-left hover:text-brand-lilac transition-colors">{t.footer.pubs}</button>
<a href="#" className="hover:text-brand-lilac transition-colors">{t.footer.volunteer}</a>
<a href="#" className="hover:text-brand-lilac transition-colors">{t.footer.contact}</a>
</div>
<div className="border-t border-white/20 pt-8 flex justify-between items-end text-brand-lilac text-sm">
<div>
<p>{t.footer.rights}</p>
<p>{t.footer.city}</p>
</div>
<div className="flex gap-4">
{[Instagram, Twitter, Facebook, Youtube].map((Icon, i) => (
<a key={i} href="#" className="hover:text-white transition-colors"><Icon size={24} /></a>
))}
</div>
</div>
</footer>
{/* --- MAIN CONTENT (SCROLLS OVER FOOTER) --- */}
<main className="relative z-10 bg-brand-lilac mb-[600px] shadow-[0_50px_100px_#2D0F41] min-h-screen">
{/* --- HEADER --- */}
<nav
className={`fixed w-full z-50 transition-all duration-300 ease-in-out py-6 px-6 lg:px-12 flex justify-between items-center bg-brand-lilac/90 backdrop-blur-sm border-b-4 border-brand-deep shadow-lg`}
>
<div className="flex items-center gap-3">
<button
onClick={() => setIsNavOpen(!isNavOpen)}
className="p-3 border-2 border-brand-deep text-brand-deep hover:bg-brand-deep hover:text-white transition-colors lg:hidden rounded-lg"
>
<Menu className="w-6 h-6" />
</button>
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setCurrentView('HOME')}
>
<Logo className="w-12 h-12 text-brand-deep" />
<span className="hidden lg:block font-display font-bold text-2xl tracking-tighter text-brand-deep">
KAMPÜS CADILARI
</span>
</div>
</div>
<div className="hidden lg:flex gap-8">
<button
onClick={() => setCurrentView('HOME')}
className={`text-lg font-display font-bold tracking-wider uppercase transition-colors relative group ${currentView === 'HOME' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'}`}
>
{t.nav.home}
<span className={`absolute -bottom-1 left-0 h-1 bg-brand-purple transition-all duration-300 ${currentView === 'HOME' ? 'w-full' : 'w-0 group-hover:w-full'}`}></span>
</button>
<button
onClick={() => setCurrentView('PUBLICATIONS')}
className={`text-lg font-display font-bold tracking-wider uppercase transition-colors relative group ${currentView === 'PUBLICATIONS' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'}`}
>
{t.nav.publications}
<span className={`absolute -bottom-1 left-0 h-1 bg-brand-purple transition-all duration-300 ${currentView === 'PUBLICATIONS' ? 'w-full' : 'w-0 group-hover:w-full'}`}></span>
</button>
{[
t.nav.translation,
t.nav.agenda
].map(item => (
<a
key={item}
href="#"
className="text-brand-deep hover:text-brand-purple text-lg font-display font-bold tracking-wider uppercase transition-colors relative group"
>
{item}
<span className="absolute -bottom-1 left-0 w-0 h-1 bg-brand-purple transition-all duration-300 group-hover:w-full"></span>
</a>
))}
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2 font-display font-bold text-brand-deep text-lg bg-white/50 px-3 py-1 rounded-full border border-brand-deep/20">
<button
onClick={() => setLang('TR')}
className={`${lang === 'TR' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'} transition-colors`}
>
TR
</button>
<span className="text-gray-400">/</span>
<button
onClick={() => setLang('EN')}
className={`${lang === 'EN' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'} transition-colors`}
>
EN
</button>
</div>
<Search className="w-7 h-7 text-brand-deep cursor-pointer hover:text-brand-purple transition-colors" strokeWidth={2.5} />
<button className="hidden md:block bg-brand-purple text-white px-8 py-3 font-display font-bold text-base tracking-widest uppercase border-2 border-brand-deep shadow-[4px_4px_0px_#2D0F41] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none transition-all rounded-lg">
{t.nav.join}
</button>
</div>
</nav>
{/* --- VIEW CONTENT --- */}
{currentView === 'HOME' ? <Home t={t} /> : <Publications t={t} />}
</main>
</div>
);
};
export default App;

126
dev/generated/CLAUDE.md Normal file
View File

@ -0,0 +1,126 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
**Kampüs Cadıları** ("Campus Witches") is a modern, accessible web platform for a Turkish feminist organization. The application is a bilingual (TR/EN) React single-page application built with TypeScript, Vite, and TailwindCSS, using the Gemini API for AI features.
## Commands
### Development
```bash
npm install # Install dependencies
npm run dev # Start dev server on http://0.0.0.0:3000
npm run build # Build for production
npm run preview # Preview production build
```
### Environment Setup
- Copy `.env.local` and set `GEMINI_API_KEY` to your Gemini API key
- The API key is injected at build time via `vite.config.ts` as both `process.env.API_KEY` and `process.env.GEMINI_API_KEY`
## Architecture
### Application Structure
This is a **view-based SPA** with client-side routing managed through state rather than a traditional router:
- **App.tsx**: Root component managing global state (language, navigation, view switching)
- **View switching**: Uses a `currentView` state variable to toggle between `'HOME'` and `'PUBLICATIONS'` views
- **Fixed footer pattern**: The footer is positioned `fixed` at the bottom, with main content scrolling over it (creates a layered poster effect)
### State Management
Global state is managed via React `useState` in `App.tsx`:
- `lang`: Toggle between 'TR' and 'EN' for bilingual support
- `currentView`: Switch between 'HOME' and 'PUBLICATIONS' views
- `isNavOpen`: Mobile navigation drawer state
Content is fully centralized in `content.ts` with the `CONTENT` object keyed by language (`TR`/`EN`).
### Component Organization
Components are stored in `/components/`:
- **Home.tsx**: Main landing page with parallax scroll effects and horizontal scrolling news section
- **Publications.tsx**: Filterable publication gallery with categories
- **Logo.tsx**: Reusable SVG logo component
- **Reveal.tsx**: Intersection Observer-based animation wrapper
- **ArticleCard.tsx, Button.tsx**: Reusable UI components
### Design System
The design system is comprehensively documented in `design.json` and includes:
**Brand colors** (defined in `index.html` TailwindCSS config):
- `brand-lilac` (#EADDFA): Primary background
- `brand-purple` (#6B2C91): Primary brand color
- `brand-deep` (#2D0F41): Text/borders (replaces black)
- `brand-red` (#ED1C24): Action/CTA color
- `brand-accent` (#9D4EDD): Vibrant highlights
**Typography**:
- **Inter**: Body text and UI (`font-sans`)
- **Oswald**: Headings, nav, buttons (`font-display`)
- **Permanent Marker**: Handwritten effects (`font-marker`)
**Visual Style**:
- Bold, poster-like aesthetic with thick borders (4px)
- Brutalist shadows: `shadow-[4px_4px_0px_#2D0F41]`
- Rounded corners: `rounded-lg`, `rounded-2xl`
- Noise overlay for texture (fixed overlay in `index.html`)
### Path Aliases
TypeScript path alias configured in `tsconfig.json`:
- `@/*` resolves to root directory
- Example: `import { Logo } from '@/components/Logo'`
### Animation Patterns
- **Parallax scrolling**: Text and images move at different rates on scroll (see `Home.tsx:31-75`)
- **Horizontal scroll sections**: Uses sticky positioning + transform based on scroll progress
- **Intersection Observer**: Used in `Reveal.tsx` for fade-in animations
- Custom keyframe animations: `blob`, `float`, `marquee` (defined in `index.html`)
### Bilingual Content
All user-facing text is stored in `content.ts` under the `CONTENT` object with `TR` and `EN` keys. To add new translatable content:
1. Add the key to both `CONTENT.TR` and `CONTENT.EN`
2. Access via `t.section.key` (where `t = CONTENT[lang]`)
### TypeScript Types
Core types are defined in `types.ts`:
- `Category`: Publication categories
- `Article`: News/blog article structure
- `Publication`: Publication metadata with type (`FANZIN`, `RAPOR`, `BROSUR`, `KITAP`)
- `Story`: User story/testimonial structure
### Build Configuration
- **Vite** is used for bundling (see `vite.config.ts`)
- **TailwindCSS** is loaded via CDN in `index.html` with inline config
- **Import maps** in `index.html` load React and dependencies from esm.sh CDN
- Dev server runs on port 3000, accessible on all network interfaces (`0.0.0.0`)
## Key Implementation Patterns
### Adding a New View
1. Create component in `/components/`
2. Add new type to `View` union in `App.tsx:9`
3. Update `currentView` state and conditionally render in `App.tsx:133`
4. Add navigation trigger (button/link) that calls `setCurrentView()`
### Adding Scroll Animations
Follow the pattern in `Home.tsx`:
1. Create refs for elements to animate
2. Calculate scroll progress in `useEffect` with `requestAnimationFrame`
3. Apply transforms via `element.style.transform`
4. Respect `prefers-reduced-motion` (checked at `Home.tsx:32`)
### Styling Components
- Use TailwindCSS utility classes with brand colors (`bg-brand-lilac`, `text-brand-deep`)
- Apply brutalist shadow pattern: `shadow-[4px_4px_0px_#2D0F41]`
- Thick borders: `border-4 border-brand-deep`
- Display font for impact: `font-display font-bold tracking-wider uppercase`

20
dev/generated/README.md Normal file
View File

@ -0,0 +1,20 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/drive/1cXtsaa17qs8ovftsyxedXaLLgGe8aV2S
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

View File

@ -0,0 +1,79 @@
import React from 'react';
import { Article, Category } from '../types';
import { ArrowRight, Clock } from 'lucide-react';
interface ArticleCardProps {
article: Article;
}
const getCategoryLabel = (category: Category) => {
switch (category) {
case 'FEMINIST_GUNDEM': return 'Feminist Gündem';
case 'CEVIRI': return 'Çeviri';
case 'KADINLARDAN_GELENLER': return 'Kadınlardan Gelenler';
case 'CADI_YAYINLARI': return 'Cadı Yayınları';
default: return 'Genel';
}
};
const getCategoryColor = (category: Category) => {
switch (category) {
case 'FEMINIST_GUNDEM': return 'bg-brand-red text-white';
case 'CEVIRI': return 'bg-brand-purple text-white';
case 'KADINLARDAN_GELENLER': return 'bg-brand-purpleLighter text-brand-purple';
default: return 'bg-gray-200 text-gray-800';
}
};
export const ArticleCard: React.FC<ArticleCardProps> = ({ article }) => {
return (
<div className="group bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-[0_10px_40px_rgba(84,25,139,0.15)] transition-all duration-300 transform hover:-translate-y-1 flex flex-col h-full border border-gray-100">
{/* Image Container */}
<div className="relative h-56 overflow-hidden">
<img
src={article.imageUrl}
alt={article.title}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<div className="absolute top-4 left-4">
<span className={`px-3 py-1 rounded-md text-xs font-bold tracking-wider uppercase ${getCategoryColor(article.category)}`}>
{getCategoryLabel(article.category)}
</span>
</div>
</div>
{/* Content */}
<div className="p-6 flex flex-col flex-grow">
<div className="flex items-center text-xs text-brand-textSecondary mb-3 space-x-3">
<span className="font-medium text-brand-purple">{article.author}</span>
<span className="w-1 h-1 rounded-full bg-gray-300"></span>
<span>{article.date}</span>
{article.readTime && (
<>
<span className="w-1 h-1 rounded-full bg-gray-300"></span>
<span className="flex items-center">
<Clock size={12} className="mr-1" />
{article.readTime}
</span>
</>
)}
</div>
<h3 className="text-xl font-bold text-brand-text mb-3 leading-tight group-hover:text-brand-purple transition-colors font-display">
{article.title}
</h3>
<p className="text-brand-textSecondary text-sm leading-relaxed mb-6 line-clamp-3">
{article.excerpt}
</p>
<div className="mt-auto pt-4 border-t border-gray-100 flex items-center justify-between">
<button className="text-sm font-semibold text-brand-purple flex items-center group/btn">
Devamını Oku
<ArrowRight size={16} className="ml-2 transform group-hover/btn:translate-x-1 transition-transform" />
</button>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,31 @@
import React from 'react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
children: React.ReactNode;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
className = '',
children,
...props
}) => {
const baseStyles = "inline-flex items-center justify-center font-semibold transition-all duration-300 rounded-full px-8 py-3 text-sm tracking-wide disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-105 active:scale-95";
const variants = {
primary: "bg-brand-purple text-white hover:bg-[#3D1265] shadow-lg shadow-brand-purple/20",
secondary: "bg-brand-red text-white hover:bg-[#c4161d] shadow-lg shadow-brand-red/20",
outline: "border-2 border-brand-purple text-brand-purple hover:bg-brand-purple hover:text-white bg-transparent",
ghost: "bg-transparent text-white hover:bg-white/10"
};
return (
<button
className={`${baseStyles} ${variants[variant]} ${className}`}
{...props}
>
{children}
</button>
);
};

View File

@ -0,0 +1,98 @@
import React from 'react';
import { Logo } from './Logo';
import { Instagram, Facebook, Twitter, Youtube, Mail, ArrowUp } from 'lucide-react';
export const Footer: React.FC = () => {
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
<footer className="bg-brand-dark text-white pt-20 pb-10">
<div className="max-w-7xl mx-auto px-4 md:px-6 lg:px-8">
{/* Main Footer Content */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
{/* Brand Column */}
<div className="space-y-6">
<div className="flex items-center space-x-3">
<Logo className="h-14 w-14 text-brand-purple" />
<div>
<h2 className="text-2xl font-bold tracking-tighter leading-none font-display">KAMPÜS</h2>
<h2 className="text-2xl font-bold tracking-tighter leading-none text-gray-400 font-display">CADILARI</h2>
</div>
</div>
<p className="text-gray-400 text-sm leading-relaxed">
Üniversitelerde, sokaklarda, meydanlarda; eşitlik, özgürlük ve laiklik için mücadele eden feminist üniversite öğrencileriyiz.
</p>
</div>
{/* Links Column 1 */}
<div>
<h3 className="text-lg font-bold mb-6 font-display text-brand-purpleLighter">ORGANİZASYON</h3>
<ul className="space-y-3">
{['Hakkımızda', 'İlkelerimiz', 'Tarihçemiz', 'İletişim', 'Bize Katıl'].map(item => (
<li key={item}>
<a href="#" className="text-gray-400 hover:text-brand-purple transition-colors text-sm">
{item}
</a>
</li>
))}
</ul>
</div>
{/* Links Column 2 */}
<div>
<h3 className="text-lg font-bold mb-6 font-display text-brand-purpleLighter">İÇERİKLER</h3>
<ul className="space-y-3">
{['Feminist Gündem', 'Cadı Yayınları', 'Çeviri', 'Kadınlardan Gelenler', 'E-Bülten'].map(item => (
<li key={item}>
<a href="#" className="text-gray-400 hover:text-brand-purple transition-colors text-sm">
{item}
</a>
</li>
))}
</ul>
</div>
{/* Contact/Social Column */}
<div>
<h3 className="text-lg font-bold mb-6 font-display text-brand-purpleLighter">TAKİP ET</h3>
<div className="flex gap-4 mb-8">
{[Instagram, Twitter, Facebook, Youtube].map((Icon, i) => (
<a
key={i}
href="#"
className="bg-white/5 hover:bg-brand-purple p-3 rounded-full transition-colors duration-300"
>
<Icon size={18} />
</a>
))}
</div>
<h3 className="text-lg font-bold mb-4 font-display text-brand-purpleLighter">İLETİŞİM</h3>
<a href="mailto:info@kampuscadilari.com" className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors">
<Mail size={16} />
<span>info@kampuscadilari.com</span>
</a>
</div>
</div>
{/* Bottom Bar */}
<div className="border-t border-white/10 pt-8 flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-gray-500 text-sm text-center md:text-left">
&copy; {new Date().getFullYear()} Kampüs Cadıları. Tüm hakları saklıdır.
</p>
<button
onClick={scrollToTop}
className="flex items-center gap-2 text-sm font-semibold text-brand-purple hover:text-white transition-colors"
>
YUKARI ÇIK <ArrowUp size={16} />
</button>
</div>
</div>
</footer>
);
};

View File

@ -0,0 +1,113 @@
import React, { useState, useEffect } from 'react';
import { Logo } from './Logo';
import { Menu, X, Search, Globe } from 'lucide-react';
import { Button } from './Button';
export const Header: React.FC = () => {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const navLinks = [
{ label: 'ANASAYFA', href: '#' },
{ label: 'CADI YAYINLARI', href: '#' },
{ label: 'ÇEVİRİ', href: '#' },
{ label: 'FEMİNİST GÜNDEM', href: '#' },
{ label: 'TARİHÇEMİZ', href: '#' },
];
return (
<header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
isScrolled
? 'bg-white/95 backdrop-blur-md shadow-sm border-b border-gray-100 py-3'
: 'bg-white border-b border-gray-100 py-5'
}`}
>
<div className="max-w-7xl mx-auto px-4 md:px-6 lg:px-8">
<div className="flex items-center justify-between">
{/* Logo Section */}
<div className="flex items-center space-x-3 cursor-pointer">
<Logo className="h-10 w-10 text-brand-purple" />
<div className="hidden md:block">
<h1 className="text-xl font-bold tracking-tighter leading-none text-brand-purple">KAMPÜS</h1>
<h1 className="text-xl font-bold tracking-tighter leading-none text-brand-text">CADILARI</h1>
</div>
</div>
{/* Desktop Navigation */}
<nav className="hidden lg:flex items-center space-x-8">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-sm font-semibold text-brand-text hover:text-brand-purple transition-colors tracking-wide"
>
{link.label}
</a>
))}
</nav>
{/* Utility Icons */}
<div className="hidden lg:flex items-center space-x-4">
<button className="p-2 hover:bg-gray-100 rounded-full transition-colors text-brand-text">
<Search size={20} />
</button>
<div className="h-4 w-px bg-gray-300"></div>
<button className="flex items-center space-x-1 text-sm font-medium text-brand-text hover:text-brand-purple">
<Globe size={16} />
<span>TR</span>
</button>
<Button variant="secondary" className="!py-2 !px-6 !text-xs">
BİZE KATIL
</Button>
</div>
{/* Mobile Menu Toggle */}
<button
className="lg:hidden p-2 text-brand-text"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
</div>
{/* Mobile Menu Overlay */}
{isMobileMenuOpen && (
<div className="absolute top-full left-0 right-0 bg-white border-b border-gray-100 shadow-xl lg:hidden p-6 animate-fade-in">
<nav className="flex flex-col space-y-4">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-lg font-bold text-brand-text hover:text-brand-purple"
onClick={() => setIsMobileMenuOpen(false)}
>
{link.label}
</a>
))}
<div className="h-px bg-gray-100 my-4"></div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<button className="flex items-center space-x-2 text-sm font-medium">
<Globe size={16} />
<span>Türkçe</span>
</button>
</div>
<Button variant="primary" className="!py-2 !px-6">BİZE KATIL</Button>
</div>
</nav>
</div>
)}
</header>
);
};

View File

@ -0,0 +1,139 @@
import React, { useState, useEffect } from 'react';
import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react';
import { Story } from '../types';
const stories: Story[] = [
{
id: '1',
title: "KELİME KELİME FEMİNİST Bİ' DÜNYA",
subtitle: "KAMPÜS CADILARI YAZI DİZİSİ",
author: "Zeynep Yılmaz",
authorImage: "https://picsum.photos/seed/author1/100/100",
content: "Feminizmin dilini, tarihini ve geleceğini tartıştığımız yeni yazı dizimiz başlıyor. Kampüslerdeki cadıların sesine kulak verin.",
imageUrl: "https://picsum.photos/seed/feminist1/1920/1080"
},
{
id: '2',
title: "GECEYİ DE SOKAKLARI DA İSTİYORUZ",
subtitle: "8 MART ÖZEL DOSYASI",
author: "Elif Demir",
authorImage: "https://picsum.photos/seed/author2/100/100",
content: "Karanlıktan korkmuyoruz, birbirimizden güç alıyoruz. 8 Mart Feminist Gece Yürüyüşü öncesi manifestomuz.",
imageUrl: "https://picsum.photos/seed/night/1920/1080"
},
{
id: '3',
title: "AKADEMİDE CİNSİYET EŞİTLİĞİ",
subtitle: "ARAŞTIRMA RAPORU",
author: "Kampüs Cadıları",
authorImage: "https://picsum.photos/seed/author3/100/100",
content: "Üniversitelerde yaşanan cinsiyet temelli ayrımcılık ve taciz vakalarına dair kapsamlı raporumuz yayınlandı.",
imageUrl: "https://picsum.photos/seed/academy/1920/1080"
}
];
export const Hero: React.FC = () => {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % stories.length);
}, 6000);
return () => clearInterval(timer);
}, []);
const nextSlide = () => setCurrentIndex((prev) => (prev + 1) % stories.length);
const prevSlide = () => setCurrentIndex((prev) => (prev - 1 + stories.length) % stories.length);
return (
<section className="relative h-[85vh] w-full bg-brand-dark overflow-hidden mt-16 md:mt-20">
{/* Background Slides */}
{stories.map((story, index) => (
<div
key={story.id}
className={`absolute inset-0 transition-opacity duration-1000 ease-in-out ${
index === currentIndex ? 'opacity-100' : 'opacity-0'
}`}
>
<div className="absolute inset-0 bg-black/60 z-10" /> {/* Overlay */}
<img
src={story.imageUrl}
alt={story.title}
className="w-full h-full object-cover"
/>
</div>
))}
{/* Content */}
<div className="absolute inset-0 z-20 flex items-center">
<div className="max-w-7xl mx-auto px-4 md:px-6 lg:px-8 w-full">
<div className="max-w-4xl">
{/* Tag/Marker */}
<div className="inline-block mb-6 animate-slide-in">
<span className="bg-brand-red text-white px-4 py-1.5 text-sm font-bold tracking-widest uppercase rounded">
KISA HİKAYE
</span>
</div>
{/* Title Block */}
<h1 className="font-display text-5xl md:text-7xl lg:text-8xl font-bold text-white leading-[0.9] mb-8 uppercase animate-slide-in">
{stories[currentIndex].title}
</h1>
{/* Description */}
<p className="text-lg md:text-xl text-gray-200 max-w-2xl mb-10 font-light leading-relaxed animate-slide-in" style={{ animationDelay: '0.1s' }}>
{stories[currentIndex].content}
</p>
{/* Author & CTA */}
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-6 animate-slide-in" style={{ animationDelay: '0.2s' }}>
<div className="flex items-center gap-4 bg-white/10 backdrop-blur-sm p-2 pr-6 rounded-full border border-white/20">
<img
src={stories[currentIndex].authorImage}
alt={stories[currentIndex].author}
className="w-10 h-10 rounded-full object-cover border-2 border-brand-purple"
/>
<div className="flex flex-col">
<span className="text-xs text-gray-300 uppercase tracking-wider">Yazar</span>
<span className="text-sm font-bold text-white">{stories[currentIndex].author}</span>
</div>
</div>
<button className="group flex items-center gap-3 bg-brand-purple hover:bg-white text-white hover:text-brand-purple px-8 py-4 rounded-full font-bold tracking-wide transition-all duration-300">
İNCELE
<ArrowRight className="transform group-hover:translate-x-1 transition-transform" />
</button>
</div>
</div>
</div>
</div>
{/* Navigation Controls */}
<div className="absolute bottom-10 right-10 z-30 flex items-center gap-4">
<button
onClick={prevSlide}
className="p-3 rounded-full border border-white/30 text-white hover:bg-brand-purple hover:border-brand-purple transition-colors"
>
<ChevronLeft size={24} />
</button>
<div className="flex gap-2">
{stories.map((_, idx) => (
<div
key={idx}
onClick={() => setCurrentIndex(idx)}
className={`h-1.5 rounded-full transition-all duration-300 cursor-pointer ${
idx === currentIndex ? 'w-8 bg-brand-red' : 'w-2 bg-white/50 hover:bg-white'
}`}
/>
))}
</div>
<button
onClick={nextSlide}
className="p-3 rounded-full border border-white/30 text-white hover:bg-brand-purple hover:border-brand-purple transition-colors"
>
<ChevronRight size={24} />
</button>
</div>
</section>
);
};

View File

@ -0,0 +1,291 @@
import React, { useEffect, useRef, useState } from 'react';
import { ArrowRight, Star, Users, Venus, Heart, PenTool, MoveRight } from 'lucide-react';
import { Reveal } from './Reveal';
interface HomeProps {
t: any;
}
const ScribbleStar = ({ className }: { className?: string }) => (
<svg viewBox="0 0 100 100" className={className} fill="currentColor">
<path d="M50 0L61 35L98 35L68 57L79 91L50 70L21 91L32 57L2 35L39 35L50 0Z" />
</svg>
);
export const Home: React.FC<HomeProps> = ({ t }) => {
// Refs for Scroll Animations
const heroRef = useRef<HTMLDivElement>(null);
const heroTextTopRef = useRef<HTMLDivElement>(null);
const heroTextBottomRef = useRef<HTMLDivElement>(null);
const heroImageRef = useRef<HTMLDivElement>(null);
const horizontalSectionRef = useRef<HTMLDivElement>(null);
const horizontalTrackRef = useRef<HTMLDivElement>(null);
const newsRef = useRef<HTMLDivElement>(null);
// State for horizontal scroll calculation
const [scrollProgress, setScrollProgress] = useState(0);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
if (mediaQuery.matches) return;
let rafId: number;
const handleScroll = () => {
const scrollY = window.scrollY;
const viewportHeight = window.innerHeight;
rafId = requestAnimationFrame(() => {
// --- HERO ANIMATION ---
if (heroRef.current && heroTextTopRef.current && heroTextBottomRef.current && heroImageRef.current) {
const heroProgress = Math.min(scrollY / viewportHeight, 1);
heroTextTopRef.current.style.transform = `translateX(-${heroProgress * 30}vw) rotate(-2deg)`;
heroTextBottomRef.current.style.transform = `translateX(${heroProgress * 30}vw) rotate(2deg)`;
heroImageRef.current.style.transform = `translateY(${heroProgress * 50}px) rotate(${heroProgress * 5}deg)`;
}
// --- HORIZONTAL SCROLL CALCULATION ---
if (horizontalSectionRef.current && horizontalTrackRef.current) {
const sectionTop = horizontalSectionRef.current.offsetTop;
const sectionHeight = horizontalSectionRef.current.offsetHeight;
const scrollDistanceFromTop = scrollY - sectionTop;
let progress = scrollDistanceFromTop / (sectionHeight - viewportHeight);
progress = Math.max(0, Math.min(progress, 1));
setScrollProgress(progress);
}
});
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
cancelAnimationFrame(rafId);
};
}, []);
// Update Horizontal Transform
useEffect(() => {
if (horizontalTrackRef.current) {
const trackWidth = horizontalTrackRef.current.scrollWidth;
const viewportWidth = window.innerWidth;
const maxTranslate = trackWidth - viewportWidth;
if (maxTranslate > 0) {
horizontalTrackRef.current.style.transform = `translateX(-${scrollProgress * maxTranslate}px)`;
}
}
}, [scrollProgress]);
const newsImages = [
{ image: "https://images.unsplash.com/photo-1596700813955-442436d423e0?auto=format&fit=crop&q=80&w=600", height: "h-96" },
{ image: "https://images.unsplash.com/photo-1572691242698-b7f3001844b2?auto=format&fit=crop&q=80&w=600", height: "h-[32rem]" }
];
const currentNewsItems = t.news.items.map((item: any, index: number) => ({
...item,
...newsImages[index]
}));
return (
<>
{/* --- HERO SECTION --- */}
<div ref={heroRef} className="relative h-screen min-h-[800px] flex items-center justify-center bg-brand-lilac overflow-hidden">
<div className="absolute top-32 left-[10%] w-12 h-12 text-brand-deep animate-float">
<ScribbleStar className="w-full h-full" />
</div>
<div className="absolute bottom-32 right-[10%] w-16 h-16 text-brand-purple animate-float" style={{ animationDelay: '1s' }}>
<ScribbleStar className="w-full h-full" />
</div>
<div className="absolute top-1/4 right-20 w-8 h-8 text-brand-red animate-pulse">
<Star className="w-full h-full fill-current" />
</div>
<div ref={heroImageRef} className="relative z-10 will-change-transform">
<div className="w-[85vw] md:w-[50vw] h-[55vh] md:h-[65vh] border-[4px] border-brand-deep bg-brand-deep relative shadow-[12px_12px_0px_#6B2C91] rounded-[2rem] overflow-hidden group rotate-1 hover:rotate-0 transition-all duration-500">
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1555848962-6e79363ec58f?auto=format&fit=crop&q=80&w=1600')] bg-cover bg-center transition-all duration-700"></div>
<div className="absolute inset-0 opacity-20 bg-[url('https://www.transparenttextures.com/patterns/dust.png')] pointer-events-none"></div>
<div className="absolute inset-0 bg-brand-purple/20 mix-blend-multiply pointer-events-none"></div>
<div className="absolute bottom-6 right-6 transform rotate-[-3deg]">
<div className="bg-brand-red text-white px-6 py-2 font-marker text-xl tracking-widest border-2 border-brand-deep shadow-[4px_4px_0px_#2D0F41] rounded-full">
{t.hero.est}
</div>
</div>
</div>
</div>
<div className="absolute inset-0 z-20 pointer-events-none flex flex-col items-center justify-center">
<div className="w-full flex justify-center pb-20 md:pb-32">
<div ref={heroTextTopRef} className="origin-center transform -rotate-2">
<h1 className="text-5xl md:text-8xl lg:text-9xl font-display font-bold text-white bg-brand-deep px-8 py-4 border-4 border-white shadow-[8px_8px_0px_rgba(0,0,0,0.1)] rounded-xl inline-block tracking-tighter whitespace-nowrap leading-none">
{t.hero.women}
</h1>
</div>
</div>
<div className="w-full flex justify-center pt-20 md:pt-32 absolute top-1/2 mt-16 md:mt-0">
<div ref={heroTextBottomRef} className="origin-center transform rotate-1">
<h1 className="text-5xl md:text-8xl lg:text-9xl font-display font-bold text-brand-deep bg-brand-lilac px-8 py-4 border-4 border-brand-deep shadow-[12px_12px_0px_#ED1C24] rounded-xl inline-block tracking-tighter whitespace-nowrap leading-none">
{t.hero.future}
</h1>
</div>
</div>
</div>
<div className="absolute bottom-8 flex flex-col items-center gap-2 text-brand-deep z-30 animate-bounce">
<span className="font-display font-bold text-lg tracking-widest uppercase">{t.hero.scroll}</span>
<ArrowRight className="w-6 h-6 rotate-90" strokeWidth={3} />
</div>
</div>
{/* --- MARQUEE BANNER --- */}
<div className="bg-brand-deep py-6 overflow-hidden -rotate-1 scale-105 border-y-4 border-white z-30 relative shadow-xl">
<div className="flex animate-marquee w-fit">
{[...Array(2)].map((_, groupIndex) => (
<div key={groupIndex} className="flex gap-8 whitespace-nowrap px-4">
{[...Array(8)].map((_, i) => (
<span key={i} className="text-4xl font-display font-bold text-white uppercase tracking-widest flex items-center gap-8">
{t.marquee} <Heart className="fill-brand-red text-brand-red w-8 h-8" />
</span>
))}
</div>
))}
</div>
</div>
{/* --- HORIZONTAL SCROLL SECTION --- */}
<div ref={horizontalSectionRef} className="relative h-[400vh] z-20">
<div className="sticky top-0 h-screen overflow-hidden flex items-center bg-brand-surface bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] bg-fixed">
<div ref={horizontalTrackRef} className="flex gap-10 md:gap-20 px-10 md:px-20 items-center h-full will-change-transform">
<div className="flex-shrink-0 w-[80vw] md:w-[40vw] flex flex-col justify-center pl-12">
<div className="bg-brand-lilac p-8 border-4 border-brand-deep shadow-[12px_12px_0px_#2D0F41] rounded-[2rem] inline-block transform -rotate-2 max-w-full">
<h2 className="text-[clamp(3rem,8vw,6rem)] font-display font-bold text-brand-deep uppercase leading-[0.9] tracking-tighter break-words">
{t.values.title}
</h2>
</div>
<ArrowRight className="w-16 h-16 md:w-24 md:h-24 text-brand-purple mt-12 animate-pulse ml-8" />
</div>
{t.values.cards.map((card: any, idx: number) => (
<div key={idx} className="flex-shrink-0 w-[85vw] md:w-[45vw] h-[60vh] md:h-[70vh] bg-white border-4 border-brand-deep p-8 md:p-12 flex flex-col justify-between shadow-[20px_20px_0px_#6B2C91] relative rounded-3xl overflow-hidden group">
<div className={`absolute -top-10 -right-10 w-40 h-40 rounded-full opacity-20 ${idx === 1 ? 'bg-brand-red' : 'bg-brand-purple'}`}></div>
<div className="flex justify-between items-start">
{idx === 0 && <Venus className="w-20 h-20 text-brand-purple" strokeWidth={1.5} />}
{idx === 1 && <Users className="w-20 h-20 text-brand-red" strokeWidth={1.5} />}
{idx === 2 && <Star className="w-20 h-20 text-brand-deep" strokeWidth={1.5} />}
<span className="font-marker text-4xl text-brand-deep/20">#{card.id}</span>
</div>
<div>
<div className="bg-brand-lilac/50 inline-block px-4 py-2 rounded-lg mb-6 border border-brand-deep/10">
<h3 className="text-5xl md:text-7xl font-display font-bold text-brand-deep uppercase tracking-tight">{card.title}</h3>
</div>
<p className="text-xl md:text-2xl text-brand-deep font-medium leading-relaxed max-w-md font-sans">
{card.desc}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* --- NEWS SECTION --- */}
<div ref={newsRef} className="bg-brand-lilac py-32 relative z-20 overflow-hidden">
<div className="absolute top-0 left-0 w-full h-full opacity-10 pointer-events-none bg-[radial-gradient(#2D0F41_1px,transparent_1px)] [background-size:20px_20px]"></div>
<div className="max-w-7xl mx-auto px-6 lg:px-12 relative">
<Reveal className="mb-20 flex items-end justify-between border-b-4 border-brand-deep pb-6">
<div className="bg-brand-deep text-white px-8 py-4 transform -skew-x-12 inline-block shadow-[8px_8px_0px_#6B2C91]">
<h2 className="text-5xl md:text-8xl font-display font-bold uppercase tracking-tighter transform skew-x-12">
{t.news.header}
</h2>
</div>
<div className="hidden md:block">
<div className="bg-brand-red text-white font-marker text-3xl px-4 py-2 rotate-3 rounded-lg shadow-md border-2 border-brand-deep">
{t.news.year}
</div>
</div>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 lg:gap-24 relative z-10">
<div className="flex flex-col gap-16">
{currentNewsItems.map((item: any, idx: number) => (
<div key={idx} className="group cursor-pointer">
<div className={`${item.height} w-full overflow-hidden relative border-4 border-brand-deep bg-brand-deep rounded-2xl shadow-[12px_12px_0px_rgba(45,15,65,0.2)] group-hover:shadow-[16px_16px_0px_#6B2C91] group-hover:-translate-y-1 transition-all duration-300`}>
<img src={item.image} className="w-full h-full object-cover grayscale mix-blend-luminosity opacity-80 group-hover:grayscale-0 group-hover:opacity-100 transition-all duration-500" />
<div className="absolute top-4 left-4 bg-brand-purple text-white px-4 py-1 font-bold uppercase tracking-widest text-sm border-2 border-white rounded-full shadow-lg">
{item.category}
</div>
</div>
<div className="mt-6 pl-2">
<div className="flex items-center gap-3 text-brand-purple font-bold text-sm mb-2 font-mono">
<span>{item.date}</span>
<div className="h-px w-8 bg-brand-purple"></div>
</div>
<h3 className="text-3xl lg:text-4xl font-display font-bold text-brand-deep group-hover:text-brand-purple transition-colors leading-tight uppercase">
{item.title}
</h3>
</div>
</div>
))}
</div>
<div className="flex flex-col gap-16 pt-0 md:pt-32">
<div className="bg-[#FFF9C4] p-8 md:p-12 flex flex-col items-center justify-center text-center relative shadow-[0_10px_40px_rgba(0,0,0,0.1)] transform rotate-2 border border-gray-200">
<div className="absolute -top-6 left-1/2 -translate-x-1/2 w-32 h-10 bg-white/40 backdrop-blur-sm border-l border-r border-white/60 transform rotate-1 shadow-sm"></div>
<PenTool className="w-16 h-16 text-brand-deep mb-6" strokeWidth={1.5} />
<h3 className="text-5xl font-display font-bold text-brand-deep uppercase leading-[0.9] mb-4 tracking-tighter">
{t.news.sticky.title_1}<br/>{t.news.sticky.title_2}
</h3>
<p className="text-brand-deep font-medium mb-8 font-marker text-lg opacity-80">{t.news.sticky.desc}</p>
<button className="bg-brand-deep text-white px-8 py-3 font-bold uppercase tracking-widest hover:bg-brand-purple transition-colors rounded-lg shadow-lg">
{t.news.sticky.btn}
</button>
</div>
<div className="group cursor-pointer mt-8">
<div className="h-80 w-full overflow-hidden relative border-4 border-brand-deep rounded-2xl bg-white shadow-[12px_12px_0px_#9D4EDD] group-hover:-translate-y-1 transition-all">
<img src="https://images.unsplash.com/photo-1532012197267-da84d127e765?auto=format&fit=crop&q=80&w=600" className="w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-all duration-500" />
<div className="absolute top-4 left-4 bg-white text-brand-deep px-4 py-2 font-bold uppercase tracking-widest text-xs border-2 border-brand-deep rounded-lg">
{t.news.secondary.category}
</div>
</div>
<h3 className="mt-6 text-3xl font-display font-bold text-brand-deep group-hover:text-brand-purple transition-colors leading-tight uppercase pl-2">
{t.news.secondary.title}
</h3>
</div>
</div>
</div>
</div>
</div>
{/* --- NEWSLETTER SECTION --- */}
<div className="relative z-30 bg-brand-purple py-24 border-y-4 border-brand-deep bg-[url('https://www.transparenttextures.com/patterns/diagmonds-light.png')]">
<div className="max-w-7xl mx-auto px-6 lg:px-12 flex flex-col md:flex-row items-center justify-between gap-12">
<div className="md:w-1/2 text-white">
<h2 className="text-6xl md:text-[7rem] font-display font-bold uppercase leading-[0.8] mb-6 tracking-tighter drop-shadow-lg">
{t.newsletter.title_1}<br/><span className="text-brand-lilac">{t.newsletter.title_2}</span>
</h2>
<p className="text-xl font-medium opacity-90 max-w-md">{t.newsletter.desc}</p>
</div>
<div className="md:w-1/2 w-full">
<div className="flex border-4 border-brand-deep shadow-[16px_16px_0px_#2D0F41] bg-white rounded-xl overflow-hidden transform rotate-1 hover:rotate-0 transition-transform">
<input type="email" placeholder={t.newsletter.placeholder} className="flex-1 px-8 py-6 bg-transparent font-bold text-brand-deep placeholder:text-gray-400 focus:outline-none text-lg uppercase" />
<button className="px-10 bg-brand-deep text-white font-bold text-2xl hover:bg-brand-red transition-colors border-l-4 border-brand-deep">
<MoveRight className="w-8 h-8" />
</button>
</div>
</div>
</div>
</div>
</>
);
};

View File

@ -0,0 +1,34 @@
import React from 'react';
export const Logo: React.FC<{ className?: string }> = ({ className = "h-12 w-12" }) => {
return (
<svg viewBox="0 0 100 120" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
{/* Female Symbol Base */}
<circle cx="50" cy="50" r="30" stroke="currentColor" strokeWidth="8" />
<line x1="50" y1="80" x2="50" y2="115" stroke="currentColor" strokeWidth="8" />
<line x1="30" y1="100" x2="70" y2="100" stroke="currentColor" strokeWidth="8" />
{/* Witch Hat / Stylized Figure */}
<path
d="M20 50 L50 5 L80 50"
fill="currentColor"
stroke="currentColor"
strokeWidth="4"
strokeLinejoin="round"
/>
<path
d="M20 50 Q50 65 80 50"
stroke="currentColor"
strokeWidth="4"
fill="none"
/>
{/* Star Accent */}
<path
d="M65 25 L68 32 L75 32 L70 37 L72 44 L65 40 L58 44 L60 37 L55 32 L62 32 Z"
fill="#ED1C24"
className="text-brand-red"
/>
</svg>
);
};

View File

@ -0,0 +1,211 @@
import React, { useState } from 'react';
import { Publication, PublicationType } from '../types';
import { Download, BookOpen, Star, Filter } from 'lucide-react';
import { Reveal } from './Reveal';
interface PublicationsProps {
t: any;
}
const SAMPLE_PUBLICATIONS: Publication[] = [
{
id: '1',
title: 'KAMPÜS CADILARI FANZİN #12',
type: 'FANZIN',
date: 'OCAK 2025',
coverUrl: 'https://images.unsplash.com/photo-1544947950-fa07a98d237f?auto=format&fit=crop&q=80&w=400',
description: 'Yeni dönem, yeni mücadeleler. Kampüslerdeki cadı avına karşı sesimizi yükseltiyoruz.',
},
{
id: '2',
title: 'GÜVENLİ KAMPÜS REHBERİ',
type: 'BROSUR',
date: 'ARALIK 2024',
coverUrl: 'https://images.unsplash.com/photo-1535905557558-afc4877a26fc?auto=format&fit=crop&q=80&w=400',
description: 'Cinsel taciz ve saldırıya karşı haklarımız, başvuru mekanizmaları ve dayanışma ağları.',
},
{
id: '3',
title: 'AKADEMİDE CİNSİYETÇİLİK RAPORU',
type: 'RAPOR',
date: 'KASIM 2024',
coverUrl: 'https://images.unsplash.com/photo-1555449377-a8b411132a58?auto=format&fit=crop&q=80&w=400',
description: 'Türkiye genelinde 50 üniversitede yapılan araştırmanın çarpıcı sonuçları.',
},
{
id: '4',
title: '8 MART ÖZEL SAYI',
type: 'FANZIN',
date: 'MART 2024',
coverUrl: 'https://images.unsplash.com/photo-1532012197267-da84d127e765?auto=format&fit=crop&q=80&w=400',
description: 'Geceyi de, sokakları da, meydanları da istiyoruz! Feminist isyan her yerde.',
},
{
id: '5',
title: 'CADI SÖZLÜĞÜ',
type: 'KITAP',
date: 'EYLÜL 2023',
coverUrl: 'https://images.unsplash.com/photo-1512820790803-83ca734da794?auto=format&fit=crop&q=80&w=400',
description: 'Feminizmin temel kavramları, bizden kelimeler ve cadıların dili.',
},
];
export const Publications: React.FC<PublicationsProps> = ({ t }) => {
const [filter, setFilter] = useState<PublicationType | 'ALL'>('ALL');
const filteredPublications = filter === 'ALL'
? SAMPLE_PUBLICATIONS
: SAMPLE_PUBLICATIONS.filter(p => p.type === filter);
return (
<div className="bg-brand-lilac min-h-screen pt-32 pb-32">
{/* Background Decoration */}
<div className="fixed inset-0 pointer-events-none opacity-20 bg-[url('https://www.transparenttextures.com/patterns/dust.png')]"></div>
{/* --- HEADER SECTION --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 mb-20 relative z-10">
<Reveal>
<div className="flex flex-col items-start gap-6">
<div className="bg-brand-deep text-white px-8 py-4 transform -skew-x-12 inline-block shadow-[12px_12px_0px_#6B2C91] border-4 border-white">
<h1 className="text-6xl md:text-9xl font-display font-bold uppercase tracking-tighter transform skew-x-12">
{t.publications.header}
</h1>
</div>
<p className="text-xl md:text-2xl font-medium text-brand-deep max-w-2xl mt-4 bg-white/50 p-4 rounded-xl border border-brand-deep/10 backdrop-blur-sm">
Kampüs Cadıları'nın ürettiği tüm fanzin, broşür, rapor ve kitaplara buradan ulaşabilir, dijital kopyalarını indirebilirsiniz.
</p>
</div>
</Reveal>
</div>
{/* --- FEATURED SECTION --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 mb-24 relative z-10">
<div className="bg-white border-4 border-brand-deep rounded-[2rem] p-6 md:p-12 shadow-[20px_20px_0px_#ED1C24] overflow-hidden relative">
<div className="absolute top-0 right-0 w-64 h-64 bg-brand-lilac rounded-full blur-3xl opacity-50 -translate-y-1/2 translate-x-1/2"></div>
<div className="flex flex-col md:flex-row gap-12 items-center relative z-10">
<div className="w-full md:w-1/2">
<div className="relative group">
<div className="absolute inset-0 bg-brand-deep rounded-xl transform rotate-3 transition-transform group-hover:rotate-6"></div>
<img
src="https://images.unsplash.com/photo-1621351183012-e2f9972dd9bf?auto=format&fit=crop&q=80&w=800"
alt="Featured Zine"
className="relative rounded-xl border-4 border-brand-deep shadow-lg transform -rotate-2 transition-transform group-hover:rotate-0 w-full h-96 object-cover"
/>
<div className="absolute top-4 left-4">
<span className="bg-brand-red text-white px-4 py-1 font-bold font-marker tracking-widest text-lg border-2 border-brand-deep shadow-[4px_4px_0px_#2D0F41] rounded-full rotate-[-5deg] inline-block">
{t.publications.featured}
</span>
</div>
</div>
</div>
<div className="w-full md:w-1/2 space-y-8">
<h2 className="text-5xl md:text-7xl font-display font-bold text-brand-deep uppercase leading-[0.9]">
KAMPÜS CADILARI<br/>
<span className="text-transparent bg-clip-text bg-gradient-to-r from-brand-purple to-brand-red">FANZİN #13</span>
</h2>
<p className="text-xl text-gray-600 font-medium leading-relaxed">
Yeni sayımızda "Kampüslerde Barınma Krizi" dosyasını ıyoruz. Yurtlardaki niteliksiz koşullar, fahiş kiralar ve barınma hakkımız üzerine sözümüz var.
</p>
<div className="flex flex-wrap gap-4">
<button className="bg-brand-deep text-white px-8 py-4 font-bold text-xl uppercase tracking-wider flex items-center gap-3 hover:bg-brand-purple transition-all shadow-[8px_8px_0px_rgba(0,0,0,0.2)] hover:translate-y-1 hover:shadow-none rounded-lg">
<Download size={24} />
{t.publications.download}
</button>
<button className="bg-transparent border-4 border-brand-deep text-brand-deep px-8 py-4 font-bold text-xl uppercase tracking-wider flex items-center gap-3 hover:bg-brand-lilac transition-all rounded-lg">
<BookOpen size={24} />
İNCELE
</button>
</div>
</div>
</div>
</div>
</div>
{/* --- FILTERS --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 mb-12 sticky top-24 z-30">
<div className="flex flex-wrap justify-center gap-4 bg-white/80 backdrop-blur-md p-4 rounded-2xl border-2 border-brand-deep/20 shadow-lg inline-flex mx-auto">
<button
onClick={() => setFilter('ALL')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'ALL' ? 'bg-brand-deep text-white border-brand-deep' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.all}
</button>
<button
onClick={() => setFilter('FANZIN')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'FANZIN' ? 'bg-brand-purple text-white border-brand-purple' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.fanzine}
</button>
<button
onClick={() => setFilter('RAPOR')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'RAPOR' ? 'bg-brand-red text-white border-brand-red' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.report}
</button>
<button
onClick={() => setFilter('BROSUR')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'BROSUR' ? 'bg-[#FFC107] text-brand-deep border-[#FFC107]' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.brochure}
</button>
</div>
</div>
{/* --- GRID --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 relative z-10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
{filteredPublications.map((pub) => (
<div key={pub.id} className="group relative">
{/* Card Background Layer */}
<div className="absolute inset-0 bg-brand-deep rounded-2xl transform translate-x-3 translate-y-3 transition-transform group-hover:translate-x-4 group-hover:translate-y-4"></div>
{/* Main Card */}
<div className="relative bg-white border-4 border-brand-deep rounded-2xl overflow-hidden flex flex-col h-full transition-transform group-hover:-translate-y-1">
{/* Image Area */}
<div className="h-64 overflow-hidden relative border-b-4 border-brand-deep bg-gray-100">
<img src={pub.coverUrl} alt={pub.title} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
<div className="absolute top-4 right-4">
<div className="bg-white text-brand-deep px-3 py-1 font-bold text-xs border-2 border-brand-deep rounded shadow-[2px_2px_0px_#2D0F41]">
{pub.date}
</div>
</div>
{/* Type Badge */}
<div className="absolute bottom-4 left-4">
<span className={`px-4 py-1 font-bold uppercase tracking-widest text-xs border-2 border-brand-deep rounded-full shadow-md ${
pub.type === 'FANZIN' ? 'bg-brand-purple text-white' :
pub.type === 'RAPOR' ? 'bg-brand-red text-white' :
pub.type === 'BROSUR' ? 'bg-[#FFC107] text-brand-deep' : 'bg-gray-800 text-white'
}`}>
{pub.type}
</span>
</div>
</div>
{/* Content Area */}
<div className="p-6 flex flex-col flex-grow">
<h3 className="text-2xl font-display font-bold text-brand-deep leading-tight mb-3 uppercase group-hover:text-brand-purple transition-colors">
{pub.title}
</h3>
<p className="text-gray-600 font-medium text-sm mb-6 flex-grow line-clamp-3">
{pub.description}
</p>
<button className="w-full bg-brand-lilac border-2 border-brand-deep text-brand-deep font-bold py-3 px-4 rounded-lg uppercase tracking-wider flex items-center justify-center gap-2 hover:bg-brand-deep hover:text-white transition-colors">
<Download size={18} />
İNDİR
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,54 @@
import React, { useEffect, useRef, useState } from 'react';
interface RevealProps {
children: React.ReactNode;
className?: string;
delay?: number;
duration?: number;
threshold?: number;
}
export const Reveal: React.FC<RevealProps> = ({
children,
className = "",
delay = 0,
duration = 1000,
threshold = 0.1
}) => {
const ref = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ threshold, rootMargin: "50px" }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, [threshold]);
return (
<div
ref={ref}
className={className}
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0) scale(1)' : 'translateY(30px) scale(0.98)',
transition: `opacity ${duration}ms ease-out, transform ${duration}ms cubic-bezier(0.16, 1, 0.3, 1)`,
transitionDelay: `${delay}ms`,
willChange: 'opacity, transform'
}}
>
{children}
</div>
);
};

179
dev/generated/content.ts Normal file
View File

@ -0,0 +1,179 @@
export const CONTENT = {
TR: {
nav: {
home: 'ANASAYFA',
publications: 'CADI YAYINLARI',
translation: 'ÇEVİRİ',
agenda: 'FEMİNİST GÜNDEM',
join: 'BİZE KATIL'
},
hero: {
women: 'GÜÇLÜ KADINLAR',
future: 'GÜÇLÜ GELECEK',
scroll: 'KAYDIR',
est: 'EST. 2018'
},
marquee: 'KADIN YAŞAM ÖZGÜRLÜK',
values: {
title: 'DEĞERLERİMİZ',
cards: [
{
id: '01',
title: 'ÖZGÜRLÜK',
desc: 'Bedenimiz, emeğimiz ve kimliğimiz üzerindeki her türlü tahakküme karşı çıkıyoruz. Özgürlük lütuf değil, haktır.'
},
{
id: '02',
title: 'DAYANIŞMA',
desc: 'Kızkardeşlik sınır tanımaz. Birimizin sesi kısıldığında diğerimiz çığlık oluruz.'
},
{
id: '03',
title: 'MÜCADELE',
desc: 'Eşit, özgür ve sömürüsüz bir dünya için kampüslerden sokaklara.'
}
]
},
news: {
header: 'SON GÜNDEM',
year: '2025',
items: [
{
title: "GÜÇLÜ, ÖZGÜR, BİRLİKTE",
date: "19 Mayıs 2025",
category: "GÜNDEM",
},
{
title: "YANAN IŞIKLAR",
date: "27 Mayıs 2025",
category: "EYLEM",
}
],
sticky: {
title_1: 'SENİN',
title_2: 'SÖZÜN',
desc: 'Yazılarını bize gönder, sesini çoğaltalım.',
btn: 'YAZI GÖNDER'
},
secondary: {
category: 'BASIN',
title: 'FEMİNİST DAYANIŞMA YAŞATIYOR'
}
},
newsletter: {
title_1: 'BİZE',
title_2: 'KATIL',
desc: 'Haftalık bültenimize abone ol, gündemi kaçırma.',
placeholder: 'E-POSTA ADRESİN'
},
footer: {
feminist: 'FEMİNİST',
about: 'HAKKIMIZDA',
pubs: 'YAYINLAR',
volunteer: 'GÖNÜLLÜ OL',
contact: 'İLETİŞİM',
rights: '© 2025 KAMPÜS CADILARI',
city: 'İSTANBUL, TÜRKİYE'
},
publications: {
header: 'YAYINLARIMIZ',
featured: 'ÖNE ÇIKAN',
download: 'İNDİR / OKU',
filters: {
all: 'TÜMÜ',
fanzine: 'FANZİN',
report: 'RAPOR',
brochure: 'BROŞÜR'
}
}
},
EN: {
nav: {
home: 'HOME',
publications: 'PUBLICATIONS',
translation: 'TRANSLATION',
agenda: 'FEMINIST AGENDA',
join: 'JOIN US'
},
hero: {
women: 'STRONG WOMEN',
future: 'STRONG FUTURE',
scroll: 'SCROLL',
est: 'EST. 2018'
},
marquee: 'WOMEN LIFE FREEDOM',
values: {
title: 'OUR VALUES',
cards: [
{
id: '01',
title: 'FREEDOM',
desc: 'We oppose all forms of domination over our bodies, labor, and identities. Freedom is not a favor, it is a right.'
},
{
id: '02',
title: 'SOLIDARITY',
desc: 'Sisterhood knows no borders. When one voice is silenced, we become the scream of the other.'
},
{
id: '03',
title: 'STRUGGLE',
desc: 'From campuses to streets for an equal, free, and exploitation-free world.'
}
]
},
news: {
header: 'LATEST NEWS',
year: '2025',
items: [
{
title: "STRONG, FREE, TOGETHER",
date: "May 19, 2025",
category: "AGENDA",
},
{
title: "BURNING LIGHTS",
date: "May 27, 2025",
category: "ACTION",
}
],
sticky: {
title_1: 'YOUR',
title_2: 'VOICE',
desc: 'Send us your writings, let us amplify your voice.',
btn: 'SUBMIT'
},
secondary: {
category: 'PRESS',
title: 'FEMINIST SOLIDARITY KEEPS ALIVE'
}
},
newsletter: {
title_1: 'JOIN',
title_2: 'US',
desc: 'Subscribe to our weekly newsletter, don\'t miss the agenda.',
placeholder: 'YOUR EMAIL ADDRESS'
},
footer: {
feminist: 'FEMINIST',
about: 'ABOUT US',
pubs: 'PUBLICATIONS',
volunteer: 'VOLUNTEER',
contact: 'CONTACT',
rights: '© 2025 CAMPUS WITCHES',
city: 'ISTANBUL, TURKEY'
},
publications: {
header: 'PUBLICATIONS',
featured: 'FEATURED',
download: 'DOWNLOAD / READ',
filters: {
all: 'ALL',
fanzine: 'FANZINE',
report: 'REPORT',
brochure: 'BROCHURE'
}
}
}
};

110
dev/generated/index.html Normal file
View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Kampüs Cadıları | Feminist Bi' Dünya</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Oswald:wght@400;500;600;700&family=Permanent+Marker&display=swap"
rel="stylesheet"
/>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
lilac: '#EADDFA', // Main Light BG (Poster style)
lilacDark: '#D0B5EA', // Secondary BG
purple: '#6B2C91', // Primary Brand Purple
deep: '#2D0F41', // Text/Borders (Almost Black)
accent: '#9D4EDD', // Vibrant Accent
red: '#ED1C24', // Action Red
surface: '#FFFCF9', // Paper White
}
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Oswald', 'sans-serif'],
marker: ['Permanent Marker', 'cursive'],
},
backgroundImage: {
'halftone': "radial-gradient(circle, #2D0F41 1px, transparent 1px)",
'paper': "url('https://www.transparenttextures.com/patterns/cream-paper.png')",
},
animation: {
'blob': 'blob 7s infinite',
'float': 'float 6s ease-in-out infinite',
'marquee': 'marquee 25s linear infinite',
},
keyframes: {
blob: {
'0%': { transform: 'translate(0px, 0px) scale(1)' },
'33%': { transform: 'translate(30px, -50px) scale(1.1)' },
'66%': { transform: 'translate(-20px, 20px) scale(0.9)' },
'100%': { transform: 'translate(0px, 0px) scale(1)' },
},
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-20px)' },
},
marquee: {
'0%': { transform: 'translateX(0%)' },
'100%': { transform: 'translateX(-50%)' },
}
},
},
},
};
</script>
<style>
body {
font-family: 'Inter', sans-serif;
color: #2D0F41;
background-color: #EADDFA;
overflow-x: hidden; /* Moved here to allow sticky position to work in children */
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Oswald', sans-serif;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* Noise Overlay */
.noise-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
opacity: 0.05;
background: url('https://www.transparenttextures.com/patterns/noise-lines.png');
}
</style>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@^19.2.3",
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
"lucide-react": "https://esm.sh/lucide-react@^0.562.0",
"react/": "https://esm.sh/react@^19.2.3/"
}
}
</script>
<link rel="stylesheet" href="/index.css">
</head>
<body>
<div id="root"></div>
<div class="noise-overlay"></div>
<script type="module" src="/index.tsx"></script>
</body>
</html>

15
dev/generated/index.tsx Normal file
View File

@ -0,0 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error("Could not find root element to mount to");
}
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

Binary file not shown.

View File

@ -0,0 +1,5 @@
{
"name": "Kampüs Cadıları",
"description": "A modern, accessible web platform for the Turkish feminist organization Kampüs Cadıları, featuring news, translations, and community stories.",
"requestFramePermissions": []
}

1777
dev/generated/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"name": "kampüs-cadıları",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.3",
"react-dom": "^19.2.3",
"lucide-react": "^0.562.0"
},
"devDependencies": {
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
}

View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"types": [
"node"
],
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}

46
dev/generated/types.ts Normal file
View File

@ -0,0 +1,46 @@
export type Category =
| 'FEMINIST_GUNDEM'
| 'CEVIRI'
| 'KADINLARDAN_GELENLER'
| 'CADI_YAYINLARI'
| 'TARIHCEMIZ';
export interface Article {
id: string;
title: string;
excerpt: string;
category: Category;
author: string;
date: string;
imageUrl: string;
readTime?: string;
}
export interface NavItem {
label: string;
href: string;
isActive?: boolean;
}
export interface Story {
id: string;
title: string;
subtitle: string;
author: string;
authorImage: string;
content: string;
imageUrl: string;
}
export type PublicationType = 'FANZIN' | 'RAPOR' | 'BROSUR' | 'KITAP';
export interface Publication {
id: string;
title: string;
type: PublicationType;
date: string;
coverUrl: string;
description: string;
downloadUrl?: string;
}

View File

@ -0,0 +1,23 @@
import path from 'path';
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.', '');
return {
server: {
port: 3000,
host: '0.0.0.0',
},
plugins: [react()],
define: {
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
}
}
};
});

View File

@ -1,237 +0,0 @@
## Color Palette
## Typography
### Font Families
#### Primary Fonts
- **Inter** - Body text, primary sans-serif (weights: 300, 400, 500, 600)
- **Oswald** - Headlines, brand font, uppercase display text (weights: 400, 500, 600)
#### Extended Font Library (Available but not actively used)
The page loads 20+ Google Fonts for maximum flexibility:
- Sans-serif: Geist, Roboto, Montserrat, Poppins, Bricolage Grotesque, Plus Jakarta Sans, Manrope, Space Grotesk, Work Sans, DM Sans, Quicksand, Nunito, Google Sans Flex
- Serif: Playfair Display, Instrument Serif, Merriweather, PT Serif, Newsreader
- Monospace: Geist Mono, Space Mono
### Type Scale
- **7xl-8xl** (`4.5rem` - `6rem`) - Hero headlines ("PLAY WONDER")
- **4xl-5xl** (`2.25rem` - `3rem`) - Section headings
- **3xl-4xl** (`1.875rem` - `2.25rem`) - Card titles, subsection headings
- **2xl** (`1.5rem`) - Secondary headings
- **xl** (`1.25rem`) - Large body text
- **lg** (`1.125rem`) - Emphasized body text
- **base** (`1rem`) - Standard body text
- **sm** (`0.875rem`) - Small text, labels
- **xs** (`0.75rem`) - Micro copy, metadata
### Font Weights
- **Light (300)** - Subtle descriptive text
- **Regular (400)** - Body text
- **Medium (500)** - Emphasized text
- **Semibold (600)** - Strong emphasis
- **Bold (700)** - Headlines, CTAs
### Typography Utilities
- **Tracking**: `tracking-tighter` (-0.05em), `tracking-tight` (-0.025em), `tracking-wide` (0.025em), `tracking-wider` (0.05em), `tracking-widest` (0.1em)
- **Leading**: `leading-tight` (1.25), `leading-[1.1]`, `leading-[0.85]`, `leading-none` (1), `leading-relaxed` (1.625)
- **Transform**: `uppercase` for labels and category tags
## Layout System
### Grid Structure
The page uses a 12-column responsive grid:
- **Hero Section**: `lg:grid-cols-12` split into 4-8 columns (sidebar + main visual)
- **Content Sections**: `md:grid-cols-2`, `lg:grid-cols-3`, `lg:grid-cols-4`
- **Bento Grid**: Asymmetric layout with `col-span-1`, `col-span-2`, `row-span-2` variations
### Spacing Scale (Tailwind)
- **Small**: `gap-1` (0.25rem), `gap-2` (0.5rem), `gap-3` (0.75rem)
- **Medium**: `gap-4` (1rem), `gap-6` (1.5rem), `gap-8` (2rem)
- **Large**: `gap-12` (3rem), `gap-16` (4rem)
- **Padding**: Consistent use of `p-8`, `p-12`, `px-6`, `py-24` for sections
### Container
- **Max Width**: `max-w-7xl` (80rem) centered with `mx-auto`
- **Horizontal Padding**: `px-6` mobile, `lg:px-12` desktop
## Design Patterns
### Navigation
- **Hamburger Menu**: Minimalist top-left icon with dropdown
- **Dropdown Menu**: Rounded corners (`rounded-2xl`), shadow (`shadow-[0_8px_30px_rgb(0,0,0,0.12)]`), clean white card
- **Menu Items**: Hover states with `hover:bg-blue-50/50`, icon + text layout
### Cards & Components
### Buttons
#### Primary CTA
- Background: `bg-blue-700/50`, `hover:bg-blue-700`
- Shape: `rounded-full`, `px-6 py-3`
- Text: Uppercase, `tracking-wide`, white with arrow icon
#### Icon Buttons
- Shape: `rounded-full`, `w-10 h-10`
- Border: `border border-blue-400`
- Hover: `hover:bg-blue-500`
#### Secondary Links
- Style: Underlined text with `border-b`
- Hover: `hover:border-white`, `hover:opacity-75`
### Decorative Elements
#### Abstract Shapes
- **Blurred Blobs**: `blur-3xl`, `opacity-20`, `mix-blend-multiply`, animated
- **Background Patterns**: Radial gradient dots (`bg-[radial-gradient(circle,transparent_20%,#eff6ff_20%)]`)
- **Typography Overlay**: Giant "GAME" text with `mix-blend-multiply`, `opacity-10`
#### Borders & Dividers
- **Subtle Lines**: `h-0.5 w-12`, `h-px`, `w-px` for accent lines
- **Border Colors**: `border-gray-100`, `border-blue-500/20`, `border-white/30` (with transparency)
## Animations
### Keyframe Animations
```css
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideIn { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
@keyframes blurIn { from { opacity: 0; filter: blur(12px); } to { opacity: 1; filter: blur(0); } }
```
### Animation Classes
- **Fade In**: `animate-fade-in` - 0.8s ease-out
- **Slide In**: `animate-slide-in` - 0.8s cubic-bezier(0.16, 1, 0.3, 1)
- **Blur In**: `animate-blur-in` - 1s ease-out
### Delay Classes
- `delay-75` (75ms), `delay-100` (100ms), `delay-150` (150ms)
- `delay-200` (200ms), `delay-300` (300ms), `delay-500` (500ms), `delay-700` (700ms)
### Transition Effects
- **All Properties**: `transition-all duration-500 ease-out`
- **Colors**: `transition-colors`
- **Transform**: `transition-transform`
- **Opacity**: `transition-opacity`
### Hover Animations
- **Scale**: `hover:scale-110`, `group-hover:scale-105`
- **Translate**: `group-hover:translate-x-1`
- **Color Shift**: Text and background color changes on hover
## Visual Effects
### Shadows
- **Card Shadows**: `shadow-xl`, `shadow-2xl`
- **Custom Shadow**: `shadow-[0_8px_30px_rgb(0,0,0,0.12)]`
- **Inner Shadow**: `shadow-inner` for recessed elements
### Backdrop Effects
- **Blur**: `backdrop-blur-sm`, `backdrop-blur-md` for glass morphism
- **Background Opacity**: `bg-white/40`, `bg-white/50`, `bg-gray-200/30`
### Blend Modes
- **Multiply**: `mix-blend-multiply` for layered effects
- **Opacity Variations**: `opacity-10`, `opacity-20`, `opacity-50`, `opacity-80`
### Border Radius
- **Small**: `rounded-md` (0.375rem), `rounded-lg` (0.5rem), `rounded-xl` (0.75rem)
- **Large**: `rounded-2xl` (1rem), `rounded-3xl` (1.5rem)
- **Custom**: `rounded-[2.5rem]`, `rounded-[3rem]`
- **Full**: `rounded-full` for circles and pills
## Iconography
### Icon Library
**Lucide Icons** - Consistent stroke-based icon system
- Stroke width: `1.5`
- Common sizes: `w-4 h-4` (small), `w-5 h-5` (medium), `w-6 h-6` (large), `w-8 h-8` (XL)
### Icon Categories
- **Navigation**: menu, arrow-left, arrow-right, home
- **Gaming**: gamepad-2, zap, shapes, rocket, sword, shield
- **Shopping**: shopping-bag
- **UI**: settings, trophy, crown, layers
## Section Breakdowns
### 1. Hero Section
- **Layout**: Split-screen (4-8 column grid)
- **Left**: White sidebar with navigation, headline, category selector
- **Right**: Slate-50 background with abstract shapes and background image
- **Typography Overlay**: Massive "GAME" text at `text-[40rem]`
- **Animation**: Staggered fade-in and slide-in effects
### 2. Section 2
- **Background**: `bg-blue-600`
- **Container**: `bg-blue-700`, rounded top corners
- **Layout**: Asymmetric grid - 1 large (2x2), 4 medium (1x1)
- **Featured Card**: gradient background, icon, title, CTA
- **Image Cards**: Full-bleed background images with text overlay
### 3. Featured Categories
- **Background**: `bg-gray-700` with animated gradient blobs
- **Cards**: Icon + text, hover scale effect
- **Decorative**: Purple and blue blurred circles with `mix-blend-multiply`
### 4. Section 4
- **Layout**: 1-3 column split (text + logo grid)
- **Logo Grid**: 4x2 grid with borders, hover states
- **Style**: Icons as placeholder logos, consistent spacing with gaps
### 5. Section 5
- **Layout**: 2-column (image + text)
- **Left**: Postage stamp effect with radial gradient pattern
- **Right**: `bg-blue-700` with metadata, title, navigation dots
## Responsive Behavior
### Breakpoints
- **Mobile**: Default styles (< 768px)
- **Tablet**: `md:` prefix (≥ 768px)
- **Desktop**: `lg:` prefix (≥ 1024px)
### Mobile Adaptations
- Single column layouts (`grid-cols-1`)
- Smaller padding (`p-8``lg:p-12`)
- Reduced text sizes (`text-7xl``lg:text-8xl`)
- Stacked flex layouts (`flex-col``md:flex-row`)
## Accessibility Features
### Text Contrast
- White text on blue 600+ backgrounds
- Dark slate text on white/light backgrounds
- Semi-transparent overlays on images ensure readability
### Interactive Elements
- Focus states: `focus:outline-none focus:ring-2 focus:ring-blue-100`
- Cursor indicators: `cursor-pointer` on interactive elements
- Clear hover states with color/opacity changes
### Selection Styling
```css
selection:bg-pink-500 selection:text-white
```
## Design Philosophy
The design follows modern web trends:
1. **Bold Typography**: Large, tight headlines with uppercase treatments
2. **Vibrant Colors**: Saturated blues and oranges with high contrast
3. **Smooth Animations**: Subtle entrance effects and hover states
4. **Card-Based Layout**: Bento grid asymmetry creates visual interest
5. **Glass Morphism**: Backdrop blur and transparency effects
6. **3D Touches**: Phone mockup rotation, layered shadows
7. **Minimalist UI**: Clean icons, simple buttons, generous whitespace
## Technical Implementation
- **Framework**: Astro with Tailwind CSS
- **CDN Resources**: Tailwind CDN, Google Fonts CDN, Lucide icons CDN
- **Image Hosting**: Supabase storage for background images
- **Optimization**: AVIF/WebP images, lazy loading implied
- **Responsive Images**: Multiple resolutions (800w, 1600w)

View File

@ -26,6 +26,7 @@
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"astro": "^5.16.4",
"lucide-react": "^0.562.0",
"lunr": "^2.3.9",
"marked": "^17.0.1",
"react": "^19.2.1",

12
pnpm-lock.yaml generated
View File

@ -38,6 +38,9 @@ importers:
astro:
specifier: ^5.16.4
version: 5.16.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(typescript@5.9.3)
lucide-react:
specifier: ^0.562.0
version: 0.562.0(react@19.2.1)
lunr:
specifier: ^2.3.9
version: 2.3.9
@ -1963,6 +1966,11 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react@0.562.0:
resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
lunr@2.3.9:
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
@ -4632,6 +4640,10 @@ snapshots:
dependencies:
yallist: 3.1.1
lucide-react@0.562.0(react@19.2.1):
dependencies:
react: 19.2.1
lunr@2.3.9: {}
magic-string@0.30.21:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

Before

Width:  |  Height:  |  Size: 749 B

View File

@ -1,132 +0,0 @@
# Placeholder Media Assets
This template references several media files that need to be replaced with your own content.
## Required Assets to Replace
### Images (`src/assets/`)
The following images in `src/assets/` are currently placeholders and should be replaced with your own:
1. **nicholai-closeup-portrait.avif**
- Purpose: Used in various components
- Recommended dimensions: 800x800px minimum
- Format: AVIF (use `pnpm convert:avif:all` to convert)
2. **nicholai-medium-portrait.avif**
- Purpose: Default OG (Open Graph) image for social media shares
- Referenced in: `src/components/BaseHead.astro:6`
- Recommended dimensions: 1200x630px (Open Graph standard)
- Format: AVIF
3. **g-star-image.avif**
- Purpose: Project/case study imagery
- Referenced in: Blog post hero images
- Recommended dimensions: 1920x1080px or larger
- Format: AVIF
4. **claude-nuke.avif**
- Purpose: Blog post hero image
- Recommended dimensions: 1920x1080px or larger
- Format: AVIF
5. **foxrenderfarm-arch-linux.avif**
- Purpose: Blog post hero image
- Recommended dimensions: 1920x1080px or larger
- Format: AVIF
6. **PENCIL_1.3.1_wipe.avif**
- Purpose: Blog post hero image
- Recommended dimensions: 1920x1080px or larger
- Format: AVIF
### Videos (`public/media/`)
The following video files need to be replaced:
1. **placeholder-video.mp4**
- Purpose: Featured project video, blog post demonstrations
- Referenced in: `src/content/sections/featured-project.mdx:18`
- Recommended: MP4 format, H.264 codec
- Max file size: Keep under 50MB for good performance
- Dimensions: 1920x1080px or 3840x2160px (4K)
2. **GSTR_01_260_breakdown.mp4** (Remove/Replace)
- Purpose: Project breakdown video
- Should be replaced with your own project video
3. **GSTR_03_070_v10_breakdown_v01.mp4** (Remove/Replace)
- Purpose: Project breakdown video
- Should be replaced with your own project video
### Favicons (`public/`)
These should be customized with your own branding:
1. **favicon.ico**
2. **favicon-32.png**
3. **favicon-192.png**
4. **apple-touch-icon.png**
5. **favicon.svg**
6. **favicon.JPG** (appears to be personal, should be replaced)
### Profile Images (`public/media/`)
1. **nicholai-wild-portrait.JPEG**
- Replace with your own portrait
- Recommended dimensions: 1200x1200px or larger
## How to Add Your Own Assets
### For Images
1. **Place your images** in the appropriate directory:
- Processed images: `src/assets/` (these get optimized by Astro)
- Static images: `public/media/` (served as-is)
2. **Convert to AVIF** for best performance:
```bash
pnpm convert:avif:all
```
3. **Update references**:
- Blog post frontmatter (heroImage field)
- Section MDX files in `src/content/sections/`
- Default OG image in `src/components/BaseHead.astro`
### For Videos
1. **Optimize your video**:
- Use H.264 codec for MP4
- Keep file size reasonable (< 50MB if possible)
- Consider providing multiple resolutions
2. **Place in `public/media/`**
3. **Update references**:
- `src/content/sections/featured-project.mdx` (videoUrl field)
- Blog posts where videos are embedded
### For Favicons
1. **Generate favicons** from your logo/brand mark using a tool like:
- [favicon.io](https://favicon.io)
- [RealFaviconGenerator](https://realfavicongenerator.net)
2. **Replace files in `public/`**
## Quick Replace Checklist
- [ ] Default OG image (`src/assets/nicholai-medium-portrait.avif`)
- [ ] All blog post hero images
- [ ] Featured project video (`/media/placeholder-video.mp4`)
- [ ] Favicon set (ico, png, svg)
- [ ] Remove or replace personal videos (GSTR files)
- [ ] Profile/portrait images
## Notes
- **AVIF format** is recommended for images as it provides excellent compression
- The template includes a conversion utility: `pnpm convert:avif:all`
- Always optimize images before uploading (compress, resize)
- Consider using a CDN for large media files in production

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

BIN
public/media/logo_black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
public/media/logo_col.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
public/media/logo_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

211
src/components/App.tsx Normal file
View File

@ -0,0 +1,211 @@
import React, { useState, useEffect } from 'react';
import { Menu, Search, Instagram, Twitter, Facebook, Youtube } from 'lucide-react';
import { Logo } from './Logo';
import { Home } from './Home';
import { Publications } from './Publications';
import { CONTENT } from '../content/content';
type View = 'HOME' | 'PUBLICATIONS';
const App: React.FC = () => {
const [isNavOpen, setIsNavOpen] = useState(false);
const [lang, setLang] = useState<'TR' | 'EN'>('TR');
const [currentView, setCurrentView] = useState<View>('HOME');
// Sync URL with currentView on mount
useEffect(() => {
const path = window.location.pathname;
if (path === '/publications') {
setCurrentView('PUBLICATIONS');
}
}, []);
// Update URL when view changes
useEffect(() => {
const path = currentView === 'PUBLICATIONS' ? '/publications' : '/';
window.history.pushState({ view: currentView }, '', path);
}, [currentView]);
// Handle browser back/forward
useEffect(() => {
const handlePopState = () => {
const path = window.location.pathname;
if (path === '/publications') {
setCurrentView('PUBLICATIONS');
} else {
setCurrentView('HOME');
}
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
const t = CONTENT[lang];
return (
<div className="bg-brand-lilac text-brand-deep antialiased font-sans w-full selection:bg-brand-purple selection:text-white">
{/* --- FOOTER (FIXED BEHIND CONTENT) --- */}
<footer className="fixed bottom-0 left-0 w-full h-[600px] bg-brand-deep flex flex-col justify-between p-8 md:p-12 z-0">
<div className="flex justify-between items-start">
<Logo variant="white" size="medium" className="w-16 h-16 md:w-24 md:h-24" />
<div className="text-right">
<h2 className="text-[15vw] font-display font-bold text-brand-purple leading-none select-none opacity-50">{t.footer.feminist}</h2>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-white font-display font-bold tracking-wider text-sm md:text-lg">
<button onClick={() => setCurrentView('HOME')} className="text-left hover:text-brand-lilac transition-colors">{t.footer.about}</button>
<button onClick={() => setCurrentView('PUBLICATIONS')} className="text-left hover:text-brand-lilac transition-colors">{t.footer.pubs}</button>
<a href="#" className="hover:text-brand-lilac transition-colors">{t.footer.volunteer}</a>
<a href="#" className="hover:text-brand-lilac transition-colors">{t.footer.contact}</a>
</div>
<div className="border-t border-white/20 pt-8 flex justify-between items-end text-brand-lilac text-sm">
<div>
<p>{t.footer.rights}</p>
<p>{t.footer.city}</p>
</div>
<div className="flex gap-4">
{[Instagram, Twitter, Facebook, Youtube].map((Icon, i) => (
<a key={i} href="#" className="hover:text-white transition-colors"><Icon size={24} /></a>
))}
</div>
</div>
</footer>
{/* --- MAIN CONTENT (SCROLLS OVER FOOTER) --- */}
<main className="relative z-10 bg-brand-lilac mb-[600px] shadow-[0_50px_100px_#2D0F41] min-h-screen">
{/* --- HEADER --- */}
<nav
className={`fixed w-full z-50 transition-all duration-300 ease-in-out py-6 px-6 lg:px-12 flex justify-between items-center bg-brand-lilac/90 backdrop-blur-sm border-b-4 border-brand-deep shadow-lg`}
>
<div className="flex items-center gap-3">
<button
onClick={() => setIsNavOpen(!isNavOpen)}
className="p-3 border-2 border-brand-deep text-brand-deep hover:bg-brand-deep hover:text-white transition-colors lg:hidden rounded-lg"
>
<Menu className="w-6 h-6" />
</button>
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setCurrentView('HOME')}
>
<Logo variant="black" size="small" className="w-12 h-12" />
<span className="hidden lg:block font-display font-bold text-2xl tracking-tighter text-brand-deep">
KAMPÜS CADILARI
</span>
</div>
</div>
<div className="hidden lg:flex gap-8">
<button
onClick={() => setCurrentView('HOME')}
className={`text-lg font-display font-bold tracking-wider uppercase transition-colors relative group ${currentView === 'HOME' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'}`}
>
{t.nav.home}
<span className={`absolute -bottom-1 left-0 h-1 bg-brand-purple transition-all duration-300 ${currentView === 'HOME' ? 'w-full' : 'w-0 group-hover:w-full'}`}></span>
</button>
<button
onClick={() => setCurrentView('PUBLICATIONS')}
className={`text-lg font-display font-bold tracking-wider uppercase transition-colors relative group ${currentView === 'PUBLICATIONS' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'}`}
>
{t.nav.publications}
<span className={`absolute -bottom-1 left-0 h-1 bg-brand-purple transition-all duration-300 ${currentView === 'PUBLICATIONS' ? 'w-full' : 'w-0 group-hover:w-full'}`}></span>
</button>
{[
t.nav.translation,
t.nav.agenda
].map(item => (
<a
key={item}
href="#"
className="text-brand-deep hover:text-brand-purple text-lg font-display font-bold tracking-wider uppercase transition-colors relative group"
>
{item}
<span className="absolute -bottom-1 left-0 w-0 h-1 bg-brand-purple transition-all duration-300 group-hover:w-full"></span>
</a>
))}
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2 font-display font-bold text-brand-deep text-lg bg-white/50 px-3 py-1 rounded-full border border-brand-deep/20">
<button
onClick={() => setLang('TR')}
className={`${lang === 'TR' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'} transition-colors`}
>
TR
</button>
<span className="text-gray-400">/</span>
<button
onClick={() => setLang('EN')}
className={`${lang === 'EN' ? 'text-brand-purple' : 'text-brand-deep hover:text-brand-purple'} transition-colors`}
>
EN
</button>
</div>
<Search className="w-7 h-7 text-brand-deep cursor-pointer hover:text-brand-purple transition-colors" strokeWidth={2.5} />
<button className="hidden md:block bg-brand-purple text-white px-8 py-3 font-display font-bold text-base tracking-widest uppercase border-2 border-brand-deep shadow-[4px_4px_0px_#2D0F41] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none transition-all rounded-lg">
{t.nav.join}
</button>
</div>
{/* Mobile Menu */}
{isNavOpen && (
<div className="absolute top-full left-0 right-0 bg-brand-lilac border-b-4 border-brand-deep shadow-xl lg:hidden p-6">
<nav className="flex flex-col gap-4">
<button
onClick={() => {
setCurrentView('HOME');
setIsNavOpen(false);
}}
className={`text-lg font-display font-bold text-left ${currentView === 'HOME' ? 'text-brand-purple' : 'text-brand-deep'}`}
>
{t.nav.home}
</button>
<button
onClick={() => {
setCurrentView('PUBLICATIONS');
setIsNavOpen(false);
}}
className={`text-lg font-display font-bold text-left ${currentView === 'PUBLICATIONS' ? 'text-brand-purple' : 'text-brand-deep'}`}
>
{t.nav.publications}
</button>
{[
t.nav.translation,
t.nav.agenda
].map(item => (
<a
key={item}
href="#"
className="text-lg font-display font-bold text-brand-deep"
onClick={() => setIsNavOpen(false)}
>
{item}
</a>
))}
<div className="h-px bg-brand-deep/20 my-4"></div>
<button className="bg-brand-purple text-white px-8 py-3 font-display font-bold text-base tracking-widest uppercase border-2 border-brand-deep shadow-[4px_4px_0px_#2D0F41] rounded-lg w-full">
{t.nav.join}
</button>
</nav>
</div>
)}
</nav>
{/* --- VIEW CONTENT --- */}
{currentView === 'HOME' ? <Home t={t} /> : <Publications t={t} />}
</main>
</div>
);
};
export default App;

View File

@ -0,0 +1,80 @@
import React from 'react';
import { Article, Category } from '../types';
import { ArrowRight, Clock } from 'lucide-react';
interface ArticleCardProps {
article: Article;
}
const getCategoryLabel = (category: Category) => {
switch (category) {
case 'FEMINIST_GUNDEM': return 'Feminist Gündem';
case 'CEVIRI': return 'Çeviri';
case 'KADINLARDAN_GELENLER': return 'Kadınlardan Gelenler';
case 'CADI_YAYINLARI': return 'Cadı Yayınları';
default: return 'Genel';
}
};
const getCategoryColor = (category: Category) => {
switch (category) {
case 'FEMINIST_GUNDEM': return 'bg-brand-red text-white';
case 'CEVIRI': return 'bg-brand-purple text-white';
case 'KADINLARDAN_GELENLER': return 'bg-brand-lilacDark text-brand-purple';
default: return 'bg-gray-200 text-gray-800';
}
};
export const ArticleCard: React.FC<ArticleCardProps> = ({ article }) => {
return (
<div className="group bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-[0_10px_40px_rgba(84,25,139,0.15)] transition-all duration-300 transform hover:-translate-y-1 flex flex-col h-full border border-gray-100">
{/* Image Container */}
<div className="relative h-56 overflow-hidden">
<img
src={article.imageUrl}
alt={article.title}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<div className="absolute top-4 left-4">
<span className={`px-3 py-1 rounded-md text-xs font-bold tracking-wider uppercase ${getCategoryColor(article.category)}`}>
{getCategoryLabel(article.category)}
</span>
</div>
</div>
{/* Content */}
<div className="p-6 flex flex-col flex-grow">
<div className="flex items-center text-xs text-gray-500 mb-3 space-x-3">
<span className="font-medium text-brand-purple">{article.author}</span>
<span className="w-1 h-1 rounded-full bg-gray-300"></span>
<span>{article.date}</span>
{article.readTime && (
<>
<span className="w-1 h-1 rounded-full bg-gray-300"></span>
<span className="flex items-center">
<Clock size={12} className="mr-1" />
{article.readTime}
</span>
</>
)}
</div>
<h3 className="text-xl font-bold text-brand-deep mb-3 leading-tight group-hover:text-brand-purple transition-colors font-display">
{article.title}
</h3>
<p className="text-gray-500 text-sm leading-relaxed mb-6 line-clamp-3">
{article.excerpt}
</p>
<div className="mt-auto pt-4 border-t border-gray-100 flex items-center justify-between">
<button className="text-sm font-semibold text-brand-purple flex items-center group/btn">
Devamını Oku
<ArrowRight size={16} className="ml-2 transform group-hover/btn:translate-x-1 transition-transform" />
</button>
</div>
</div>
</div>
);
};

View File

@ -1,54 +1,64 @@
---
import '../styles/global.css';
import type { ImageMetadata } from 'astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
interface Props {
title: string;
description: string;
image?: ImageMetadata;
type?: 'website' | 'article';
publishedTime?: Date;
modifiedTime?: Date;
title?: string;
description?: string;
image?: ImageMetadata;
type?: 'website' | 'article';
publishedTime?: Date;
modifiedTime?: Date;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const {
title,
description,
image,
type = 'website',
publishedTime,
modifiedTime,
title = SITE_TITLE,
description = SITE_DESCRIPTION,
image,
type = 'website',
publishedTime,
modifiedTime,
} = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="generator" content={Astro.generator} />
<!-- Font preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
{image && (
<>
<meta property="og:image" content={new URL(image.src, Astro.url)} />
<meta name="twitter:image" content={new URL(image.src, Astro.url).toString()} />
</>
)}
<!-- Open Graph / Facebook -->
<meta property="og:type" content={type} />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
{image && <meta property="og:image" content={new URL(image.src, Astro.url)} />}
{publishedTime && <meta property="article:published_time" content={publishedTime.toISOString()} />}
{modifiedTime && <meta property="article:modified_time" content={modifiedTime.toISOString()} />}
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
{image && <meta property="twitter:image" content={new URL(image.src, Astro.url)} />}
<!-- Theme Color -->
<meta name="theme-color" content="#6B2C91" />

View File

@ -1,25 +0,0 @@
---
import FormattedDate from './FormattedDate.astro';
interface Props {
title: string;
description: string;
pubDate: Date;
href: string;
readTime?: string;
}
const { title, description, pubDate, href, readTime } = Astro.props;
---
<article>
<a href={href}>
<h3>{title}</h3>
</a>
<p>
<FormattedDate date={pubDate} />
{readTime && <span> • {readTime}</span>}
</p>
<p>{description}</p>
<a href={href}>Read more</a>
</article>

32
src/components/Button.tsx Normal file
View File

@ -0,0 +1,32 @@
import React from 'react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
children: React.ReactNode;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
className = '',
children,
...props
}) => {
const baseStyles = "inline-flex items-center justify-center font-semibold transition-all duration-300 rounded-full px-8 py-3 text-sm tracking-wide disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-105 active:scale-95";
const variants = {
primary: "bg-brand-purple text-white hover:bg-[#3D1265] shadow-lg shadow-brand-purple/20",
secondary: "bg-brand-red text-white hover:bg-[#c4161d] shadow-lg shadow-brand-red/20",
outline: "border-2 border-brand-purple text-brand-purple hover:bg-brand-purple hover:text-white bg-transparent",
ghost: "bg-transparent text-white hover:bg-white/10"
};
return (
<button
className={`${baseStyles} ${variants[variant]} ${className}`}
{...props}
>
{children}
</button>
);
};

View File

@ -1,17 +0,0 @@
---
interface Props {
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
</time>

291
src/components/Home.tsx Normal file
View File

@ -0,0 +1,291 @@
import React, { useEffect, useRef, useState } from 'react';
import { ArrowRight, Star, Users, Venus, Heart, PenTool, MoveRight } from 'lucide-react';
import { Reveal } from './Reveal';
interface HomeProps {
t: any;
}
const ScribbleStar = ({ className }: { className?: string }) => (
<svg viewBox="0 0 100 100" className={className} fill="currentColor">
<path d="M50 0L61 35L98 35L68 57L79 91L50 70L21 91L32 57L2 35L39 35L50 0Z" />
</svg>
);
export const Home: React.FC<HomeProps> = ({ t }) => {
// Refs for Scroll Animations
const heroRef = useRef<HTMLDivElement>(null);
const heroTextTopRef = useRef<HTMLDivElement>(null);
const heroTextBottomRef = useRef<HTMLDivElement>(null);
const heroImageRef = useRef<HTMLDivElement>(null);
const horizontalSectionRef = useRef<HTMLDivElement>(null);
const horizontalTrackRef = useRef<HTMLDivElement>(null);
const newsRef = useRef<HTMLDivElement>(null);
// State for horizontal scroll calculation
const [scrollProgress, setScrollProgress] = useState(0);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
if (mediaQuery.matches) return;
let rafId: number;
const handleScroll = () => {
const scrollY = window.scrollY;
const viewportHeight = window.innerHeight;
rafId = requestAnimationFrame(() => {
// --- HERO ANIMATION ---
if (heroRef.current && heroTextTopRef.current && heroTextBottomRef.current && heroImageRef.current) {
const heroProgress = Math.min(scrollY / viewportHeight, 1);
heroTextTopRef.current.style.transform = `translateX(-${heroProgress * 30}vw) rotate(-2deg)`;
heroTextBottomRef.current.style.transform = `translateX(${heroProgress * 30}vw) rotate(2deg)`;
heroImageRef.current.style.transform = `translateY(${heroProgress * 50}px) rotate(${heroProgress * 5}deg)`;
}
// --- HORIZONTAL SCROLL CALCULATION ---
if (horizontalSectionRef.current && horizontalTrackRef.current) {
const sectionTop = horizontalSectionRef.current.offsetTop;
const sectionHeight = horizontalSectionRef.current.offsetHeight;
const scrollDistanceFromTop = scrollY - sectionTop;
let progress = scrollDistanceFromTop / (sectionHeight - viewportHeight);
progress = Math.max(0, Math.min(progress, 1));
setScrollProgress(progress);
}
});
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
cancelAnimationFrame(rafId);
};
}, []);
// Update Horizontal Transform
useEffect(() => {
if (horizontalTrackRef.current) {
const trackWidth = horizontalTrackRef.current.scrollWidth;
const viewportWidth = window.innerWidth;
const maxTranslate = trackWidth - viewportWidth;
if (maxTranslate > 0) {
horizontalTrackRef.current.style.transform = `translateX(-${scrollProgress * maxTranslate}px)`;
}
}
}, [scrollProgress]);
const newsImages = [
{ image: "https://images.unsplash.com/photo-1596700813955-442436d423e0?auto=format&fit=crop&q=80&w=600", height: "h-96" },
{ image: "https://images.unsplash.com/photo-1572691242698-b7f3001844b2?auto=format&fit=crop&q=80&w=600", height: "h-[32rem]" }
];
const currentNewsItems = t.news.items.map((item: any, index: number) => ({
...item,
...newsImages[index]
}));
return (
<>
{/* --- HERO SECTION --- */}
<div ref={heroRef} className="relative h-screen min-h-[800px] flex items-center justify-center bg-brand-lilac overflow-hidden">
<div className="absolute top-32 left-[10%] w-12 h-12 text-brand-deep animate-float">
<ScribbleStar className="w-full h-full" />
</div>
<div className="absolute bottom-32 right-[10%] w-16 h-16 text-brand-purple animate-float" style={{ animationDelay: '1s' }}>
<ScribbleStar className="w-full h-full" />
</div>
<div className="absolute top-1/4 right-20 w-8 h-8 text-brand-red animate-pulse">
<Star className="w-full h-full fill-current" />
</div>
<div ref={heroImageRef} className="relative z-10 will-change-transform">
<div className="w-[85vw] md:w-[50vw] h-[55vh] md:h-[65vh] border-[4px] border-brand-deep bg-brand-deep relative shadow-[12px_12px_0px_#6B2C91] rounded-[2rem] overflow-hidden group rotate-1 hover:rotate-0 transition-all duration-500">
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1555848962-6e79363ec58f?auto=format&fit=crop&q=80&w=1600')] bg-cover bg-center transition-all duration-700"></div>
<div className="absolute inset-0 opacity-20 bg-[url('https://www.transparenttextures.com/patterns/dust.png')] pointer-events-none"></div>
<div className="absolute inset-0 bg-brand-purple/20 mix-blend-multiply pointer-events-none"></div>
<div className="absolute bottom-6 right-6 transform rotate-[-3deg]">
<div className="bg-brand-red text-white px-6 py-2 font-marker text-xl tracking-widest border-2 border-brand-deep shadow-[4px_4px_0px_#2D0F41] rounded-full">
{t.hero.est}
</div>
</div>
</div>
</div>
<div className="absolute inset-0 z-20 pointer-events-none flex flex-col items-center justify-center">
<div className="w-full flex justify-center pb-20 md:pb-32">
<div ref={heroTextTopRef} className="origin-center transform -rotate-2">
<h1 className="text-5xl md:text-8xl lg:text-9xl font-display font-bold text-white bg-brand-deep px-8 py-4 border-4 border-white shadow-[8px_8px_0px_rgba(0,0,0,0.1)] rounded-xl inline-block tracking-tighter whitespace-nowrap leading-none">
{t.hero.women}
</h1>
</div>
</div>
<div className="w-full flex justify-center pt-20 md:pt-32 absolute top-1/2 mt-16 md:mt-0">
<div ref={heroTextBottomRef} className="origin-center transform rotate-1">
<h1 className="text-5xl md:text-8xl lg:text-9xl font-display font-bold text-brand-deep bg-brand-lilac px-8 py-4 border-4 border-brand-deep shadow-[12px_12px_0px_#ED1C24] rounded-xl inline-block tracking-tighter whitespace-nowrap leading-none">
{t.hero.future}
</h1>
</div>
</div>
</div>
<div className="absolute bottom-8 flex flex-col items-center gap-2 text-brand-deep z-30 animate-bounce">
<span className="font-display font-bold text-lg tracking-widest uppercase">{t.hero.scroll}</span>
<ArrowRight className="w-6 h-6 rotate-90" strokeWidth={3} />
</div>
</div>
{/* --- MARQUEE BANNER --- */}
<div className="bg-brand-deep py-6 overflow-hidden -rotate-1 scale-105 border-y-4 border-white z-30 relative shadow-xl">
<div className="flex animate-marquee w-fit">
{[...Array(2)].map((_, groupIndex) => (
<div key={groupIndex} className="flex gap-8 whitespace-nowrap px-4">
{[...Array(8)].map((_, i) => (
<span key={i} className="text-4xl font-display font-bold text-white uppercase tracking-widest flex items-center gap-8">
{t.marquee} <Heart className="fill-brand-red text-brand-red w-8 h-8" />
</span>
))}
</div>
))}
</div>
</div>
{/* --- HORIZONTAL SCROLL SECTION --- */}
<div ref={horizontalSectionRef} className="relative h-[400vh] z-20">
<div className="sticky top-0 h-screen overflow-hidden flex items-center bg-brand-surface bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] bg-fixed">
<div ref={horizontalTrackRef} className="flex gap-10 md:gap-20 px-10 md:px-20 items-center h-full will-change-transform">
<div className="flex-shrink-0 w-[80vw] md:w-[40vw] flex flex-col justify-center pl-12">
<div className="bg-brand-lilac p-8 border-4 border-brand-deep shadow-[12px_12px_0px_#2D0F41] rounded-[2rem] inline-block transform -rotate-2 max-w-full">
<h2 className="text-[clamp(3rem,8vw,6rem)] font-display font-bold text-brand-deep uppercase leading-[0.9] tracking-tighter break-words">
{t.values.title}
</h2>
</div>
<ArrowRight className="w-16 h-16 md:w-24 md:h-24 text-brand-purple mt-12 animate-pulse ml-8" />
</div>
{t.values.cards.map((card: any, idx: number) => (
<div key={idx} className="flex-shrink-0 w-[85vw] md:w-[45vw] h-[60vh] md:h-[70vh] bg-white border-4 border-brand-deep p-8 md:p-12 flex flex-col justify-between shadow-[20px_20px_0px_#6B2C91] relative rounded-3xl overflow-hidden group">
<div className={`absolute -top-10 -right-10 w-40 h-40 rounded-full opacity-20 ${idx === 1 ? 'bg-brand-red' : 'bg-brand-purple'}`}></div>
<div className="flex justify-between items-start">
{idx === 0 && <Venus className="w-20 h-20 text-brand-purple" strokeWidth={1.5} />}
{idx === 1 && <Users className="w-20 h-20 text-brand-red" strokeWidth={1.5} />}
{idx === 2 && <Star className="w-20 h-20 text-brand-deep" strokeWidth={1.5} />}
<span className="font-marker text-4xl text-brand-deep/20">#{card.id}</span>
</div>
<div>
<div className="bg-brand-lilac/50 inline-block px-4 py-2 rounded-lg mb-6 border border-brand-deep/10">
<h3 className="text-5xl md:text-7xl font-display font-bold text-brand-deep uppercase tracking-tight">{card.title}</h3>
</div>
<p className="text-xl md:text-2xl text-brand-deep font-medium leading-relaxed max-w-md font-sans">
{card.desc}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* --- NEWS SECTION --- */}
<div ref={newsRef} className="bg-brand-lilac py-32 relative z-20 overflow-hidden">
<div className="absolute top-0 left-0 w-full h-full opacity-10 pointer-events-none bg-[radial-gradient(#2D0F41_1px,transparent_1px)] [background-size:20px_20px]"></div>
<div className="max-w-7xl mx-auto px-6 lg:px-12 relative">
<Reveal className="mb-20 flex items-end justify-between border-b-4 border-brand-deep pb-6">
<div className="bg-brand-deep text-white px-8 py-4 transform -skew-x-12 inline-block shadow-[8px_8px_0px_#6B2C91]">
<h2 className="text-5xl md:text-8xl font-display font-bold uppercase tracking-tighter transform skew-x-12">
{t.news.header}
</h2>
</div>
<div className="hidden md:block">
<div className="bg-brand-red text-white font-marker text-3xl px-4 py-2 rotate-3 rounded-lg shadow-md border-2 border-brand-deep">
{t.news.year}
</div>
</div>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 lg:gap-24 relative z-10">
<div className="flex flex-col gap-16">
{currentNewsItems.map((item: any, idx: number) => (
<div key={idx} className="group cursor-pointer">
<div className={`${item.height} w-full overflow-hidden relative border-4 border-brand-deep bg-brand-deep rounded-2xl shadow-[12px_12px_0px_rgba(45,15,65,0.2)] group-hover:shadow-[16px_16px_0px_#6B2C91] group-hover:-translate-y-1 transition-all duration-300`}>
<img src={item.image} className="w-full h-full object-cover grayscale mix-blend-luminosity opacity-80 group-hover:grayscale-0 group-hover:opacity-100 transition-all duration-500" />
<div className="absolute top-4 left-4 bg-brand-purple text-white px-4 py-1 font-bold uppercase tracking-widest text-sm border-2 border-white rounded-full shadow-lg">
{item.category}
</div>
</div>
<div className="mt-6 pl-2">
<div className="flex items-center gap-3 text-brand-purple font-bold text-sm mb-2 font-mono">
<span>{item.date}</span>
<div className="h-px w-8 bg-brand-purple"></div>
</div>
<h3 className="text-3xl lg:text-4xl font-display font-bold text-brand-deep group-hover:text-brand-purple transition-colors leading-tight uppercase">
{item.title}
</h3>
</div>
</div>
))}
</div>
<div className="flex flex-col gap-16 pt-0 md:pt-32">
<div className="bg-[#FFF9C4] p-8 md:p-12 flex flex-col items-center justify-center text-center relative shadow-[0_10px_40px_rgba(0,0,0,0.1)] transform rotate-2 border border-gray-200">
<div className="absolute -top-6 left-1/2 -translate-x-1/2 w-32 h-10 bg-white/40 backdrop-blur-sm border-l border-r border-white/60 transform rotate-1 shadow-sm"></div>
<PenTool className="w-16 h-16 text-brand-deep mb-6" strokeWidth={1.5} />
<h3 className="text-5xl font-display font-bold text-brand-deep uppercase leading-[0.9] mb-4 tracking-tighter">
{t.news.sticky.title_1}<br/>{t.news.sticky.title_2}
</h3>
<p className="text-brand-deep font-medium mb-8 font-marker text-lg opacity-80">{t.news.sticky.desc}</p>
<button className="bg-brand-deep text-white px-8 py-3 font-bold uppercase tracking-widest hover:bg-brand-purple transition-colors rounded-lg shadow-lg">
{t.news.sticky.btn}
</button>
</div>
<div className="group cursor-pointer mt-8">
<div className="h-80 w-full overflow-hidden relative border-4 border-brand-deep rounded-2xl bg-white shadow-[12px_12px_0px_#9D4EDD] group-hover:-translate-y-1 transition-all">
<img src="https://images.unsplash.com/photo-1532012197267-da84d127e765?auto=format&fit=crop&q=80&w=600" className="w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-all duration-500" />
<div className="absolute top-4 left-4 bg-white text-brand-deep px-4 py-2 font-bold uppercase tracking-widest text-xs border-2 border-brand-deep rounded-lg">
{t.news.secondary.category}
</div>
</div>
<h3 className="mt-6 text-3xl font-display font-bold text-brand-deep group-hover:text-brand-purple transition-colors leading-tight uppercase pl-2">
{t.news.secondary.title}
</h3>
</div>
</div>
</div>
</div>
</div>
{/* --- NEWSLETTER SECTION --- */}
<div className="relative z-30 bg-brand-purple py-24 border-y-4 border-brand-deep bg-[url('https://www.transparenttextures.com/patterns/diagmonds-light.png')]">
<div className="max-w-7xl mx-auto px-6 lg:px-12 flex flex-col md:flex-row items-center justify-between gap-12">
<div className="md:w-1/2 text-white">
<h2 className="text-6xl md:text-[7rem] font-display font-bold uppercase leading-[0.8] mb-6 tracking-tighter drop-shadow-lg">
{t.newsletter.title_1}<br/><span className="text-brand-lilac">{t.newsletter.title_2}</span>
</h2>
<p className="text-xl font-medium opacity-90 max-w-md">{t.newsletter.desc}</p>
</div>
<div className="md:w-1/2 w-full">
<div className="flex border-4 border-brand-deep shadow-[16px_16px_0px_#2D0F41] bg-white rounded-xl overflow-hidden transform rotate-1 hover:rotate-0 transition-transform">
<input type="email" placeholder={t.newsletter.placeholder} className="flex-1 px-8 py-6 bg-transparent font-bold text-brand-deep placeholder:text-gray-400 focus:outline-none text-lg uppercase" />
<button className="px-10 bg-brand-deep text-white font-bold text-2xl hover:bg-brand-red transition-colors border-l-4 border-brand-deep">
<MoveRight className="w-8 h-8" />
</button>
</div>
</div>
</div>
</div>
</>
);
};

42
src/components/Logo.astro Normal file
View File

@ -0,0 +1,42 @@
---
export interface Props {
variant?: 'black' | 'col' | 'white';
size?: 'small' | 'medium' | 'large';
class?: string;
}
const { variant = 'black', size = 'small', class: className = '' } = Astro.props;
// Size dimensions (matching aspect ratio ~0.589)
const sizes = {
small: { width: 28, height: 48 },
medium: { width: 57, height: 96 },
large: { width: 118, height: 200 }
};
const { width, height } = sizes[size];
// Generate paths for responsive images
const basePath = `/media/logos/logo_${variant}_${size}`;
---
<picture class={className}>
<source
type="image/webp"
srcset={`${basePath}@1x.webp 1x, ${basePath}@2x.webp 2x`}
/>
<source
type="image/png"
srcset={`${basePath}@1x.png 1x, ${basePath}@2x.png 2x`}
/>
<img
src={`${basePath}@1x.png`}
alt="Kampüs Cadıları Logo"
width={width}
height={height}
loading="lazy"
decoding="async"
class={className}
/>
</picture>

46
src/components/Logo.tsx Normal file
View File

@ -0,0 +1,46 @@
import React from 'react';
interface LogoProps {
variant?: 'black' | 'col' | 'white';
size?: 'small' | 'medium' | 'large';
className?: string;
}
// Size dimensions (matching aspect ratio ~0.589)
const sizes = {
small: { width: 28, height: 48 },
medium: { width: 57, height: 96 },
large: { width: 118, height: 200 }
};
export const Logo: React.FC<LogoProps> = ({
variant = 'black',
size = 'small',
className = ''
}) => {
const { width, height } = sizes[size];
const basePath = `/media/logos/logo_${variant}_${size}`;
return (
<picture>
<source
type="image/webp"
srcSet={`${basePath}@1x.webp 1x, ${basePath}@2x.webp 2x`}
/>
<source
type="image/png"
srcSet={`${basePath}@1x.png 1x, ${basePath}@2x.png 2x`}
/>
<img
src={`${basePath}@1x.png`}
alt="Kampüs Cadıları Logo"
width={width}
height={height}
loading="lazy"
decoding="async"
className={className}
/>
</picture>
);
};

View File

@ -0,0 +1,211 @@
import React, { useState } from 'react';
import { Publication, PublicationType } from '../types';
import { Download, BookOpen } from 'lucide-react';
import { Reveal } from './Reveal';
interface PublicationsProps {
t: any;
}
const SAMPLE_PUBLICATIONS: Publication[] = [
{
id: '1',
title: 'KAMPÜS CADILARI FANZİN #12',
type: 'FANZIN',
date: 'OCAK 2025',
coverUrl: 'https://images.unsplash.com/photo-1544947950-fa07a98d237f?auto=format&fit=crop&q=80&w=400',
description: 'Yeni dönem, yeni mücadeleler. Kampüslerdeki cadı avına karşı sesimizi yükseltiyoruz.',
},
{
id: '2',
title: 'GÜVENLİ KAMPÜS REHBERİ',
type: 'BROSUR',
date: 'ARALIK 2024',
coverUrl: 'https://images.unsplash.com/photo-1535905557558-afc4877a26fc?auto=format&fit=crop&q=80&w=400',
description: 'Cinsel taciz ve saldırıya karşı haklarımız, başvuru mekanizmaları ve dayanışma ağları.',
},
{
id: '3',
title: 'AKADEMİDE CİNSİYETÇİLİK RAPORU',
type: 'RAPOR',
date: 'KASIM 2024',
coverUrl: 'https://images.unsplash.com/photo-1555449377-a8b411132a58?auto=format&fit=crop&q=80&w=400',
description: 'Türkiye genelinde 50 üniversitede yapılan araştırmanın çarpıcı sonuçları.',
},
{
id: '4',
title: '8 MART ÖZEL SAYI',
type: 'FANZIN',
date: 'MART 2024',
coverUrl: 'https://images.unsplash.com/photo-1532012197267-da84d127e765?auto=format&fit=crop&q=80&w=400',
description: 'Geceyi de, sokakları da, meydanları da istiyoruz! Feminist isyan her yerde.',
},
{
id: '5',
title: 'CADI SÖZLÜĞÜ',
type: 'KITAP',
date: 'EYLÜL 2023',
coverUrl: 'https://images.unsplash.com/photo-1512820790803-83ca734da794?auto=format&fit=crop&q=80&w=400',
description: 'Feminizmin temel kavramları, bizden kelimeler ve cadıların dili.',
},
];
export const Publications: React.FC<PublicationsProps> = ({ t }) => {
const [filter, setFilter] = useState<PublicationType | 'ALL'>('ALL');
const filteredPublications = filter === 'ALL'
? SAMPLE_PUBLICATIONS
: SAMPLE_PUBLICATIONS.filter(p => p.type === filter);
return (
<div className="bg-brand-lilac min-h-screen pt-32 pb-32">
{/* Background Decoration */}
<div className="fixed inset-0 pointer-events-none opacity-20 bg-[url('https://www.transparenttextures.com/patterns/dust.png')]"></div>
{/* --- HEADER SECTION --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 mb-20 relative z-10">
<Reveal>
<div className="flex flex-col items-start gap-6">
<div className="bg-brand-deep text-white px-8 py-4 transform -skew-x-12 inline-block shadow-[12px_12px_0px_#6B2C91] border-4 border-white">
<h1 className="text-6xl md:text-9xl font-display font-bold uppercase tracking-tighter transform skew-x-12">
{t.publications.header}
</h1>
</div>
<p className="text-xl md:text-2xl font-medium text-brand-deep max-w-2xl mt-4 bg-white/50 p-4 rounded-xl border border-brand-deep/10 backdrop-blur-sm">
Kampüs Cadıları'nın ürettiği tüm fanzin, broşür, rapor ve kitaplara buradan ulaşabilir, dijital kopyalarını indirebilirsiniz.
</p>
</div>
</Reveal>
</div>
{/* --- FEATURED SECTION --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 mb-24 relative z-10">
<div className="bg-white border-4 border-brand-deep rounded-[2rem] p-6 md:p-12 shadow-[20px_20px_0px_#ED1C24] overflow-hidden relative">
<div className="absolute top-0 right-0 w-64 h-64 bg-brand-lilac rounded-full blur-3xl opacity-50 -translate-y-1/2 translate-x-1/2"></div>
<div className="flex flex-col md:flex-row gap-12 items-center relative z-10">
<div className="w-full md:w-1/2">
<div className="relative group">
<div className="absolute inset-0 bg-brand-deep rounded-xl transform rotate-3 transition-transform group-hover:rotate-6"></div>
<img
src="https://images.unsplash.com/photo-1621351183012-e2f9972dd9bf?auto=format&fit=crop&q=80&w=800"
alt="Featured Zine"
className="relative rounded-xl border-4 border-brand-deep shadow-lg transform -rotate-2 transition-transform group-hover:rotate-0 w-full h-96 object-cover"
/>
<div className="absolute top-4 left-4">
<span className="bg-brand-red text-white px-4 py-1 font-bold font-marker tracking-widest text-lg border-2 border-brand-deep shadow-[4px_4px_0px_#2D0F41] rounded-full rotate-[-5deg] inline-block">
{t.publications.featured}
</span>
</div>
</div>
</div>
<div className="w-full md:w-1/2 space-y-8">
<h2 className="text-5xl md:text-7xl font-display font-bold text-brand-deep uppercase leading-[0.9]">
KAMPÜS CADILARI<br/>
<span className="text-transparent bg-clip-text bg-gradient-to-r from-brand-purple to-brand-red">FANZİN #13</span>
</h2>
<p className="text-xl text-gray-600 font-medium leading-relaxed">
Yeni sayımızda "Kampüslerde Barınma Krizi" dosyasını ıyoruz. Yurtlardaki niteliksiz koşullar, fahiş kiralar ve barınma hakkımız üzerine sözümüz var.
</p>
<div className="flex flex-wrap gap-4">
<button className="bg-brand-deep text-white px-8 py-4 font-bold text-xl uppercase tracking-wider flex items-center gap-3 hover:bg-brand-purple transition-all shadow-[8px_8px_0px_rgba(0,0,0,0.2)] hover:translate-y-1 hover:shadow-none rounded-lg">
<Download size={24} />
{t.publications.download}
</button>
<button className="bg-transparent border-4 border-brand-deep text-brand-deep px-8 py-4 font-bold text-xl uppercase tracking-wider flex items-center gap-3 hover:bg-brand-lilac transition-all rounded-lg">
<BookOpen size={24} />
İNCELE
</button>
</div>
</div>
</div>
</div>
</div>
{/* --- FILTERS --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 mb-12 sticky top-24 z-30">
<div className="flex flex-wrap justify-center gap-4 bg-white/80 backdrop-blur-md p-4 rounded-2xl border-2 border-brand-deep/20 shadow-lg inline-flex mx-auto">
<button
onClick={() => setFilter('ALL')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'ALL' ? 'bg-brand-deep text-white border-brand-deep' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.all}
</button>
<button
onClick={() => setFilter('FANZIN')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'FANZIN' ? 'bg-brand-purple text-white border-brand-purple' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.fanzine}
</button>
<button
onClick={() => setFilter('RAPOR')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'RAPOR' ? 'bg-brand-red text-white border-brand-red' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.report}
</button>
<button
onClick={() => setFilter('BROSUR')}
className={`px-6 py-2 rounded-full font-bold uppercase tracking-wider border-2 transition-all ${filter === 'BROSUR' ? 'bg-[#FFC107] text-brand-deep border-[#FFC107]' : 'bg-transparent text-brand-deep border-transparent hover:bg-brand-lilac'}`}
>
{t.publications.filters.brochure}
</button>
</div>
</div>
{/* --- GRID --- */}
<div className="max-w-7xl mx-auto px-6 lg:px-12 relative z-10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
{filteredPublications.map((pub) => (
<div key={pub.id} className="group relative">
{/* Card Background Layer */}
<div className="absolute inset-0 bg-brand-deep rounded-2xl transform translate-x-3 translate-y-3 transition-transform group-hover:translate-x-4 group-hover:translate-y-4"></div>
{/* Main Card */}
<div className="relative bg-white border-4 border-brand-deep rounded-2xl overflow-hidden flex flex-col h-full transition-transform group-hover:-translate-y-1">
{/* Image Area */}
<div className="h-64 overflow-hidden relative border-b-4 border-brand-deep bg-gray-100">
<img src={pub.coverUrl} alt={pub.title} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
<div className="absolute top-4 right-4">
<div className="bg-white text-brand-deep px-3 py-1 font-bold text-xs border-2 border-brand-deep rounded shadow-[2px_2px_0px_#2D0F41]">
{pub.date}
</div>
</div>
{/* Type Badge */}
<div className="absolute bottom-4 left-4">
<span className={`px-4 py-1 font-bold uppercase tracking-widest text-xs border-2 border-brand-deep rounded-full shadow-md ${
pub.type === 'FANZIN' ? 'bg-brand-purple text-white' :
pub.type === 'RAPOR' ? 'bg-brand-red text-white' :
pub.type === 'BROSUR' ? 'bg-[#FFC107] text-brand-deep' : 'bg-gray-800 text-white'
}`}>
{pub.type}
</span>
</div>
</div>
{/* Content Area */}
<div className="p-6 flex flex-col flex-grow">
<h3 className="text-2xl font-display font-bold text-brand-deep leading-tight mb-3 uppercase group-hover:text-brand-purple transition-colors">
{pub.title}
</h3>
<p className="text-gray-600 font-medium text-sm mb-6 flex-grow line-clamp-3">
{pub.description}
</p>
<button className="w-full bg-brand-lilac border-2 border-brand-deep text-brand-deep font-bold py-3 px-4 rounded-lg uppercase tracking-wider flex items-center justify-center gap-2 hover:bg-brand-deep hover:text-white transition-colors">
<Download size={18} />
İNDİR
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};

55
src/components/Reveal.tsx Normal file
View File

@ -0,0 +1,55 @@
import React, { useEffect, useRef, useState } from 'react';
interface RevealProps {
children: React.ReactNode;
className?: string;
delay?: number;
duration?: number;
threshold?: number;
}
export const Reveal: React.FC<RevealProps> = ({
children,
className = "",
delay = 0,
duration = 1000,
threshold = 0.1
}) => {
const ref = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ threshold, rootMargin: "50px" }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, [threshold]);
return (
<div
ref={ref}
className={className}
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0) scale(1)' : 'translateY(30px) scale(0.98)',
transition: `opacity ${duration}ms ease-out, transform ${duration}ms cubic-bezier(0.16, 1, 0.3, 1)`,
transitionDelay: `${delay}ms`,
willChange: 'opacity, transform'
}}
>
{children}
</div>
);
};

View File

@ -2,17 +2,19 @@
// You can import this data from anywhere in your site by using the `import` keyword.
// SEO-optimized title (under 60 characters for full display in search results)
export const SITE_TITLE = 'Your Name — Your Profession';
export const SITE_TITLE = 'Anasayfa - kampuscadilari.org';
// SEO-optimized description (under 160 characters, includes keywords and CTA)
export const SITE_DESCRIPTION = 'Your professional description here. Describe what you do, who you work with, and what makes you unique.';
export const SITE_DESCRIPTION = 'KIZKARDEŞİM, SİTEMİZDE SENİ BEKLEYENLER... kadınlardan gelenler yazısı ByKampüs Cadıları Mayıs 22, 2025 Kadınlardan Gelenler şsdlvmşlm s hyt jhg w thgd sfdfhn yth fbx cgm yjtrh egs bfnh jye rsgdbx fmh jteyrs gfc gkyrtue yrsg... Read More feminist gündem yazısı ByKampüs Cadıları Mayıs 22, 2025 Feminist Gündem şsldmvşslmv sdv sf nrd vca dvbrh ntr fv xv.';
// Visible in raw HTML output for diagnostics (curl/view-source). Keep short.
export const HTML_MARKER = "Built with Astro Template";
export const SOCIAL_LINKS = {
email: 'your@email.com',
website: 'https://yoursite.com',
linkedin: 'https://linkedin.com/in/yourprofile',
github: 'https://github.com/yourusername'
email: 'kampuscadilari8@gmail.com',
website: 'https://kampuscadilari.org/',
x: 'https://x.com/kamps_cadilari',
instagram: 'https://www.instagram.com/kampus.cadilari/',
tiktok: 'https://www.tiktok.com/@kampuscadilari',
youtube: 'https://www.youtube.com/@kampuscadilari'
};

180
src/content/content.ts Normal file
View File

@ -0,0 +1,180 @@
export const CONTENT = {
TR: {
nav: {
home: 'ANASAYFA',
publications: 'CADI YAYINLARI',
translation: 'ÇEVİRİ',
agenda: 'FEMİNİST GÜNDEM',
join: 'BİZE KATIL'
},
hero: {
women: 'GÜÇLÜ KADINLAR',
future: 'GÜÇLÜ GELECEK',
scroll: 'KAYDIR',
est: 'EST. 2018'
},
marquee: 'KADIN YAŞAM ÖZGÜRLÜK',
values: {
title: 'DEĞERLERİMİZ',
cards: [
{
id: '01',
title: 'ÖZGÜRLÜK',
desc: 'Bedenimiz, emeğimiz ve kimliğimiz üzerindeki her türlü tahakküme karşı çıkıyoruz. Özgürlük lütuf değil, haktır.'
},
{
id: '02',
title: 'DAYANIŞMA',
desc: 'Kızkardeşlik sınır tanımaz. Birimizin sesi kısıldığında diğerimiz çığlık oluruz.'
},
{
id: '03',
title: 'MÜCADELE',
desc: 'Eşit, özgür ve sömürüsüz bir dünya için kampüslerden sokaklara.'
}
]
},
news: {
header: 'SON GÜNDEM',
year: '2025',
items: [
{
title: "GÜÇLÜ, ÖZGÜR, BİRLİKTE",
date: "19 Mayıs 2025",
category: "GÜNDEM",
},
{
title: "YANAN IŞIKLAR",
date: "27 Mayıs 2025",
category: "EYLEM",
}
],
sticky: {
title_1: 'SENİN',
title_2: 'SÖZÜN',
desc: 'Yazılarını bize gönder, sesini çoğaltalım.',
btn: 'YAZI GÖNDER'
},
secondary: {
category: 'BASIN',
title: 'FEMİNİST DAYANIŞMA YAŞATIYOR'
}
},
newsletter: {
title_1: 'BİZE',
title_2: 'KATIL',
desc: 'Haftalık bültenimize abone ol, gündemi kaçırma.',
placeholder: 'E-POSTA ADRESİN'
},
footer: {
feminist: 'FEMİNİST',
about: 'HAKKIMIZDA',
pubs: 'YAYINLAR',
volunteer: 'GÖNÜLLÜ OL',
contact: 'İLETİŞİM',
rights: '© 2025 KAMPÜS CADILARI',
city: 'İSTANBUL, TÜRKİYE'
},
publications: {
header: 'YAYINLARIMIZ',
featured: 'ÖNE ÇIKAN',
download: 'İNDİR / OKU',
filters: {
all: 'TÜMÜ',
fanzine: 'FANZİN',
report: 'RAPOR',
brochure: 'BROŞÜR'
}
}
},
EN: {
nav: {
home: 'HOME',
publications: 'PUBLICATIONS',
translation: 'TRANSLATION',
agenda: 'FEMINIST AGENDA',
join: 'JOIN US'
},
hero: {
women: 'STRONG WOMEN',
future: 'STRONG FUTURE',
scroll: 'SCROLL',
est: 'EST. 2018'
},
marquee: 'WOMEN LIFE FREEDOM',
values: {
title: 'OUR VALUES',
cards: [
{
id: '01',
title: 'FREEDOM',
desc: 'We oppose all forms of domination over our bodies, labor, and identities. Freedom is not a favor, it is a right.'
},
{
id: '02',
title: 'SOLIDARITY',
desc: 'Sisterhood knows no borders. When one voice is silenced, we become the scream of the other.'
},
{
id: '03',
title: 'STRUGGLE',
desc: 'From campuses to streets for an equal, free, and exploitation-free world.'
}
]
},
news: {
header: 'LATEST NEWS',
year: '2025',
items: [
{
title: "STRONG, FREE, TOGETHER",
date: "May 19, 2025",
category: "AGENDA",
},
{
title: "BURNING LIGHTS",
date: "May 27, 2025",
category: "ACTION",
}
],
sticky: {
title_1: 'YOUR',
title_2: 'VOICE',
desc: 'Send us your writings, let us amplify your voice.',
btn: 'SUBMIT'
},
secondary: {
category: 'PRESS',
title: 'FEMINIST SOLIDARITY KEEPS ALIVE'
}
},
newsletter: {
title_1: 'JOIN',
title_2: 'US',
desc: 'Subscribe to our weekly newsletter, don\'t miss the agenda.',
placeholder: 'YOUR EMAIL ADDRESS'
},
footer: {
feminist: 'FEMINIST',
about: 'ABOUT US',
pubs: 'PUBLICATIONS',
volunteer: 'VOLUNTEER',
contact: 'CONTACT',
rights: '© 2025 CAMPUS WITCHES',
city: 'ISTANBUL, TURKEY'
},
publications: {
header: 'PUBLICATIONS',
featured: 'FEATURED',
download: 'DOWNLOAD / READ',
filters: {
all: 'ALL',
fanzine: 'FANZINE',
report: 'REPORT',
brochure: 'BROCHURE'
}
}
}
};

View File

@ -1,7 +1,7 @@
---
import type { ImageMetadata } from 'astro';
import BaseHead from '../components/BaseHead.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
import '../styles/global.css';
interface Props {
title?: string;
@ -13,8 +13,8 @@ interface Props {
}
const {
title = SITE_TITLE,
description = SITE_DESCRIPTION,
title = 'Kampüs Cadıları | Feminist Bi\' Dünya',
description = 'Üniversitelerde, sokaklarda, meydanlarda; eşitlik, özgürlük ve laiklik için mücadele eden feminist üniversite öğrencileriyiz.',
image,
type = 'website',
publishedTime,
@ -23,7 +23,7 @@ const {
---
<!DOCTYPE html>
<html lang="en">
<html lang="tr">
<head>
<BaseHead
title={title}
@ -37,5 +37,7 @@ const {
<body>
<slot />
<!-- Noise Overlay -->
<div class="noise-overlay"></div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More