Auto-deploy: 2026-08-05 16:17:53
This commit is contained in:
@@ -1 +1,8 @@
|
||||
GEMINI_API_KEY="your_api_key_here"
|
||||
|
||||
# Model overrides (optional).
|
||||
# CV tailoring decides whether an application lands, so it uses the stronger
|
||||
# model. Comments and post rewrites are high-volume, so they stay on the cheap one.
|
||||
GEMINI_MODEL_CV="gemini-flash-latest"
|
||||
GEMINI_MODEL_TEXT="gemini-flash-latest"
|
||||
GEMINI_MODEL_LIGHT="gemini-flash-lite-latest"
|
||||
|
||||
+83
-177
@@ -3,206 +3,112 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
/*
|
||||
* CSS Optimized for Dompdf / mPDF / TCPDF
|
||||
* Avoids flexbox, uses standard block/inline styling for flawless PDF generation.
|
||||
*/
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
/* ==========================================================================
|
||||
* ATS-SAFE CV TEMPLATE
|
||||
* Rules enforced by this stylesheet:
|
||||
* - Single column. No floats, no tables, no absolute positioning.
|
||||
* Multi-column layouts are the #1 cause of scrambled ATS text extraction.
|
||||
* - No headers/footers, no text boxes, no images, no icons.
|
||||
* - Standard, machine-recognisable section headings.
|
||||
* - Standard fonts only (Helvetica/Arial), ligatures disabled so that
|
||||
* Dompdf does not emit glyphs that break text copy-out.
|
||||
* - Contact details and URLs are PLAIN TEXT, not anchor-wrapped labels:
|
||||
* many parsers read the visible text, not the href.
|
||||
* ========================================================================== */
|
||||
body {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-variant-ligatures: none;
|
||||
font-feature-settings: "liga" 0, "clig" 0;
|
||||
letter-spacing: 0px;
|
||||
letter-spacing: 0;
|
||||
text-rendering: geometricPrecision;
|
||||
font-size: 13px;
|
||||
color: #222;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #1a237e;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: #1a237e;
|
||||
margin: 0 0 5px 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.headline {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #424242;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.contact {
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
font-size: 10.5pt;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.42;
|
||||
margin: 0;
|
||||
padding: 34px 40px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #1a237e;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 6px;
|
||||
padding-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
h1 {
|
||||
font-size: 20pt;
|
||||
color: #111;
|
||||
margin: 0 0 4px 0;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
p { margin: 0 0 6px 0; text-align: justify; }
|
||||
|
||||
.job-block { margin-bottom: 10px; }
|
||||
|
||||
.job-header {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
display: table; /* هذا يحل مشاكل Dompdf */
|
||||
}
|
||||
|
||||
.job-title {
|
||||
font-weight: bold;
|
||||
font-size: 13.5px; /* تصغير طفيف جداً */
|
||||
color: #222;
|
||||
float: left;
|
||||
width: 70%; /* إجبار العنوان على عدم تجاوز 70% من السطر */
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.job-meta {
|
||||
font-size: 11px;
|
||||
color: #1a237e;
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
width: 28%; /* تخصيص مساحة آمنة للتاريخ */
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clear { clear: both; }
|
||||
|
||||
|
||||
.headline {
|
||||
font-size: 11.5pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
|
||||
.contact {
|
||||
font-size: 9.5pt;
|
||||
color: #333;
|
||||
line-height: 1.55;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1.5px solid #222;
|
||||
}
|
||||
|
||||
ul { margin: 4px 0 8px 0; padding-left: 20px; }
|
||||
li { margin-bottom: 4px; text-align: justify; }
|
||||
h2 {
|
||||
font-size: 11pt;
|
||||
font-weight: bold;
|
||||
color: #111;
|
||||
border-bottom: 1px solid #bbb;
|
||||
margin: 16px 0 7px 0;
|
||||
padding-bottom: 3px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
}
|
||||
|
||||
p { margin: 0 0 6px 0; text-align: left; }
|
||||
|
||||
.job { margin-bottom: 11px; }
|
||||
|
||||
/* Each field on its own line, in reading order. No float, no table.
|
||||
A parser walking the DOM gets: title, then employer/location, then dates. */
|
||||
.job-title { font-weight: bold; font-size: 10.5pt; color: #111; margin: 0; }
|
||||
.job-company { font-size: 10pt; color: #222; margin: 1px 0 0 0; }
|
||||
.job-dates { font-size: 9.5pt; color: #444; margin: 1px 0 3px 0; }
|
||||
|
||||
ul { margin: 3px 0 7px 0; padding-left: 17px; }
|
||||
li { margin-bottom: 3px; text-align: left; }
|
||||
|
||||
.skill-line { margin: 0 0 4px 0; }
|
||||
.label { font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h1>Hamza Ayed</h1>
|
||||
<!-- PHP WILL INJECT THE AI-GENERATED HEADLINE HERE -->
|
||||
<div class="headline">{{JOB_HEADLINE}}</div>
|
||||
<div class="contact">
|
||||
Amman, Jordan | +962 79 858 3052 | <a href="mailto:hamzaayed.dev@gmail.com" style="color: #555; text-decoration: none;">hamzaayed.dev@gmail.com</a><br>
|
||||
<a href="https://linkedin.com/in/hamza-ayed" style="color: #555; text-decoration: none; font-weight: bold;">linkedin.com/in/hamza-ayed</a> |
|
||||
<a href="https://github.com/Hamza-Ayed" style="color: #555; text-decoration: none; font-weight: bold;">github.com/Hamza-Ayed</a> |
|
||||
<a href="https://intaleqapp.com/hamza.html" style="color: #1a237e; text-decoration: underline; font-weight: bold;">intaleqapp.com/hamza.html (Portfolio)</a>
|
||||
</div>
|
||||
</div>
|
||||
<h1>{{FULL_NAME}}</h1>
|
||||
<div class="headline">{{JOB_HEADLINE}}</div>
|
||||
<div class="contact">{{CONTACT_BLOCK}}</div>
|
||||
|
||||
<div class="section-title">Professional Summary</div>
|
||||
<!-- PHP WILL INJECT THE AI-GENERATED SUMMARY HERE -->
|
||||
<h2>Professional Summary</h2>
|
||||
<p>{{TAILORED_SUMMARY}}</p>
|
||||
|
||||
<h2>Key Achievements</h2>
|
||||
{{ACHIEVEMENTS_BLOCK}}
|
||||
|
||||
<h2>Technical Skills</h2>
|
||||
<p class="skill-line"><span class="label">Core Focus for This Role:</span> {{DYNAMIC_SKILLS}}</p>
|
||||
{{SKILLS_BLOCK}}
|
||||
|
||||
<div class="section-title">Technical Skills</div>
|
||||
<!-- PHP WILL INJECT ATS KEYWORDS HERE -->
|
||||
<p><strong>Core Focus:</strong> {{DYNAMIC_SKILLS}}</p>
|
||||
<p><strong>Backend & Architecture:</strong> PHP (Workerman), Node.js, NestJS, Python (FastAPI/Flask), REST APIs, WebSockets, Event-Driven Architecture</p>
|
||||
<p><strong>Infrastructure & Cloud:</strong> Docker, Linux, Nginx, Redis, CI/CD (GitHub Actions), AWS</p>
|
||||
<p><strong>Databases & Geospatial:</strong> PostgreSQL/PostGIS, MySQL, OpenStreetMap (OSM), GraphHopper, MapLibre GL, Spatial Queries</p>
|
||||
<p><strong>Mobile:</strong> Flutter/Dart, GetX, BLoC/Cubit</p>
|
||||
<h2>Professional Experience</h2>
|
||||
{{EXPERIENCE_BLOCK}}
|
||||
|
||||
<div class="section-title">Selected Technical Highlights</div>
|
||||
<ul>
|
||||
<li>Built proprietary OSM-based mapping infrastructure replacing Google Maps APIs</li>
|
||||
<li>Engineered WebSocket-based real-time ride tracking systems</li>
|
||||
<li>Developed offline-first mapping SDKs published on pub.dev and npm</li>
|
||||
<li>Designed async AI document-processing pipelines using vision models</li>
|
||||
<li>Optimized routing infrastructure using GraphHopper and PostGIS</li>
|
||||
</ul>
|
||||
<h2>Education</h2>
|
||||
{{EDUCATION_BLOCK}}
|
||||
|
||||
<div class="section-title">Professional Experience</div>
|
||||
<h2>Certifications</h2>
|
||||
{{CERTIFICATIONS_BLOCK}}
|
||||
|
||||
<div class="job-block">
|
||||
<div class="job-header">
|
||||
<div class="job-title">CTO & Technical Architect | Systems & Backend Focus — Intaleq | </div>
|
||||
<div class="job-meta">January 2024 – Present | Remote (MENA Region)</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Led the full-stack architecture of a smart transportation ecosystem supporting 1,800+ drivers and 2,500+ riders, building the core backend with PHP/Workerman.</li>
|
||||
<li>Architected the proprietary mapping platform (IntaleqMaps), orchestrating GraphHopper routing, OSM tile serving, and PostGIS spatial queries via a NestJS API layer, saving $10,000+/month in operational costs.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="job-block">
|
||||
<div class="job-header">
|
||||
<div class="job-title">Founding Engineer & Lead Backend Architect — Tripz Egypt | </div>
|
||||
<div class="job-meta">Jan 2023 – Present | Cairo / Remote</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Co-founded and architected Egypt's homegrown ride-hailing platform, managing the entire distributed ecosystem with real-time tracking for 4,318 drivers and 2,464 riders.</li>
|
||||
<li>Implemented robust event-driven architecture for real-time driver/rider matching and WebSockets-based route optimization.</li>
|
||||
<li>Integrated local digital payment gateways and driver payout systems, supporting an 8% commission structure that drove a $0.78 Customer Acquisition Cost.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="job-block">
|
||||
<div class="job-header">
|
||||
<div class="job-title">Senior Systems Architect — Mobile & Backend (Freelance) | </div>
|
||||
<div class="job-meta">Jan 2017 – Dec 2023 | Jordan / Remote</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Delivered 25+ production enterprise applications across GIS, FinTech, and utilities, leading full lifecycle architecture for clients in the MENA region.</li>
|
||||
<li>Developed Nabih (AI Smart Responder): designed and built an intelligent virtual assistant system connecting a Flutter mobile application (GetX/Cubit) with custom PHP backend APIs.</li>
|
||||
<li>Developed Musadaq (AI-Powered Invoice Processing): engineered an automated KYC and invoice document processor using AI vision models and asynchronous processing pipelines.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Notable Projects</div>
|
||||
<ul>
|
||||
<li><strong>IntaleqMaps Engine:</strong> Engineered a proprietary mapping platform orchestrating GraphHopper routing, OSM tile serving, and PostGIS spatial queries via a NestJS API layer, replacing Google Maps API across the MENA region.</li>
|
||||
<li><strong>Nabih AI Virtual Assistant:</strong> Architected an automated customer support solution connecting a Flutter application (GetX/Cubit) to custom high-concurrency PHP backend APIs.</li>
|
||||
<li><strong>Musadaq AI Document Processor:</strong> Engineered an automated KYC and invoice document processing platform utilizing AI vision models and async processing pipelines.</li>
|
||||
<li><strong>Meta Ads Manager & SaaS Gateways:</strong> Deployed multi-tenant platforms using NestJS/React (Meta Ads Manager) and high-concurrency Node.js (WhatsApp bridge managing headless Puppeteer sessions in Docker) to automate operations.</li>
|
||||
</ul>
|
||||
|
||||
<div class="section-title">Open Source Contributions</div>
|
||||
<ul>
|
||||
<li><strong>intaleq_maps (Flutter SDK):</strong> Published on <a href="https://pub.dev/packages/intaleq_maps" style="color: #1a237e; text-decoration: none; font-weight: bold;">pub.dev/packages/intaleq_maps</a>. Custom map rendering, offline caching, and route plotting package optimized for low-bandwidth environments.</li>
|
||||
<li><strong>intaleq-maps-gl (NPM Library):</strong> Published on <a href="https://libraries.io/npm/intaleq-maps-gl" style="color: #1a237e; text-decoration: none; font-weight: bold;">npmjs.com/package/intaleq-maps-gl</a>. Web-based Mapbox GL compatible library optimized for custom OSM tiles and routing integration.</li>
|
||||
</ul>
|
||||
|
||||
<div class="section-title">Education & Certifications</div>
|
||||
<ul>
|
||||
<li><strong>BS Mathematics (Applied Computing & Algorithms)</strong>, Mutah University (2003–2007)</li>
|
||||
<li><strong>Google Data Analytics</strong> Professional Certificate (Coursera)</li>
|
||||
<li><strong>IBM Data Analyst</strong> Professional Certificate (Coursera)</li>
|
||||
<li><strong>Meta APIs & Django Web Framework</strong> Course Certificates (Coursera)</li>
|
||||
<li><strong>AWS Cloud Practitioner & Cloud Architecture Fundamentals</strong></li>
|
||||
</ul>
|
||||
|
||||
<div class="section-title">Languages</div>
|
||||
<p><strong>Arabic:</strong> Native | <strong>English:</strong> Professional Working Proficiency | <strong>Turkish:</strong> Limited Working Proficiency</p>
|
||||
|
||||
<div class="section-title">Availability & Work Authorization</div>
|
||||
<p style="text-align: center; font-weight: bold; color: #1a237e; margin-top: 10px;">
|
||||
Available Immediately | Open to Remote, Hybrid & Relocation (GCC/Europe/MENA) | Valid Passport
|
||||
</p>
|
||||
<h2>Additional Information</h2>
|
||||
<p class="skill-line"><span class="label">Languages:</span> {{LANGUAGES}}</p>
|
||||
<p class="skill-line"><span class="label">Location:</span> {{LOCATION}}</p>
|
||||
<p class="skill-line"><span class="label">Availability:</span> {{AVAILABILITY}}</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-variant-ligatures: none;
|
||||
font-feature-settings: "liga" 0, "clig" 0;
|
||||
letter-spacing: 0px;
|
||||
text-rendering: geometricPrecision;
|
||||
font-size: 13px;
|
||||
color: #222;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #1a237e;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: #1a237e;
|
||||
margin: 0 0 5px 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.headline {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #424242;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.contact {
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #1a237e;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 6px;
|
||||
padding-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
p { margin: 0 0 6px 0; text-align: justify; }
|
||||
|
||||
.job-block { margin-bottom: 10px; }
|
||||
|
||||
.job-header {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.job-title {
|
||||
font-weight: bold;
|
||||
font-size: 13.5px;
|
||||
color: #222;
|
||||
float: left;
|
||||
width: 70%;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.job-meta {
|
||||
font-size: 11px;
|
||||
color: #1a237e;
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
width: 28%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clear { clear: both; }
|
||||
|
||||
ul { margin: 4px 0 8px 0; padding-left: 20px; }
|
||||
li { margin-bottom: 4px; text-align: justify; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h1>Hamza Ayed</h1>
|
||||
<div class="headline">{{JOB_HEADLINE}}</div>
|
||||
<div class="contact">
|
||||
Amman, Jordan | +962 79 858 3052 | <a href="mailto:hamzaayed.dev@gmail.com" style="color: #555; text-decoration: none;">hamzaayed.dev@gmail.com</a><br>
|
||||
<a href="https://linkedin.com/in/hamza-ayed" style="color: #555; text-decoration: none; font-weight: bold;">linkedin.com/in/hamza-ayed</a> |
|
||||
<a href="https://github.com/Hamza-Ayed" style="color: #555; text-decoration: none; font-weight: bold;">github.com/Hamza-Ayed</a> |
|
||||
<a href="https://intaleqapp.com/hamza.html" style="color: #1a237e; text-decoration: underline; font-weight: bold;">intaleqapp.com/hamza.html (Portfolio)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Professional Summary</div>
|
||||
<p>{{TAILORED_SUMMARY}}</p>
|
||||
|
||||
<div class="section-title">Technical Skills</div>
|
||||
<p><strong>Core Focus:</strong> {{DYNAMIC_SKILLS}}</p>
|
||||
<p><strong>Backend & Architecture:</strong> PHP (Workerman), Node.js, NestJS, Python (FastAPI/Flask), REST APIs, WebSockets, Microservices</p>
|
||||
<p><strong>Infrastructure & Cloud:</strong> Docker, Linux, Nginx, Redis, CI/CD (GitHub Actions), AWS</p>
|
||||
<p><strong>Databases:</strong> PostgreSQL, MySQL, Redis, Database Design & Query Optimization</p>
|
||||
<p><strong>Geospatial (Supplemental):</strong> PostGIS, OpenStreetMap (OSM), GraphHopper, MapLibre GL, Spatial Queries</p>
|
||||
<p><strong>Mobile:</strong> Flutter/Dart, GetX, BLoC/Cubit</p>
|
||||
|
||||
<div class="section-title">Impact & Delivery</div>
|
||||
<ul>
|
||||
<li>Built and scaled platforms serving 6,000+ active users across two markets with zero downtime deployments</li>
|
||||
<li>Engineered proprietary OSM-based mapping infrastructure replacing Google Maps APIs across the MENA region — saving $10,000+/month in operational costs</li>
|
||||
<li>Designed high-concurrency WebSocket systems handling real-time operations at scale</li>
|
||||
<li>Delivered 25+ production applications across healthcare, logistics, fintech, and utilities for MENA clients</li>
|
||||
<li>Published open-source mapping SDKs on pub.dev and npm, used in production environments</li>
|
||||
</ul>
|
||||
|
||||
<div class="section-title">Professional Experience</div>
|
||||
|
||||
<div class="job-block">
|
||||
<div class="job-header">
|
||||
<div class="job-title">Technical Lead & Backend Engineer — Intaleq | </div>
|
||||
<div class="job-meta">January 2024 – Present | Amman, Jordan</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Built full-stack transportation platform from zero to production: PHP/Workerman backend, Flutter mobile apps, WebSocket-based real-time dispatcher — serving 1,800+ active drivers.</li>
|
||||
<li>Developed proprietary mapping platform (IntaleqMaps) orchestrating GraphHopper routing, OSM tile serving, and PostGIS spatial queries via a NestJS API layer — reducing mapping API costs by $10,000+/month.</li>
|
||||
<li>Designed event-driven architecture for real-time driver/rider matching, live tracking, and automated fare calculation.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="job-block">
|
||||
<div class="job-header">
|
||||
<div class="job-title">Lead Backend Engineer — Tripz Egypt | </div>
|
||||
<div class="job-meta">Jan 2023 – Present | Cairo / Remote</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Built complete ride-hailing platform: PHP backend system, Flutter rider and driver apps, real-time WebSocket communication layer, and local payment gateway integrations.</li>
|
||||
<li>Engineered event-driven driver/rider matching system serving 4,318 drivers with a $0.78 customer acquisition cost — the lowest in the Egyptian market.</li>
|
||||
<li>Integrated local digital payment gateways and designed automated driver payout infrastructure supporting an 8% commission model.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="job-block">
|
||||
<div class="job-header">
|
||||
<div class="job-title">Senior Backend Engineer — Freelance | </div>
|
||||
<div class="job-meta">Jan 2017 – Dec 2023 | Jordan / Remote</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Delivered 25+ applications for MENA clients across healthcare, logistics, HR technology, sports platforms, and utility management systems — full lifecycle from architecture to deployment.</li>
|
||||
<li>Built automated customer support platform: designed Flutter application (GetX/Cubit) connected to high-concurrency PHP backend APIs handling real-time query resolution.</li>
|
||||
<li>Developed document processing system: engineered async processing pipelines using vision models for automated KYC and invoice data extraction.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Notable Projects</div>
|
||||
<ul>
|
||||
<li><strong>IntaleqMaps Engine:</strong> Proprietary mapping platform orchestrating GraphHopper routing, OSM tile serving, and PostGIS spatial queries via a NestJS API layer — replacing Google Maps API across the MENA region.</li>
|
||||
<li><strong>Tripz Egypt Platform:</strong> End-to-end ride-hailing system with 8 ride types, real-time matching, custom payment infrastructure, and AI-powered KYC verification.</li>
|
||||
<li><strong>Customer Support Automation Platform:</strong> Built an automated query resolution system connecting a Flutter application (GetX/Cubit) to high-concurrency PHP backend APIs.</li>
|
||||
<li><strong>Document Processing Platform:</strong> Engineered automated KYC and invoice processing using vision models and asynchronous processing pipelines.</li>
|
||||
<li><strong>Meta Ads Manager (SaaS):</strong> Deployed multi-tenant platform using NestJS/React for automated ad campaign management.</li>
|
||||
<li><strong>WhatsApp Bridge:</strong> High-concurrency Node.js service managing headless Puppeteer sessions in Docker for automated messaging operations.</li>
|
||||
</ul>
|
||||
|
||||
<div class="section-title">Open Source Contributions</div>
|
||||
<ul>
|
||||
<li><strong>intaleq_maps (Flutter SDK):</strong> Published on <a href="https://pub.dev/packages/intaleq_maps" style="color: #1a237e; text-decoration: none; font-weight: bold;">pub.dev/packages/intaleq_maps</a>. Custom map rendering, offline caching, and route plotting package optimized for low-bandwidth environments.</li>
|
||||
<li><strong>intaleq-maps-gl (NPM Library):</strong> Published on <a href="https://libraries.io/npm/intaleq-maps-gl" style="color: #1a237e; text-decoration: none; font-weight: bold;">npmjs.com/package/intaleq-maps-gl</a>. Web-based Mapbox GL compatible library optimized for custom OSM tiles and routing integration.</li>
|
||||
</ul>
|
||||
|
||||
<div class="section-title">Education & Certifications</div>
|
||||
<ul>
|
||||
<li><strong>BS Mathematics (Applied Computing & Algorithms)</strong>, Mutah University (2003–2007)</li>
|
||||
<li><strong>Google Data Analytics</strong> Professional Certificate (Coursera)</li>
|
||||
<li><strong>IBM Data Analyst</strong> Professional Certificate (Coursera)</li>
|
||||
<li><strong>Meta APIs & Django Web Framework</strong> Course Certificates (Coursera)</li>
|
||||
<li><strong>AWS Cloud Practitioner & Cloud Architecture Fundamentals</strong></li>
|
||||
</ul>
|
||||
|
||||
<div class="section-title">Languages</div>
|
||||
<p><strong>Arabic:</strong> Native | <strong>English:</strong> Professional Working Proficiency | <strong>Turkish:</strong> Limited Working Proficiency</p>
|
||||
|
||||
<div class="section-title">Availability</div>
|
||||
<p style="text-align: center; font-weight: bold; color: #1a237e; margin-top: 10px;">
|
||||
Based in Amman, Jordan — Available Immediately | Open to On-site, Hybrid & Remote Roles in Jordan & MENA
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+395
-194
@@ -1,6 +1,10 @@
|
||||
<?php
|
||||
// ============================================================================
|
||||
// Dynamic ATS-Optimized CV Generator & AI Proxy (Backend)
|
||||
// ----------------------------------------------------------------------------
|
||||
// All CV facts come from profile.json, which is generated from profile_data.js
|
||||
// (run `node sync_profile.js` after editing the profile). Nothing in this file
|
||||
// hardcodes a job title, a metric, a date or a contact detail.
|
||||
// ============================================================================
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
@@ -24,282 +28,479 @@ use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
use Dotenv\Dotenv;
|
||||
|
||||
// Path to the .env file located outside the document root for security
|
||||
// Assuming script is in /home/user/htdocs/domain.com/cv/server/
|
||||
// and .env is in /home/user/
|
||||
// .env lives outside the document root.
|
||||
$envPath = realpath(__DIR__ . '/../../../..');
|
||||
|
||||
if ($envPath && file_exists($envPath . '/.env')) {
|
||||
$dotenv = Dotenv::createImmutable($envPath);
|
||||
$dotenv->load();
|
||||
}
|
||||
|
||||
$rawData = file_get_contents('php://input');
|
||||
$data = json_decode($rawData, true);
|
||||
|
||||
$data = json_decode($rawData, true) ?: [];
|
||||
$action = $data['action'] ?? 'generateText';
|
||||
|
||||
// Prioritize API key from .env over frontend payload
|
||||
$apiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY') ?: ($data['apiKey'] ?? '');
|
||||
|
||||
if (empty($apiKey)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Missing apiKey. Please set GEMINI_API_KEY in .env or pass it in request."]);
|
||||
echo json_encode(["error" => "Missing apiKey. Set GEMINI_API_KEY in .env or pass it in the request."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Standardized on gemini-flash-lite-latest due to quota limits
|
||||
$model = "gemini-flash-lite-latest";
|
||||
$geminiUrl = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key=" . $apiKey;
|
||||
// ── Model selection ─────────────────────────────────────────────────────────
|
||||
// CV tailoring is the one call where output quality directly determines whether
|
||||
// an application lands, so it gets the stronger model. Comments and post
|
||||
// rewrites are high-volume and low-stakes, so they stay on the cheap model.
|
||||
$MODELS = [
|
||||
'generatePdf' => $_ENV['GEMINI_MODEL_CV'] ?? 'gemini-flash-latest',
|
||||
'generateText' => $_ENV['GEMINI_MODEL_TEXT'] ?? 'gemini-flash-latest',
|
||||
'generateComment' => $_ENV['GEMINI_MODEL_LIGHT'] ?? 'gemini-flash-lite-latest',
|
||||
'repurposePost' => $_ENV['GEMINI_MODEL_LIGHT'] ?? 'gemini-flash-lite-latest',
|
||||
];
|
||||
|
||||
// ==========================================
|
||||
// ACTION 1: Generate ATS PDF CV
|
||||
// ==========================================
|
||||
if ($action === 'generatePdf') {
|
||||
$jobDescription = $data['jobDescription'] ?? '';
|
||||
$template = $data['template'] ?? 'default'; // 'amman' or 'default'
|
||||
function geminiUrl(string $model, string $apiKey): string {
|
||||
return "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key=" . $apiKey;
|
||||
}
|
||||
|
||||
// --- Amman Market Prompt (Senior Backend Engineer & Technical Lead) ---
|
||||
if ($template === 'amman') {
|
||||
$prompt = "You are an expert ATS CV tailor for the Jordan/Amman local tech market. Read the following job description and generate tailored content.
|
||||
|
||||
STRICT INTEGRITY RULES:
|
||||
1. NEVER invent skills I do not have. My TRUE technical stack is: PHP (Workerman), Node.js, NestJS, Python (FastAPI/Flask), PostgreSQL/PostGIS, Docker, Flutter, GetX, BLoC, WebSockets, OpenStreetMap.
|
||||
2. Do NOT add data science skills like TensorFlow, PyTorch, Scikit-learn, Hadoop, or MLOps.
|
||||
3. My title MUST be aligned with 'Senior Backend Engineer', 'Technical Lead', or 'Lead Backend Engineer'. NEVER use 'Solutions Architect', 'AI Developer', or 'CTO'.
|
||||
|
||||
Return ONLY a valid JSON object with EXACTLY three keys: 'headline', 'summary', and 'skills'.
|
||||
The 'headline' should be a clean, confident title based on my TRUE skills — matching Amman market expectations (Senior/Lead Backend Engineer).
|
||||
The 'summary' should open with a hook about building and scaling production systems in the MENA region, emphasizing delivery, cost optimization, and hands-on engineering. Connect my real achievements to the job requirements. Keep it professional and direct — NO academic fluff, NO 'I leverage my expertise' phrases.
|
||||
The 'skills' should be a comma-separated list of 10 highly relevant ATS keywords from MY ACTUAL SKILLS that match the job. Prioritize backend, API, database, and infrastructure skills over GIS.
|
||||
Do NOT use markdown blocks like ```json, just return raw JSON text.
|
||||
|
||||
Job Description:
|
||||
" . substr($jobDescription, 0, 4000);
|
||||
}
|
||||
// --- Default Prompt (Current — Enterprise/GCC) ---
|
||||
else {
|
||||
$prompt = "You are an expert ATS CV tailor. Read the following job description and generate tailored content for my CV to maximize my chances of getting an interview.
|
||||
|
||||
STRICT INTEGRITY RULES:
|
||||
1. NEVER invent skills I do not have. My TRUE technical stack is: PHP (Workerman), Node.js, NestJS, Python (FastAPI/Flask), PostgreSQL/PostGIS, Docker, Flutter, GetX, BLoC, WebSockets, OpenStreetMap.
|
||||
2. Do NOT add data science skills like TensorFlow, PyTorch, Scikit-learn, Hadoop, or MLOps.
|
||||
3. My title MUST be aligned with 'Solutions Architect', 'Senior Backend Engineer', or 'Senior Mobile Engineer'. NEVER title me 'Senior AI Developer'.
|
||||
|
||||
Return ONLY a valid JSON object with EXACTLY three keys: 'headline', 'summary', and 'skills'.
|
||||
The 'headline' should be a clean, confident title based on my TRUE skills.
|
||||
The 'summary' MUST open with exactly this hook: 'Built two production ride-hailing platforms from zero to thousands of users, on proprietary infrastructure, in high-complexity and emerging markets.' Then use the next 2 sentences to seamlessly tie my relevant TRUE skills to the job description requirements.
|
||||
The 'skills' should be a comma-separated list of 10 highly relevant ATS keywords from MY ACTUAL SKILLS that match the job.
|
||||
Do NOT use markdown blocks like ```json, just return raw JSON text.
|
||||
|
||||
Job Description:
|
||||
" . substr($jobDescription, 0, 4000);
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
"contents" => [["parts" => [["text" => $prompt]]]],
|
||||
"generationConfig" => ["temperature" => 0.2, "responseMimeType" => "application/json"]
|
||||
]);
|
||||
|
||||
$ch = curl_init($geminiUrl);
|
||||
function callGemini(string $url, array $payload): array {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlErr = curl_error($ch);
|
||||
curl_close($ch);
|
||||
return ['body' => $response, 'code' => $httpCode, 'error' => $curlErr];
|
||||
}
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
function loadProfile(): array {
|
||||
$path = __DIR__ . '/profile.json';
|
||||
if (!file_exists($path)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Gemini API Error", "details" => json_decode($response)]);
|
||||
echo json_encode(["error" => "profile.json missing. Run 'node sync_profile.js' and redeploy."]);
|
||||
exit;
|
||||
}
|
||||
$profile = json_decode(file_get_contents($path), true);
|
||||
if (!is_array($profile)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "profile.json is not valid JSON."]);
|
||||
exit;
|
||||
}
|
||||
return $profile;
|
||||
}
|
||||
|
||||
function e(?string $s): string {
|
||||
return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ACTION 1: Generate ATS PDF CV
|
||||
// ============================================================================
|
||||
if ($action === 'generatePdf') {
|
||||
$jobDescription = trim($data['jobDescription'] ?? '');
|
||||
$jobTitle = trim($data['jobTitle'] ?? '');
|
||||
// 'amman' biases toward the local Jordanian market; 'default' toward
|
||||
// GCC / international / remote. It changes EMPHASIS ONLY — never facts.
|
||||
$market = ($data['template'] ?? 'default') === 'amman' ? 'amman' : 'default';
|
||||
|
||||
if ($jobDescription === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "jobDescription is required to tailor the CV."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$responseData = json_decode($response, true);
|
||||
$profile = loadProfile();
|
||||
$id = $profile['identity'];
|
||||
$bounds = $profile['boundaries'];
|
||||
|
||||
// ── Build the reference blocks the model must work from ────────────────
|
||||
$skillsRef = '';
|
||||
foreach ($profile['skills'] as $group => $items) {
|
||||
$skillsRef .= "- {$group}: " . implode(', ', $items) . "\n";
|
||||
}
|
||||
|
||||
$experienceRef = '';
|
||||
foreach ($profile['experience'] as $ri => $role) {
|
||||
$experienceRef .= "ROLE INDEX {$ri}: {$role['title']} — {$role['company']} ({$role['start']} – {$role['end']}, {$role['location']})\n";
|
||||
foreach ($role['bullets'] as $bi => $bullet) {
|
||||
$experienceRef .= " [{$ri}.{$bi}] {$bullet['text']}\n";
|
||||
}
|
||||
}
|
||||
|
||||
$marketGuidance = $market === 'amman'
|
||||
? "TARGET MARKET: Jordan / Amman local tech market. Titles here are conservative — " .
|
||||
"prefer 'Senior Mobile Engineer', 'Lead Mobile Engineer' or 'Mobile Technical Lead' " .
|
||||
"over 'Architect' unless the posting itself uses 'Architect'. Emphasise hands-on " .
|
||||
"delivery, cost control and breadth. State availability for on-site/hybrid in Amman."
|
||||
: "TARGET MARKET: GCC / international / remote. 'Senior Mobile Architect' and " .
|
||||
"'Founding Engineer' land well here. Emphasise 0-to-1 platform ownership, scale " .
|
||||
"metrics, real-time systems and infrastructure cost reduction. Note openness to " .
|
||||
"relocation and remote work.";
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
You are an expert ATS résumé tailor. Rewrite my CV content so it scores as highly
|
||||
as possible against the job description below WITHOUT stating anything untrue.
|
||||
|
||||
{$marketGuidance}
|
||||
|
||||
=== ATS METHODOLOGY — APPLY ALL OF IT ===
|
||||
1. EXACT-TERM MIRRORING: ATS matching is literal. If the posting says "React Native"
|
||||
and I have "Flutter", do NOT write "React Native". But if the posting says
|
||||
"Flutter" or "Dart" or "WebSocket", use THAT EXACT WORD, not a synonym.
|
||||
2. DUAL FORM FOR ACRONYMS: e.g. "Continuous Integration / Continuous Deployment (CI/CD)".
|
||||
3. TITLE ALIGNMENT: the headline should mirror the posting's title when that title
|
||||
honestly describes my record and appears in ALLOWED TITLES.
|
||||
4. KEYWORD FREQUENCY: a critical keyword should appear 2-3 times across summary,
|
||||
skills and at least one experience bullet — always inside a real sentence.
|
||||
5. BULLET FORMULA: "Accomplished [X] as measured by [Y] by doing [Z]." Strong
|
||||
past-tense verb first. Outcome before task. A number beats an adjective.
|
||||
6. HARD SKILLS OVER SOFT SKILLS.
|
||||
|
||||
=== STRICT INTEGRITY RULES — VIOLATION INVALIDATES THE OUTPUT ===
|
||||
A. NEVER invent a skill, employer, metric, date or certification.
|
||||
B. ALLOWED TITLES ONLY: {allowed}
|
||||
C. FORBIDDEN TITLES: {forbidden}
|
||||
D. NEVER CLAIM (no production experience): {never}
|
||||
E. Every number must be copied exactly from MY REAL DATA. No rounding, no new numbers.
|
||||
F. When rewriting an experience bullet you may change the WORDING to carry the
|
||||
job's keywords, but the FACTS, NUMBERS and SCOPE must remain identical to the
|
||||
original bullet you were given.
|
||||
|
||||
=== MY REAL DATA ===
|
||||
NAME: {$id['fullName']}
|
||||
CURRENT POSITIONING: {$id['primaryTitle']} — {$id['positioningLine']}
|
||||
MASTER SUMMARY: {$profile['summary']}
|
||||
|
||||
SKILLS (you may only select from these):
|
||||
{$skillsRef}
|
||||
EXPERIENCE (you may only rewrite these exact bullets):
|
||||
{$experienceRef}
|
||||
HONEST LIMITS: {limits}
|
||||
|
||||
=== TARGET JOB ===
|
||||
Title: {$jobTitle}
|
||||
Description:
|
||||
{jobdesc}
|
||||
|
||||
=== RETURN FORMAT ===
|
||||
Return ONLY raw JSON (no markdown fences) with EXACTLY these keys:
|
||||
{
|
||||
"headline": "One line. Title mirroring the job + 3-4 exact hard skills I truly have.",
|
||||
"summary": "3-4 sentences. Opens with what I built and the numbers. Mirrors the job's exact terminology.",
|
||||
"core_skills": "Comma-separated list of exactly 10 keywords copied verbatim from the job posting that I genuinely have.",
|
||||
"roles": [
|
||||
{
|
||||
"role_index": 0,
|
||||
"bullet_ids": ["0.0", "0.2"],
|
||||
"bullets": ["Rewritten text of bullet 0.0", "Rewritten text of bullet 0.2"]
|
||||
}
|
||||
],
|
||||
"ats_keywords_matched": ["exact terms from the posting that now appear in the CV"],
|
||||
"ats_keywords_missing": ["requirements from the posting I genuinely cannot claim"],
|
||||
"estimated_ats_score": 0
|
||||
}
|
||||
|
||||
RULES FOR "roles":
|
||||
- Include EVERY role index that exists in MY REAL DATA, in the same order.
|
||||
- For each role select the 3-4 bullets MOST relevant to this job (fewer for the
|
||||
oldest role). "bullets" must align 1:1 with "bullet_ids".
|
||||
- Each rewritten bullet must stay factually identical to its original.
|
||||
PROMPT;
|
||||
|
||||
$prompt = str_replace(
|
||||
['{allowed}', '{forbidden}', '{never}', '{limits}', '{jobdesc}'],
|
||||
[
|
||||
implode(', ', $profile['allowedTitles']),
|
||||
implode(', ', $profile['forbiddenTitles']),
|
||||
implode(', ', $bounds['neverClaim']),
|
||||
implode(' | ', $bounds['limitedIn']),
|
||||
mb_substr($jobDescription, 0, 6000)
|
||||
],
|
||||
$prompt
|
||||
);
|
||||
|
||||
$res = callGemini(geminiUrl($MODELS['generatePdf'], $apiKey), [
|
||||
"contents" => [["parts" => [["text" => $prompt]]]],
|
||||
"generationConfig" => [
|
||||
"temperature" => 0.25,
|
||||
"maxOutputTokens" => 4096,
|
||||
"responseMimeType" => "application/json"
|
||||
]
|
||||
]);
|
||||
|
||||
if ($res['code'] !== 200) {
|
||||
http_response_code(502);
|
||||
echo json_encode(["error" => "Gemini API Error", "details" => json_decode($res['body'])]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$responseData = json_decode($res['body'], true);
|
||||
$aiText = $responseData['candidates'][0]['content']['parts'][0]['text'] ?? '{}';
|
||||
$aiText = str_replace(['```json', '```'], '', $aiText);
|
||||
$parsedJson = json_decode(trim($aiText), true);
|
||||
$aiText = trim(str_replace(['```json', '```'], '', $aiText));
|
||||
$ai = json_decode($aiText, true) ?: [];
|
||||
|
||||
$defaultHeadline = ($template === 'amman')
|
||||
? "Senior Backend Engineer & Technical Lead"
|
||||
: "Solutions Architect & Technical Leader";
|
||||
$headline = $parsedJson['headline'] ?? $defaultHeadline;
|
||||
$summary = $parsedJson['summary'] ?? "Experienced professional with a strong background in software engineering.";
|
||||
$skills = $parsedJson['skills'] ?? "Architecture, APIs, Cloud, Backend Systems, System Design";
|
||||
// ── Server-side integrity guard ─────────────────────────────────────────
|
||||
// The model is instructed not to fabricate, but instruction-following is not
|
||||
// a guarantee. Anything it produces is scanned for terms I have no right to
|
||||
// claim; a hit means we fall back to the untailored master content rather
|
||||
// than ship a CV that will fail a technical screen.
|
||||
$violations = [];
|
||||
$haystack = strtolower(json_encode($ai, JSON_UNESCAPED_UNICODE));
|
||||
foreach (array_merge($bounds['neverClaim'], $profile['forbiddenTitles']) as $banned) {
|
||||
if (strpos($haystack, strtolower($banned)) !== false) {
|
||||
$violations[] = $banned;
|
||||
}
|
||||
}
|
||||
$integrityClean = empty($violations);
|
||||
|
||||
$templateFile = ($template === 'amman')
|
||||
? __DIR__ . '/cv_template_amman.html'
|
||||
: __DIR__ . '/cv_template.html';
|
||||
$html = file_get_contents($templateFile);
|
||||
$html = str_replace('{{JOB_HEADLINE}}', htmlspecialchars($headline), $html);
|
||||
$html = str_replace('{{TAILORED_SUMMARY}}', htmlspecialchars($summary), $html);
|
||||
$html = str_replace('{{DYNAMIC_SKILLS}}', htmlspecialchars($skills), $html);
|
||||
$headline = ($integrityClean && !empty($ai['headline']))
|
||||
? $ai['headline']
|
||||
: $id['primaryTitle'] . ' — ' . $id['positioningLine'];
|
||||
|
||||
$summary = ($integrityClean && !empty($ai['summary']))
|
||||
? $ai['summary']
|
||||
: $profile['summary'];
|
||||
|
||||
if ($integrityClean && !empty($ai['core_skills'])) {
|
||||
$coreSkills = $ai['core_skills'];
|
||||
} else {
|
||||
// Fallback spreads across every skill group. Taking the first N of the
|
||||
// flattened list would just duplicate the "Mobile & Architecture" line
|
||||
// verbatim, wasting the most valuable slot on the page.
|
||||
$picked = [];
|
||||
foreach ($profile['skills'] as $items) {
|
||||
$picked = array_merge($picked, array_slice($items, 0, 3));
|
||||
}
|
||||
$coreSkills = implode(', ', array_slice($picked, 0, 10));
|
||||
}
|
||||
|
||||
// ── Render blocks ───────────────────────────────────────────────────────
|
||||
// Contact details sit on separate block-level lines with explicit
|
||||
// separators. A bare <br> can collapse during text extraction and glue the
|
||||
// email to the LinkedIn URL, which loses the recruiter both.
|
||||
$contactBlock =
|
||||
'<div>' . e($id['location']) . ' | ' . e($id['phone'])
|
||||
. ' | ' . e($id['email']) . '</div>'
|
||||
. '<div>' . e($id['linkedin']) . ' | ' . e($id['portfolio'])
|
||||
. ' | ' . e($id['productSite']) . '</div>';
|
||||
|
||||
// Metrics are rendered as one linear list. The master CV shows them as a
|
||||
// four-column band, which looks good to a human and extracts as garbage in
|
||||
// an ATS — this is the parser-safe equivalent of the same information.
|
||||
$achievements = '<ul>';
|
||||
foreach ($profile['headlineMetrics'] as $m) {
|
||||
$achievements .= '<li><strong>' . e($m['value']) . '</strong> — ' . e($m['label']) . '</li>';
|
||||
}
|
||||
foreach ($profile['achievementsAtAGlance'] as $a) {
|
||||
$achievements .= '<li>' . e($a) . '</li>';
|
||||
}
|
||||
$achievements .= '</ul>';
|
||||
|
||||
$skillsBlock = '';
|
||||
foreach ($profile['skills'] as $group => $items) {
|
||||
$skillsBlock .= '<p class="skill-line"><span class="label">' . e($group) . ':</span> '
|
||||
. e(implode(', ', $items)) . '</p>';
|
||||
}
|
||||
|
||||
// Index the AI's per-role selections so we can fall back role-by-role.
|
||||
$aiRoles = [];
|
||||
if ($integrityClean && !empty($ai['roles']) && is_array($ai['roles'])) {
|
||||
foreach ($ai['roles'] as $r) {
|
||||
if (isset($r['role_index'])) {
|
||||
$aiRoles[(int)$r['role_index']] = $r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$experienceBlock = '';
|
||||
foreach ($profile['experience'] as $ri => $role) {
|
||||
$experienceBlock .= '<div class="job">';
|
||||
$experienceBlock .= '<p class="job-title">' . e($role['title']) . '</p>';
|
||||
$experienceBlock .= '<p class="job-company">' . e($role['company']) . ' — ' . e($role['location']) . '</p>';
|
||||
$experienceBlock .= '<p class="job-dates">' . e($role['start']) . ' – ' . e($role['end']) . '</p>';
|
||||
|
||||
// Prefer the tailored rewrite; fall back to the master bullets whenever
|
||||
// the model returned nothing usable for this role.
|
||||
$bullets = [];
|
||||
if (isset($aiRoles[$ri]['bullets']) && is_array($aiRoles[$ri]['bullets'])) {
|
||||
foreach ($aiRoles[$ri]['bullets'] as $b) {
|
||||
$b = trim((string)$b);
|
||||
if ($b !== '') $bullets[] = $b;
|
||||
}
|
||||
}
|
||||
if (empty($bullets)) {
|
||||
foreach ($role['bullets'] as $b) {
|
||||
$bullets[] = $b['text'];
|
||||
}
|
||||
}
|
||||
|
||||
$experienceBlock .= '<ul>';
|
||||
foreach ($bullets as $b) {
|
||||
$experienceBlock .= '<li>' . e($b) . '</li>';
|
||||
}
|
||||
$experienceBlock .= '</ul></div>';
|
||||
}
|
||||
|
||||
$edu = $profile['education'];
|
||||
$educationBlock = '<p>' . e($edu['degree']) . ' — ' . e($edu['institution']) . '. ' . e($edu['note']) . '</p>';
|
||||
|
||||
$certBlock = '<ul>';
|
||||
foreach ($profile['certifications'] as $c) {
|
||||
$certBlock .= '<li>' . e($c) . '</li>';
|
||||
}
|
||||
$certBlock .= '</ul>';
|
||||
|
||||
$availability = $market === 'amman'
|
||||
? 'Based in Amman, Jordan. Available immediately for on-site, hybrid and remote roles.'
|
||||
: $id['availability'];
|
||||
|
||||
$html = file_get_contents(__DIR__ . '/cv_template.html');
|
||||
$html = strtr($html, [
|
||||
'{{FULL_NAME}}' => e($id['fullName']),
|
||||
'{{JOB_HEADLINE}}' => e($headline),
|
||||
'{{CONTACT_BLOCK}}' => $contactBlock,
|
||||
'{{TAILORED_SUMMARY}}' => e($summary),
|
||||
'{{ACHIEVEMENTS_BLOCK}}' => $achievements,
|
||||
'{{DYNAMIC_SKILLS}}' => e($coreSkills),
|
||||
'{{SKILLS_BLOCK}}' => $skillsBlock,
|
||||
'{{EXPERIENCE_BLOCK}}' => $experienceBlock,
|
||||
'{{EDUCATION_BLOCK}}' => $educationBlock,
|
||||
'{{CERTIFICATIONS_BLOCK}}' => $certBlock,
|
||||
'{{LANGUAGES}}' => e($id['languages']),
|
||||
'{{LOCATION}}' => e($id['location']),
|
||||
'{{AVAILABILITY}}' => e($availability),
|
||||
]);
|
||||
|
||||
try {
|
||||
$options = new Options();
|
||||
$options->set('isHtml5ParserEnabled', true);
|
||||
$options->set('defaultFont', 'Helvetica');
|
||||
$dompdf = new Dompdf($options);
|
||||
$dompdf->loadHtml($html);
|
||||
$dompdf->loadHtml($html, 'UTF-8');
|
||||
$dompdf->setPaper('A4', 'portrait');
|
||||
$dompdf->render();
|
||||
|
||||
$pdfOutput = $dompdf->output();
|
||||
$rawJobTitle = $data['jobTitle'] ?? 'Job';
|
||||
$safeJobTitle = preg_replace('/[^a-zA-Z0-9\-_]/', '_', $rawJobTitle);
|
||||
$safeJobTitle = trim($safeJobTitle, '_');
|
||||
$fileName = "Hamza_Ayed - {$safeJobTitle}.pdf";
|
||||
|
||||
// ATS parsers key on the filename too — "Name - Role.pdf" reads cleanly.
|
||||
$safeName = preg_replace('/[^a-zA-Z0-9\-_ ]/', '', $id['fullName']);
|
||||
$safeTitle = preg_replace('/[^a-zA-Z0-9\-_ ]/', '', $jobTitle ?: 'CV');
|
||||
$safeTitle = trim(preg_replace('/\s+/', ' ', $safeTitle));
|
||||
$fileName = trim("{$safeName} - {$safeTitle}") . '.pdf';
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"pdf" => base64_encode($pdfOutput),
|
||||
"filename" => $fileName
|
||||
"success" => true,
|
||||
"pdf" => base64_encode($pdfOutput),
|
||||
"filename" => $fileName,
|
||||
"ats" => [
|
||||
"score" => $ai['estimated_ats_score'] ?? null,
|
||||
"matched" => $ai['ats_keywords_matched'] ?? [],
|
||||
"missing" => $ai['ats_keywords_missing'] ?? [],
|
||||
"tailored" => $integrityClean && !empty($aiRoles),
|
||||
"violations" => $violations,
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
} catch (Exception $ex) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "PDF Generation Failed", "details" => $e->getMessage()]);
|
||||
echo json_encode(["error" => "PDF Generation Failed", "details" => $ex->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ACTION 2: Standard Proxy (Text generation)
|
||||
// ==========================================
|
||||
// ============================================================================
|
||||
// ACTION 2: Standard Proxy (text generation)
|
||||
// ============================================================================
|
||||
if ($action === 'generateText') {
|
||||
$prompt = $data['prompt'] ?? '';
|
||||
|
||||
$payload = json_encode([
|
||||
"contents" => [["parts" => [["text" => $prompt]]]],
|
||||
"generationConfig" => ["temperature" => 0.7, "maxOutputTokens" => 8192]
|
||||
]);
|
||||
|
||||
$ch = curl_init($geminiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
http_response_code($httpCode);
|
||||
echo $response;
|
||||
if (trim($prompt) === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "prompt is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Pass the exact Gemini response back to the extension
|
||||
echo $response;
|
||||
$res = callGemini(geminiUrl($MODELS['generateText'], $apiKey), [
|
||||
"contents" => [["parts" => [["text" => $prompt]]]],
|
||||
"generationConfig" => ["temperature" => 0.6, "maxOutputTokens" => 8192]
|
||||
]);
|
||||
|
||||
if ($res['code'] !== 200) {
|
||||
http_response_code($res['code'] ?: 502);
|
||||
echo $res['body'] ?: json_encode(["error" => $res['error'] ?: 'Upstream failure']);
|
||||
exit;
|
||||
}
|
||||
echo $res['body'];
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ACTION 3: Smart Comment Generator
|
||||
// ==========================================
|
||||
if ($action === 'generateComment') {
|
||||
$postText = substr($data['postText'] ?? '', 0, 3000);
|
||||
// ============================================================================
|
||||
// ACTION 3 & 4: Prompt-file backed generators (comment / repurpose)
|
||||
// ============================================================================
|
||||
$fileBacked = [
|
||||
'generateComment' => ['file' => 'comment_prompt.txt', 'key' => 'comment', 'tokens' => 700, 'temp' => 0.8],
|
||||
'repurposePost' => ['file' => 'repurpose_prompt.txt', 'key' => 'result', 'tokens' => 1200, 'temp' => 0.85],
|
||||
];
|
||||
|
||||
if (empty($postText)) {
|
||||
if (isset($fileBacked[$action])) {
|
||||
$cfg = $fileBacked[$action];
|
||||
$postText = mb_substr($data['postText'] ?? '', 0, 3000);
|
||||
|
||||
if (trim($postText) === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "postText is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$promptFile = __DIR__ . '/prompts/comment_prompt.txt';
|
||||
$promptFile = __DIR__ . '/prompts/' . $cfg['file'];
|
||||
if (!file_exists($promptFile)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Comment prompt file not found on server."]);
|
||||
echo json_encode(["error" => "Prompt file {$cfg['file']} not found on server."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Give the writer prompts the real identity instead of a stale copy baked
|
||||
// into the text file. {{PROFILE}} is optional in the template.
|
||||
$profile = loadProfile();
|
||||
$promptTemplate = file_get_contents($promptFile);
|
||||
$prompt = str_replace('{{POST_TEXT}}', $postText, $promptTemplate);
|
||||
|
||||
$payload = json_encode([
|
||||
"contents" => [["parts" => [["text" => $prompt]]]],
|
||||
"generationConfig" => ["temperature" => 0.75, "maxOutputTokens" => 256]
|
||||
$prompt = strtr($promptTemplate, [
|
||||
'{{POST_TEXT}}' => $postText,
|
||||
'{{PROFILE}}' => $profile['profileText'] ?? '',
|
||||
'{{VOICE}}' => $profile['voice']['tone'] ?? '',
|
||||
'{{BANNED}}' => implode(' / ', $profile['voice']['banned'] ?? []),
|
||||
]);
|
||||
|
||||
$ch = curl_init($geminiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Gemini API Error", "details" => json_decode($response)]);
|
||||
exit;
|
||||
$genConfig = [
|
||||
"temperature" => $cfg['temp'],
|
||||
"maxOutputTokens" => $cfg['tokens']
|
||||
];
|
||||
// The comment generator must return strict JSON; the repurposer returns prose.
|
||||
if ($action === 'generateComment') {
|
||||
$genConfig['responseMimeType'] = 'application/json';
|
||||
}
|
||||
|
||||
$responseData = json_decode($response, true);
|
||||
$commentText = trim($responseData['candidates'][0]['content']['parts'][0]['text'] ?? '');
|
||||
|
||||
if (empty($commentText)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Empty comment from AI."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(["success" => true, "comment" => $commentText]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ACTION 4: Repurpose Post Generator
|
||||
// ==========================================
|
||||
if ($action === 'repurposePost') {
|
||||
$postText = substr($data['postText'] ?? '', 0, 3000);
|
||||
|
||||
if (empty($postText)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "postText is required."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$promptFile = __DIR__ . '/prompts/repurpose_prompt.txt';
|
||||
if (!file_exists($promptFile)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Repurpose prompt file not found on server."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$promptTemplate = file_get_contents($promptFile);
|
||||
$prompt = str_replace('{{POST_TEXT}}', $postText, $promptTemplate);
|
||||
|
||||
$payload = json_encode([
|
||||
$res = callGemini(geminiUrl($MODELS[$action], $apiKey), [
|
||||
"contents" => [["parts" => [["text" => $prompt]]]],
|
||||
"generationConfig" => ["temperature" => 0.75, "maxOutputTokens" => 800]
|
||||
"generationConfig" => $genConfig
|
||||
]);
|
||||
|
||||
$ch = curl_init($geminiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Gemini API Error", "details" => json_decode($response)]);
|
||||
if ($res['code'] !== 200) {
|
||||
http_response_code(502);
|
||||
echo json_encode(["error" => "Gemini API Error", "details" => json_decode($res['body'])]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$responseData = json_decode($response, true);
|
||||
$resultText = trim($responseData['candidates'][0]['content']['parts'][0]['text'] ?? '');
|
||||
$responseData = json_decode($res['body'], true);
|
||||
$text = trim($responseData['candidates'][0]['content']['parts'][0]['text'] ?? '');
|
||||
|
||||
if (empty($resultText)) {
|
||||
http_response_code(500);
|
||||
if ($text === '') {
|
||||
http_response_code(502);
|
||||
echo json_encode(["error" => "Empty result from AI."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(["success" => true, "result" => $resultText]);
|
||||
echo json_encode(["success" => true, $cfg['key'] => $text]);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Unknown action: " . $action]);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,21 +1,78 @@
|
||||
You are a Solutions Architect and Engineering Leader. Read the following LinkedIn post and generate a JSON response.
|
||||
You are writing LinkedIn comments as Hamza Ayed. Read the post below and return a JSON response.
|
||||
|
||||
STRICT RULES FOR THE COMMENT:
|
||||
- Max 3 sentences. It MUST flow naturally as a single, coherent, human-written comment. Do NOT output disconnected, bullet-point-like statements.
|
||||
- Tone: Calm, authoritative, direct, and operator-level. Avoid corporate jargon, fluff, ego, preaching, or generic praise clichés (never start with "Great post", "Well said", etc.).
|
||||
- INDUSTRY RELEVANCE (CRITICAL): Do NOT mention ride-hailing, mobility, GIS, or transportation unless the post is explicitly and directly about maps, routing, logistics, or transportation. For general posts (like team management, system design, workplace culture, innovation), comment from a general systems architect/tech lead perspective.
|
||||
- TONE & STYLE: Write a comment that encourages professional discussion and attracts profile visits.
|
||||
- LANGUAGE: Match the language of the post. If the post is in Arabic, write the comment in Arabic. If the post is in English, write the comment in English.
|
||||
- Output the comment exactly as plain text in the JSON string (no markdown formatting inside).
|
||||
=== WHO YOU ARE ===
|
||||
{{PROFILE}}
|
||||
|
||||
STRICT RULES FOR THE ARABIC SUMMARY & CREDIBILITY CHECK:
|
||||
- Analyze the post objectively. No flattery. No automatic agreement.
|
||||
- Detect any exaggerated metrics, vanity metrics, logical flaws, or emotional manipulation (e.g., VC/startup hustle culture tropes).
|
||||
- Provide a 2-3 sentence Arabic response. The first sentence should summarize the post. The following sentences MUST be a brutal, honest credibility assessment pointing out risks or flaws in the author's logic. (Note: The summary and credibility check MUST ALWAYS be in Arabic, regardless of the post's language).
|
||||
=== VOICE ===
|
||||
{{VOICE}}
|
||||
|
||||
Return ONLY a valid JSON object with EXACTLY two keys:
|
||||
1. "arabic_summary": The objective summary and credibility assessment in Arabic.
|
||||
2. "comment": The comment following the rules above (matching the post's language: Arabic if the post is Arabic, English if the post is English).
|
||||
You have shipped things. You are not selling, not networking, not performing.
|
||||
You comment because you have something specific to add — or because something in
|
||||
the post is wrong and worth naming politely.
|
||||
|
||||
POST TEXT:
|
||||
=== THE COMMENT — HARD RULES ===
|
||||
|
||||
1. LENGTH: 2-3 sentences. One coherent paragraph that reads like a person typed
|
||||
it in one go. Never a list, never disconnected statements, never a mini-essay.
|
||||
|
||||
2. IT MUST ADD SOMETHING. Every comment has to do exactly one of these:
|
||||
(a) contribute a concrete detail, constraint or trade-off the post missed;
|
||||
(b) share a specific thing you saw when you did this yourself;
|
||||
(c) ask a sharp, genuine question that moves the discussion forward;
|
||||
(d) respectfully complicate a claim that is too clean to be true.
|
||||
If you cannot do any of these honestly, set "comment" to "" (empty string) and
|
||||
explain why in the Arabic analysis. A skipped comment is better than a generic
|
||||
one — generic comments actively damage a technical reputation.
|
||||
|
||||
3. NEVER OPEN WITH PRAISE. Banned openers include: "Great post", "Well said",
|
||||
"Couldn't agree more", "This resonates", "Spot on", "So true", "Love this",
|
||||
"Thanks for sharing", "كلام سليم", "أحسنت", "بالضبط", "مقال رائع".
|
||||
Open on the substance instead.
|
||||
|
||||
4. NO SELF-PROMOTION. Do not name Tripz, Intaleq, your job title, your metrics or
|
||||
your portfolio. Your credibility must come from the precision of what you say,
|
||||
not from a credential drop. At most you may say "when I built dispatch systems"
|
||||
as context — never as a boast, and only when directly relevant.
|
||||
|
||||
5. DOMAIN DISCIPLINE (critical): Do NOT bring up ride-hailing, mobility, mapping,
|
||||
GIS, dispatch or transportation UNLESS the post is explicitly about routing,
|
||||
logistics, maps or transport. For general posts (management, hiring, system
|
||||
design, culture, career advice), comment as an experienced engineer, full stop.
|
||||
|
||||
6. LANGUAGE: Match the post exactly. Arabic post → Arabic comment (natural,
|
||||
professional, conversational Arabic — not translated-English Arabic).
|
||||
English post → English comment.
|
||||
|
||||
7. BANNED PHRASING (instant fail): {{BANNED}}
|
||||
Also banned: em-dash-heavy rhetorical constructions, "it's not X, it's Y"
|
||||
parallelism, rhetorical questions used as filler, and any sentence that would
|
||||
survive unchanged under a different post.
|
||||
|
||||
8. NO hashtags. NO emojis. NO markdown. Plain text only.
|
||||
|
||||
9. NO FABRICATION. Never invent a statistic, a benchmark, a study or an anecdote
|
||||
that did not happen.
|
||||
|
||||
=== THE ARABIC ANALYSIS — HARD RULES ===
|
||||
|
||||
Always in Arabic, regardless of the post's language. 3-4 sentences total:
|
||||
- Sentence 1: what the post actually claims, stripped of rhetoric.
|
||||
- Sentences 2+: a blunt credibility assessment. Name specifically:
|
||||
* unverifiable or vanity metrics
|
||||
* survivorship bias, or a sample of one presented as a rule
|
||||
* hustle-culture / VC-narrative framing
|
||||
* logical gaps, false dichotomies, correlation sold as causation
|
||||
* whether this is a disguised sales pitch or recruitment ad
|
||||
- If the post is genuinely solid, say so plainly and say why — do not manufacture
|
||||
criticism. Honesty runs in both directions.
|
||||
- Close with a one-word engagement call: "يستاهل تعليق" or "تجاهله".
|
||||
|
||||
=== OUTPUT ===
|
||||
Return ONLY a valid JSON object with EXACTLY these two keys:
|
||||
{
|
||||
"arabic_summary": "التحليل والمصداقية بالعربية",
|
||||
"comment": "the comment in the post's language, or empty string if not worth commenting"
|
||||
}
|
||||
|
||||
=== POST TEXT ===
|
||||
{{POST_TEXT}}
|
||||
|
||||
@@ -1,21 +1,65 @@
|
||||
You are Hamza Ayed, a Senior Solutions Architect and technical leader.
|
||||
Your task is to REPURPOSE an existing LinkedIn post into an entirely new, original post written in your unique voice.
|
||||
You are Hamza Ayed. Take the post below and write an entirely NEW, original post
|
||||
in your own voice on the same underlying topic.
|
||||
|
||||
YOUR BACKGROUND & PHILOSOPHY:
|
||||
- Built "Intaleq" and "Tripz" platforms from scratch.
|
||||
- Created "Intaleq Maps" to bypass Google Maps, saving massive costs and achieving independence.
|
||||
- You believe in building independent infrastructure, cost-efficiency, and tech sovereignty rather than relying entirely on 3rd parties.
|
||||
=== WHO YOU ARE ===
|
||||
{{PROFILE}}
|
||||
|
||||
STRICT RULES:
|
||||
1. CORE CONCEPT: Extract the main idea from the provided post, but write a COMPLETELY NEW post. Do not just summarize. Reframe it from your Architect/Leadership perspective.
|
||||
2. LANGUAGE: Match the language of the original post. If Arabic, use conversational, engaging, professional Arabic (AVOID AI clichés like "في العصر الرقمي", "مما لا شك فيه", "يعد", "ختاماً"). Write naturally like a seasoned expert.
|
||||
3. STRUCTURE:
|
||||
- Hook: A strong opening statement (no emojis in the very first sentence).
|
||||
- Body: Your perspective on the topic. Subtly weave in your philosophy if relevant (without revealing secrets).
|
||||
- Conclusion: End with a strong closing thought or an open question to encourage engagement.
|
||||
4. IMAGE PROMPT: At the very end of your response, leave two blank lines, then output EXACTLY this header: "--- IMAGE PROMPT ---", followed by a highly detailed, descriptive prompt in English for an AI image generator (like Midjourney or DALL-E) to create a stunning, professional accompanying visual for your post.
|
||||
5. NO EMOJIS in the image prompt section. You may use a few appropriate emojis in the main post text.
|
||||
6. OUTPUT FORMAT: Return the new post text, then the image prompt section. Nothing else.
|
||||
=== VOICE ===
|
||||
{{VOICE}}
|
||||
|
||||
POST TO REPURPOSE:
|
||||
=== YOUR PHILOSOPHY (weave in only when genuinely relevant) ===
|
||||
- You build independent infrastructure instead of renting it. Self-hosted maps,
|
||||
routing and CI/CD were cost decisions before they were ideological ones.
|
||||
- You launch in constrained markets — unstable networks, missing payment APIs,
|
||||
low-end devices. Constraints are the interesting part of the engineering.
|
||||
- You are suspicious of advice that has never survived contact with production.
|
||||
- You measure things. A claim without a number is an opinion.
|
||||
|
||||
=== HARD RULES ===
|
||||
|
||||
1. ORIGINAL, NOT A SUMMARY. Extract the underlying idea, then write something new
|
||||
from your own angle. If you find yourself restating the original's structure,
|
||||
start over. Someone who read both posts should not feel they read the same one.
|
||||
|
||||
2. NO FABRICATION. You may only reference achievements, numbers and projects that
|
||||
appear in WHO YOU ARE above. Never invent a metric, a client, a benchmark or a
|
||||
war story. If the topic needs an example you do not have, write about the
|
||||
principle instead of faking the anecdote.
|
||||
|
||||
3. LANGUAGE: Match the original post. Arabic → natural, professional,
|
||||
conversational Arabic. Write like a working engineer talking, not like a
|
||||
translated press release.
|
||||
|
||||
4. BANNED PHRASING: {{BANNED}}
|
||||
Also avoid: opening with a one-word sentence for drama, "Here's the thing",
|
||||
"Let that sink in", numbered listicles of obvious advice, and any closing line
|
||||
that asks for engagement in a transparently manufactured way.
|
||||
|
||||
5. STRUCTURE:
|
||||
- HOOK: a concrete, specific opening statement. No emoji in the first sentence.
|
||||
A real observation beats a provocation.
|
||||
- BODY: your actual position, with the reasoning visible. Name the trade-off,
|
||||
not just the conclusion. Short paragraphs — LinkedIn is read on phones.
|
||||
- CLOSE: a genuine question or a sharp closing thought. It should be a question
|
||||
you would actually want answered.
|
||||
|
||||
6. LENGTH: 120-220 words. Long enough to say something, short enough to be read.
|
||||
|
||||
7. NO hashtags. At most 1-2 emoji in the body, and only if they earn their place.
|
||||
|
||||
8. NEVER reveal proprietary implementation details, client names, or anything
|
||||
under NDA.
|
||||
|
||||
=== IMAGE PROMPT ===
|
||||
After the post, leave two blank lines, then output EXACTLY this header:
|
||||
--- IMAGE PROMPT ---
|
||||
Then a detailed English prompt for an AI image generator: a professional,
|
||||
editorial-quality visual that matches the post's idea. Describe subject,
|
||||
composition, lighting, colour palette and mood. No text or lettering in the
|
||||
image. No emojis in this section.
|
||||
|
||||
=== OUTPUT ===
|
||||
The new post text, then the image prompt section. Nothing else.
|
||||
|
||||
=== POST TO REPURPOSE ===
|
||||
{{POST_TEXT}}
|
||||
|
||||
Reference in New Issue
Block a user