<!DOCTYPE html><html lang="{{ app.request.locale|default('fr') }}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{% block title %}WhileResume{% endblock %}</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,400;1,9..144,600&family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><style>:root {--violet: #D0BEFF;--violet-deep: #8B6FC0;--violet-dark: #6B4FA0;--violet-soft: #F0EAFF;--violet-ultra: #F8F5FF;--violet-glow: rgba(208,190,255,0.35);--ink: #1E1E2E;--ink-80: #3D3D52;--ink-60: #62627A;--ink-40: #9090A7;--ink-20: #C8C8D6;--ink-10: #E6E6ED;--ink-05: #F2F2F6;--cream: #FBFAFF;--white: #FFFFFF;--emerald: #34C77B;--emerald-soft: #EDFCF4;--ocean: #4E8AE6;--ocean-soft: #EDF3FE;--amber: #E6A23C;--amber-soft: #FEF6E8;--rose: #E8457A;--rose-soft: #FEF0F4;--danger: #EF4444;--danger-soft: #FEF2F2;--font-display: 'Fraunces', serif;--font-body: 'Outfit', sans-serif;--font-mono: 'JetBrains Mono', monospace;--r-sm: 10px;--r-md: 14px;--r-lg: 20px;--r-xl: 28px;--r-full: 100px;--ease: cubic-bezier(0.16, 1, 0.3, 1);--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);}*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }html { scroll-behavior: smooth; }body {font-family: var(--font-body);background: var(--cream);color: var(--ink);-webkit-font-smoothing: antialiased;line-height: 1.6;overflow-x: hidden;min-height: 100vh;}body::after {content: '';position: fixed;inset: 0;pointer-events: none;z-index: 9999;opacity: 0.018;background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");background-size: 200px;}/* TOP BAR */.topbar {position: fixed;top: 0; left: 0; right: 0;z-index: 90;display: flex;align-items: center;justify-content: space-between;padding: 0 40px;height: 60px;background: rgba(251,250,255,0.88);backdrop-filter: blur(16px) saturate(1.6);border-bottom: 1px solid rgba(208,190,255,0.15);}.topbar-logo {font-family: var(--font-display);font-size: 1.3rem;font-weight: 600;text-decoration: none;color: var(--ink);letter-spacing: -0.03em;}.topbar-logo span { color: var(--violet-dark); font-style: italic; }.topbar-right { display: flex; align-items: center; gap: 16px; }.topbar-save {display: none;align-items: center;gap: 6px;font-size: 0.75rem;color: var(--ink-60);}.topbar-save.visible { display: flex; }.topbar-save.saved { color: var(--emerald); }.topbar-save.saving { color: var(--ink-60); }.topbar-save.error { color: var(--danger); }.topbar-save-dot {width: 6px; height: 6px; border-radius: 50%;background: currentColor;}.topbar-save.saving .topbar-save-dot { animation: pulse 1.2s ease-in-out infinite; }.topbar-link {font-size: 0.82rem;font-weight: 500;color: var(--ink-60);text-decoration: none;transition: color 0.2s;}.topbar-link:hover { color: var(--violet-dark); }/* Bouton violet "Retour au dashboard" dans la topbar */.topbar-btn-primary {display: inline-flex;align-items: center;gap: 8px;padding: 9px 18px;background: linear-gradient(135deg, var(--violet-deep), var(--violet-dark));color: white;font-family: var(--font-body);font-size: 0.82rem;font-weight: 600;text-decoration: none;border-radius: var(--r-full);box-shadow: 0 4px 14px var(--violet-glow);transition: all 0.25s var(--ease);white-space: nowrap;}.topbar-btn-primary:hover {transform: translateY(-1px);box-shadow: 0 8px 22px rgba(139, 111, 192, 0.35);background: linear-gradient(135deg, var(--violet-dark), var(--violet-deep));}.topbar-btn-primary:active {transform: translateY(0);box-shadow: 0 3px 10px var(--violet-glow);}.topbar-btn-primary svg {flex-shrink: 0;}.topbar-avatar {width: 34px;height: 34px;border-radius: 50%;background: linear-gradient(135deg, var(--violet), var(--violet-deep));color: white;display: flex;align-items: center;justify-content: center;font-weight: 700;font-size: 0.72rem;}/* PAGE CONTAINER */.page-container {min-height: 100vh;padding: 110px 100px 60px;position: relative;}.page-inner {max-width: 820px;margin: 0 auto;position: relative;}/* DECO BLOBS */.page-container::before,.page-container::after {content: '';position: fixed;border-radius: 50%;pointer-events: none;opacity: 0.4;z-index: 0;}.page-container::before {width: 520px; height: 520px;top: -120px; right: -160px;background: radial-gradient(circle, var(--violet-soft), transparent 70%);}.page-container::after {width: 400px; height: 400px;bottom: -80px; left: -120px;background: radial-gradient(circle, var(--ocean-soft), transparent 70%);}/* STEPS PROGRESS (top of page under topbar) */.tunnel-progress {position: fixed;top: 60px;left: 0; right: 0;z-index: 80;background: rgba(251,250,255,0.85);backdrop-filter: blur(12px);border-bottom: 1px solid rgba(208,190,255,0.1);padding: 16px 40px;}.tunnel-progress-inner {max-width: 820px;margin: 0 auto;display: flex;align-items: center;gap: 16px;}.tp-step {display: flex;align-items: center;gap: 10px;flex: 1;opacity: 0.4;transition: opacity 0.3s;}.tp-step.active, .tp-step.done { opacity: 1; }.tp-dot {width: 26px; height: 26px;border-radius: 50%;background: var(--ink-10);color: var(--ink-40);display: flex;align-items: center;justify-content: center;font-size: 0.72rem;font-weight: 700;flex-shrink: 0;transition: all 0.3s var(--ease-spring);}.tp-step.active .tp-dot {background: var(--violet-deep);color: white;box-shadow: 0 0 0 4px var(--violet-glow);}.tp-step.done .tp-dot {background: var(--emerald);color: white;}.tp-label {font-size: 0.78rem;font-weight: 500;color: var(--ink-60);white-space: nowrap;}.tp-step.active .tp-label { color: var(--violet-dark); font-weight: 600; }.tp-step.done .tp-label { color: var(--emerald); }.tp-line {flex: 1;height: 2px;background: var(--ink-10);position: relative;overflow: hidden;min-width: 20px;}.tp-line::after {content: '';position: absolute;top: 0; left: 0;width: 100%;height: 100%;background: var(--emerald);transform: scaleX(0);transform-origin: left;transition: transform 0.5s var(--ease);}.tp-line.done::after { transform: scaleX(1); }.page-container { padding-top: 150px; }/* BADGE / TITLES */.badge {display: inline-flex;align-items: center;gap: 8px;padding: 6px 16px;border-radius: var(--r-full);font-size: 0.7rem;font-weight: 600;text-transform: uppercase;letter-spacing: 0.1em;margin-bottom: 24px;background: var(--violet-soft);color: var(--violet-dark);}.badge-num {width: 20px; height: 20px;border-radius: 50%;display: flex;align-items: center;justify-content: center;font-size: 0.65rem;font-weight: 700;background: var(--violet-deep);color: white;}.stitle {font-family: var(--font-display);font-size: clamp(2rem, 4vw, 3rem);font-weight: 400;line-height: 1.15;letter-spacing: -0.03em;margin-bottom: 14px;}.stitle em { font-style: italic; color: var(--violet-dark); }.sdesc {font-size: 1.05rem;color: var(--ink-60);max-width: 540px;margin-bottom: 40px;font-weight: 300;}/* FORM ELEMENTS (communs) */.fg { display: grid; gap: 16px; }.fg.c2 { grid-template-columns: 1fr 1fr; }.fg.c3 { grid-template-columns: 1fr 1fr 1fr; }.flabel {display: block;font-size: 0.72rem;font-weight: 600;text-transform: uppercase;letter-spacing: 0.08em;color: var(--ink-40);margin-bottom: 8px;}.flabel .req { color: var(--rose); margin-left: 2px; }.flabel .opt { opacity: 0.5; text-transform: none; letter-spacing: 0; }.finput, .ftextarea, .fselect {width: 100%;padding: 13px 16px;border: 1.5px solid var(--ink-10);border-radius: var(--r-md);font-family: var(--font-body);font-size: 0.92rem;color: var(--ink);background: var(--white);outline: none;transition: border-color 0.25s, box-shadow 0.25s;}.finput:focus, .ftextarea:focus, .fselect:focus {border-color: var(--violet);box-shadow: 0 0 0 4px var(--violet-glow);}.finput.invalid, .tag-input-wrap.invalid { border-color: var(--danger); background: var(--danger-soft); }.finput::placeholder, .ftextarea::placeholder { color: var(--ink-20); }.ftextarea {resize: vertical;min-height: 130px;line-height: 1.7;}.fselect {appearance: none;-webkit-appearance: none;background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239090A7' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>");background-repeat: no-repeat;background-position: right 14px center;background-size: 16px;padding-right: 40px;cursor: pointer;}.field { margin-bottom: 14px; }.field:last-child { margin-bottom: 0; }.section-label {font-size: 0.72rem;font-weight: 600;text-transform: uppercase;letter-spacing: 0.08em;color: var(--ink-40);margin: 32px 0 14px;}.section-label:first-of-type { margin-top: 0; }/* BUTTONS */.btn-primary, .btn-secondary, .btn-ghost {display: inline-flex;align-items: center;justify-content: center;gap: 10px;padding: 15px 32px;border: none;border-radius: var(--r-full);font-family: var(--font-body);font-size: 0.95rem;font-weight: 600;cursor: pointer;transition: all 0.3s var(--ease);text-decoration: none;}.btn-primary {background: linear-gradient(135deg, var(--violet), var(--violet-deep));color: white;box-shadow: 0 6px 24px var(--violet-glow);}.btn-primary:hover {transform: translateY(-2px);box-shadow: 0 10px 32px rgba(208,190,255,0.5);}.btn-primary:disabled {opacity: 0.5;cursor: not-allowed;transform: none;}.btn-secondary {background: var(--white);color: var(--ink-80);border: 1.5px solid var(--ink-10);}.btn-secondary:hover { border-color: var(--ink-20); background: var(--ink-05); }.btn-ghost {background: transparent;color: var(--ink-60);padding: 10px 18px;}.btn-ghost:hover { color: var(--ink); background: var(--ink-05); }.cta-row {display: flex;gap: 12px;margin-top: 36px;flex-wrap: wrap;}@keyframes pulse {0%, 100% { opacity: .6; transform: scale(.9); }50% { opacity: 1; transform: scale(1.1); }}@keyframes fadeUp {from { opacity: 0; transform: translateY(20px); }to { opacity: 1; transform: translateY(0); }}.fade-up { animation: fadeUp 0.6s var(--ease) both; }.fade-up.d1 { animation-delay: 0.08s; }.fade-up.d2 { animation-delay: 0.16s; }.fade-up.d3 { animation-delay: 0.24s; }.fade-up.d4 { animation-delay: 0.32s; }/* ═══════════════════════════════════LOADER OVERLAY═══════════════════════════════════ */.loader-overlay {position: fixed;inset: 0;z-index: 9998;background: rgba(251,250,255,0.92);backdrop-filter: blur(8px);display: flex;flex-direction: column;align-items: center;justify-content: center;gap: 24px;opacity: 1;transition: opacity 0.4s var(--ease);pointer-events: all;}.loader-overlay.hidden {opacity: 0;pointer-events: none;}.loader-orb {width: 84px;height: 84px;position: relative;}.loader-ring {position: absolute;inset: 0;border-radius: 50%;border: 2.5px solid var(--violet-soft);}.loader-ring::after {content: '';position: absolute;inset: -2.5px;border-radius: 50%;border: 3px solid transparent;border-top-color: var(--violet-deep);animation: spin 1.2s linear infinite;}.loader-ring:nth-child(2) { inset: 14px; }.loader-ring:nth-child(2)::after {border-top-color: var(--violet);animation-duration: 1.8s;animation-direction: reverse;}.loader-core {position: absolute;inset: 26px;border-radius: 50%;background: var(--violet-soft);display: flex;align-items: center;justify-content: center;}.loader-core svg {color: var(--violet-deep);animation: pulse 2.2s ease-in-out infinite;}.loader-text {font-family: var(--font-display);font-size: 1.05rem;font-weight: 500;color: var(--ink-80);letter-spacing: -0.01em;}.loader-sub {font-size: 0.82rem;color: var(--ink-40);font-weight: 400;max-width: 280px;text-align: center;margin-top: -16px;}@keyframes spin { to { transform: rotate(360deg); } }@media (max-width: 760px) {.page-container { padding: 150px 20px 40px; }.topbar { padding: 0 20px; }.tunnel-progress { padding: 14px 20px; }.tp-label { display: none; }.fg.c2, .fg.c3 { grid-template-columns: 1fr; }}{% block extra_css %}{% endblock %}</style></head><body><div class="loader-overlay{% if not show_loader|default(false) %} hidden{% endif %}" id="loaderOverlay"><div class="loader-orb"><div class="loader-ring"></div><div class="loader-ring"></div><div class="loader-core"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></div></div><div class="loader-text" id="loaderText">{{ 'generate2.loader.default_title'|trans({}, 'whr-public') }}</div><div class="loader-sub" id="loaderSub">{{ 'generate2.loader.default_sub'|trans({}, 'whr-public') }}</div></div><header class="topbar">{# Mode public : logo cliquable vers la homepage WhileResume (pas de dashboard puisque pas de compte) #}<a href="{{ path('locale_whileresume_homepage') }}"><img src="/uploads/logo.png" style="margin-top:10px;" alt="WhileResume" /></a><div class="topbar-right"><div class="topbar-save" id="saveIndicator"><div class="topbar-save-dot"></div><span id="saveText">{{ 'generate2.topbar.saved'|trans({}, 'whr-public') }}</span></div>{# Mode public : pas de lien dashboard ni d'avatar — visiteur anonyme #}{# On garde un placeholder discret indiquant le temps restant pour rappeler les 48h #}{% if draft is defined and draft and draft.expiresAt %}<span class="topbar-link" id="cvpub-expires" data-expires-at="{{ draft.expiresAt|date('c') }}" style="font-size:0.78rem;color:var(--ink-40)">{# Le JS calculera le temps restant et l'affichera ici #}</span>{% endif %}<a href="{% if app.request.locale == "fr" %}{{ path('locale_whileresume_homepage',{'_locale':app.request.locale}) }}{% else %}{{ path('whileresume_homepage') }}{% endif %}" class="topbar-btn-primary"><svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24"><path d="M3 12l9-9 9 9M5 10v10a1 1 0 001 1h4v-6h4v6h4a1 1 0 001-1V10"/></svg>{{ 'generate2.topbar.backhomepage'|trans({}, 'whr-candidates') }}</a></div></header>{% block progress %}<div class="tunnel-progress"><div class="tunnel-progress-inner"><div class="tp-step {{ current_step == 1 ? 'active' : (current_step > 1 ? 'done' : '') }}"><div class="tp-dot">{{ current_step > 1 ? '✓' : '1' }}</div><span class="tp-label">{{ 'generate2.progress.step_choice'|trans({}, 'whr-public') }}</span></div><div class="tp-line {{ current_step > 1 ? 'done' : '' }}"></div><div class="tp-step {{ current_step == 2 ? 'active' : (current_step > 2 ? 'done' : '') }}"><div class="tp-dot">{{ current_step > 2 ? '✓' : '2' }}</div><span class="tp-label">{{ 'generate2.progress.step_theme'|trans({}, 'whr-public') }}</span></div><div class="tp-line {{ current_step > 2 ? 'done' : '' }}"></div><div class="tp-step {{ current_step == 3 ? 'active' : (current_step > 3 ? 'done' : '') }}"><div class="tp-dot">{{ current_step > 3 ? '✓' : '3' }}</div><span class="tp-label">{{ 'generate2.progress.step_info'|trans({}, 'whr-public') }}</span></div></div></div>{% endblock %}<main class="page-container"><div class="page-inner">{% block body %}{% endblock %}</div></main>{# ════════════════════════════════════════════════════════════════════════════CONTEXT POUR LE JS (data-attributes)════════════════════════════════════════════════════════════════════════════Approche imperméable aux caractères de contrôle / encodage cassé :chaque trad est un attribut HTML séparé, escapé par Twig via |e('html_attr').Si UNE trad est corrompue, ça affecte SEULEMENT cet attribut, pas le reste.#}{# Détecte si la requête courante utilise une route avec préfixe locale.Si oui, on génère TOUTES les URLs des routes du tunnel avec préfixe pourque la navigation conserve /fr/, /de/, etc. dans l'URL. #}{% set _current_route = app.request.attributes.get('_route') %}{% set _has_locale_prefix = _current_route starts with 'locale_' %}<div id="__cv_ctx_root__" style="display:none"data-api-base="{{ api_base|default('/api/cv-public')|e('html_attr') }}"data-jwt="{{ jwt|default('')|e('html_attr') }}"data-locale="{{ app.request.locale|default('fr')|e('html_attr') }}"data-is-public="{{ is_public|default(true) ? '1' : '0' }}"data-public-slug="{{ public_slug|default('')|e('html_attr') }}"data-user-first-name="{{ (user_data.first_name|default(''))|e('html_attr') }}"data-user-last-name="{{ (user_data.last_name|default(''))|e('html_attr') }}"data-user-email="{{ (user_data.email|default(''))|e('html_attr') }}"data-route-choice="{{ public_slug|default('') ? (_has_locale_prefix ? path('locale_cv_public_choice', { slug: public_slug, _locale: app.request.locale }) : path('cv_public_choice', { slug: public_slug }))|e('html_attr') : '' }}"data-route-theme="{{ public_slug|default('') ? (_has_locale_prefix ? path('locale_cv_public_theme', { slug: public_slug, _locale: app.request.locale }) : path('cv_public_theme', { slug: public_slug }))|e('html_attr') : '' }}"data-route-form="{{ public_slug|default('') ? (_has_locale_prefix ? path('locale_cv_public_form', { slug: public_slug, _locale: app.request.locale }) : path('cv_public_form', { slug: public_slug }))|e('html_attr') : '' }}"data-route-register="{{ public_slug|default('') ? (_has_locale_prefix ? path('locale_cv_public_register', { slug: public_slug, _locale: app.request.locale }) : path('cv_public_register', { slug: public_slug }))|e('html_attr') : '' }}"data-route-generate="{{ public_slug|default('') ? (_has_locale_prefix ? path('locale_cv_public_generate', { slug: public_slug, _locale: app.request.locale }) : path('cv_public_generate', { slug: public_slug }))|e('html_attr') : '' }}"data-i18n-saving="{{ 'generate2.topbar.saving'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-saved="{{ 'generate2.topbar.saved'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-save-error="{{ 'generate2.topbar.save_error'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-load-error="{{ 'generate2.form.error.load'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-analyze-error="{{ 'generate2.form.analyzing.error'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-generate-error="{{ 'generate2.form.error.generate'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-missing-fields="{{ 'generate2.form.error.missing_fields'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-generate-first="{{ 'generate2.form.error.generate_first'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-preview-loading="{{ 'generate2.done.preview_loading'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-preview-unavailable="{{ 'generate2.done.preview_unavailable'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-preview-fallback="{{ 'generate2.done.preview_fallback'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-download-error="{{ 'generate2.form.error.download'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-file-too-large="{{ 'generate2.form.upload.error_too_large'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-file-must-be-pdf="{{ 'generate2.form.upload.error_must_be_pdf'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-generating="{{ 'generate2.loader.generate_title'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-generating-hint="{{ 'generate2.loader.generate_sub'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-generate-cta="{{ 'generate2.form.cta_generate'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-generating-cta="{{ 'generate2.form.cta_generating'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-analyze="{{ 'generate2.loader.analyze_done_title'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-analyze-sub="{{ 'generate2.loader.analyze_done_sub'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-prefs="{{ 'generate2.loader.prefs_title'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-prefs-sub="{{ 'generate2.loader.prefs_sub'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-form-scratch="{{ 'generate2.loader.form_scratch_title'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-form-scratch-sub="{{ 'generate2.loader.form_scratch_sub'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-form-edit="{{ 'generate2.loader.form_edit_title'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-form-edit-sub="{{ 'generate2.loader.form_edit_sub'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-form-analyze="{{ 'generate2.loader.form_analyze_title'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-loader-form-analyze-sub="{{ 'generate2.loader.form_analyze_sub'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-network-error="{{ 'generate2.form.error.network'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-title-language="{{ 'generate2.form.item_title.language'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-title-experience="{{ 'generate2.form.item_title.experience'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-title-degree="{{ 'generate2.form.item_title.degree'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-title-certification="{{ 'generate2.form.item_title.certification'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-title-project="{{ 'generate2.form.item_title.project'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-language="{{ 'generate2.form.label.language'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-level="{{ 'generate2.form.label.level'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-position="{{ 'generate2.form.label.position'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-company="{{ 'generate2.form.label.company'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-start-date="{{ 'generate2.form.label.start_date'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-end-date="{{ 'generate2.form.label.end_date'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-city="{{ 'generate2.form.label.city'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-contract-type="{{ 'generate2.form.label.contract_type'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-description="{{ 'generate2.form.label.description'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-degree="{{ 'generate2.form.label.degree'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-school="{{ 'generate2.form.label.school'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-field="{{ 'generate2.form.label.field'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-cert-name="{{ 'generate2.form.label.cert_name'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-issuer="{{ 'generate2.form.label.issuer'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-year="{{ 'generate2.form.label.year'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-label-project-name="{{ 'generate2.form.label.project_name'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-lang-name="{{ 'generate2.form.placeholder.lang_name'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-lang-level="{{ 'generate2.form.placeholder.lang_level'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-position="{{ 'generate2.form.placeholder.position'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-company="{{ 'generate2.form.placeholder.company'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-present="{{ 'generate2.form.placeholder.end_date_present'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-choose="{{ 'generate2.common.choose'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-exp-desc="{{ 'generate2.form.placeholder.exp_desc'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-degree="{{ 'generate2.form.placeholder.degree'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-school="{{ 'generate2.form.placeholder.school'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-field="{{ 'generate2.form.placeholder.field'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-cert-name="{{ 'generate2.form.placeholder.cert_name'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-issuer="{{ 'generate2.form.placeholder.issuer'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-project-name="{{ 'generate2.form.placeholder.project_name'|trans({}, 'whr-public')|e('html_attr') }}"data-i18n-ph-project-desc="{{ 'generate2.form.placeholder.project_desc'|trans({}, 'whr-public')|e('html_attr') }}"></div><script>/*** Construction de window.__CV_CTX__ depuis les data-attributes du div root.* Approche imperméable aux caractères qui cassent : pas de json_encode,* pas de JSON inline, juste des attributs HTML standards.*/(function () {const FALLBACK = {apiBase: '/api/cv-public',jwt: null,locale: 'fr',user: { first_name: '', last_name: '', email: '' },isPublic: true,publicSlug: '',routes: { choice: '', theme: '', form: '', register: '', generate: '' },i18n: {}};const root = document.getElementById('__cv_ctx_root__');if (!root) {console.error('[CV] __cv_ctx_root__ introuvable, fallback utilisé');window.__CV_CTX__ = FALLBACK;return;}const ds = root.dataset;// Construction de l'objet i18n depuis tous les data-i18n-* présents// Le dataset HTML5 fait déjà la conversion data-i18n-save-error -> i18nSaveErrorconst i18n = {};Object.keys(ds).forEach(key => {if (!key.startsWith('i18n')) return;const trKey = key.charAt(4).toLowerCase() + key.slice(5);i18n[trKey] = ds[key] || '';});window.__CV_CTX__ = {apiBase: ds.apiBase || FALLBACK.apiBase,jwt: ds.jwt || null,locale: ds.locale || FALLBACK.locale,isPublic: ds.isPublic === '1',publicSlug: ds.publicSlug || '',user: {first_name: ds.userFirstName || '',last_name: ds.userLastName || '',email: ds.userEmail || ''},routes: {choice: ds.routeChoice || '',theme: ds.routeTheme || '',form: ds.routeForm || '',register: ds.routeRegister || '',generate: ds.routeGenerate || ''},i18n: i18n};})();/*** MODE PUBLIC : pas de JWT à envoyer.*/window.authHeaders = function () {return { 'Content-Type': 'application/json' };};/*** Ajoute le slug au body d'une requête API publique.*/window.publicBody = function (payload) {return Object.assign({}, payload || {}, {slug: window.__CV_CTX__.publicSlug});};/*** Ajoute le slug en query string pour les GET.*/window.publicQuery = function () {const slug = window.__CV_CTX__.publicSlug;return slug ? '?slug=' + encodeURIComponent(slug) : '';};window.setSaveStatus = function (status) {const el = document.getElementById('saveIndicator');const txt = document.getElementById('saveText');if (!el) return;el.classList.remove('saving', 'saved', 'error');el.classList.add(status, 'visible');if (txt) {const i18n = window.__CV_CTX__.i18n || {};txt.textContent = status === 'saving' ? (i18n.saving || 'Enregistrement…'): status === 'saved' ? (i18n.saved || 'Enregistré'): (i18n.saveError || 'Erreur');}};window.cvLoader = {show(text, sub) {const o = document.getElementById('loaderOverlay');if (!o) return;const t = document.getElementById('loaderText');const s = document.getElementById('loaderSub');if (t && text) t.textContent = text;if (s && sub !== undefined) s.textContent = sub || '';o.classList.remove('hidden');},hide() {const o = document.getElementById('loaderOverlay');if (o) o.classList.add('hidden');}};/*** Filet de sécurité : si le loader reste visible 5s, fermeture forcée.*/window.addEventListener('load', () => {setTimeout(() => {const o = document.getElementById('loaderOverlay');if (o && !o.classList.contains('hidden')) {console.warn('[CV] Loader bloqué après 5s, fermeture forcée');o.classList.add('hidden');}}, 5000);});/*** Countdown 48h dans la topbar.*/(function () {const el = document.getElementById('cvpub-expires');if (!el || !el.dataset.expiresAt) return;const expiresAt = new Date(el.dataset.expiresAt);function update() {const now = new Date();const ms = expiresAt - now;if (ms <= 0) {el.textContent = 'Brouillon expiré';el.style.color = 'var(--danger)';return;}const h = Math.floor(ms / 3600000);const m = Math.floor((ms % 3600000) / 60000);el.textContent = h >= 1? 'Expire dans ' + h + 'h' + (m > 0 ? ' ' + m + 'min' : ''): 'Expire dans ' + m + 'min';if (h < 2) el.style.color = 'var(--amber)';if (h < 1) el.style.color = 'var(--danger)';}update();setInterval(update, 60000);})();</script>{% block body_js %}{% endblock %}</body></html>