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.
28
.cursor/debug.log
Normal 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"}
|
||||||
326
.cursor/plans/convert_vite_react_app_to_astro_2308e4b5.plan.md
Normal 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
|
||||||
559
dev/brief.md
@ -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
|
|
||||||
@ -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...
|
|
||||||
221
dev/design.json
@ -1,11 +1,218 @@
|
|||||||
{
|
{
|
||||||
"_readme": "Design system documentation. Add your design tokens, color palette, typography scale, and component patterns here as your project grows.",
|
"meta": {
|
||||||
"design_system": {
|
"name": "Kampüs Cadıları Design System",
|
||||||
"name": "Your Design System",
|
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"colors": {},
|
"lastUpdated": "2025-05-19"
|
||||||
"typography": {},
|
},
|
||||||
"spacing": {},
|
"brand": {
|
||||||
"components": {}
|
"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
@ -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
@ -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
@ -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
@ -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`
|
||||||
79
dev/generated/components/ArticleCard.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
31
dev/generated/components/Button.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
98
dev/generated/components/Footer.tsx
Normal 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">
|
||||||
|
© {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>
|
||||||
|
);
|
||||||
|
};
|
||||||
113
dev/generated/components/Header.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
139
dev/generated/components/Hero.tsx
Normal 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 Bİ 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
291
dev/generated/components/Home.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
34
dev/generated/components/Logo.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
211
dev/generated/components/Publications.tsx
Normal 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ı açı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>
|
||||||
|
);
|
||||||
|
};
|
||||||
54
dev/generated/components/Reveal.tsx
Normal 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
@ -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
@ -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
@ -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>
|
||||||
|
);
|
||||||
BIN
dev/generated/kampüs-cadıları.zip
Normal file
5
dev/generated/metadata.json
Normal 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
22
dev/generated/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
dev/generated/tsconfig.json
Normal 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
@ -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;
|
||||||
|
}
|
||||||
23
dev/generated/vite.config.ts
Normal 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, '.'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -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)
|
|
||||||
|
|
||||||
@ -26,6 +26,7 @@
|
|||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"astro": "^5.16.4",
|
"astro": "^5.16.4",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
"lunr": "^2.3.9",
|
"lunr": "^2.3.9",
|
||||||
"marked": "^17.0.1",
|
"marked": "^17.0.1",
|
||||||
"react": "^19.2.1",
|
"react": "^19.2.1",
|
||||||
|
|||||||
12
pnpm-lock.yaml
generated
@ -38,6 +38,9 @@ importers:
|
|||||||
astro:
|
astro:
|
||||||
specifier: ^5.16.4
|
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)
|
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:
|
lunr:
|
||||||
specifier: ^2.3.9
|
specifier: ^2.3.9
|
||||||
version: 2.3.9
|
version: 2.3.9
|
||||||
@ -1963,6 +1966,11 @@ packages:
|
|||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
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:
|
lunr@2.3.9:
|
||||||
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
|
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
|
||||||
|
|
||||||
@ -4632,6 +4640,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
||||||
|
lucide-react@0.562.0(react@19.2.1):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.1
|
||||||
|
|
||||||
lunr@2.3.9: {}
|
lunr@2.3.9: {}
|
||||||
|
|
||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@ -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 |
@ -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
|
|
||||||
|
Before Width: | Height: | Size: 1.6 MiB |
BIN
public/media/logo_black.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/media/logo_col.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
public/media/logo_white.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
public/media/logos/logo_black_large@1x.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
public/media/logos/logo_black_large@1x.webp
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/media/logos/logo_black_large@2x.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/media/logos/logo_black_large@2x.webp
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/media/logos/logo_black_medium@1x.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/media/logos/logo_black_medium@1x.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/media/logos/logo_black_medium@2x.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/media/logos/logo_black_medium@2x.webp
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/media/logos/logo_black_small@1x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/media/logos/logo_black_small@1x.webp
Normal file
|
After Width: | Height: | Size: 610 B |
BIN
public/media/logos/logo_black_small@2x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/media/logos/logo_black_small@2x.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/media/logos/logo_col_large@1x.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/media/logos/logo_col_large@1x.webp
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
public/media/logos/logo_col_large@2x.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/media/logos/logo_col_large@2x.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/media/logos/logo_col_medium@1x.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
public/media/logos/logo_col_medium@1x.webp
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/media/logos/logo_col_medium@2x.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/media/logos/logo_col_medium@2x.webp
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
public/media/logos/logo_col_small@1x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/media/logos/logo_col_small@1x.webp
Normal file
|
After Width: | Height: | Size: 942 B |
BIN
public/media/logos/logo_col_small@2x.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
public/media/logos/logo_col_small@2x.webp
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/media/logos/logo_white_large@1x.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/media/logos/logo_white_large@1x.webp
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
public/media/logos/logo_white_large@2x.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/media/logos/logo_white_large@2x.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/media/logos/logo_white_medium@1x.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/media/logos/logo_white_medium@1x.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/media/logos/logo_white_medium@2x.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/media/logos/logo_white_medium@2x.webp
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
public/media/logos/logo_white_small@1x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/media/logos/logo_white_small@1x.webp
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/media/logos/logo_white_small@2x.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/media/logos/logo_white_small@2x.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
211
src/components/App.tsx
Normal 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;
|
||||||
80
src/components/ArticleCard.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@ -1,54 +1,64 @@
|
|||||||
---
|
---
|
||||||
import '../styles/global.css';
|
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title?: string;
|
||||||
description: string;
|
description?: string;
|
||||||
image?: ImageMetadata;
|
image?: ImageMetadata;
|
||||||
type?: 'website' | 'article';
|
type?: 'website' | 'article';
|
||||||
publishedTime?: Date;
|
publishedTime?: Date;
|
||||||
modifiedTime?: Date;
|
modifiedTime?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title,
|
title = SITE_TITLE,
|
||||||
description,
|
description = SITE_DESCRIPTION,
|
||||||
image,
|
image,
|
||||||
type = 'website',
|
type = 'website',
|
||||||
publishedTime,
|
publishedTime,
|
||||||
modifiedTime,
|
modifiedTime,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
|
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- Global Metadata -->
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<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} />
|
<link rel="canonical" href={canonicalURL} />
|
||||||
|
|
||||||
|
<!-- Primary Meta Tags -->
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<meta name="title" content={title} />
|
<meta name="title" content={title} />
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
|
|
||||||
{image && (
|
<!-- Open Graph / Facebook -->
|
||||||
<>
|
|
||||||
<meta property="og:image" content={new URL(image.src, Astro.url)} />
|
|
||||||
<meta name="twitter:image" content={new URL(image.src, Astro.url).toString()} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<meta property="og:type" content={type} />
|
<meta property="og:type" content={type} />
|
||||||
<meta property="og:url" content={Astro.url} />
|
<meta property="og:url" content={Astro.url} />
|
||||||
<meta property="og:title" content={title} />
|
<meta property="og:title" content={title} />
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
|
{image && <meta property="og:image" content={new URL(image.src, Astro.url)} />}
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:title" content={title} />
|
|
||||||
<meta name="twitter:description" content={description} />
|
|
||||||
|
|
||||||
{publishedTime && <meta property="article:published_time" content={publishedTime.toISOString()} />}
|
{publishedTime && <meta property="article:published_time" content={publishedTime.toISOString()} />}
|
||||||
{modifiedTime && <meta property="article:modified_time" content={modifiedTime.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" />
|
||||||
|
|
||||||
|
|||||||
@ -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
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@ -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
@ -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
@ -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
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
211
src/components/Publications.tsx
Normal 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ı açı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
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@ -2,17 +2,19 @@
|
|||||||
// You can import this data from anywhere in your site by using the `import` keyword.
|
// 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)
|
// 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)
|
// 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.
|
// Visible in raw HTML output for diagnostics (curl/view-source). Keep short.
|
||||||
export const HTML_MARKER = "Built with Astro Template";
|
export const HTML_MARKER = "Built with Astro Template";
|
||||||
|
|
||||||
export const SOCIAL_LINKS = {
|
export const SOCIAL_LINKS = {
|
||||||
email: 'your@email.com',
|
email: 'kampuscadilari8@gmail.com',
|
||||||
website: 'https://yoursite.com',
|
website: 'https://kampuscadilari.org/',
|
||||||
linkedin: 'https://linkedin.com/in/yourprofile',
|
x: 'https://x.com/kamps_cadilari',
|
||||||
github: 'https://github.com/yourusername'
|
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
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
import BaseHead from '../components/BaseHead.astro';
|
import BaseHead from '../components/BaseHead.astro';
|
||||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
import '../styles/global.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -13,8 +13,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = SITE_TITLE,
|
title = 'Kampüs Cadıları | Feminist Bi\' Dünya',
|
||||||
description = SITE_DESCRIPTION,
|
description = 'Üniversitelerde, sokaklarda, meydanlarda; eşitlik, özgürlük ve laiklik için mücadele eden feminist üniversite öğrencileriyiz.',
|
||||||
image,
|
image,
|
||||||
type = 'website',
|
type = 'website',
|
||||||
publishedTime,
|
publishedTime,
|
||||||
@ -23,7 +23,7 @@ const {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="tr">
|
||||||
<head>
|
<head>
|
||||||
<BaseHead
|
<BaseHead
|
||||||
title={title}
|
title={title}
|
||||||
@ -37,5 +37,7 @@ const {
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<slot />
|
<slot />
|
||||||
|
<!-- Noise Overlay -->
|
||||||
|
<div class="noise-overlay"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||