From 86993fbc666bd2b1881f208284e0a1b1b37d39b0 Mon Sep 17 00:00:00 2001 From: Gelu Mihes Date: Mon, 4 May 2026 14:34:38 +0300 Subject: [PATCH] Changes --- web/wwwroot/css/myai.css | 736 +++++++++++++++++++++++++++++- web/wwwroot/cv-matcher/index.html | 184 ++++++-- web/wwwroot/favicon.ico | Bin 15406 -> 11731 bytes web/wwwroot/img/favicon-256.png | Bin 0 -> 2858 bytes web/wwwroot/img/myai-banner.svg | 17 + web/wwwroot/img/myai-logo.svg | 18 + web/wwwroot/index.html | 179 +++++--- web/wwwroot/js/myai.js | 684 ++++++++++++++++++--------- web/wwwroot/legal/cookies-en.html | 32 +- web/wwwroot/legal/cookies-ro.html | 12 +- web/wwwroot/legal/css/legal.css | 280 +++++++++--- web/wwwroot/legal/privacy-en.html | 32 +- web/wwwroot/legal/privacy-ro.html | 12 +- web/wwwroot/legal/terms-en.html | 12 +- web/wwwroot/legal/terms-ro.html | 12 +- 15 files changed, 1766 insertions(+), 444 deletions(-) create mode 100644 web/wwwroot/img/favicon-256.png create mode 100644 web/wwwroot/img/myai-banner.svg create mode 100644 web/wwwroot/img/myai-logo.svg diff --git a/web/wwwroot/css/myai.css b/web/wwwroot/css/myai.css index 14ba18c..bf4a8d1 100644 --- a/web/wwwroot/css/myai.css +++ b/web/wwwroot/css/myai.css @@ -1 +1,735 @@ -:root{--bg:#041120;--bg-soft:#0a1c34;--panel:rgba(10,22,42,.82);--panel-border:rgba(255,255,255,.1);--text:#eaf1ff;--muted:#9bb0d0;--primary:#5fa0ff;--primary-strong:#8b6cff;--card-radius:28px;--shadow:0 18px 60px rgba(0,0,0,.28)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;font-family:Inter,sans-serif;background:radial-gradient(circle at top left,#12345d 0%,#071326 35%,#030915 100%);color:var(--text)}a{color:inherit;text-decoration:none}img{max-width:100%;display:block}.container{width:100%;max-width:1120px;margin:0 auto;padding-left:20px;padding-right:20px}.site-shell{overflow:hidden}.header{position:sticky;top:0;z-index:20;background:rgba(3,11,23,.76);backdrop-filter:blur(16px);border-bottom:1px solid rgba(255,255,255,.08)}.nav-wrap{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:84px}.brand{display:flex;align-items:center;gap:14px}.brand-mark{width:48px;height:48px}.ai-mark{display:inline-flex;align-items:center;justify-content:center;border-radius:16px;background:linear-gradient(135deg,var(--primary),var(--primary-strong));font-weight:900;color:#fff;box-shadow:0 18px 40px rgba(95,160,255,.24)}.brand-text{display:block;font-size:1.7rem;font-weight:800}.brand small{display:block;color:var(--muted);margin-top:2px}.nav{display:flex;align-items:center;gap:32px}.nav a{color:#d7e3fb;font-weight:600}.nav a:hover{color:#fff}.menu-toggle{display:none;background:transparent;border:0;padding:8px}.menu-toggle span{display:block;width:24px;height:2px;background:#fff;margin:5px 0}.hero{padding:72px 0 48px}.hero-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:42px;align-items:center}.eyebrow{display:inline-flex;align-items:center;gap:8px;margin-bottom:14px;color:#8fb8ff;text-transform:uppercase;letter-spacing:.18em;font-size:.78rem;font-weight:800}.hero h1{font-size:clamp(2.3rem,5vw,4.8rem);line-height:1.02;margin:0 0 24px;letter-spacing:-.05em}.hero-text{font-size:1.12rem;line-height:1.8;color:var(--muted);max-width:650px}.hero-actions{display:flex;gap:14px;flex-wrap:wrap;margin-top:30px}.btn{border-radius:999px;padding:13px 22px;font-weight:800;border:1px solid rgba(255,255,255,.12)}.btn-primary{background:linear-gradient(135deg,var(--primary),var(--primary-strong));border:0;color:#fff}.btn-secondary{background:rgba(255,255,255,.06);color:#fff}.section{padding:76px 0}.section-heading{max-width:720px;margin-bottom:34px}.section-heading h2,.contact h2,.ai-panel h2{font-size:clamp(2rem,4vw,3.2rem);line-height:1.05;letter-spacing:-.04em;margin:0 0 18px}.section-heading p,.contact p{color:var(--muted);font-size:1.05rem;line-height:1.8}.ai-console-card,.ai-panel,.demo-card,.contact-form{background:var(--panel);border:1px solid var(--panel-border);border-radius:var(--card-radius);box-shadow:var(--shadow)}.ai-console-card{padding:30px}.console-line{display:flex;gap:16px;align-items:center;margin:12px 0;padding:16px 18px;border-radius:18px;background:rgba(255,255,255,.05);color:#dce8ff}.console-line span{min-width:76px;color:#8fb8ff;font-weight:900}.demo-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.demo-card{display:block;padding:26px;min-height:260px;transition:transform .2s ease,border-color .2s ease}.demo-card:hover{transform:translateY(-4px);border-color:rgba(95,160,255,.45)}.demo-card h3{font-size:1.55rem;margin:18px 0 12px}.demo-card p{color:var(--muted);line-height:1.7}.muted-card{opacity:.7}.product-tag{display:inline-flex;border-radius:999px;padding:7px 12px;background:rgba(95,160,255,.12);color:#b9d4ff;border:1px solid rgba(95,160,255,.18);font-weight:800;font-size:.82rem}.matcher-grid{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:start}.ai-panel{padding:28px}.ai-panel label,.contact-form label{display:block;margin-bottom:18px}.ai-panel label span,.contact-form label span{display:block;margin-bottom:8px;color:#c3d4f2;font-weight:700}.ai-panel input,.ai-panel textarea,.contact-form input,.contact-form textarea{width:100%;border:1px solid rgba(255,255,255,.12);border-radius:18px;background:rgba(0,0,0,.25);color:#fff;padding:15px 16px;outline:none}.ai-panel input:focus,.ai-panel textarea:focus,.contact-form input:focus,.contact-form textarea:focus{border-color:rgba(95,160,255,.65)}.file-drop{display:block;border:1px dashed rgba(143,184,255,.45);border-radius:22px;background:rgba(95,160,255,.07);padding:22px;cursor:pointer}.file-drop input{display:none}.file-drop strong{display:block;font-size:1.1rem}.file-drop span{color:var(--muted)!important;margin-top:8px}.consent-inline{display:flex;gap:12px;align-items:flex-start;margin:18px 0 22px;color:#b7c7e4;line-height:1.6}.consent-inline input{width:auto;margin-top:5px}.consent-inline label{margin:0}.result-panel{position:sticky;top:110px}.empty-result{color:var(--muted);line-height:1.8;padding:20px;border-radius:18px;background:rgba(0,0,0,.25)}.score-badge{display:inline-flex;align-items:center;justify-content:center;width:104px;height:104px;border-radius:50%;background:linear-gradient(135deg,var(--primary),var(--primary-strong));font-size:2rem;font-weight:900;margin:10px 0 20px}.result-list{padding-left:18px;color:#d7e3fb;line-height:1.8}.contact{background:rgba(255,255,255,.03)}.contact-grid{display:grid;grid-template-columns:.9fr 1.1fr;gap:32px;align-items:start}.contact-list{display:grid;gap:14px;margin-top:24px}.contact-list div{padding:18px;border-radius:20px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08)}.contact-list span{display:block;color:var(--muted);font-size:.88rem}.contact-form{padding:28px}.form-message{display:block;margin-top:14px}.text-success{color:#7ef2a7!important}.text-danger{color:#ff8a8a!important}.footer{padding:26px 0;border-top:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.18)}.footer-wrap{display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;color:var(--muted)}.footer-links{display:flex;gap:18px;flex-wrap:wrap}.cookie-overlay{position:fixed;left:0;right:0;bottom:20px;z-index:50;padding:0 20px}.cookie-box{max-width:980px;margin:auto;padding:20px;border-radius:24px;background:#071326;border:1px solid rgba(255,255,255,.16);box-shadow:var(--shadow);display:flex;align-items:center;justify-content:space-between;gap:20px}.cookie-text{color:#dce8ff;line-height:1.6}.cookie-text a{color:#9cc5ff;text-decoration:underline}.cookie-actions{display:flex;gap:10px;flex-wrap:wrap}.cookie-manage{position:fixed;right:20px;bottom:20px;z-index:40}.loader-overlay{position:fixed;inset:0;z-index:80;background:rgba(0,0,0,.55);align-items:center;justify-content:center}.loader-box{padding:20px 30px;border-radius:18px;background:#071326;border:1px solid rgba(255,255,255,.16);font-weight:800}.shake{animation:shake .35s}@keyframes shake{25%{transform:translateX(-5px)}50%{transform:translateX(5px)}75%{transform:translateX(-3px)}}@media (max-width:900px){.hero-grid,.matcher-grid,.contact-grid,.demo-grid{grid-template-columns:1fr}.result-panel{position:static}.nav{position:absolute;top:84px;left:20px;right:20px;display:none;flex-direction:column;align-items:flex-start;background:#071326;border:1px solid rgba(255,255,255,.12);border-radius:20px;padding:20px}.nav.is-open{display:flex}.menu-toggle{display:block}.cookie-box{align-items:flex-start;flex-direction:column}}@media (max-width:560px){.hero{padding-top:46px}.section{padding:56px 0}.footer-wrap{align-items:flex-start;flex-direction:column}.hero-actions .btn{width:100%;text-align:center}} +:root { + --bg: #041120; + --bg-soft: #0a1c34; + --panel: rgba(10,22,42,.82); + --panel-border: rgba(255,255,255,.1); + --text: #eaf1ff; + --muted: #9bb0d0; + --primary: #5fa0ff; + --primary-strong: #8b6cff; + --card-radius: 28px; + --shadow: 0 18px 60px rgba(0,0,0,.28) +} + +* { + box-sizing: border-box +} + +html { + scroll-behavior: smooth +} + +body { + margin: 0; + font-family: Inter,sans-serif; + background: radial-gradient(circle at top left,#12345d 0%,#071326 35%,#030915 100%); + color: var(--text) +} + +a { + color: inherit; + text-decoration: none +} + +img { + max-width: 100%; + display: block +} + +.container { + width: 100%; + max-width: 1120px; + margin: 0 auto; + padding-left: 20px; + padding-right: 20px +} + +.site-shell { + overflow: hidden +} + +.header { + position: sticky; + top: 0; + z-index: 20; + background: rgba(3,11,23,.76); + backdrop-filter: blur(16px); + border-bottom: 1px solid rgba(255,255,255,.08) +} + +.nav-wrap { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + min-height: 84px +} + +.brand { + display: flex; + align-items: center; + gap: 14px +} + +.brand-mark { + width: 48px; + height: 48px +} + +.ai-mark { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 16px; + background: linear-gradient(135deg,var(--primary),var(--primary-strong)); + font-weight: 900; + color: #fff; + box-shadow: 0 18px 40px rgba(95,160,255,.24) +} + +.brand-text { + display: block; + font-size: 1.7rem; + font-weight: 800 +} + +.brand small { + display: block; + color: var(--muted); + margin-top: 2px +} + +.nav { + display: flex; + align-items: center; + gap: 32px +} + + .nav a { + color: #d7e3fb; + font-weight: 600 + } + + .nav a:hover { + color: #fff + } + +.menu-toggle { + display: none; + background: transparent; + border: 0; + padding: 8px +} + + .menu-toggle span { + display: block; + width: 24px; + height: 2px; + background: #fff; + margin: 5px 0 + } + +.hero { + padding: 72px 0 48px +} + +.hero-grid { + display: grid; + grid-template-columns: 1.05fr .95fr; + gap: 42px; + align-items: center +} + +.eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + margin-bottom: 14px; + color: #8fb8ff; + text-transform: uppercase; + letter-spacing: .18em; + font-size: .78rem; + font-weight: 800 +} + +.hero h1 { + font-size: clamp(2.3rem,5vw,4.8rem); + line-height: 1.02; + margin: 0 0 24px; + letter-spacing: -.05em +} + +.hero-text { + font-size: 1.12rem; + line-height: 1.8; + color: var(--muted); + max-width: 650px +} + +.hero-actions { + display: flex; + gap: 14px; + flex-wrap: wrap; + margin-top: 30px +} + +.btn { + border-radius: 999px; + padding: 13px 22px; + font-weight: 800; + border: 1px solid rgba(255,255,255,.12) +} + +.btn-primary { + background: linear-gradient(135deg,var(--primary),var(--primary-strong)); + border: 0; + color: #fff +} + +.btn-secondary { + background: rgba(255,255,255,.06); + color: #fff +} + +.section { + padding: 76px 0 +} + +.section-heading { + max-width: 720px; + margin-bottom: 34px +} + + .section-heading h2, .contact h2, .ai-panel h2 { + font-size: clamp(2rem,4vw,3.2rem); + line-height: 1.05; + letter-spacing: -.04em; + margin: 0 0 18px + } + + .section-heading p, .contact p { + color: var(--muted); + font-size: 1.05rem; + line-height: 1.8 + } + +.ai-console-card, .ai-panel, .demo-card, .contact-form { + background: var(--panel); + border: 1px solid var(--panel-border); + border-radius: var(--card-radius); + box-shadow: var(--shadow) +} + +.ai-console-card { + padding: 30px +} + +.console-line { + display: flex; + gap: 16px; + align-items: center; + margin: 12px 0; + padding: 16px 18px; + border-radius: 18px; + background: rgba(255,255,255,.05); + color: #dce8ff +} + + .console-line span { + min-width: 76px; + color: #8fb8ff; + font-weight: 900 + } + +.demo-grid { + display: grid; + grid-template-columns: repeat(3,1fr); + gap: 20px +} + +.demo-card { + display: block; + padding: 26px; + min-height: 260px; + transition: transform .2s ease,border-color .2s ease +} + + .demo-card:hover { + transform: translateY(-4px); + border-color: rgba(95,160,255,.45) + } + + .demo-card h3 { + font-size: 1.55rem; + margin: 18px 0 12px + } + + .demo-card p { + color: var(--muted); + line-height: 1.7 + } + +.muted-card { + opacity: .7 +} + +.product-tag { + display: inline-flex; + border-radius: 999px; + padding: 7px 12px; + background: rgba(95,160,255,.12); + color: #b9d4ff; + border: 1px solid rgba(95,160,255,.18); + font-weight: 800; + font-size: .82rem +} + +.matcher-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; + align-items: start +} + +.ai-panel { + padding: 28px +} + + .ai-panel label, .contact-form label { + display: block; + margin-bottom: 18px + } + + .ai-panel label span, .contact-form label span { + display: block; + margin-bottom: 8px; + color: #c3d4f2; + font-weight: 700 + } + + .ai-panel input, .ai-panel textarea, .contact-form input, .contact-form textarea { + width: 100%; + border: 1px solid rgba(255,255,255,.12); + border-radius: 18px; + background: rgba(0,0,0,.25); + color: #fff; + padding: 15px 16px; + outline: none + } + + .ai-panel input:focus, .ai-panel textarea:focus, .contact-form input:focus, .contact-form textarea:focus { + border-color: rgba(95,160,255,.65) + } + +.file-drop { + display: block; + border: 1px dashed rgba(143,184,255,.45); + border-radius: 22px; + background: rgba(95,160,255,.07); + padding: 22px; + cursor: pointer +} + + .file-drop input { + display: none + } + + .file-drop strong { + display: block; + font-size: 1.1rem + } + + .file-drop span { + color: var(--muted) !important; + margin-top: 8px + } + +.consent-inline { + display: flex; + gap: 12px; + align-items: flex-start; + margin: 18px 0 22px; + color: #b7c7e4; + line-height: 1.6 +} + + .consent-inline input { + width: auto; + margin-top: 5px + } + + .consent-inline label { + margin: 0 + } + +.result-panel { + position: sticky; + top: 110px +} + +.empty-result { + color: var(--muted); + line-height: 1.8; + padding: 20px; + border-radius: 18px; + background: rgba(0,0,0,.25) +} + +.score-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 104px; + height: 104px; + border-radius: 50%; + background: linear-gradient(135deg,var(--primary),var(--primary-strong)); + font-size: 2rem; + font-weight: 900; + margin: 10px 0 20px +} + +.result-list { + padding-left: 18px; + color: #d7e3fb; + line-height: 1.8 +} + +.contact { + background: rgba(255,255,255,.03) +} + +.contact-grid { + display: grid; + grid-template-columns: .9fr 1.1fr; + gap: 32px; + align-items: start +} + +.contact-list { + display: grid; + gap: 14px; + margin-top: 24px +} + + .contact-list div { + padding: 18px; + border-radius: 20px; + background: rgba(255,255,255,.05); + border: 1px solid rgba(255,255,255,.08) + } + + .contact-list span { + display: block; + color: var(--muted); + font-size: .88rem + } + +.contact-form { + padding: 28px +} + +.form-message { + display: block; + margin-top: 14px +} + +.text-success { + color: #7ef2a7 !important +} + +.text-danger { + color: #ff8a8a !important +} + +.footer { + padding: 26px 0; + border-top: 1px solid rgba(255,255,255,.08); + background: rgba(0,0,0,.18) +} + +.footer-wrap { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + flex-wrap: wrap; + color: var(--muted) +} + +.footer-links { + display: flex; + gap: 18px; + flex-wrap: wrap +} + +.cookie-overlay { + position: fixed; + left: 0; + right: 0; + bottom: 20px; + z-index: 50; + padding: 0 20px +} + +.cookie-box { + max-width: 980px; + margin: auto; + padding: 20px; + border-radius: 24px; + background: #071326; + border: 1px solid rgba(255,255,255,.16); + box-shadow: var(--shadow); + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px +} + +.cookie-text { + color: #dce8ff; + line-height: 1.6 +} + + .cookie-text a { + color: #9cc5ff; + text-decoration: underline + } + +.cookie-actions { + display: flex; + gap: 10px; + flex-wrap: wrap +} + +.cookie-manage { + position: fixed; + bottom: 20px; + z-index: 40 +} + +.loader-overlay { + position: fixed; + inset: 0; + z-index: 80; + background: rgba(0,0,0,.55); + align-items: center; + justify-content: center +} + +.loader-box { + padding: 20px 30px; + border-radius: 18px; + background: #071326; + border: 1px solid rgba(255,255,255,.16); + font-weight: 800 +} + +.shake { + animation: shake .35s +} + +@keyframes shake { + 25% { + transform: translateX(-5px) + } + + 50% { + transform: translateX(5px) + } + + 75% { + transform: translateX(-3px) + } +} + +@media (max-width:900px) { + .hero-grid, .matcher-grid, .contact-grid, .demo-grid { + grid-template-columns: 1fr + } + + .result-panel { + position: static + } + + .nav { + position: absolute; + top: 84px; + left: 20px; + right: 20px; + display: none; + flex-direction: column; + align-items: flex-start; + background: #071326; + border: 1px solid rgba(255,255,255,.12); + border-radius: 20px; + padding: 20px + } + + .nav.is-open { + display: flex + } + + .menu-toggle { + display: block + } + + .cookie-box { + align-items: flex-start; + flex-direction: column + } +} + +@media (max-width:560px) { + .hero { + padding-top: 46px + } + + .section { + padding: 56px 0 + } + + .footer-wrap { + align-items: flex-start; + flex-direction: column + } + + .hero-actions .btn { + width: 100%; + text-align: center + } +} + +/* MyAi brand + main-page language selector */ +.brand-mark img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 16px 26px rgba(95,160,255,.24)) +} + +.nav-actions { + display: flex; + align-items: center; + gap: 12px +} + +.lang-switch { + display: flex; + align-items: center; + gap: 8px; + padding: 6px; + border-radius: 18px; + background: rgba(255,255,255,.04); + border: 1px solid rgba(255,255,255,.1) +} + +.lang-flag { + width: 46px; + height: 32px; + padding: 3px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid transparent; + border-radius: 12px; + background: transparent; + cursor: pointer; + opacity: .78; + transition: transform .2s ease,border-color .2s ease,background-color .2s ease,opacity .2s ease +} + + .lang-flag img { + width: 100%; + height: 100%; + display: block; + border-radius: 8px; + object-fit: cover; + box-shadow: 0 8px 18px rgba(0,0,0,.18) + } + + .lang-flag:hover { + opacity: 1; + transform: translateY(-1px) + } + + .lang-flag[aria-pressed=true] { + opacity: 1; + border-color: rgba(95,160,255,.65); + background: rgba(95,160,255,.1) + } + +.banner-card { + overflow: hidden +} + +.showcase-banner { + width: 100%; + border-radius: 22px; + margin-bottom: 18px; + border: 1px solid rgba(255,255,255,.12); + box-shadow: 0 20px 55px rgba(0,0,0,.22) +} + +.console-line b { + font-weight: 700; + color: #dce8ff +} + +@media (max-width:900px) { + .nav-actions { + margin-left: auto + } + + .nav-wrap { + position: relative + } + + .menu-toggle { + order: 4 + } + + .nav { + z-index: 30 + } + + .lang-switch { + padding: 4px + } + + .lang-flag { + width: 42px; + height: 30px + } +} + +@media (max-width:560px) { + .brand-text { + font-size: 1.35rem + } + + .brand small { + display: none + } + + .brand-mark { + width: 42px; + height: 42px + } + + .nav-actions { + gap: 6px + } + + .lang-switch { + gap: 4px + } + + .lang-flag { + width: 36px; + height: 27px + } + + .showcase-banner { + border-radius: 18px + } +} diff --git a/web/wwwroot/cv-matcher/index.html b/web/wwwroot/cv-matcher/index.html index 809b42c..8c60122 100644 --- a/web/wwwroot/cv-matcher/index.html +++ b/web/wwwroot/cv-matcher/index.html @@ -11,6 +11,8 @@ + + @@ -21,79 +23,177 @@
-
- AI CV Matcher -

Upload your CV, add a job link, and see how well they match.

-

The backend should extract text from the PDF, create RAG context from your CV, retrieve relevant experience for the job, then score strengths, gaps and next actions.

- + AI CV Matcher +

Upload your CV, add a job link, and see how well they match.

+

The backend should extract text from the PDF, create RAG context from your CV, retrieve relevant experience for the job, then score strengths, gaps and next actions.

+
-
-
1 PDF text extraction
-
2 CV chunking + embeddings
-
3 Job URL/description parsing
-
4 Match score + evidence
+
-
- Input -

CV and job details

- - - - - + Input +

CV and job details

+ + + + +
-
-
- Contact -

Want this adapted for your workflow?

-

This form uses the existing template contact API endpoint.

-
Contact personMihes Gelu
+ Contact +

Want this adapted for your workflow?

+

This form uses the existing template contact API endpoint.

+
+
+ Contact person + Mihes Gelu +
+
+ Phone + + +40 722-523-764 + +
+
- - - - + + + +
- - +
- - - - - + + + - + \ No newline at end of file diff --git a/web/wwwroot/favicon.ico b/web/wwwroot/favicon.ico index 76041149167b42d862e42a7da2d6925c2ea45965..f16a662378672f1072feaf217e52c95e7c9636fd 100644 GIT binary patch literal 11731 zcmdUVbx>Six8?1oaR?BCdvJG4kS18r;O-JMxVr@R5F|JRCs=TINw5&yHMqO?bbjAA z^WIcVy*KmcbX9ksbJmu7d)L`(-BS$!AOH!##s=UoN?-;A0Pp{fDE~FrM*sj}FaU6H z{AxhOIK^rHblK)}D|`S3W1&;O49z9<1EbO0!M2>=mFiqe>9#BfUh zQ|7IN3jF(TMr;%$_$S3V%oqSbSuzr0Y91L$pW3X|#9j>eTO750SzLlb(UU^zRP^dT zQSucoXjXl%L!pV$vb7)~Nq@0`DiVwjYS{#_T%+RSJV0sO-Q7fD`@);kTd2rWO-3_d z$38It>CtYdjzLlHM(Pgmrp3zGGJ0)NFe|Q~E1^f2>@yyPt)%_d^q|JK`cxsLXE8ksky(raTxc!aVQ6Y8h$w2@VN3^UE8Wh*QJd$-ZM6AZ`zpNv=N5xH ziVd+XqUFc(55+A+DjHS3b>*Y#0Z`ehYSb}!0Wy+`5*2TZg8mO>rG&$j|CH6)%j*IF z5NQ5Y*1i-4TQzlfS-0EyRSjPwn4nHPV3>g~6^ z=snh=2|)Y&B;WZL+-ICLF;SP=s?s7wKHicu7=8<&X7t>LiE%IIAmtwl;>Fu}G4-%P9&nPLuI60SszpcA1xkh>u zqSpTmr@X>=sK_Z5c(v0Qq4rS;a+7r)SnY&LPz!1~`jn%@w&r6r9KrGFPjSEkuq_3! z4JqSDndEHsgi5rB=gu1QhOx%lG0s=j@~h5()|1*P$I9`iwjPHr%gLaf+t`uM$@-!K z-vq3Vu-Q3p;(;JozUh=DTKD5Tn}Xp8m+=?c9b;?bkIxXo2|K&SRxic8uZH3{Tv3_Q zjoqGI`Jo`kb`J~Rtl8A0;FGkLOEQ49(JH5hn_l$!jv-^GKxa;-^l&<3jqm->BMUtC z-3W`t(&}Sf6acE(){e)Dx+1o&^8GEBZH~>kSK#nq7jo<1U!q0{ut#LQOz>pRCy`)1 zOmQ2)_9WN0zM;nA2mF&nx$Nx=GinxR8dnembZV_6FqDByzd z>f)4P9WwMQc8-ieB=L#se67Z<@^z;QS@yIo8>o&0UMGlqD*v8D(zE+jFqS^Y>RW!| zIZ(o-9f=e2OkZ3_j_R|CM3M}F;Ajz1m&{lD$H&)!Jk%1g=SMe!O&Q3e8n`b%+h$U| zpe>`5@LWAOb#`jn*Q_}*R7CHu&>w>#qiIx&C%xj(;|nCipfC03hf8 z+FXZeVt+SRXovGkdiyih5RF_TC`u4(NB}-3n_fI&Ot-8lFUbK)0;$@b354m7VKE? zI^*@Z6hjYzFmWq418OHd#SW8@y|;&c(letHCKZvQWNfiOV;n!n6U-s>|KH=8IKCjpFr)z6z2ZN7EUF_jzNL1 z*WLH)8P*->@*g$GTL+>z3l>i>-YBb$L=QA2{NpGGBM+lfv-m!!t3# z36f^G^HWm)B>#2?;{ST=zU|Seu~^&7i-(Z(^X4kKFEyHAGL7ZB7=(YQ`#6wE!~q-f zR^+|zF_qx>#Mi)0r#*a(i6d&oh_9@%r_KsKIHflny(QbFLp4u#DpGoe**5|`fwmI5 zwpQHqeZl6Eq_`H;^XnMMcXz!*(k-q{p%|z z*FiMOKA&^vkZs)=blarAtRbnZGJLXLCI)9#S5luM$?&?ra;)`cPIonZ134w{9Y0dD z&{e!tGCxWlE?KV*oa;A5sEMh1@BS&`!-F55qM3DTNzQvDAFvF_IhXQT_weRoqmhpX zhdonq1n4sUL(sHjecDTotT5&j`ilDN{&fD83iOTsGW-d?Rnq8~E@t5>D>*Bl2R@90 zuyKDXl!?NP)hoi*3u0TJIS);?o;3vgh|*>MW~5J$G=k2WF_1O$0j{Ox$0-t)=ZTUa zh1=6!Z;c=;B8O28FLU}~L(8-b=JNM5Tfg_-1uR~s#fI+6Ppb_!6R0YHJDyNX`47D2 zm@EzLJPpW0baJf`UTP@J$|92o)VG;>XH4_YB`I4SYjBZ5t3e*kAF4nBo_F zUL@B#s_<*}^VTCqYVmwP>C|z>NI`XtsFuv*W3(jlY6NYS?d4{A6MpeeX!R(&Ht1b> zZ8c4xD*XpsEEs6pkL|R@pz0?NE>h_p!pmvb88_WN#^h1Wl{y$bCySohIJSk=^l$4z zo0w7z53ITOJ3I9@IDd;;vW9%FKp_#*-chqp}d&>-D z>W#6X9e>H!q4V)&lK;!+GMc=7!CNW*TFSg#Vsl!&*r1{;oNZ`Qq-x1c8dK$9rtP=x zYmIHFF!eJ5+~)P&R=b>%E$(M)jm{`4(nt^mUJ7@%wYTk^Bb`~plfkH%kQ8=@30hLe z`@27RUa{|~C6YGR&MgQ^Y35Lr$HsjhovNQG&M+~lyphcWG~5Kl5X#=h?RD7`1aM!zmGf%fmIMkF83b%U@ z^|R*ak`R8H{Tl9d-!$Ss`ocoB^0;{Uzv-1wI1c@%SC(2i!i|xU{@p9n4WrdG$p%C_ zoR2fVw8#@^wXnS>=)$i*s8E^z?mI4_AmhmpVXu3?9)*fpUC%BR-#ct4&&1RImR9Py z>3T4qP--eq46kHN1YMen$v(bpc|86Lgtw{R4~F3iWGu{?{q;S8!!d_>m_1h$%

m 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 literal 15406 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 + + + + + + + + + + + + + MyAi.ro + Applied AI engineering showcase + RAG + diff --git a/web/wwwroot/img/myai-logo.svg b/web/wwwroot/img/myai-logo.svg new file mode 100644 index 0000000..fdffd46 --- /dev/null +++ b/web/wwwroot/img/myai-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/web/wwwroot/index.html b/web/wwwroot/index.html index 2ddd27e..69c178c 100644 --- a/web/wwwroot/index.html +++ b/web/wwwroot/index.html @@ -11,134 +11,189 @@ + + - - + +

-
-
- Navigator -

Select an AI demo

-

Start with the CV Matcher. More demos can be added here without changing the site structure.

+ Navigator +

Select an AI demo

+

Start with the CV Matcher. More demos can be added here without changing the site structure.

- Available -

CV Matcher

-

Upload a CV PDF, provide a job link or description, extract RAG context and generate a match score with strengths and gaps.

- Open demo → + Available +

CV Matcher

+

Upload a CV PDF, provide a job link or description, extract RAG context and generate a match score with strengths and gaps.

+ Open demo →
- Next -

RAG Playground

-

Experiment with chunk size, retrieval count and source context transparency.

+ Next +

RAG Playground

+

Experiment with chunk size, retrieval count and source context transparency.

- Next -

Agent Automation

-

Job discovery, filtering, ranking and notification workflows.

+ Next +

Agent Automation

+

Job discovery, filtering, ranking and notification workflows.

-
- Contact -

Discuss an AI integration or custom software project

-

Use the form and it will submit through the existing contact API endpoint from the template.

+ Contact +

Discuss an AI integration or custom software project

+

Use the form and it will submit through the existing contact API endpoint from the template.

-
Contact personMihes Gelu
- - +
+ Contact person + Mihes Gelu +
+
+ Phone + + +40 722-523-764 + +
+
+ WhatsApp + + +40 744-564-177 + +
- - - - + + + +
-
- - - + - - - - - + + + + - + \ No newline at end of file diff --git a/web/wwwroot/js/myai.js b/web/wwwroot/js/myai.js index c057698..aa79325 100644 --- a/web/wwwroot/js/myai.js +++ b/web/wwwroot/js/myai.js @@ -1,243 +1,499 @@ (function ($) { - "use strict"; + "use strict"; - var reCaptchaSiteKey = null; - var gTagManagerId = null; - var CONSENT_KEY = "myai_cookie_consent"; + var reCaptchaSiteKey = null; + var gTagManagerId = null; + var CONSENT_KEY = "myai_cookie_consent"; + var LANG_KEY = "myai_lang"; - $('#year').text(new Date().getFullYear()); + var i18n = { + en: { + "brand.subtitle": "AI engineering showcase", + "nav.demos": "Demos", + "nav.contact": "Contact", + "nav.navigator": "Navigator", + "nav.matcher": "Matcher", + "home.eyebrow": "Applied AI lab", + "home.title": "Production-minded AI demos, not generic chatbot wrappers.", + "home.text": "MyAi.ro is a technical showcase for practical AI systems: document understanding, retrieval, matching, automation and decision support.", + "home.openCv": "Open CV Matcher", + "home.step1": "CV.pdf", + "home.step2": "skills, projects, experience", + "home.step3": "relevant CV context", + "home.step4": "job match + gaps", + "home.navigator": "Navigator", + "home.selectDemo": "Select an AI demo", + "home.selectText": "Our first steps in AI integrations.", + "tag.available": "Available", + "tag.next": "Next", + "demo.cv.title": "CV Matcher", + "demo.cv.text": "Upload a CV PDF, provide a job link or description, extract RAG context and generate a match score with strengths and gaps.", + "demo.open": "Open demo →", + "demo.rag.title": "RAG Playground", + "demo.rag.text": "Experiment with chunk size, retrieval count and source context transparency.", + "demo.agent.title": "Agent Automation", + "demo.agent.text": "Job discovery, filtering, ranking and notification workflows.", + "contact.title": "Discuss an AI integration or custom software project", + "contact.text": "Fill in the form fields and will contact you.", + "contact.person": "Contact person", + "contact.phone": "Phone", + "form.name": "Name", + "form.namePlaceholder": "Your name", + "form.email": "Email", + "form.emailPlaceholder": "name@company.com", + "form.message": "Message", + "form.messagePlaceholder": "Tell me what you want to build.", + "form.send": "Send message", + "form.thanks": "Thank you for your message.", + "form.captchaFailed": "Captcha verification failed.", + "form.failed": "Failed to send the message.", + "footer.rights": "All rights reserved", + "footer.top": "Back to top", + "legal.terms": "Terms", + "legal.privacy": "Privacy", + "legal.cookies": "Cookies", + "status.sending": "Sending...", + "cookies.title": "Cookies", + "cookies.text": "We use necessary cookies and, with your consent, analytics through Google Tag Manager.", + "cookies.policy": "Privacy policy", + "cookies.reject": "Reject", + "cookies.necessary": "Necessary only", + "cookies.accept": "Accept analytics", + "cookies.settings": "Cookie settings", + "cv.brand": "CV Matcher", + "cv.eyebrow": "AI CV Matcher", + "cv.title": "Upload your CV, add a job link, and see how well they match.", + "cv.text": "The backend should extract text from the PDF, create RAG context from your CV, retrieve relevant experience for the job, then score strengths, gaps and next actions.", + "cv.try": "Try matcher", + "cv.back": "Back to navigator", + "cv.step1": "PDF text extraction", + "cv.step2": "CV chunking + embeddings", + "cv.step3": "Job URL/description parsing", + "cv.step4": "Match score + evidence", + "cv.input": "Input", + "cv.details": "CV and job details", + "cv.upload": "Upload CV PDF", + "cv.fileHint": "PDF only, max size handled by backend", + "cv.jobLink": "Job link", + "cv.jobDescription": "Or paste job description", + "cv.jobPlaceholder": "Paste the job description if the page cannot be crawled.", + "cv.gdpr": "I agree that my CV is processed for this matching demo. Do not upload sensitive documents unless you trust the deployment.", + "cv.submit": "Extract CV and match job", + "cv.result": "Result", + "cv.analysis": "Match analysis", + "cv.empty": "Upload a CV and provide a job link or description to generate a result.", + "cv.contactTitle": "Want this adapted for your workflow?", + "cv.contactText": "This form uses the existing template contact API endpoint.", + "cv.noFile": "Please upload a CV PDF.", + "cv.noJob": "Add a job link or paste a job description.", + "cv.noConsent": "GDPR consent is required.", + "cv.processing": "Processing...", + "cv.extracting": "Extracting CV and matching job...", + "cv.processingLong": "Processing CV PDF and job input. Backend endpoints must be available.", + "cv.cvFailed": "CV extraction failed", + "cv.matchFailed": "Job matching failed", + "cv.completed": "Match completed.", + "cv.backendMissing": "The frontend is ready, but the backend endpoints /api/rag/cv and /api/rag/match-job must be implemented.", + "cv.noSummary": "No summary returned.", + "cv.noItems": "No items returned.", + "cv.strengths": "Strengths", + "cv.gaps": "Gaps", + "cv.evidence": "Retrieved CV evidence" + }, + ro: { + "brand.subtitle": "prezentare inginerie AI", + "nav.demos": "Demo-uri", + "nav.contact": "Contact", + "nav.navigator": "Navigator", + "nav.matcher": "Matcher", + "home.eyebrow": "Laborator AI aplicat", + "home.title": "Demo-uri AI orientate spre producție, nu simple wrapper-e de chatbot.", + "home.text": "MyAi.ro este o prezentare tehnică pentru sisteme AI practice: înțelegere documente, retrieval, potrivire, automatizare și suport decizional.", + "home.openCv": "Deschide CV Matcher", + "home.step1": "CV.pdf", + "home.step2": "competențe, proiecte, experiență", + "home.step3": "context relevant din CV", + "home.step4": "potrivire job + lipsuri", + "home.navigator": "Navigator", + "home.selectDemo": "Alege un demo AI", + "home.selectText": "Primele noastre projecte", + "tag.available": "Disponibil", + "tag.next": "Urmează", + "demo.cv.title": "CV Matcher", + "demo.cv.text": "Încarcă un CV PDF, adaugă un link sau o descriere de job, extrage context RAG și generează un scor de potrivire cu puncte forte și lipsuri.", + "demo.open": "Deschide demo →", + "demo.rag.title": "RAG Playground", + "demo.rag.text": "Experimentează cu dimensiunea chunk-urilor, numărul de rezultate și transparența surselor.", + "demo.agent.title": "Automatizare cu agenți", + "demo.agent.text": "Fluxuri pentru descoperire joburi, filtrare, ranking și notificări.", + "contact.title": "Discută o integrare AI sau un proiect software custom", + "contact.text": "Complecteaza formularul si te vom contacta.", + "contact.person": "Persoană de contact", + "contact.phone": "Telefon", + "form.name": "Nume", + "form.namePlaceholder": "Numele tău", + "form.email": "Email", + "form.emailPlaceholder": "nume@companie.ro", + "form.message": "Mesaj", + "form.messagePlaceholder": "Spune-mi ce vrei să construiești.", + "form.send": "Trimite mesajul", + "form.thanks": "Mulțumesc pentru mesaj.", + "form.captchaFailed": "Verificarea Captcha a eșuat.", + "form.failed": "Mesajul nu a putut fi trimis.", + "footer.rights": "Toate drepturile rezervate", + "footer.top": "Înapoi sus", + "legal.terms": "Termeni", + "legal.privacy": "Confidențialitate", + "legal.cookies": "Cookies", + "status.sending": "Se trimite...", + "cookies.title": "Cookies", + "cookies.text": "Folosim cookies necesare și, cu acordul tău, analytics prin Google Tag Manager.", + "cookies.policy": "Politica de confidențialitate", + "cookies.reject": "Respinge", + "cookies.necessary": "Doar necesare", + "cookies.accept": "Accept analytics", + "cookies.settings": "Setări cookies", + "cv.brand": "CV Matcher", + "cv.eyebrow": "AI CV Matcher", + "cv.title": "Încarcă CV-ul, adaugă un link de job și vezi cât de bine se potrivesc.", + "cv.text": "Backend-ul ar trebui să extragă textul din PDF, să creeze context RAG din CV, să recupereze experiența relevantă pentru job, apoi să calculeze punctele forte, lipsurile și pașii următori.", + "cv.try": "Încearcă matcherul", + "cv.back": "Înapoi la navigator", + "cv.step1": "Extragere text PDF", + "cv.step2": "Chunking CV + embeddings", + "cv.step3": "Parsare URL/descriere job", + "cv.step4": "Scor potrivire + dovezi", + "cv.input": "Date de intrare", + "cv.details": "CV și detalii job", + "cv.upload": "Încarcă CV PDF", + "cv.fileHint": "Doar PDF, limita de mărime este gestionată de backend", + "cv.jobLink": "Link job", + "cv.jobDescription": "Sau lipește descrierea jobului", + "cv.jobPlaceholder": "Lipește descrierea jobului dacă pagina nu poate fi citită automat.", + "cv.gdpr": "Sunt de acord ca CV-ul meu să fie procesat pentru acest demo de matching. Nu încărca documente sensibile dacă nu ai încredere în deployment.", + "cv.submit": "Extrage CV și compară jobul", + "cv.result": "Rezultat", + "cv.analysis": "Analiză potrivire", + "cv.empty": "Încarcă un CV și adaugă un link sau o descriere de job pentru a genera rezultatul.", + "cv.contactTitle": "Vrei să adaptezi asta pentru workflow-ul tău?", + "cv.contactText": "Formularul folosește endpoint-ul existent de contact din template.", + "cv.noFile": "Te rog încarcă un CV PDF.", + "cv.noJob": "Adaugă un link de job sau lipește descrierea jobului.", + "cv.noConsent": "Consimțământul GDPR este obligatoriu.", + "cv.processing": "Se procesează...", + "cv.extracting": "Se extrage CV-ul și se compară jobul...", + "cv.processingLong": "Se procesează PDF-ul și informațiile despre job. Endpoint-urile backend trebuie să fie disponibile.", + "cv.cvFailed": "Extragerea CV-ului a eșuat", + "cv.matchFailed": "Matching-ul jobului a eșuat", + "cv.completed": "Matching finalizat.", + "cv.backendMissing": "Frontend-ul este pregătit, dar endpoint-urile backend /api/rag/cv și /api/rag/match-job trebuie implementate.", + "cv.noSummary": "Nu a fost returnat niciun sumar.", + "cv.noItems": "Nu au fost returnate elemente.", + "cv.strengths": "Puncte forte", + "cv.gaps": "Lipsuri", + "cv.evidence": "Dovezi recuperate din CV" + } + }; - $('#menuToggle').on('click', function () { - var $nav = $('#mainNav'); - var open = !$nav.hasClass('is-open'); - $nav.toggleClass('is-open', open); - $(this).attr('aria-expanded', open ? 'true' : 'false'); - }); + function browserLang() { + return ((navigator.language || navigator.userLanguage || 'en').toLowerCase().indexOf('ro') === 0) ? 'ro' : 'en'; + } - $('.nav a').on('click', function () { - $('#mainNav').removeClass('is-open'); - $('#menuToggle').attr('aria-expanded', 'false'); - }); + function currentLang() { + return localStorage.getItem(LANG_KEY) || browserLang(); + } - function getRecaptchaWebKey() { - return $.get('/api/contact').done(function (res) { - reCaptchaSiteKey = res; - if (reCaptchaSiteKey && !window.__recaptcha_loaded) { - window.__recaptcha_loaded = true; - var script = document.createElement('script'); - script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?render=' + reCaptchaSiteKey); - document.head.appendChild(script); - } - }).fail(function () { - console.warn('Could not load reCaptcha site key from /api/contact'); - }); - } + function t(key) { + var lang = currentLang(); + return (i18n[lang] && i18n[lang][key]) || i18n.en[key] || key; + } - function getGoogleTagManagerId() { - return $.get('/api/google/tagmanager').done(function (res) { - gTagManagerId = res; - }).fail(function () { - console.warn('Could not load Google Tag Manager id from /api/google/tagmanager'); - }); - } + function updateLegalLinks(lang) { + $('[data-legal]').each(function () { + var page = $(this).data('legal'); + $(this).attr('href', '/legal/' + page + '-' + lang + '.html'); + }); + } - function loadGoogleTagManager() { - if (window.__gtm_loaded || !gTagManagerId) return; - window.__gtm_loaded = true; - window.dataLayer = window.dataLayer || []; - window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); - var script = document.createElement('script'); - script.async = true; - script.src = 'https://www.googletagmanager.com/gtm/js?id=' + gTagManagerId; - document.head.appendChild(script); - } + function applyLanguage(lang) { + localStorage.setItem(LANG_KEY, lang); + document.documentElement.lang = lang; + updateLegalLinks(lang); + $('[data-i18n]').each(function () { + $(this).text(t($(this).data('i18n'))); + }); + $('[data-i18n-placeholder]').each(function () { + $(this).attr('placeholder', t($(this).data('i18n-placeholder'))); + }); + $('.lang-flag').attr('aria-pressed', 'false'); + $('.lang-flag[data-lang="' + lang + '"]').attr('aria-pressed', 'true'); + } - function getConsent() { - try { return JSON.parse(localStorage.getItem(CONSENT_KEY)); } catch { return null; } - } + $('#year').text(new Date().getFullYear()); + applyLanguage(currentLang()); + $('.lang-flag').on('click', function () { + applyLanguage($(this).data('lang')); + }); - function setConsent(consent) { - localStorage.setItem(CONSENT_KEY, JSON.stringify(consent)); - } + $('#menuToggle').on('click', function () { + var $nav = $('#mainNav'); + var open = !$nav.hasClass('is-open'); + $nav.toggleClass('is-open', open); + $(this).attr('aria-expanded', open ? 'true' : 'false'); + }); + $('.nav a').on('click', function () { + $('#mainNav').removeClass('is-open'); + $('#menuToggle').attr('aria-expanded', 'false'); + }); - function applyConsent(consent) { - if (consent && consent.analytics === true) loadGoogleTagManager(); - } + function getRecaptchaWebKey() { + return $.get('/api/contact').done(function (res) { + reCaptchaSiteKey = res; + if (reCaptchaSiteKey && !window.__recaptcha_loaded) { + window.__recaptcha_loaded = true; + var script = document.createElement('script'); + script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?render=' + reCaptchaSiteKey); + document.head.appendChild(script); + } + }).fail(function () { + console.warn('Could not load reCaptcha site key from /api/contact'); + }); + } - function showBanner() { $('#cookieBanner').fadeIn(200); } - function hideBanner() { $('#cookieBanner').fadeOut(200); } - function showManage() { $('#cookieManage').show(); } + function getGoogleTagManagerId() { + return $.get('/api/google/tagmanager').done(function (res) { + gTagManagerId = res; + }).fail(function () { + console.warn('Could not load Google Tag Manager id from /api/google/tagmanager'); + }); + } - $('#cookieReject, #cookieNecessary').on('click', function () { - setConsent({ necessary: true, analytics: false, ts: new Date().toISOString() }); - hideBanner(); - showManage(); - }); + function loadGoogleTagManager() { + if (window.__gtm_loaded || !gTagManagerId) return; + window.__gtm_loaded = true; + window.dataLayer = window.dataLayer || []; + window.dataLayer.push({ + 'gtm.start': new Date().getTime(), + event: 'gtm.js' + }); + var script = document.createElement('script'); + script.async = true; + script.src = 'https://www.googletagmanager.com/gtm/js?id=' + gTagManagerId; + document.head.appendChild(script); + } - $('#cookieAccept').on('click', function () { - var consent = { necessary: true, analytics: true, ts: new Date().toISOString() }; - setConsent(consent); - applyConsent(consent); - hideBanner(); - showManage(); - }); + function getConsent() { + try { + return JSON.parse(localStorage.getItem(CONSENT_KEY)); + } catch (e) { + return null; + } + } - $('#cookieManage').on('click', function (e) { - e.preventDefault(); - showBanner(); - }); + function setConsent(consent) { + localStorage.setItem(CONSENT_KEY, JSON.stringify(consent)); + } - function initConsent() { - var consent = getConsent(); - if (!consent) showBanner(); - else { applyConsent(consent); showManage(); } - } + function applyConsent(consent) { + if (consent && consent.analytics === true) loadGoogleTagManager(); + } - function submitMSG(valid, msg) { - var msgClasses = valid ? 'form-message text-success' : 'form-message text-danger'; - $('#msgSubmit').removeClass().addClass(msgClasses).text(msg); - } + function showBanner() { + $('#cookieBanner').fadeIn(200); + } - function formSuccess() { - $('#contactForm')[0].reset(); - submitMSG(true, 'Thank you for your message.'); - } + function hideBanner() { + $('#cookieBanner').fadeOut(200); + } - function formError() { - $('#contactForm').removeClass().addClass('contact-form shake').one('animationend', function () { - $(this).removeClass('shake'); - }); - } + function showManage() { + $('#cookieManage').show(); + } + $('#cookieReject, #cookieNecessary').on('click', function () { + setConsent({ + necessary: true, + analytics: false, + ts: new Date().toISOString() + }); + hideBanner(); + showManage(); + }); + $('#cookieAccept').on('click', function () { + var consent = { + necessary: true, + analytics: true, + ts: new Date().toISOString() + }; + setConsent(consent); + applyConsent(consent); + hideBanner(); + showManage(); + }); + $('#cookieManage').on('click', function (e) { + e.preventDefault(); + showBanner(); + }); - $('#contactForm').on('submit', function (event) { - event.preventDefault(); - var loader = $('#contactLoader'); - var button = $('#submit'); - loader.css('display', 'flex'); - button.prop('disabled', true); - $('#msgSubmit').text(''); + function initConsent() { + var consent = getConsent(); + if (!consent) showBanner(); + else { + applyConsent(consent); + showManage(); + } + } - function postContact(token) { - var message = { - Name: $('#name').val(), - Email: $('#email').val(), - Subject: '[MyAi.ro contact request]', - Message: $('#message').val(), - CaptchaToken: token || '' - }; - $.ajax({ - type: 'POST', - url: '/api/contact', - data: JSON.stringify(message), - contentType: 'application/json; charset=utf-8', - dataType: 'json' - }).done(function (resp) { - if (resp && resp.ok === true) formSuccess(); - else submitMSG(false, 'Captcha verification failed.'); - }).fail(function () { - submitMSG(false, 'Failed to send the message.'); - formError(); - }).always(function () { - loader.hide(); - button.prop('disabled', false); - }); - } + function submitMSG(valid, msg) { + $('#msgSubmit').removeClass().addClass(valid ? 'form-message text-success' : 'form-message text-danger').text(msg); + } - if (window.grecaptcha && reCaptchaSiteKey) { - grecaptcha.ready(function () { - grecaptcha.execute(reCaptchaSiteKey, { action: 'contact' }).then(postContact); - }); - } else { - postContact(''); - } - }); + function formSuccess() { + $('#contactForm')[0].reset(); + submitMSG(true, t('form.thanks')); + } - $('#cvFile').on('change', function () { - var file = this.files && this.files[0]; - $('#cvFileName').text(file ? file.name : 'PDF only, max size handled by backend'); - }); + function formError() { + $('#contactForm').removeClass().addClass('contact-form shake').one('animationend', function () { + $(this).removeClass('shake'); + }); + } - $('#cvMatcherForm').on('submit', async function (event) { - event.preventDefault(); - var file = $('#cvFile')[0] && $('#cvFile')[0].files[0]; - var jobUrl = $('#jobUrl').val(); - var jobDescription = $('#jobDescription').val(); - var consent = $('#gdprConsent').is(':checked'); - var $msg = $('#matcherMsg'); - var $button = $('#matchSubmit'); - var $result = $('#matchResult'); + $('#contactForm').on('submit', function (event) { + event.preventDefault(); + var loader = $('#contactLoader'), + button = $('#submit'); + loader.css('display', 'flex'); + button.prop('disabled', true); + $('#msgSubmit').text(''); - if (!file) { $msg.removeClass().addClass('form-message text-danger').text('Please upload a CV PDF.'); return; } - if (!jobUrl && !jobDescription) { $msg.removeClass().addClass('form-message text-danger').text('Add a job link or paste a job description.'); return; } - if (!consent) { $msg.removeClass().addClass('form-message text-danger').text('GDPR consent is required.'); return; } + function postContact(token) { + var message = { + Name: $('#name').val(), + Email: $('#email').val(), + Subject: '[MyAi.ro contact request]', + Message: $('#message').val(), + CaptchaToken: token || '' + }; + $.ajax({ + type: 'POST', + url: '/api/contact', + data: JSON.stringify(message), + contentType: 'application/json; charset=utf-8', + dataType: 'json' + }).done(function (resp) { + if (resp && resp.ok === true) formSuccess(); + else submitMSG(false, t('form.captchaFailed')); + }).fail(function () { + submitMSG(false, t('form.failed')); + formError(); + }).always(function () { + loader.hide(); + button.prop('disabled', false); + }); + } + if (window.grecaptcha && reCaptchaSiteKey) { + grecaptcha.ready(function () { + grecaptcha.execute(reCaptchaSiteKey, { + action: 'contact' + }).then(postContact); + }); + } else { + postContact(''); + } + }); - $button.prop('disabled', true).text('Processing...'); - $msg.removeClass().addClass('form-message').text('Extracting CV and matching job...'); - $result.html('
Processing CV PDF and job input. Backend endpoints must be available.
'); + $('#cvFile').on('change', function () { + var file = this.files && this.files[0]; + $('#cvFileName').text(file ? file.name : t('cv.fileHint')); + }); + $('#cvMatcherForm').on('submit', async function (event) { + event.preventDefault(); + var file = $('#cvFile')[0] && $('#cvFile')[0].files[0]; + var jobUrl = $('#jobUrl').val(); + var jobDescription = $('#jobDescription').val(); + var consent = $('#gdprConsent').is(':checked'); + var $msg = $('#matcherMsg'), + $button = $('#matchSubmit'), + $result = $('#matchResult'); + if (!file) { + $msg.removeClass().addClass('form-message text-danger').text(t('cv.noFile')); + return; + } + if (!jobUrl && !jobDescription) { + $msg.removeClass().addClass('form-message text-danger').text(t('cv.noJob')); + return; + } + if (!consent) { + $msg.removeClass().addClass('form-message text-danger').text(t('cv.noConsent')); + return; + } + $button.prop('disabled', true).text(t('cv.processing')); + $msg.removeClass().addClass('form-message').text(t('cv.extracting')); + $result.html('
' + escapeHtml(t('cv.processingLong')) + '
'); + try { + var formData = new FormData(); + formData.append('cv', file); + formData.append('gdprConsent', String(consent)); + var cvResponse = await fetch('/api/rag/cv', { + method: 'POST', + body: formData + }); + if (!cvResponse.ok) throw new Error(t('cv.cvFailed')); + var cvData = await cvResponse.json(); + var matchResponse = await fetch('/api/rag/match-job', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + cvDocumentId: cvData.documentId || cvData.cvDocumentId, + jobUrl: jobUrl, + jobDescription: jobDescription, + gdprConsent: consent + }) + }); + if (!matchResponse.ok) throw new Error(t('cv.matchFailed')); + var match = await matchResponse.json(); + renderMatchResult(match); + $msg.removeClass().addClass('form-message text-success').text(t('cv.completed')); + } catch (err) { + console.error(err); + $msg.removeClass().addClass('form-message text-danger').text(err.message || t('cv.matchFailed')); + $result.html('
' + escapeHtml(t('cv.backendMissing')) + '
'); + } finally { + $button.prop('disabled', false).text(t('cv.submit')); + } + }); - try { - var formData = new FormData(); - formData.append('cv', file); - formData.append('gdprConsent', String(consent)); + function renderMatchResult(match) { + var score = match.score || match.matchScore || 0; + var summary = match.summary || t('cv.noSummary'); + var strengths = match.strengths || []; + var gaps = match.gaps || match.missingSkills || []; + var evidence = match.evidence || match.retrievedChunks || []; - var cvResponse = await fetch('/api/rag/cv', { method: 'POST', body: formData }); - if (!cvResponse.ok) throw new Error('CV extraction failed'); - var cvData = await cvResponse.json(); + function list(items) { + if (!items || !items.length) return '

' + escapeHtml(t('cv.noItems')) + '

'; + return '
    ' + items.map(function (x) { + var text = typeof x === 'string' ? x : (x.text || x.title || JSON.stringify(x)); + return '
  • ' + escapeHtml(text) + '
  • '; + }).join('') + '
'; + } + $('#matchResult').html('
' + Number(score).toFixed(0) + '%

' + escapeHtml(summary) + '

' + t('cv.strengths') + '

' + list(strengths) + '

' + t('cv.gaps') + '

' + list(gaps) + '

' + t('cv.evidence') + '

' + list(evidence)); + } - var matchResponse = await fetch('/api/rag/match-job', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - cvDocumentId: cvData.documentId || cvData.cvDocumentId, - jobUrl: jobUrl, - jobDescription: jobDescription, - gdprConsent: consent - }) - }); - if (!matchResponse.ok) throw new Error('Job matching failed'); - var match = await matchResponse.json(); - renderMatchResult(match); - $msg.removeClass().addClass('form-message text-success').text('Match completed.'); - } catch (err) { - console.error(err); - $msg.removeClass().addClass('form-message text-danger').text(err.message || 'Failed to run the matcher.'); - $result.html('
The frontend is ready, but the backend endpoints /api/rag/cv and /api/rag/match-job must be implemented.
'); - } finally { - $button.prop('disabled', false).text('Extract CV and match job'); - } - }); - - function renderMatchResult(match) { - var score = match.score || match.matchScore || 0; - var summary = match.summary || 'No summary returned.'; - var strengths = match.strengths || []; - var gaps = match.gaps || match.missingSkills || []; - var evidence = match.evidence || match.retrievedChunks || []; - - function list(items) { - if (!items || !items.length) return '

No items returned.

'; - return '
    ' + items.map(function (x) { - var text = typeof x === 'string' ? x : (x.text || x.title || JSON.stringify(x)); - return '
  • ' + escapeHtml(text) + '
  • '; - }).join('') + '
'; - } - - $('#matchResult').html( - '
' + Number(score).toFixed(0) + '%
' + - '

' + escapeHtml(summary) + '

' + - '

Strengths

' + list(strengths) + - '

Gaps

' + list(gaps) + - '

Retrieved CV evidence

' + list(evidence) - ); - } - - function escapeHtml(value) { - return String(value).replace(/[&<>'"]/g, function (char) { - return ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' })[char]; - }); - } - - $(window).on('load', function () { - $.when(getRecaptchaWebKey(), getGoogleTagManagerId()).always(initConsent); - }); -})(jQuery); + function escapeHtml(value) { + return String(value).replace(/[&<>'"]/g, function (char) { + return ({ + '&': '&', + '<': '<', + '>': '>', + "'": ''', + '"': '"' + })[char]; + }); + } + $(window).on('load', function () { + $.when(getRecaptchaWebKey(), getGoogleTagManagerId()).always(initConsent); + }); +})(jQuery); \ No newline at end of file diff --git a/web/wwwroot/legal/cookies-en.html b/web/wwwroot/legal/cookies-en.html index 37ee1ac..eb8d57c 100644 --- a/web/wwwroot/legal/cookies-en.html +++ b/web/wwwroot/legal/cookies-en.html @@ -9,22 +9,24 @@
-
- -
- myAi -
-
myAiLegal pages
-
-
Cookies
diff --git a/web/wwwroot/legal/cookies-ro.html b/web/wwwroot/legal/cookies-ro.html index 74f2cc4..4341744 100644 --- a/web/wwwroot/legal/cookies-ro.html +++ b/web/wwwroot/legal/cookies-ro.html @@ -10,11 +10,13 @@
- -
- myAi -
-
myAiPagini legale
+
+ + MyAi.ro + + + Inapoi +
diff --git a/web/wwwroot/legal/css/legal.css b/web/wwwroot/legal/css/legal.css index e18f636..f70b423 100644 --- a/web/wwwroot/legal/css/legal.css +++ b/web/wwwroot/legal/css/legal.css @@ -1,86 +1,216 @@ -:root{ - --bg:#07192f; - --bg2:#0a2441; - --card:#0e1f37; - --text:#eaf2ff; - --muted:#b4c5dd; - --line:rgba(255,255,255,.10); - --accent:#7eb7ff; -} -*{box-sizing:border-box} -body{ - margin:0; - font-family:Arial,Helvetica,sans-serif; - background: - radial-gradient(circle at top left, rgba(79,140,255,.18), transparent 28%), - linear-gradient(180deg, var(--bg) 0%, #05111f 100%); - color:var(--text); - line-height:1.75; -} -a{color:var(--accent);text-decoration:none} -a:hover{text-decoration:none} -.wrap{max-width:1100px;margin:0 auto;padding:28px 20px 60px} -.topbar{ - display:flex;justify-content:space-between;align-items:center;gap:16px; - padding:10px 0 22px;margin-bottom:24px;border-bottom:1px solid var(--line); -} -.brand{display:flex;align-items:center;gap:14px;} -.brand-badge { - width: 48px; - height: 48px; - background: none; +:root { + --bg: #07192f; + --bg2: #0a2441; + --card: #0e1f37; + --text: #eaf2ff; + --muted: #b4c5dd; + --line: rgba(255,255,255,.10); + --accent: #7eb7ff; } -.brand-badge img { - width: 100%; - height: 100%; - object-fit: contain; +* { + box-sizing: border-box } -.brand-copy small{display:block;color:var(--muted);font-weight:400} -.switcher{display:flex;align-items:center;gap:10px} -.switcher a{ - display:inline-flex;align-items:center;justify-content:center; - width:44px;height:44px;border-radius:999px;border:1px solid var(--line); - background:rgba(255,255,255,.04);transition:.2s ease; +body { + margin: 0; + font-family: Arial,Helvetica,sans-serif; + background: radial-gradient(circle at top left, rgba(79,140,255,.18), transparent 28%), linear-gradient(180deg, var(--bg) 0%, #05111f 100%); + color: var(--text); + line-height: 1.75; } -.switcher a:hover{text-decoration:none;transform:translateY(-1px)} -.switcher a.active{border-color:rgba(126,183,255,.65);box-shadow:0 0 0 3px rgba(126,183,255,.13)} -.switcher img{width:24px;height:24px;display:block} -.hero,.content,.footer{ - background:linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.02)); - border:1px solid var(--line); - border-radius:22px; - box-shadow:0 25px 60px rgba(0,0,0,.18); + +a { + color: var(--accent); + text-decoration: none } -.hero{padding:28px 30px;margin-bottom:18px} -.kicker{ - display:inline-block;padding:8px 12px;border-radius:999px; - background:rgba(255,255,255,.04);border:1px solid var(--line);color:var(--muted); - font-size:13px;margin-bottom:12px + + a:hover { + text-decoration: none + } + +.wrap { + max-width: 1100px; + margin: 0 auto; + padding: 28px 20px 60px } -.hero h1{margin:0 0 8px;font-size:40px;line-height:1.08} -.hero p{margin:0;color:var(--muted);max-width:780px} -.content{padding:30px} -.content h2{font-size:28px;line-height:1.15;margin:28px 0 10px} -.content h2:first-child{margin-top:0} -.content p{margin:0 0 14px;color:#deebff} -.content ul{margin:0 0 18px 22px;padding:0} -.content li{margin-bottom:8px;color:#deebff} -.notice{ - margin:16px 0 18px;padding:16px 18px;border-radius:18px; - background:rgba(126,183,255,.08);border:1px solid rgba(126,183,255,.18) + +.topbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + padding: 10px 0 22px; + margin-bottom: 24px; + border-bottom: 1px solid var(--line); } -.footer{ - margin-top:18px;padding:22px 26px;display:flex;justify-content:space-between;gap:16px;align-items:center;flex-wrap:wrap + +.brand { + display: flex; + align-items: center; + gap: 14px; } -.footer-links{display:flex;gap:18px;flex-wrap:wrap} -.footer small{color:var(--muted)} -.meta{color:var(--muted);font-size:14px} -@media (max-width:768px){ - .hero h1{font-size:32px} - .content{padding:22px} - .content h2{font-size:24px} - .topbar{align-items:flex-start;flex-direction:column} + +.brand-text { + font-size: 1.35rem +} + +.brand small { + display: none +} + +.brand-mark { + width: 42px; + height: 42px +} + +.switcher { + display: flex; + align-items: center; + gap: 10px +} + + .switcher a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border-radius: 999px; + border: 1px solid var(--line); + background: rgba(255,255,255,.04); + transition: .2s ease; + } + + .switcher a:hover { + text-decoration: none; + transform: translateY(-1px) + } + + .switcher a.active { + border-color: rgba(126,183,255,.65); + box-shadow: 0 0 0 3px rgba(126,183,255,.13) + } + + .switcher img { + width: 24px; + height: 24px; + display: block + } + +.hero, .content, .footer { + background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.02)); + border: 1px solid var(--line); + border-radius: 22px; + box-shadow: 0 25px 60px rgba(0,0,0,.18); +} + +.hero { + padding: 28px 30px; + margin-bottom: 18px +} + +.kicker { + display: inline-block; + padding: 8px 12px; + border-radius: 999px; + background: rgba(255,255,255,.04); + border: 1px solid var(--line); + color: var(--muted); + font-size: 13px; + margin-bottom: 12px +} + +.hero h1 { + margin: 0 0 8px; + font-size: 40px; + line-height: 1.08 +} + +.hero p { + margin: 0; + color: var(--muted); + max-width: 780px +} + +.content { + padding: 30px +} + + .content h2 { + font-size: 28px; + line-height: 1.15; + margin: 28px 0 10px + } + + .content h2:first-child { + margin-top: 0 + } + + .content p { + margin: 0 0 14px; + color: #deebff + } + + .content ul { + margin: 0 0 18px 22px; + padding: 0 + } + + .content li { + margin-bottom: 8px; + color: #deebff + } + +.notice { + margin: 16px 0 18px; + padding: 16px 18px; + border-radius: 18px; + background: rgba(126,183,255,.08); + border: 1px solid rgba(126,183,255,.18) +} + +.footer { + margin-top: 18px; + padding: 22px 26px; + display: flex; + justify-content: space-between; + gap: 16px; + align-items: center; + flex-wrap: wrap +} + +.footer-links { + display: flex; + gap: 18px; + flex-wrap: wrap +} + +.footer small { + color: var(--muted) +} + +.meta { + color: var(--muted); + font-size: 14px +} + +@media (max-width:768px) { + .hero h1 { + font-size: 32px + } + + .content { + padding: 22px + } + + .content h2 { + font-size: 24px + } + + .topbar { + align-items: flex-start; + flex-direction: column + } } diff --git a/web/wwwroot/legal/privacy-en.html b/web/wwwroot/legal/privacy-en.html index 531ecf6..2e2c29e 100644 --- a/web/wwwroot/legal/privacy-en.html +++ b/web/wwwroot/legal/privacy-en.html @@ -9,22 +9,24 @@