feat: initial commit — site + admin panel + Postgres content pipeline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
AyrisAI
2026-07-21 01:24:47 +03:00
co-authored by Claude Fable 5
commit 3420d32271
188 changed files with 25053 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "dev",
"runtimeExecutable": "corepack",
"runtimeArgs": ["yarn", "dev"],
"port": 5173
}
]
}
+12
View File
@@ -0,0 +1,12 @@
node_modules
dist
dist-ssr
.dist-staging
.vite-ssg-temp
dev-dist
.env
.git
.history
.vscode
.claude
*.log
+30
View File
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
.dist-staging
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.history
.vite-ssg-temp
dev-dist
.env
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}
+23
View File
@@ -0,0 +1,23 @@
# Müco Mutfak — statik site + admin API tek container
# Sunucu, açılışta PUBLISH_ON_BOOT=true ile DB'den güncel içeriği çekip build alır.
FROM node:22-alpine
WORKDIR /app
RUN corepack enable
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
# İlk build (repo'daki JSON'larla) — boot'ta DB içeriğiyle yenilenir
RUN yarn build
ENV NODE_ENV=production
ENV ADMIN_PORT=3001
ENV PUBLISH_ON_BOOT=true
EXPOSE 3001
CMD ["node", "server/index.mjs"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Cansu Arı
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+47
View File
@@ -0,0 +1,47 @@
# Müco Mutfak Corporate Website
Progressive Web App
## Requirements
**Node.js:** 22.22.2
## How to run
```code
yarn install
```
### Run for dev
```code
yarn dev
```
### Run for prod
```code
yarn build
```
## Prod Deploy
Web sitesi Cloudflare Pages üzerinden ücretsiz plan ile yayına alınmıştır.
Github repo ile bağlantılıdır.
Github repoya commit atıldığında otomatik olarak deploy olur.
Tüm environment'lar Cloudflare'de mevcuttur.
Recaptcha için Cloudflare üzerinden Turnstile kullanılmıştır.
Email için ise Resend kullanılmıştır.
## Color Pallette
- #3C393D
- #F1F4F5
- #DFEDEE
- #3FA0C7
- #8FCFE1
## Font
- All Round Gothic Font Family
- Shadows Into Light Regular
+44
View File
@@ -0,0 +1,44 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Button: typeof import('./src/components/Button.vue')['default']
Footer: typeof import('./src/components/Footer.vue')['default']
Header: typeof import('./src/components/Header.vue')['default']
HeroPromo: typeof import('./src/components/Hero/Promo.vue')['default']
LanguageSwitcher: typeof import('./src/components/LanguageSwitcher.vue')['default']
MenuCampaings: typeof import('./src/components/Menu/Campaings.vue')['default']
MenuContact: typeof import('./src/components/Menu/Contact.vue')['default']
MenuDesktop: typeof import('./src/components/Menu/Desktop.vue')['default']
MenuDesktopOld: typeof import('./src/components/Menu/Desktop-old.vue')['default']
MenuHome: typeof import('./src/components/Menu/Home.vue')['default']
MenuLayout: typeof import('./src/components/Menu/Layout.vue')['default']
MenuMobile: typeof import('./src/components/Menu/Mobile.vue')['default']
MenuMobileList: typeof import('./src/components/Menu/MobileList.vue')['default']
MenuSurvey: typeof import('./src/components/Menu/Survey.vue')['default']
MenuWrapper: typeof import('./src/components/Menu/MenuWrapper.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SectionsBanner: typeof import('./src/components/Sections/Banner.vue')['default']
SectionsBreakfast: typeof import('./src/components/Sections/Breakfast.vue')['default']
SectionsBreakfastEn: typeof import('./src/components/Sections/BreakfastEn.vue')['default']
SectionsBreakfastOld: typeof import('./src/components/Sections/Breakfast-old.vue')['default']
SectionsContact: typeof import('./src/components/Sections/Contact.vue')['default']
SectionsCTA: typeof import('./src/components/Sections/CTA.vue')['default']
SectionsGallery: typeof import('./src/components/Sections/Gallery.vue')['default']
SectionsHighlight: typeof import('./src/components/Sections/Highlight.vue')['default']
SectionsOverview: typeof import('./src/components/Sections/Overview.vue')['default']
SectionsParallax: typeof import('./src/components/Sections/Parallax.vue')['default']
SectionsTestimonials: typeof import('./src/components/Sections/Testimonials.vue')['default']
TopBar: typeof import('./src/components/TopBar.vue')['default']
}
}
+584
View File
@@ -0,0 +1,584 @@
// functions/contact/index.ts
declare global {
interface KVNamespace {
get(key: string): Promise<string | null>;
put(
key: string,
value: string,
options?: { expirationTtl?: number },
): Promise<void>;
}
}
interface Env {
RESEND_API_KEY: string;
EMAILS_API: string;
EMAILS_TO: string;
EMAILS_TO_CC: string;
EMAILS_FROM: string;
TURNSTILE_SECRET_KEY?: string;
RATE_LIMIT_KV?: KVNamespace;
}
function escapeHtml(input: unknown): string {
return String(input ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function safeEmailHref(email: string): string {
return `mailto:${encodeURIComponent(email)}`;
}
type Validated = {
fullName: string;
email: string;
phone?: string;
subject: "job-application" | "suggestion" | "something-else";
subjectOther?: string;
message: string;
consent: boolean;
};
function validatePayload(
body: any,
): { ok: true; value: Validated } | { ok: false; error: string } {
if (!body || typeof body !== "object") {
return { ok: false, error: "Invalid payload" };
}
const fullName = String(body.fullName ?? "").trim();
if (fullName.length < 4 || fullName.length > 100) {
return { ok: false, error: "Invalid name length" };
}
const email = String(body.email ?? "").trim();
const emailRe = /^(?:[^\s@]+)@(?:[^\s@]+)\.[^\s@]{2,}$/i;
if (!emailRe.test(email) || email.length > 254) {
return { ok: false, error: "Invalid email" };
}
const phone = String(body.phone ?? "").trim();
if (phone) {
const phoneRe =
/^\+?[1-9]\d{0,3}[\s\-().]?\d{1,4}[\s\-().]?\d{1,4}[\s\-().]?\d{1,9}$/;
if (!phoneRe.test(phone) || phone.length > 30) {
return { ok: false, error: "Invalid phone" };
}
}
const subject = String(body.subject ?? "");
const allowedSubjects = ["job-application", "suggestion", "something-else"];
if (!allowedSubjects.includes(subject)) {
return { ok: false, error: "Invalid subject" };
}
const subjectOther = String(body.subjectOther ?? "").trim();
if (subject === "something-else" && subjectOther.length === 0) {
return { ok: false, error: "Subject other required" };
}
if (subjectOther.length > 120) {
return { ok: false, error: "subjectOther too long" };
}
const message = String(body.message ?? "").trim();
if (message.length < 25 || message.length > 600) {
return { ok: false, error: "Invalid message length" };
}
const consent = Boolean(body.consent);
if (!consent) {
return { ok: false, error: "Consent required" };
}
return {
ok: true,
value: {
fullName,
email,
phone,
subject: subject as Validated["subject"],
subjectOther,
message,
consent,
},
};
}
function isAllowedOrigin(request: Request): boolean {
const origin = request.headers.get("Origin") || "";
const allowed = ["https://mucomutfak.com", "https://www.mucomutfak.com"];
if (!origin) return true;
return allowed.includes(origin);
}
async function verifyTurnstile(
secret: string | undefined,
token: string | undefined,
remoteIp?: string,
) {
if (!secret) return { ok: false, error: "Turnstile not configured" };
if (!token) return { ok: false, error: "Missing Turnstile token" };
try {
const form = new URLSearchParams();
form.set("secret", secret);
form.set("response", token);
if (remoteIp) {
form.set("remoteip", remoteIp);
}
const res = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
body: form,
},
);
const json = await res.json();
if (json.success) {
return { ok: true };
}
return {
ok: false,
error: "Turnstile failed",
details: json["error-codes"] || json,
};
} catch {
return { ok: false, error: "Turnstile verification error" };
}
}
async function rateLimitCheck(
ip: string | null,
kv: KVNamespace | undefined,
): Promise<{ ok: true } | { ok: false; retryAfter?: number }> {
if (!kv || !ip) return { ok: true };
const key = `cf_rl:${ip}:${new Date().toISOString().slice(0, 16)}`;
try {
const existing = await kv.get(key);
const count = existing ? parseInt(existing, 10) : 0;
const limitPerMinute = 6;
if (count >= limitPerMinute) {
return { ok: false, retryAfter: 60 };
}
await kv.put(key, String(count + 1), {
expirationTtl: 65,
});
return { ok: true };
} catch (e) {
console.error("KV rate limit error", e);
return { ok: true };
}
}
export async function onRequestPost({
request,
env,
}: {
request: Request;
env: Env;
}) {
try {
console.log("[contact] function invoked", {
origin: request.headers.get("Origin"),
hasSecret: !!env.TURNSTILE_SECRET_KEY,
hasKV: !!env.RATE_LIMIT_KV,
});
if (!isAllowedOrigin(request)) {
return new Response(
JSON.stringify({
success: false,
error: "Forbidden",
}),
{
status: 403,
headers: {
"Content-Type": "application/json",
},
},
);
}
const body = await request.json().catch(() => null);
if (body?.checkIfYouAreNotARobot) {
console.warn("Robot triggered from request");
return new Response(
JSON.stringify({
success: true,
}),
{
headers: {
"Content-Type": "application/json",
},
},
);
}
const validated = validatePayload(body);
if (!validated.ok) {
return new Response(
JSON.stringify({
success: false,
error: validated.error,
}),
{
status: 400,
headers: {
"Content-Type": "application/json",
},
},
);
}
const data = validated.value;
const ip =
request.headers.get("cf-connecting-ip") ||
request.headers.get("x-forwarded-for") ||
null;
const rateLimit = await rateLimitCheck(ip, env.RATE_LIMIT_KV);
if (!rateLimit.ok) {
return new Response(
JSON.stringify({
success: false,
error: "Rate limit exceeded",
}),
{
status: 429,
headers: {
"Content-Type": "application/json",
},
},
);
}
const turnstileToken = String(body?.turnstileToken ?? "");
if (env.TURNSTILE_SECRET_KEY) {
console.log("[contact] using TURNSTILE_SECRET_KEY");
const turnstile = await verifyTurnstile(
env.TURNSTILE_SECRET_KEY,
turnstileToken,
ip || undefined,
);
if (!turnstile.ok) {
console.warn("Turnstile failed", turnstile);
return new Response(
JSON.stringify({
success: false,
error: "Bot verification failed",
}),
{
status: 400,
headers: {
"Content-Type": "application/json",
},
},
);
}
}
const subjectLine =
data.subject === "something-else"
? data.subjectOther || "Contact Form"
: data.subject;
const receivedAt = new Date().toISOString();
const sourceOrigin =
request.headers.get("Origin") || request.headers.get("Referer") || "N/A";
const clientIp = ip || "N/A";
const subjectOtherSafe =
data.subject === "something-else" ? data.subjectOther || "" : "";
const consentText = data.consent ? "Yes" : "No";
const phoneText = data.phone || "-";
const quotedMessage = String(data.message)
.split("\n")
.map((line) => `> ${line}`)
.join("\n");
const mailText = `
Yeni Mesajınız Var!
── İletişim Detayları ─────────────────────
Ad Soyad: ${data.fullName}
E-posta: ${data.email}
Telefon: ${phoneText}
Konu: ${subjectLine} ${subjectOtherSafe ? " - " + subjectOtherSafe : ""}
İletişim İzni: ${consentText}
── Mesaj ─────────────────────────────
${data.message}
── Meta ─────────────────────────────────
Origin: ${sourceOrigin}
IP: ${clientIp}
Tarih: ${receivedAt}
── Yanıtla ─
> Ad Soyad: ${data.fullName}
> E-posta: ${data.email}
> Telefon: ${phoneText}
> Konu: ${subjectLine} ${subjectOtherSafe ? " - " + subjectOtherSafe : ""}
>
${quotedMessage}
`;
const mailHtml = `
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>
.wrapper { width:100%; background:#f5f7fb; margin:0; padding:24px 12px; }
.container { width:100%; max-width:640px; margin:0 auto; background:#ffffff; border-radius:12px; border:1px solid #e5e7eb; overflow:hidden; }
.header { padding:20px 24px; background:#0f172a; color:#ffffff; }
.title { margin:0; font-size:20px; line-height:1.3; }
.section { padding:20px 24px; }
.kv { width:100%; border-collapse:collapse; }
.kv th { text-align:left; padding:8px 0; font-size:13px; color:#64748b; width:160px; vertical-align:top; }
.kv td { padding:8px 0; font-size:14px; color:#0f172a; }
.divider { height:1px; background:#e5e7eb; margin:12px 0; }
.message { white-space:pre-wrap; padding:12px; border-radius:8px; background:#f8fafc; border:1px solid #e2e8f0; color:#0f172a; font-size:14px; }
.footer { padding:16px 24px; font-size:12px; color:#94a3b8; background:#fafafa; }
.pill { display:inline-block; padding:4px 10px; border-radius:9999px; border:1px solid #e2e8f0; font-size:12px; color:#0f172a; background:#f8fafc; }
.btn { display:inline-block; font-size:14px; text-decoration:none; padding:10px 14px; border-radius:8px; border:1px solid #0f172a; }
@media (max-width: 480px) {
.section { padding:16px; }
.header { padding:16px; }
.kv th { width:120px; }
}
</style>
</head>
<body style="margin:0; padding:0; background:#f5f7fb;">
<div class="wrapper">
<table role="presentation" class="container" cellpadding="0" cellspacing="0">
<tr>
<td class="header">
<h2 class="title">Yeni Mesajınız Var!</h2>
<div style="margin-top:8px;">
<span class="pill">${escapeHtml(subjectLine)}</span>
</div>
</td>
</tr>
<tr>
<td class="section">
<table class="kv" role="presentation">
<tr>
<th>Ad Soyad</th>
<td>${escapeHtml(data.fullName)}</td>
</tr>
<tr>
<th>E-posta</th>
<td>
<a href="${safeEmailHref(data.email)}" style="color:#0f172a;">
${escapeHtml(data.email)}
</a>
</td>
</tr>
<tr>
<th>Telefon</th>
<td>${escapeHtml(phoneText)}</td>
</tr>
<tr>
<th>Konu</th>
<td>
${escapeHtml(subjectLine)}
${subjectOtherSafe ? " - " + escapeHtml(subjectOtherSafe) : ""}
</td>
</tr>
<tr>
<th>İletişim İzni</th>
<td>${escapeHtml(consentText)}</td>
</tr>
</table>
<div style="margin-bottom:6px;">Mesaj</div>
<div class="message">
${escapeHtml(data.message)}
</div>
<div class="divider"></div>
<div style="font-weight:600; margin-bottom:6px;">
Reply-ready quote
</div>
<blockquote style="margin:0; padding-left:12px; border-left:3px solid #e2e8f0; color:#334155;">
<div>
<strong>Kimden:</strong>
${escapeHtml(data.fullName)} &lt;${escapeHtml(data.email)}&gt;
</div>
<div>
<strong>Telefon:</strong>
${escapeHtml(phoneText)}
</div>
<div>
<strong>Konu:</strong>
${escapeHtml(subjectLine)}
${subjectOtherSafe ? " - " + escapeHtml(subjectOtherSafe) : ""}
</div>
<div style="margin-top:8px; white-space:pre-wrap;">
${escapeHtml(data.message)}
</div>
</blockquote>
<div style="margin-top:16px;">
<a class="btn" href="${safeEmailHref(data.email)}">
Yanıtla ${escapeHtml(data.fullName)}
</a>
</div>
</td>
</tr>
<tr>
<td class="section" style="padding-top:0;">
<table class="kv" role="presentation">
<tr>
<th>Origin</th>
<td>${escapeHtml(sourceOrigin)}</td>
</tr>
<tr>
<th>IP</th>
<td>${escapeHtml(clientIp)}</td>
</tr>
<tr>
<th>Tarih</th>
<td>${escapeHtml(receivedAt)}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="footer">
Bu e-posta iletişim formundan otomatik olarak gönderilmiştir.
</td>
</tr>
</table>
</div>
</body>
</html>`;
const cc = env.EMAILS_TO_CC
? env.EMAILS_TO_CC.split(",")
.map((email) => email.trim())
.filter(Boolean)
: [];
const mailResponse = await fetch(env.EMAILS_API, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${env.RESEND_API_KEY}`,
},
body: JSON.stringify({
from: env.EMAILS_FROM,
to: [env.EMAILS_TO],
cc,
subject: `Yeni Mesajınız Var: ${subjectLine}${
subjectOtherSafe ? " - " + subjectOtherSafe : ""
}`,
text: mailText,
html: mailHtml,
reply_to: data.email,
}),
});
if (!mailResponse.ok) {
const detail = await mailResponse.text().catch(() => "no details");
console.error("Resend error:", mailResponse.status, detail);
return new Response(
JSON.stringify({
success: false,
error: "Unable to send message",
}),
{
status: 500,
headers: {
"Content-Type": "application/json",
},
},
);
}
const responseJson = await mailResponse.json().catch(() => ({}));
return new Response(
JSON.stringify({
success: true,
id: responseJson.id || null,
}),
{
headers: {
"Content-Type": "application/json",
},
},
);
} catch (err) {
console.error("Unhandled error in contact function", err);
return new Response(
JSON.stringify({
success: false,
error: "Server error",
}),
{
status: 500,
headers: {
"Content-Type": "application/json",
},
},
);
}
}
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#3FA0C7" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
{
"name": "mucomutfak",
"private": true,
"version": "1.1.5",
"type": "module",
"scripts": {
"dev": "vite",
"admin": "node server/index.mjs",
"build": "vite-ssg build",
"preview": "vite preview --outDir dist --strictPort"
},
"dependencies": {
"@iconify/vue": "^5.0.0",
"@tailwindcss/vite": "^4.1.13",
"@vueuse/head": "^2.0.0",
"add": "^2.0.6",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"gsap": "^3.15.0",
"locomotive-scroll": "^4.1.4",
"markdown-it": "^14.1.1",
"pg": "^8.22.0",
"swiper": "^12.1.3",
"tailwindcss": "^4.1.13",
"vite-plugin-pwa": "^1.1.0",
"vite-plugin-sitemap": "^0.8.2",
"vite-ssg": "^28.1.0",
"vue": "^3.5.21",
"vue-i18n": "9",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2",
"@types/node": "^24.6.1",
"@vitejs/plugin-vue": "^6.0.1",
"prettier": "^3.6.2",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.6"
},
"packageManager": "yarn@1.22.22"
}
+20
View File
@@ -0,0 +1,20 @@
/*
Cache-Control: public, max-age=1, must-revalidate
/*.html
Cache-Control: public, max-age=1, must-revalidate
/assets/*
Cache-Control: public, max-age=1, must-revalidate
/*.json
Cache-Control: public, max-age=1, must-revalidate
/sw.js
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
/workbox-*.js
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
/manifest.webmanifest
Cache-Control: public, max-age=1, must-revalidate
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

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