Przejdź do głównej treści

Generator CV Online

Grafika SVG CV

Stwórz swoje profesjonalne CV w formacie PDF w kilka minut!
Dzięki prostemu kreatorowi możesz:

  • Wprowadzić dane osobowe, doświadczenie, edukację i umiejętności
  • Wybrać nowoczesny szablon dopasowany do Twojego stylu
  • Oglądać podgląd CV na bieżąco podczas edycji
  • Pobierać gotowe CV do formatu PDF jednym kliknięciem

Polityka prywatności

Twoje dane są wprowadzane tylko w przeglądarce – nie są wysyłane na żadne serwery i nie są nigdzie przechowywane.
Masz pełną kontrolę nad swoimi informacjami.

Wypróbuj teraz i stwórz CV, które zrobi wrażenie!

Dane CV

Zdjęcie zostanie osadzone w podglądzie CV.

Doświadczenie


Edukacja


Instrukcja: wypełnij pola, dodaj doświadczenia i edukację. Kliknij "Aktualizuj podgląd", a potem "Generuj PDF".

Podgląd CV

Moje zdjęcie CV
PDF: format A4, borderless wygenerowany po stronie klienta.

Kod po stronie przeglądarki

  <style>
    .cv-preview {
      width: 190mm;
      min-height: 270mm;
      padding: 24px;
      background: white;
      color: #222;
      box-shadow: 0 0 8px rgba(0,0,0,0.08);
      margin: 0 auto;
    }
    .photo {
      width: 96px;
      height: 96px;
      object-fit: cover;
      border-radius: 8px;
      border: 1px solid #ddd;
    }
    .section-title { font-weight: 700; margin-bottom: 8px; }
    .muted { color: #666; }
    .template-compact .cv-header { display:flex; gap:16px; align-items:center; }
    .template-modern .cv-header { border-bottom:4px solid #0d6efd; padding-bottom:12px; margin-bottom:12px }
    @media print {
      .page-break { page-break-before: always; }
    }
  </style>
<div id="app">
    <div class="row gy-3">
      <div class="col-lg-5">
        <div class="card">
          <div class="card-body">
            <h3 class="h4 card-title">Dane CV</h3>

            <form id="cvForm" onsubmit="return false;">
              <div class="mb-2">
                <label for="fullName" class="form-label">Imię i nazwisko</label>
                <input id="fullName" class="form-control" placeholder="Jan Kowalski">
              </div>

              <div class="mb-2 row">
                <div class="col-8">
                  <label for="title" class="form-label">Stanowisko / nagłówek</label>
                  <input id="title" class="form-control" placeholder="Frontend Developer">
                </div>
                <div class="col-4">
                  <label for="age" class="form-label">Wiek</label>
                  <input id="age" type="number" min="0" class="form-control" placeholder="30">
                </div>
              </div>

              <div class="mb-2">
                <label for="contact" class="form-label">Kontakt (email / telefon / miasto)</label>
                <input id="contact" class="form-control" placeholder="jan@example.com • +48 600 000 000 • Warszawa">
              </div>

              <div class="mb-2">
                <label for="profile" class="form-label">Profil — krótki opis</label>
                <textarea id="profile" rows="3" class="form-control" placeholder="Krótki akapit o Tobie..."></textarea>
              </div>

              <div class="mb-2">
                <label for="photoInput" class="form-label">Zdjęcie (opcjonalnie)</label>
                <input id="photoInput" type="file" accept="image/*" class="form-control">
                <div class="form-text">Zdjęcie zostanie osadzone w podglądzie CV.</div>
              </div>

              <hr>
              <div class="d-flex justify-content-between align-items-center mb-2">
                <h4 class="h6 m-0">Doświadczenie</h4>
                <button class="btn btn-sm btn-primary" id="addExpBtn">Dodaj pozycję</button>
              </div>
              <div id="experienceList"></div>

              <hr>
              <div class="d-flex justify-content-between align-items-center mb-2">
                <h4 class="h6 m-0">Edukacja</h4>
                <button class="btn btn-sm btn-primary" id="addEduBtn">Dodaj pozycję</button>
              </div>
              <div id="educationList"></div>

              <hr>
              <div class="mb-2">
                <label for="skills" class="form-label">Umiejętności (oddziel przecinkami)</label>
                <input id="skills" class="form-control" placeholder="JavaScript, HTML, CSS, React">
              </div>

              <div class="mb-2">
                <label for="templateSelect" class="form-label">Szablon</label>
                <select id="templateSelect" class="form-select">
                  <option value="modern">Nowoczesny (niebieski akcent)</option>
                  <option value="compact">Kompaktowy</option>
                </select>
              </div>

              <div class="d-grid gap-2">
                <button id="updatePreview" class="btn btn-primary">Aktualizuj podgląd</button>
                <button id="generatePDF" class="btn btn-success">Generuj PDF</button>
              </div>

            </form>
          </div>
        </div>

        <div class="mt-3 text-muted small">Instrukcja: wypełnij pola, dodaj doświadczenia i edukację. Kliknij "Aktualizuj podgląd", a potem "Generuj PDF".</div>
      </div>

      <div class="col-lg-7">
        <div class="card">
          <div class="card-body">
            <h3 class="h5 card-title">Podgląd CV</h3>
            <div id="previewWrapper" class="border bg-white p-3" style="overflow:auto;">
              <div id="cvPreview" class="cv-preview template-modern">
                <div class="cv-header d-flex justify-content-between align-items-start">
                  <div id="cvHeaderText"></div>
                  <img id="cvPhoto" class="photo d-none" alt="Moje zdjęcie CV">
                </div>
                <div id="cvContent"></div>
              </div>
            </div>
          </div>
        </div>
        <div class="mt-2 text-end">
          <small class="text-muted">PDF: format A4, borderless wygenerowany po stronie klienta.</small>
        </div>
      </div>
    </div>    
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js"></script> 
  <script>
    const experienceList = document.getElementById('experienceList');
    const educationList = document.getElementById('educationList');
    const photoInput = document.getElementById('photoInput');
    let photoDataUrl = null;

    addExperience();
    addEducation();

    document.getElementById('addExpBtn').addEventListener('click', (e)=>{ e.preventDefault(); addExperience(); });
    document.getElementById('addEduBtn').addEventListener('click', (e)=>{ e.preventDefault(); addEducation(); });
    document.getElementById('updatePreview').addEventListener('click', (e)=>{ e.preventDefault(); renderPreview(); });
    document.getElementById('generatePDF').addEventListener('click', (e)=>{ e.preventDefault(); generatePDF(); });

    photoInput.addEventListener('change', function(){
      const f = this.files[0];
      if (!f) {
        photoDataUrl = null;
        document.getElementById('cvPhoto').classList.add('d-none');
        return;
      }
      const reader = new FileReader();
      reader.onload = function(ev){
        photoDataUrl = ev.target.result;
        const img = document.getElementById('cvPhoto');
        img.src = photoDataUrl;
        img.classList.remove('d-none');
      };
      reader.readAsDataURL(f);
    });

    function createFieldGroup(html){
      const div = document.createElement('div');
      div.className = 'mb-2 border rounded p-2';
      div.innerHTML = html;
      return div;
    }

    function addExperience(data={position:'',company:'',from:'',to:'',description:''}){
      const idx = experienceList.children.length;
      const html = `
        <div class="d-flex justify-content-between mb-2">
          <strong>Pozycja ${idx+1}</strong>
          <div>
            <button class="btn btn-sm btn-danger remove-exp">Usuń</button>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 mb-2"><input class="form-control exp-position" placeholder="Stanowisko" aria-label="Nazwa stanowiska" value="${escapeHtml(data.position)}"></div>
          <div class="col-md-6 mb-2"><input class="form-control exp-company" placeholder="Firma" aria-label="Nazwa firmy" value="${escapeHtml(data.company)}"></div>
          <div class="col-md-4 mb-2"><input class="form-control exp-from" placeholder="Od (np. 2021)" aria-label="Od dnia" value="${escapeHtml(data.from)}"></div>
          <div class="col-md-4 mb-2"><input class="form-control exp-to" placeholder="Do (np. 2023 / obecnie)" aria-label="Do dnia" value="${escapeHtml(data.to)}"></div>
          <div class="col-12 mb-2"><textarea class="form-control exp-desc" rows="2" placeholder="Krótki opis obowiązków" aria-label="Opis obowiązków">${escapeHtml(data.description)}</textarea></div>
        </div>
      `;
      const node = createFieldGroup(html);
      node.querySelector('.remove-exp').addEventListener('click', ()=>{ node.remove(); });
      experienceList.appendChild(node);
    }

    function addEducation(data={degree:'',institution:'',from:'',to:''}){
      const idx = educationList.children.length;
      const html = `
        <div class="d-flex justify-content-between mb-2">
          <strong>Edukacja ${idx+1}</strong>
          <div>
            <button class="btn btn-sm btn-danger remove-edu">Usuń</button>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 mb-2"><input class="form-control edu-degree" placeholder="Stopień / kierunek" aria-label="Stopień" value="${escapeHtml(data.degree)}"></div>
          <div class="col-md-6 mb-2"><input class="form-control edu-inst" placeholder="Uczelnia / szkoła" aria-label="Uczelnia" value="${escapeHtml(data.institution)}"></div>
          <div class="col-md-4 mb-2"><input class="form-control edu-from" placeholder="Od" aria-label="Od dnia" value="${escapeHtml(data.from)}"></div>
          <div class="col-md-4 mb-2"><input class="form-control edu-to" placeholder="Do" aria-label="Do dnia" value="${escapeHtml(data.to)}"></div>
        </div>
      `;
      const node = createFieldGroup(html);
      node.querySelector('.remove-edu').addEventListener('click', ()=>{ node.remove(); });
      educationList.appendChild(node);
    }

    function escapeHtml(s){
      if (!s) return '';
      return String(s).replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;');
    }

    function collectForm(){
      const data = {};
      data.fullName = document.getElementById('fullName').value.trim();
      data.title = document.getElementById('title').value.trim();
      data.age = document.getElementById('age').value.trim();
      data.contact = document.getElementById('contact').value.trim();
      data.profile = document.getElementById('profile').value.trim();
      data.skills = document.getElementById('skills').value.trim().split(',').map(s=>s.trim()).filter(Boolean);
      data.template = document.getElementById('templateSelect').value;
      data.experience = Array.from(experienceList.children).map(group => ({
        position: group.querySelector('.exp-position')?.value || '',
        company: group.querySelector('.exp-company')?.value || '',
        from: group.querySelector('.exp-from')?.value || '',
        to: group.querySelector('.exp-to')?.value || '',
        description: group.querySelector('.exp-desc')?.value || ''
      }));
      data.education = Array.from(educationList.children).map(group => ({
        degree: group.querySelector('.edu-degree')?.value || '',
        institution: group.querySelector('.edu-inst')?.value || '',
        from: group.querySelector('.edu-from')?.value || '',
        to: group.querySelector('.edu-to')?.value || ''
      }));
      data.photo = photoDataUrl;
      return data;
    }

    function renderPreview(){
      const d = collectForm();
      const cvPreview = document.getElementById('cvPreview');
      cvPreview.className = 'cv-preview ' + (d.template === 'compact' ? 'template-compact' : 'template-modern');

      const skillsHtml = d.skills.length ? `<div class="mb-2"><strong class="section-title">Umiejętności</strong><div>${d.skills.map(s=>`<span class="badge bg-light text-dark me-1">${escapeHtml(s)}</span>`).join('')}</div></div>` : '';
      const expHtml = d.experience.filter(e=> e.position || e.company).map(e=>`
        <div class="mb-2">
          <div><strong>${escapeHtml(e.position)}</strong> <span class="muted">— ${escapeHtml(e.company)}</span></div>
          <div class="muted small">${escapeHtml(e.from)} — ${escapeHtml(e.to)}</div>
          <div class="mt-1">${escapeHtml(e.description)}</div>
        </div>
      `).join('');
      const eduHtml = d.education.filter(e=> e.degree || e.institution).map(e=>`
        <div class="mb-2">
          <div><strong>${escapeHtml(e.degree)}</strong></div>
          <div class="muted small">${escapeHtml(e.institution)} • ${escapeHtml(e.from)} — ${escapeHtml(e.to)}</div>
        </div>
      `).join('');

      const headerText = `
        <div style="font-size:20px;font-weight:700">${escapeHtml(d.fullName || 'Imię Nazwisko')}</div>
        <div class="muted">${escapeHtml(d.title || '')} ${d.age ? '• ' + escapeHtml(d.age) : ''}</div>
        <div class="muted small mt-2">${escapeHtml(d.contact || '')}</div>
      `;
      document.getElementById('cvHeaderText').innerHTML = headerText;

      if (!d.photo) {
        document.getElementById('cvPhoto').classList.add('d-none');
      } else {
        const img = document.getElementById('cvPhoto');
        img.src = d.photo;
        img.classList.remove('d-none');
      }

      document.getElementById('cvContent').innerHTML = `
        ${d.profile ? `<div class="mb-3"><strong class="section-title">Profil</strong><div>${escapeHtml(d.profile)}</div></div>` : ''}
        ${expHtml ? `<div class="mb-3"><strong class="section-title">Doświadczenie</strong>${expHtml}</div>` : ''}
        ${eduHtml ? `<div class="mb-3"><strong class="section-title">Edukacja</strong>${eduHtml}</div>` : ''}
        ${skillsHtml}
      `;
    }

    renderPreview();

    function generatePDF(){
      const filename = (document.getElementById('fullName').value.trim() || 'CV') + '.pdf';
      const opt = {
        margin:       [8, 8],
        filename:     filename,
        image:        { type: 'jpeg', quality: 0.98 },
        html2canvas:  { scale: 2, useCORS: true, logging: false },
        jsPDF:        { unit: 'mm', format: 'a4', orientation: 'portrait' }
      };
      renderPreview();
      const prev = document.getElementById('cvPreview');
      prev.style.boxShadow = 'none';
      html2pdf().set(opt).from(prev).save().finally(()=>{ prev.style.boxShadow = ''; });
    }
  </script>

Kod po stronie serwera

Informacja

Aplikacja nie korzysta z kodu po stronie serwera.

Tagi

JavaScript

Dziękujemy!
()

Informacja o cookies

Moja strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania. Nie używam ciasteczek w celach marketingowych ani analitycznych. Korzystając z mojej strony, wyrażasz zgodę na stosowanie tych plików. Możesz dowiedzieć się więcej w mojej polityce prywatności.