Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05e7808d71 | ||
|
|
1f8051ae1a | ||
|
|
90df7bde9e | ||
|
|
cd58dc5259 | ||
|
|
e1bbe5455d |
@@ -1,227 +0,0 @@
|
||||
{
|
||||
"base_commands": [
|
||||
".",
|
||||
"[",
|
||||
"[[",
|
||||
"ag",
|
||||
"awk",
|
||||
"basename",
|
||||
"bash",
|
||||
"bc",
|
||||
"break",
|
||||
"cat",
|
||||
"cd",
|
||||
"chmod",
|
||||
"clear",
|
||||
"cmp",
|
||||
"column",
|
||||
"comm",
|
||||
"command",
|
||||
"continue",
|
||||
"cp",
|
||||
"curl",
|
||||
"cut",
|
||||
"date",
|
||||
"df",
|
||||
"diff",
|
||||
"dig",
|
||||
"dirname",
|
||||
"du",
|
||||
"echo",
|
||||
"egrep",
|
||||
"env",
|
||||
"eval",
|
||||
"exec",
|
||||
"exit",
|
||||
"expand",
|
||||
"export",
|
||||
"expr",
|
||||
"false",
|
||||
"fd",
|
||||
"fgrep",
|
||||
"file",
|
||||
"find",
|
||||
"fmt",
|
||||
"fold",
|
||||
"gawk",
|
||||
"gh",
|
||||
"git",
|
||||
"grep",
|
||||
"gunzip",
|
||||
"gzip",
|
||||
"head",
|
||||
"help",
|
||||
"host",
|
||||
"iconv",
|
||||
"id",
|
||||
"jobs",
|
||||
"join",
|
||||
"jq",
|
||||
"kill",
|
||||
"killall",
|
||||
"less",
|
||||
"let",
|
||||
"ln",
|
||||
"ls",
|
||||
"lsof",
|
||||
"man",
|
||||
"mkdir",
|
||||
"mktemp",
|
||||
"more",
|
||||
"mv",
|
||||
"nl",
|
||||
"paste",
|
||||
"pgrep",
|
||||
"ping",
|
||||
"pkill",
|
||||
"popd",
|
||||
"printenv",
|
||||
"printf",
|
||||
"ps",
|
||||
"pushd",
|
||||
"pwd",
|
||||
"read",
|
||||
"readlink",
|
||||
"realpath",
|
||||
"reset",
|
||||
"return",
|
||||
"rev",
|
||||
"rg",
|
||||
"rm",
|
||||
"rmdir",
|
||||
"sed",
|
||||
"seq",
|
||||
"set",
|
||||
"sh",
|
||||
"shuf",
|
||||
"sleep",
|
||||
"sort",
|
||||
"source",
|
||||
"split",
|
||||
"stat",
|
||||
"tail",
|
||||
"tar",
|
||||
"tee",
|
||||
"test",
|
||||
"time",
|
||||
"timeout",
|
||||
"touch",
|
||||
"tr",
|
||||
"tree",
|
||||
"true",
|
||||
"type",
|
||||
"uname",
|
||||
"unexpand",
|
||||
"uniq",
|
||||
"unset",
|
||||
"unzip",
|
||||
"watch",
|
||||
"wc",
|
||||
"wget",
|
||||
"whereis",
|
||||
"which",
|
||||
"whoami",
|
||||
"xargs",
|
||||
"yes",
|
||||
"yq",
|
||||
"zip",
|
||||
"zsh"
|
||||
],
|
||||
"stack_commands": [
|
||||
"ar",
|
||||
"clang",
|
||||
"clang++",
|
||||
"cmake",
|
||||
"composer",
|
||||
"dive",
|
||||
"docker",
|
||||
"docker-buildx",
|
||||
"docker-compose",
|
||||
"dockerfile",
|
||||
"eslint",
|
||||
"g++",
|
||||
"gcc",
|
||||
"ipython",
|
||||
"jupyter",
|
||||
"ld",
|
||||
"make",
|
||||
"meson",
|
||||
"next",
|
||||
"ninja",
|
||||
"nm",
|
||||
"node",
|
||||
"notebook",
|
||||
"npm",
|
||||
"npx",
|
||||
"objdump",
|
||||
"pdb",
|
||||
"php",
|
||||
"pip",
|
||||
"pip3",
|
||||
"pipx",
|
||||
"pnpm",
|
||||
"pnpx",
|
||||
"pudb",
|
||||
"python",
|
||||
"python3",
|
||||
"react-scripts",
|
||||
"strip",
|
||||
"ts-node",
|
||||
"tsc",
|
||||
"tsx",
|
||||
"vitest"
|
||||
],
|
||||
"script_commands": [
|
||||
"bun",
|
||||
"npm",
|
||||
"pnpm",
|
||||
"yarn"
|
||||
],
|
||||
"custom_commands": [],
|
||||
"detected_stack": {
|
||||
"languages": [
|
||||
"python",
|
||||
"javascript",
|
||||
"typescript",
|
||||
"php",
|
||||
"c"
|
||||
],
|
||||
"package_managers": [
|
||||
"pnpm"
|
||||
],
|
||||
"frameworks": [
|
||||
"nextjs",
|
||||
"react",
|
||||
"vitest",
|
||||
"eslint"
|
||||
],
|
||||
"databases": [],
|
||||
"infrastructure": [
|
||||
"docker"
|
||||
],
|
||||
"cloud_providers": [],
|
||||
"code_quality_tools": [],
|
||||
"version_managers": []
|
||||
},
|
||||
"custom_scripts": {
|
||||
"npm_scripts": [
|
||||
"dev",
|
||||
"build",
|
||||
"start",
|
||||
"lint",
|
||||
"build:images",
|
||||
"generate:images",
|
||||
"generate:images:dry",
|
||||
"test",
|
||||
"test:coverage"
|
||||
],
|
||||
"make_targets": [],
|
||||
"poetry_scripts": [],
|
||||
"cargo_aliases": [],
|
||||
"shell_scripts": []
|
||||
},
|
||||
"project_dir": "C:\\Users\\damja\\WebstormProjects\\Portfolio",
|
||||
"created_at": "2026-01-22T15:28:38.237190",
|
||||
"project_hash": "c4ad399e16be367eb4e6b076fe1d9ee3",
|
||||
"inherited_from": "C:\\Users\\damja\\WebstormProjects\\Portfolio"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"active": true,
|
||||
"spec": "030-add-unit-tests-vitest-configured-but-no-tests-exis",
|
||||
"state": "building",
|
||||
"subtasks": {
|
||||
"completed": 2,
|
||||
"total": 8,
|
||||
"in_progress": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"phase": {
|
||||
"current": "Utility Functions Tests",
|
||||
"id": null,
|
||||
"total": 2
|
||||
},
|
||||
"workers": {
|
||||
"active": 0,
|
||||
"max": 1
|
||||
},
|
||||
"session": {
|
||||
"number": 4,
|
||||
"started_at": "2026-01-25T06:18:19.433683"
|
||||
},
|
||||
"last_update": "2026-01-25T06:32:23.146357"
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"sandbox": {
|
||||
"enabled": true,
|
||||
"autoAllowBashIfSandboxed": true
|
||||
},
|
||||
"permissions": {
|
||||
"defaultMode": "acceptEdits",
|
||||
"allow": [
|
||||
"Read(./**)",
|
||||
"Write(./**)",
|
||||
"Edit(./**)",
|
||||
"Glob(./**)",
|
||||
"Grep(./**)",
|
||||
"Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Glob(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Grep(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis\\.auto-claude\\specs\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis\\.auto-claude\\specs\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis\\.auto-claude\\specs\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)",
|
||||
"Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
|
||||
"Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
|
||||
"Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
|
||||
"Glob(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
|
||||
"Grep(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
|
||||
"Bash(*)",
|
||||
"WebFetch(*)",
|
||||
"WebSearch(*)",
|
||||
"mcp__context7__resolve-library-id(*)",
|
||||
"mcp__context7__get-library-docs(*)",
|
||||
"mcp__graphiti-memory__search_nodes(*)",
|
||||
"mcp__graphiti-memory__search_facts(*)",
|
||||
"mcp__graphiti-memory__add_episode(*)",
|
||||
"mcp__graphiti-memory__get_episodes(*)",
|
||||
"mcp__graphiti-memory__get_entity_edge(*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnpm-store
|
||||
|
||||
# Build outputs
|
||||
.next
|
||||
out
|
||||
build
|
||||
dist
|
||||
|
||||
# Development
|
||||
.env*.local
|
||||
.env.development
|
||||
.env.test
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
|
||||
# Misc
|
||||
*.md
|
||||
!README.md
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
coverage
|
||||
.nyc_output
|
||||
@@ -1,4 +1,32 @@
|
||||
<<<<<<< HEAD
|
||||
# Supabase
|
||||
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
|
||||
|
||||
# DeepSeek API
|
||||
DEEPSEEK_API_KEY=your_deepseek_api_key
|
||||
|
||||
# Analytics
|
||||
NEXT_PUBLIC_GA_TRACKING_ID=your_ga_tracking_id
|
||||
|
||||
# Site URL
|
||||
NEXT_PUBLIC_SITE_URL=https://your-domain.com
|
||||
=======
|
||||
# Supabase Configuration
|
||||
<<<<<<< HEAD
|
||||
# Get these from your Supabase project settings: https://app.supabase.com
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key-here
|
||||
|
||||
# Analytics
|
||||
# Google Analytics tracking ID (format: G-XXXXXXXXXX)
|
||||
NEXT_PUBLIC_GA_TRACKING_ID=G-XXXXXXXXXX
|
||||
|
||||
# Site Configuration
|
||||
# The public URL where your site is hosted (e.g., https://damjan-savic.com)
|
||||
NEXT_PUBLIC_SITE_URL=https://your-domain.com
|
||||
=======
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
|
||||
@@ -10,3 +38,5 @@ NEXT_PUBLIC_SITE_URL=https://damjan-savic.com
|
||||
|
||||
# OpenAI API Key (for image generation)
|
||||
OPENAI_API_KEY=sk-your-api-key-here
|
||||
>>>>>>> origin/master
|
||||
>>>>>>> origin/master
|
||||
|
||||
@@ -4,7 +4,6 @@ src/pages-vite/
|
||||
src/hooks/
|
||||
src/services/
|
||||
src/utils/
|
||||
src/i18n/locales-old/
|
||||
*.bak
|
||||
|
||||
# Build output
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
package-lock.json
|
||||
|
||||
# Project-specific directories
|
||||
CoderConda/
|
||||
scripts/
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
test-results/
|
||||
tests/
|
||||
playwright-report/
|
||||
.playwright-mcp/
|
||||
|
||||
# Production
|
||||
dist
|
||||
@@ -83,5 +92,60 @@ supabase/.temp/
|
||||
# Source images (originals before optimization)
|
||||
source-images/
|
||||
|
||||
# Auto Claude data directory
|
||||
# Auto Claude files
|
||||
.auto-claude/
|
||||
.auto-claude-*
|
||||
.claude_settings.json
|
||||
|
||||
# Windows reserved names
|
||||
nul
|
||||
|
||||
|
||||
|
||||
# Temporary/reference images
|
||||
*.jpg
|
||||
*.pen
|
||||
|
||||
# Build/test output files
|
||||
*-output.txt
|
||||
*_output.txt
|
||||
build-output*.txt
|
||||
dev-output.txt
|
||||
install-output.txt
|
||||
npm-install-output.txt
|
||||
dev-server.pid
|
||||
context.json
|
||||
project_index.json
|
||||
*.bak
|
||||
|
||||
# Test scripts
|
||||
test*.txt
|
||||
test*.sh
|
||||
verify-*.sh
|
||||
verify-*.js
|
||||
verify-*.mjs
|
||||
current-headers.txt
|
||||
|
||||
# Docker (if not using)
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.dockerignore
|
||||
|
||||
# Documentation/verification files (generated)
|
||||
CONTRIBUTING.md
|
||||
E2E_VERIFICATION.md
|
||||
e2e-blog-verification.md
|
||||
HEADERS_INVESTIGATION.md
|
||||
MANUAL_INTERVENTION_REQUIRED.md
|
||||
MIGRATION_READY.md
|
||||
OPTIMIZATION_RECOMMENDATIONS.md
|
||||
OPTIMIZATION_REPORT.md
|
||||
PAGESPEED_VERIFICATION_REPORT.md
|
||||
PERFORMANCE_BASELINE.md
|
||||
QA_*.md
|
||||
SECURITY_HEADERS_VERIFICATION.md
|
||||
SERVERLESS_COMPATIBILITY_VERIFICATION.md
|
||||
SUBTASK*.md
|
||||
TESTING-VERIFICATION.md
|
||||
VERIFICATION_REPORT.md
|
||||
VERIFICATION_STEPS.md
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Base image
|
||||
FROM node:20-alpine AS base
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Next.js collects anonymous telemetry data - disable it
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -14,9 +14,9 @@ Personal portfolio website showcasing my work as an **AI & Automation Specialist
|
||||
## Tech Stack
|
||||
|
||||
### Frontend
|
||||
- **React 18** - UI Framework with Suspense & Lazy Loading
|
||||
- **Next.js 15** - React framework with SSR & SSG
|
||||
- **React 19** - UI Framework with Suspense & Lazy Loading
|
||||
- **TypeScript** - Type-safe development
|
||||
- **Vite** - Build tool & dev server
|
||||
- **Tailwind CSS** - Utility-first styling with custom design tokens
|
||||
- **Framer Motion** - Animations & page transitions
|
||||
|
||||
@@ -30,9 +30,9 @@ Personal portfolio website showcasing my work as an **AI & Automation Specialist
|
||||
- **Google Analytics 4** - Privacy-compliant analytics with cookie consent
|
||||
|
||||
### PWA & Performance
|
||||
- **Workbox** - Service Worker & caching strategies
|
||||
- **vite-plugin-pwa** - Progressive Web App functionality
|
||||
- **Code Splitting** - Vendor chunks for React, MDX, i18n, UI libraries
|
||||
- **Next.js Image Optimization** - Automatic image optimization with AVIF/WebP
|
||||
- **Code Splitting** - Automatic route-based code splitting
|
||||
- **Caching Strategies** - Custom headers for static assets
|
||||
|
||||
### Testing & Quality
|
||||
- **Vitest** - Unit testing
|
||||
@@ -85,25 +85,135 @@ src/
|
||||
└── App.tsx # Root component
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The application requires several environment variables to function correctly. Create a `.env` file in the root directory based on `.env.example`:
|
||||
|
||||
### Required Variables
|
||||
|
||||
#### `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- **Purpose:** Base URL for your Supabase project
|
||||
- **Format:** `https://your-project-id.supabase.co`
|
||||
- **How to get:** Navigate to your [Supabase project settings](https://app.supabase.com) → Settings → API → Project URL
|
||||
- **Example:** `https://mxadgucxhmstlzsbgmoz.supabase.co`
|
||||
|
||||
#### `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
- **Purpose:** Anonymous/public key for client-side Supabase authentication
|
||||
- **Format:** Long alphanumeric string (JWT token)
|
||||
- **How to get:** Navigate to your [Supabase project settings](https://app.supabase.com) → Settings → API → Project API keys → `anon` `public`
|
||||
- **Security:** Safe to use in client-side code (public key)
|
||||
|
||||
#### `NEXT_PUBLIC_GA_TRACKING_ID`
|
||||
- **Purpose:** Google Analytics 4 tracking ID for analytics
|
||||
- **Format:** `G-XXXXXXXXXX`
|
||||
- **How to get:** Create a GA4 property in [Google Analytics](https://analytics.google.com) → Admin → Data Streams → Web → Measurement ID
|
||||
- **Optional:** Can be omitted if you don't want analytics tracking
|
||||
|
||||
#### `NEXT_PUBLIC_SITE_URL`
|
||||
- **Purpose:** The public URL where your site is hosted (used for SEO, canonical URLs, and sitemap generation)
|
||||
- **Format:** `https://your-domain.com` (no trailing slash)
|
||||
- **Examples:**
|
||||
- Production: `https://damjan-savic.com`
|
||||
- Development: `http://localhost:3000`
|
||||
- **Note:** Update this when deploying to production
|
||||
|
||||
### Setup Instructions
|
||||
|
||||
1. Copy the example environment file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Fill in your actual values in the `.env` file
|
||||
|
||||
3. Restart your development server after changing environment variables
|
||||
|
||||
### Important Notes
|
||||
|
||||
- All variables prefixed with `NEXT_PUBLIC_` are exposed to the browser
|
||||
- Never commit your `.env` file to version control (it's in `.gitignore`)
|
||||
- For production deployment, set these variables in your hosting platform's environment settings
|
||||
- The `.env.example` file shows the required format and should be kept updated
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
pnpm install
|
||||
|
||||
# Start dev server
|
||||
npm run dev
|
||||
pnpm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
pnpm run build
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
pnpm test
|
||||
|
||||
# Preview production build
|
||||
npm run preview
|
||||
pnpm run preview
|
||||
```
|
||||
|
||||
<<<<<<< HEAD
|
||||
## Docker Deployment
|
||||
|
||||
The application includes Docker support for containerized deployment.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env` file with the following variables:
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
NEXT_PUBLIC_GA_TRACKING_ID=your_ga_tracking_id
|
||||
NEXT_PUBLIC_SITE_URL=your_site_url
|
||||
```
|
||||
|
||||
### Using Docker Compose (Recommended)
|
||||
|
||||
```bash
|
||||
# Build and start the container
|
||||
docker-compose up -d
|
||||
|
||||
# Stop the container
|
||||
docker-compose down
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
The application will be available at `http://localhost:3003`
|
||||
|
||||
### Using Docker Directly
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -t portfolio-website .
|
||||
|
||||
# Run the container
|
||||
docker run -p 3003:3000 \
|
||||
-e NEXT_PUBLIC_SUPABASE_URL=your_supabase_url \
|
||||
-e NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key \
|
||||
-e NEXT_PUBLIC_GA_TRACKING_ID=your_ga_tracking_id \
|
||||
-e NEXT_PUBLIC_SITE_URL=your_site_url \
|
||||
portfolio-website
|
||||
```
|
||||
|
||||
### Container Details
|
||||
|
||||
- **Port Mapping:** 3003 (host) → 3000 (container)
|
||||
- **Base Image:** Node 20 Alpine
|
||||
- **Package Manager:** pnpm
|
||||
- **Health Check:** Automated health checks every 30 seconds
|
||||
- **Restart Policy:** unless-stopped
|
||||
|
||||
=======
|
||||
## Development Scripts
|
||||
|
||||
The project includes **11 utility scripts** for automating development tasks. See [scripts/README.md](scripts/README.md) for full documentation.
|
||||
@@ -123,6 +233,7 @@ node scripts/generate-sitemap.js # Generate sitemap
|
||||
node scripts/pagespeed-check.js # Run performance tests
|
||||
```
|
||||
|
||||
>>>>>>> origin/master
|
||||
## Key Technologies Used
|
||||
|
||||
**Languages:** TypeScript, Python, MDX
|
||||
@@ -133,7 +244,7 @@ node scripts/pagespeed-check.js # Run performance tests
|
||||
|
||||
**Backend:** Supabase (PostgreSQL, Auth), WebSocket
|
||||
|
||||
**Build Tools:** Vite, PostCSS, Terser
|
||||
**Build Tools:** Next.js, PostCSS
|
||||
|
||||
**Testing:** Vitest, Testing Library, JSDOM
|
||||
|
||||
@@ -147,4 +258,4 @@ node scripts/pagespeed-check.js # Run performance tests
|
||||
|
||||
---
|
||||
|
||||
Built with React + TypeScript + Vite
|
||||
Built with Next.js + TypeScript
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"files_to_modify": {
|
||||
"frontend": [
|
||||
"src/components/seo/JsonLd.tsx",
|
||||
"src/components/seo/index.ts"
|
||||
]
|
||||
},
|
||||
"files_to_create": {
|
||||
"frontend": [
|
||||
"src/components/seo/schemas/PersonJsonLd.tsx",
|
||||
"src/components/seo/schemas/ProfessionalServiceJsonLd.tsx",
|
||||
"src/components/seo/schemas/BreadcrumbJsonLd.tsx",
|
||||
"src/components/seo/schemas/WebPageJsonLd.tsx",
|
||||
"src/components/seo/schemas/FAQJsonLd.tsx",
|
||||
"src/components/seo/schemas/ServiceJsonLd.tsx",
|
||||
"src/components/seo/schemas/OrganizationJsonLd.tsx",
|
||||
"src/components/seo/schemas/ArticleJsonLd.tsx",
|
||||
"src/components/seo/schemas/HowToJsonLd.tsx",
|
||||
"src/components/seo/schemas/ProfilePageJsonLd.tsx",
|
||||
"src/components/seo/schemas/VideoJsonLd.tsx",
|
||||
"src/components/seo/schemas/SoftwareAppJsonLd.tsx",
|
||||
"src/components/seo/schemas/WebSiteJsonLd.tsx",
|
||||
"src/components/seo/schemas/index.ts"
|
||||
]
|
||||
},
|
||||
"files_to_reference": [
|
||||
"src/components/about/index.ts",
|
||||
"src/components/layout/index.ts",
|
||||
"src/components/portfolio/index.ts"
|
||||
],
|
||||
"patterns": {
|
||||
"component_organization": "Each component type gets its own directory with multiple component files and a barrel export index.ts",
|
||||
"barrel_export": "index.ts re-exports all components from the directory using named exports",
|
||||
"import_paths": "Imports use path alias @/components/... for cleaner imports",
|
||||
"typescript": "All components use TypeScript with proper interface definitions"
|
||||
},
|
||||
"existing_implementations": {
|
||||
"description": "Found 14 JSON-LD schema components in a single 758-line file at src/components/seo/JsonLd.tsx",
|
||||
"relevant_files": [
|
||||
"src/components/seo/JsonLd.tsx",
|
||||
"src/components/seo/index.ts",
|
||||
"src/app/[locale]/layout.tsx (imports PersonJsonLd, ProfessionalServiceJsonLd, OrganizationJsonLd, WebSiteJsonLd)",
|
||||
"src/app/[locale]/about/page.tsx (imports ProfilePageJsonLd, BreadcrumbJsonLd)",
|
||||
"src/app/[locale]/blog/[slug]/page.tsx (imports BreadcrumbJsonLd, ArticleJsonLd)",
|
||||
"src/app/[locale]/leistungen/page.tsx (imports BreadcrumbJsonLd, FAQJsonLd)"
|
||||
],
|
||||
"components_to_split": [
|
||||
"PersonJsonLd",
|
||||
"ProfessionalServiceJsonLd",
|
||||
"BreadcrumbJsonLd",
|
||||
"WebPageJsonLd",
|
||||
"FAQJsonLd",
|
||||
"ServiceJsonLd",
|
||||
"OrganizationJsonLd",
|
||||
"ArticleJsonLd",
|
||||
"HowToJsonLd",
|
||||
"ProfilePageJsonLd",
|
||||
"VideoJsonLd",
|
||||
"SoftwareAppJsonLd",
|
||||
"WebSiteJsonLd"
|
||||
],
|
||||
"usage_locations": [
|
||||
"src/app/[locale]/layout.tsx",
|
||||
"src/app/[locale]/about/page.tsx",
|
||||
"src/app/[locale]/blog/page.tsx",
|
||||
"src/app/[locale]/blog/[slug]/page.tsx",
|
||||
"src/app/[locale]/contact/page.tsx",
|
||||
"src/app/[locale]/leistungen/page.tsx",
|
||||
"src/app/[locale]/leistungen/[slug]/page.tsx",
|
||||
"src/app/[locale]/leistungen/standort/[city]/page.tsx",
|
||||
"src/app/[locale]/portfolio/[slug]/page.tsx"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
services:
|
||||
portfolio:
|
||||
container_name: portfolio-website
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3003:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
|
||||
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
||||
- NEXT_PUBLIC_GA_TRACKING_ID=${NEXT_PUBLIC_GA_TRACKING_ID}
|
||||
- NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -7,12 +7,25 @@ const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
||||
const nextConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
|
||||
// Enable React strict mode for better error detection
|
||||
reactStrictMode: true,
|
||||
|
||||
// Temporarily ignore TypeScript during build (pre-existing issues)
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
|
||||
// Remove X-Powered-By header for security
|
||||
poweredByHeader: false,
|
||||
|
||||
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
|
||||
|
||||
images: {
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
|
||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||
// Cache remote images for 30 days
|
||||
minimumCacheTTL: 60 * 60 * 24 * 30,
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
@@ -22,14 +35,21 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
|
||||
experimental: {
|
||||
optimizePackageImports: ['lucide-react', 'framer-motion'],
|
||||
// Tree-shake large packages for smaller bundles
|
||||
optimizePackageImports: [
|
||||
'lucide-react',
|
||||
'framer-motion',
|
||||
'@supabase/supabase-js',
|
||||
'clsx',
|
||||
'tailwind-merge',
|
||||
'react-intersection-observer',
|
||||
],
|
||||
},
|
||||
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: [
|
||||
// Security headers for all routes
|
||||
// Note: Content-Security-Policy is handled by @next-safe/middleware in src/middleware.ts
|
||||
const securityHeaders = [
|
||||
{
|
||||
key: 'X-DNS-Prefetch-Control',
|
||||
value: 'on',
|
||||
@@ -42,36 +62,54 @@ const nextConfig: NextConfig = {
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff',
|
||||
},
|
||||
],
|
||||
{
|
||||
key: 'X-XSS-Protection',
|
||||
value: '1; mode=block',
|
||||
},
|
||||
{
|
||||
source: '/fonts/:path*',
|
||||
headers: [
|
||||
key: 'Referrer-Policy',
|
||||
value: 'strict-origin-when-cross-origin',
|
||||
},
|
||||
{
|
||||
// Enforce HTTPS (1 year, include subdomains, allow preload list)
|
||||
key: 'Strict-Transport-Security',
|
||||
value: 'max-age=31536000; includeSubDomains; preload',
|
||||
},
|
||||
{
|
||||
// Restrict browser features for security
|
||||
key: 'Permissions-Policy',
|
||||
value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
|
||||
},
|
||||
];
|
||||
|
||||
// Long-term caching for immutable assets
|
||||
const immutableCacheHeader = [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: 'public, max-age=31536000, immutable',
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
// Apply security headers to all routes
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: securityHeaders,
|
||||
},
|
||||
// Cache static assets aggressively
|
||||
{
|
||||
source: '/fonts/:path*',
|
||||
headers: immutableCacheHeader,
|
||||
},
|
||||
{
|
||||
source: '/images/:path*',
|
||||
headers: [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: 'public, max-age=31536000, immutable',
|
||||
},
|
||||
],
|
||||
headers: immutableCacheHeader,
|
||||
},
|
||||
{
|
||||
source: '/_next/static/:path*',
|
||||
headers: [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: 'public, max-age=31536000, immutable',
|
||||
headers: immutableCacheHeader,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Content-Language Headers für jede Sprache (wichtig für Bing)
|
||||
// Content-Language headers for SEO (important for Bing and other search engines)
|
||||
{
|
||||
source: '/de/:path*',
|
||||
headers: [
|
||||
@@ -102,7 +140,7 @@ const nextConfig: NextConfig = {
|
||||
];
|
||||
},
|
||||
|
||||
// Compiler-Optimierungen
|
||||
// Compiler optimizations
|
||||
compiler: {
|
||||
removeConsole: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
|
||||
@@ -19,45 +19,49 @@
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"openai": "^4.77.0",
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/mdx": "^15.1.0",
|
||||
"@mdx-js/loader": "^3.1.1",
|
||||
"@mdx-js/mdx": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@next-safe/middleware": "^0.10.0",
|
||||
"@next/mdx": "^16.1.6",
|
||||
"@supabase/ssr": "^0.8.0",
|
||||
"@supabase/supabase-js": "^2.39.7",
|
||||
"@supabase/supabase-js": "^2.93.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.18.2",
|
||||
"framer-motion": "^12.29.2",
|
||||
"gray-matter": "^4.0.3",
|
||||
"lucide-react": "^0.475.0",
|
||||
"next": "^15.1.0",
|
||||
"next-intl": "^3.26.0",
|
||||
"lucide-react": "^0.563.0",
|
||||
"next": "^16.1.6",
|
||||
"next-intl": "^4.8.1",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-intersection-observer": "^9.8.1",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"openai": "^6.17.0",
|
||||
"pg": "^8.18.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-ga4": "^2.1.0",
|
||||
"react-intersection-observer": "^10.0.2",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"web-vitals": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@playwright/test": "^1.58.1",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitest/coverage-v8": "1.6.1",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-next": "^15.1.0",
|
||||
"jsdom": "^23.2.0",
|
||||
"postcss": "^8.4.35",
|
||||
"sharp": "^0.34.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.5.3",
|
||||
"vitest": "^1.3.1"
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/pg": "^8.16.0",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"autoprefixer": "^10.4.24",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "^16.1.6",
|
||||
"jsdom": "^27.4.0",
|
||||
"playwright-lighthouse": "^4.0.0",
|
||||
"postcss": "^8.5.6",
|
||||
"sharp": "^0.34.5",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Playwright configuration for E2E and performance testing.
|
||||
*
|
||||
* This configuration is optimized for:
|
||||
* - Chromium-only testing (required for Lighthouse integration)
|
||||
* - Performance audits with playwright-lighthouse
|
||||
* - Testing Next.js application across all locales (de, en, sr)
|
||||
*
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*/
|
||||
|
||||
export default defineConfig({
|
||||
// Test directory
|
||||
testDir: './tests/e2e',
|
||||
|
||||
// Run tests in files in parallel
|
||||
fullyParallel: true,
|
||||
|
||||
// Fail the build on CI if you accidentally left test.only in the source code
|
||||
forbidOnly: !!process.env.CI,
|
||||
|
||||
// Retry on CI only
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
|
||||
// Opt out of parallel tests on CI
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
|
||||
// Reporter to use
|
||||
reporter: [
|
||||
['html', { open: 'never' }],
|
||||
['list'],
|
||||
],
|
||||
|
||||
// Shared settings for all projects
|
||||
use: {
|
||||
// Base URL to use in actions like `await page.goto('/')`
|
||||
baseURL: 'http://localhost:3000',
|
||||
|
||||
// Collect trace when retrying the failed test
|
||||
trace: 'on-first-retry',
|
||||
|
||||
// Take screenshot on failure
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
|
||||
// Configure projects - Chromium only for Lighthouse compatibility
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
// Launch options for Lighthouse
|
||||
launchOptions: {
|
||||
args: ['--remote-debugging-port=9222'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Mobile Chrome for responsive testing
|
||||
{
|
||||
name: 'mobile-chrome',
|
||||
use: {
|
||||
...devices['Pixel 5'],
|
||||
launchOptions: {
|
||||
args: ['--remote-debugging-port=9223'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Tablet viewport for responsive testing
|
||||
{
|
||||
name: 'tablet',
|
||||
use: {
|
||||
viewport: { width: 768, height: 1024 },
|
||||
launchOptions: {
|
||||
args: ['--remote-debugging-port=9224'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Configure web server to start before tests
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120000,
|
||||
},
|
||||
|
||||
// Global timeout for each test
|
||||
timeout: 60000,
|
||||
|
||||
// Expect timeout
|
||||
expect: {
|
||||
timeout: 10000,
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
'@tailwindcss/postcss': {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"project_type": "single",
|
||||
"services": {
|
||||
"frontend": {
|
||||
"path": ".",
|
||||
"tech_stack": ["typescript", "react", "nextjs", "tailwindcss"],
|
||||
"framework": "Next.js 15 (App Router)",
|
||||
"port": 3000,
|
||||
"dev_command": "npm run dev",
|
||||
"build_command": "npm run build",
|
||||
"test_command": "npm test"
|
||||
}
|
||||
},
|
||||
"infrastructure": {
|
||||
"docker": false,
|
||||
"database": null,
|
||||
"ci_cd": false
|
||||
},
|
||||
"conventions": {
|
||||
"linter": "eslint",
|
||||
"formatter": "prettier",
|
||||
"testing": "vitest",
|
||||
"component_pattern": "Each component directory contains multiple .tsx files with a barrel export index.ts",
|
||||
"import_pattern": "Path aliases using @/ for src directory",
|
||||
"export_pattern": "index.ts re-exports components using export { ComponentName } or export { default as ComponentName }"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 35 KiB |