+
+
+ {t('common.developers')}
+
+
+ {t('pages.developers.ctaTitle')}
+
+
+ {t('pages.developers.ctaSubtitle')}
-
-
- 100%
-
-
- {t('home.feature2Title', 'Accuracy you can trust')}
-
-
- {t('home.feature2Desc', 'Get pixel-perfect, editable files in seconds with zero quality loss.')}
-
-
-
-
-
-
-
- {t('home.feature3Title', 'Built-in security')}
-
-
- {t('home.feature3Desc', 'Access files securely, protected by automatic encryption.')}
-
+
+
+ {t('pages.developers.openDocs')}
+
+
+
+ {t('pages.developers.getApiKey')}
+
- {/* Ad Slot - Bottom */}
+ {/* โโ Bottom CTA Banner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */}
+
+ {/* Decorative blobs */}
+
+
+
+
+
+ {t('home.ctaBannerLabel', 'Get started today')}
+
+
+ {t('home.ctaBannerTitle', 'Ready to convert your files?')}
+
+
+ {t('home.ctaBannerSubtitle', 'Join thousands of users who convert, compress, and edit their files every day โ completely free.')}
+
+
+
+ {t('home.ctaBrowseTools', 'Browse All Tools')}
+
+
+
+ {t('home.ctaCreateAccount', 'Create Free Account')}
+
+
+
+
+
+ {/* โโ Ad Slot - Bottom โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */}
>
);
diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css
index 6b06ffe..86edbc0 100644
--- a/frontend/src/styles/global.css
+++ b/frontend/src/styles/global.css
@@ -114,17 +114,84 @@
animation: fadeSlideIn 0.15s ease-out;
}
-/* Hero upload zone โ larger variant for the homepage */
+/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Hero Upload Zone โ premium glassmorphism card for the homepage
+ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.hero-upload-zone {
- @apply flex flex-col items-center justify-center rounded-2xl border-2 border-dashed border-slate-300 bg-gradient-to-b from-slate-50 to-white p-10 text-center transition-all duration-200 cursor-pointer sm:p-12 dark:border-slate-600 dark:from-slate-800/60 dark:to-slate-800/30;
+ @apply relative flex flex-col items-center justify-center rounded-3xl border border-slate-200/80 bg-white/80 backdrop-blur-sm p-10 text-center transition-all duration-300 ease-in-out cursor-pointer sm:p-14 shadow-sm dark:border-slate-700/60 dark:bg-slate-800/60 dark:backdrop-blur-sm;
+ background-image: radial-gradient(ellipse at top, rgba(219, 234, 254, 0.3) 0%, transparent 70%);
+}
+
+.dark .hero-upload-zone {
+ background-image: radial-gradient(ellipse at top, rgba(30, 58, 138, 0.15) 0%, transparent 70%);
+}
+
+.hero-upload-zone::before {
+ content: '';
+ @apply absolute inset-0 rounded-3xl transition-opacity duration-300 opacity-0;
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.06) 0%, rgba(168, 85, 247, 0.04) 100%);
+}
+
+.hero-upload-zone:hover::before {
+ @apply opacity-100;
}
.hero-upload-zone:hover {
- @apply border-primary-400 bg-gradient-to-b from-primary-50 to-white shadow-lg dark:border-primary-500 dark:from-primary-900/20 dark:to-slate-800/30;
+ @apply border-primary-300 shadow-lg shadow-primary-100/50 -translate-y-1 dark:border-primary-600/60 dark:shadow-primary-900/30;
}
.hero-upload-zone.drag-active {
- @apply border-primary-500 bg-gradient-to-b from-primary-100 to-primary-50 ring-2 ring-primary-300 shadow-xl dark:border-primary-400 dark:from-primary-900/30 dark:to-primary-900/10 dark:ring-primary-600;
+ @apply border-primary-500 shadow-2xl shadow-primary-200/60 scale-[1.02] dark:border-primary-400 dark:shadow-primary-900/40;
+ background-image: radial-gradient(ellipse at top, rgba(191, 219, 254, 0.5) 0%, rgba(219, 234, 254, 0.2) 100%);
+}
+
+/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Glassmorphism card utility
+ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+.glass-card {
+ @apply bg-white/70 backdrop-blur-md border border-white/50 shadow-sm dark:bg-slate-800/60 dark:border-slate-700/50;
+}
+
+/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Gradient hero mesh background
+ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+.hero-gradient-bg {
+ background:
+ radial-gradient(ellipse 80% 60% at 50% -20%, rgba(59, 130, 246, 0.12) 0%, transparent 70%),
+ radial-gradient(ellipse 60% 40% at 80% 20%, rgba(168, 85, 247, 0.06) 0%, transparent 60%),
+ linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
+}
+
+.dark .hero-gradient-bg {
+ background:
+ radial-gradient(ellipse 80% 60% at 50% -20%, rgba(30, 58, 138, 0.3) 0%, transparent 70%),
+ radial-gradient(ellipse 60% 40% at 80% 20%, rgba(88, 28, 135, 0.15) 0%, transparent 60%),
+ linear-gradient(180deg, #0f172a 0%, #0f172a 100%);
+}
+
+/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Shimmer loading effect
+ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+@keyframes shimmer-sweep {
+ 0% { background-position: -200% center; }
+ 100% { background-position: 200% center; }
+}
+
+.shimmer-text {
+ background: linear-gradient(90deg, #1e40af 30%, #7c3aed 50%, #1e40af 70%);
+ background-size: 200% auto;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ animation: shimmer-sweep 4s linear infinite;
+}
+
+/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ How it Works โ connector line between steps
+ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+.step-connector {
+ @apply absolute top-8 left-[calc(50%+2.5rem)] hidden h-px w-[calc(100%-5rem)] sm:block;
+ background: linear-gradient(90deg, rgba(59, 130, 246, 0.4) 0%, rgba(59, 130, 246, 0.1) 100%);
}
/* Modal animations */
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 5f12e73..1f9cc16 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -38,6 +38,51 @@ export default {
sans: ['Inter', 'Tajawal', 'system-ui', 'sans-serif'],
arabic: ['Tajawal', 'Inter', 'sans-serif'],
},
+ borderRadius: {
+ '3xl': '1.5rem',
+ '4xl': '2rem',
+ },
+ boxShadow: {
+ 'glow': '0 0 20px -4px rgba(59, 130, 246, 0.4)',
+ 'glow-lg': '0 0 40px -8px rgba(59, 130, 246, 0.5)',
+ },
+ transitionTimingFunction: {
+ 'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)',
+ },
+ animation: {
+ 'float': 'float 3s ease-in-out infinite',
+ 'shimmer': 'shimmer 2.5s linear infinite',
+ 'fade-up': 'fadeUp 0.5s ease-out forwards',
+ 'fade-in': 'fadeIn 0.4s ease-out forwards',
+ 'scale-in': 'scaleIn 0.3s ease-out forwards',
+ 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
+ },
+ keyframes: {
+ float: {
+ '0%, 100%': { transform: 'translateY(0px)' },
+ '50%': { transform: 'translateY(-10px)' },
+ },
+ shimmer: {
+ '0%': { backgroundPosition: '-200% center' },
+ '100%': { backgroundPosition: '200% center' },
+ },
+ fadeUp: {
+ '0%': { opacity: '0', transform: 'translateY(24px)' },
+ '100%': { opacity: '1', transform: 'translateY(0)' },
+ },
+ fadeIn: {
+ '0%': { opacity: '0' },
+ '100%': { opacity: '1' },
+ },
+ scaleIn: {
+ '0%': { opacity: '0', transform: 'scale(0.92)' },
+ '100%': { opacity: '1', transform: 'scale(1)' },
+ },
+ },
+ backgroundImage: {
+ 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
+ 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
+ },
},
},
plugins: [],
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index 1926206..9f6e5b3 100644
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -28,20 +28,44 @@ if [ ! -f ".env" ]; then
exit 1
fi
-echo -e "${YELLOW}1/7 โ Pulling latest code...${NC}"
+read_env_value() {
+ local key="$1"
+ local fallback="${2:-}"
+ local shell_value="${!key-}"
+ local file_value
+
+ file_value="$(grep -E "^${key}=" .env 2>/dev/null | tail -n 1 | cut -d= -f2- || true)"
+
+ if [ -n "$shell_value" ]; then
+ printf '%s' "$shell_value"
+ elif [ -n "$file_value" ]; then
+ printf '%s' "$file_value"
+ else
+ printf '%s' "$fallback"
+ fi
+}
+
+normalize_bool() {
+ printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
+}
+
+INDEXNOW_AUTO_SUBMIT_VALUE="$(normalize_bool "$(read_env_value INDEXNOW_AUTO_SUBMIT 1)")"
+INDEXNOW_STRICT_VALUE="$(normalize_bool "$(read_env_value INDEXNOW_STRICT false)")"
+
+echo -e "${YELLOW}1/8 โ Pulling latest code...${NC}"
git pull origin main 2>/dev/null || echo "Not a git repo or no remote, skipping pull."
-echo -e "${YELLOW}2/7 โ Building Docker images...${NC}"
+echo -e "${YELLOW}2/8 โ Building Docker images...${NC}"
docker compose -f docker-compose.prod.yml build --no-cache
-echo -e "${YELLOW}3/7 โ Stopping old containers...${NC}"
+echo -e "${YELLOW}3/8 โ Stopping old containers...${NC}"
docker compose -f docker-compose.prod.yml down --remove-orphans
-echo -e "${YELLOW}4/7 โ Starting services...${NC}"
+echo -e "${YELLOW}4/8 โ Starting services...${NC}"
docker compose -f docker-compose.prod.yml up -d
if [ "${SKIP_AI_RUNTIME_CHECKS:-0}" != "1" ]; then
- echo -e "${YELLOW}5/7 โ Verifying AI runtime in backend + worker...${NC}"
+ echo -e "${YELLOW}5/8 โ Verifying AI runtime in backend + worker...${NC}"
for service in backend celery_worker; do
if ! docker compose -f docker-compose.prod.yml exec -T "$service" python - <<'PY'
import importlib.util
@@ -69,10 +93,10 @@ PY
fi
done
else
- echo -e "${YELLOW}5/7 โ Skipping AI runtime checks (SKIP_AI_RUNTIME_CHECKS=1).${NC}"
+ echo -e "${YELLOW}5/8 โ Skipping AI runtime checks (SKIP_AI_RUNTIME_CHECKS=1).${NC}"
fi
-echo -e "${YELLOW}6/7 โ Waiting for health check...${NC}"
+echo -e "${YELLOW}6/8 โ Waiting for health check...${NC}"
sleep 10
# Health check
@@ -84,7 +108,23 @@ else
exit 1
fi
-echo -e "${YELLOW}7/7 โ Current containers:${NC}"
+if [ "${INDEXNOW_AUTO_SUBMIT_VALUE:-1}" = "1" ] || [ "${INDEXNOW_AUTO_SUBMIT_VALUE:-true}" = "true" ]; then
+ echo -e "${YELLOW}7/8 โ Submitting URLs to IndexNow...${NC}"
+ if docker compose -f docker-compose.prod.yml run --rm frontend_build_step node scripts/submit-indexnow.mjs; then
+ echo -e "${GREEN}โ IndexNow notification completed.${NC}"
+ else
+ if [ "$INDEXNOW_STRICT_VALUE" = "1" ] || [ "$INDEXNOW_STRICT_VALUE" = "true" ]; then
+ echo -e "${RED}โ IndexNow notification failed and INDEXNOW_STRICT is enabled.${NC}"
+ exit 1
+ fi
+
+ echo -e "${YELLOW}! IndexNow notification failed; deployment will continue.${NC}"
+ fi
+else
+ echo -e "${YELLOW}7/8 โ Skipping IndexNow notification (INDEXNOW_AUTO_SUBMIT=0).${NC}"
+fi
+
+echo -e "${YELLOW}8/8 โ Current containers:${NC}"
docker compose -f docker-compose.prod.yml ps
echo ""