final
This commit is contained in:
parent
62f408d236
commit
dad190e7dd
200
index.php
200
index.php
@ -1,150 +1,68 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<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=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Testul de Sănătate (HealthType)</title>
|
||||
<link rel="stylesheet" href="styles.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<main id="app" class="app" aria-live="polite">
|
||||
<!-- Start Screen -->
|
||||
<section id="start-screen" class="screen screen--start" aria-hidden="false">
|
||||
<h1 class="title">Să începem!</h1>
|
||||
<p class="subtitle">Răspunde sincer. Compară-te cu persoane de aceeași vârstă. Pentru unele întrebări poți alege mai multe opțiuni.</p>
|
||||
<div class="sex-select" role="list">
|
||||
<button type="button" class="sex-card" data-sex="female" aria-label="Sunt Femeie">Sunt Femeie</button>
|
||||
<button type="button" class="sex-card" data-sex="male" aria-label="Sunt Bărbat">Sunt Bărbat</button>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</section>
|
||||
|
||||
<!-- Questionnaire Screen -->
|
||||
<section id="question-screen" class="screen screen--question" aria-hidden="true">
|
||||
<header class="q-header">
|
||||
<div class="progress" aria-hidden="true">
|
||||
<span id="progress-text">Întrebarea 0/0</span>
|
||||
<div class="progress-bar" aria-hidden="true">
|
||||
<div id="progress-fill" class="progress-fill" style="width:0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-top">
|
||||
<button id="btn-back" class="btn" type="button" aria-label="Înapoi">Înapoi</button>
|
||||
<button id="btn-reset" class="btn btn--ghost" type="button">Reia testul</button>
|
||||
</div>
|
||||
</header>
|
||||
<form id="question-form" novalidate>
|
||||
<div id="question-container" class="question-container" role="region" aria-live="polite">
|
||||
<!-- Questions rendered here by script.js -->
|
||||
</div>
|
||||
<div class="q-actions">
|
||||
<div id="validation-message" class="validation" aria-live="assertive"></div>
|
||||
<button id="btn-next" class="btn btn--primary" type="button">Înainte</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Result Screen -->
|
||||
<section id="result-screen" class="screen screen--result" aria-hidden="true">
|
||||
<header>
|
||||
<h2 id="result-title">Rezultat</h2>
|
||||
</header>
|
||||
<div id="result-content" class="result-content" role="region" aria-live="polite">
|
||||
<!-- Result rendered here by script.js -->
|
||||
</div>
|
||||
<div class="result-actions">
|
||||
<button id="btn-print" class="btn btn--primary">Descarcă PDF</button>
|
||||
<button id="btn-reset-result" class="btn btn--ghost" type="button">Reia testul</button>
|
||||
</div>
|
||||
|
||||
<!-- Content for printing -->
|
||||
<div class="print-summary" id="print-summary-content">
|
||||
<!-- Summary for printing is populated here -->
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
|
||||
<script src="script.js?v=<?php echo time(); ?>" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
525
script.js
Normal file
525
script.js
Normal file
@ -0,0 +1,525 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const app = document.getElementById('app');
|
||||
const screens = {
|
||||
start: document.getElementById('start-screen'),
|
||||
question: document.getElementById('question-screen'),
|
||||
result: document.getElementById('result-screen'),
|
||||
};
|
||||
|
||||
const buttons = {
|
||||
sex: document.querySelectorAll('.sex-card'),
|
||||
back: document.getElementById('btn-back'),
|
||||
reset: document.getElementById('btn-reset'),
|
||||
next: document.getElementById('btn-next'),
|
||||
print: document.getElementById('btn-print'),
|
||||
};
|
||||
|
||||
const questionElements = {
|
||||
container: document.getElementById('question-container'),
|
||||
form: document.getElementById('question-form'),
|
||||
progressText: document.getElementById('progress-text'),
|
||||
progressFill: document.getElementById('progress-fill'),
|
||||
validation: document.getElementById('validation-message'),
|
||||
};
|
||||
|
||||
const resultElements = {
|
||||
title: document.getElementById('result-title'),
|
||||
content: document.getElementById('result-content'),
|
||||
printSummary: document.getElementById('print-summary-content'),
|
||||
};
|
||||
|
||||
let state = {
|
||||
sex: null, // 'male' or 'female'
|
||||
currentQuestionIndex: 0,
|
||||
answers: [], // { questionId, answerValue }
|
||||
history: [], // previous question indices
|
||||
height: { value: null, unit: 'cm' },
|
||||
weight: { value: null, unit: 'kg' },
|
||||
};
|
||||
|
||||
const data = {
|
||||
female: [
|
||||
{ id: 'F1', text: 'Cum este menstruația ta?', help: 'Alege o singură variantă.', type: 'radio', options: [
|
||||
{ text: 'Regulată, fără dureri sau disconfort', value: 0 },
|
||||
{ text: 'Neregulată, dar fără alte simptome', value: 1 },
|
||||
{ text: 'Dureri, crampe, cheaguri, sângerări abundente', value: 2 },
|
||||
{ text: 'Am intrat la menopauză', value: 3, next: 'F2' },
|
||||
]},
|
||||
{ id: 'F1.1', text: 'Ai sindrom premenstrual (dureri de sâni, iritabilitate, balonare)?', type: 'radio', options: [
|
||||
{ text: 'Nu, niciodată', value: 0 },
|
||||
{ text: 'Uneori, dar ușor', value: 1 },
|
||||
{ text: 'Da, intens și frecvent', value: 2 },
|
||||
]},
|
||||
{ id: 'F2', text: 'Ai simptome specifice menopauzei (bufeuri, transpirații nocturne, uscăciune vaginală)?', type: 'radio', options: [
|
||||
{ text: 'Nu / Nu este cazul', value: 0 },
|
||||
{ text: 'Da, moderate', value: 1 },
|
||||
{ text: 'Da, severe și frecvente', value: 2 },
|
||||
]},
|
||||
// Common questions start here, but can be specific if needed
|
||||
...getCommonQuestions('female'),
|
||||
],
|
||||
male: [
|
||||
{ id: 'M1', text: 'Ai probleme cu urinarea (jet slab, urinări frecvente/nocturne)?', type: 'radio', options: [
|
||||
{ text: 'Nu, deloc', value: 0 },
|
||||
{ text: 'Uneori sau moderat', value: 1 },
|
||||
{ text: 'Da, frecvent și deranjant', value: 2 },
|
||||
]},
|
||||
{ id: 'M2', text: 'Ai observat o scădere a libidoului sau a performanței sexuale?', type: 'radio', options: [
|
||||
{ text: 'Nu, totul este normal', value: 0 },
|
||||
{ text: 'O scădere ușoară', value: 1 },
|
||||
{ text: 'O scădere semnificativă', value: 2 },
|
||||
]},
|
||||
...getCommonQuestions('male'),
|
||||
],
|
||||
};
|
||||
|
||||
function getCommonQuestions(sex) {
|
||||
return [
|
||||
{ id: 'C1', text: 'Cum este nivelul tău de energie pe parcursul zilei?', type: 'radio', options: [
|
||||
{ text: 'Constant și ridicat', value: 0 },
|
||||
{ text: 'Scade după-amiaza, dar îmi revin', value: 1 },
|
||||
{ text: 'Scăzut, oboseală cronică', value: 2 },
|
||||
]},
|
||||
{ id: 'C2', text: 'Cum dormi?', help: 'Poți alege mai multe variante.', type: 'checkbox', options: [
|
||||
{ text: 'Adorm greu sau mă trezesc des', value: 1 },
|
||||
{ text: 'Nu mă simt odihnit dimineața', value: 1 },
|
||||
{ text: 'Sforăi sau am apnee în somn', value: 1 },
|
||||
{ text: 'Dorm bine și mă simt odihnit (bifează doar aceasta)', value: 0, exclusive: true },
|
||||
]},
|
||||
{ id: 'C3', text: 'Cum este digestia ta?', help: 'Poți alege mai multe variante.', type: 'checkbox', options: [
|
||||
{ text: 'Balonare, gaze, disconfort abdominal', value: 1 },
|
||||
{ text: 'Constipație sau diaree frecventă', value: 1 },
|
||||
{ text: 'Arsuri la stomac, reflux acid', value: 1 },
|
||||
{ text: 'Digestie normală, fără probleme (bifează doar aceasta)', value: 0, exclusive: true },
|
||||
]},
|
||||
{ id: 'C4', text: 'Cum reacționezi la stres?', type: 'radio', options: [
|
||||
{ text: 'Calm, gestionez bine situațiile', value: 0 },
|
||||
{ text: 'Anxios, iritabil, copleșit', value: 1 },
|
||||
{ text: 'Apatie, lipsă de motivație', value: 2 },
|
||||
]},
|
||||
{ id: 'C5', text: 'Ai pofte alimentare necontrolate (dulciuri, carbohidrați, sărat)?', type: 'radio', options: [
|
||||
{ text: 'Rareori sau deloc', value: 0 },
|
||||
{ text: 'Uneori, mai ales la stres', value: 1 },
|
||||
{ text: 'Frecvent și intens', value: 2 },
|
||||
]},
|
||||
{ id: 'C6', text: 'Cum este pielea, părul și unghiile tale?', help: 'Poți alege mai multe variante.', type: 'checkbox', options: [
|
||||
{ text: 'Piele uscată, păr fragil, unghii casante', value: 1 },
|
||||
{ text: 'Acnee, ten gras, mătreață', value: 1 },
|
||||
{ text: 'Căderea excesivă a părului', value: 1 },
|
||||
{ text: 'Aspect sănătos, fără probleme (bifează doar aceasta)', value: 0, exclusive: true },
|
||||
]},
|
||||
{ id: 'C7', text: 'Ai dureri articulare sau musculare inexplicabile?', type: 'radio', options: [
|
||||
{ text: 'Nu', value: 0 },
|
||||
{ text: 'Da, ocazional', value: 1 },
|
||||
{ text: 'Da, cronice și răspândite', value: 2 },
|
||||
]},
|
||||
{ id: 'C8', text: 'Cum este starea ta de spirit generală?', type: 'radio', options: [
|
||||
{ text: 'Bună, optimistă', value: 0 },
|
||||
{ text: 'Variabilă, cu episoade de tristețe', value: 1 },
|
||||
{ text: 'Predominant tristă, apatică', value: 2 },
|
||||
]},
|
||||
{ id: 'HW', text: 'Introdu înălțimea și greutatea ta.', type: 'hw' },
|
||||
];
|
||||
}
|
||||
|
||||
const results = {
|
||||
A: {
|
||||
title: "HealthType A: Vitalitate Optimă",
|
||||
description: "Profilul tău indică un echilibru hormonal și metabolic excelent. Ai o bază solidă pe care poți construi și optimiza.",
|
||||
recommendations: [
|
||||
"Continuă cu stilul de viață sănătos: alimentație curată, mișcare regulată și management eficient al stresului.",
|
||||
"Explorează practici de longevitate: post intermitent, exerciții de respirație (Wim Hof, Buteyko), expunere la frig.",
|
||||
"Concentrează-te pe personalizarea dietei în funcție de obiectivele tale (performanță sportivă, claritate mentală).",
|
||||
],
|
||||
},
|
||||
B: {
|
||||
title: "HealthType B: Stres Adrenal și Oboseală",
|
||||
description: "Sistemul tău adrenal pare a fi suprasolicitat. Acest lucru duce la oboseală, anxietate și dificultăți de concentrare.",
|
||||
recommendations: [
|
||||
"Prioritizează somnul: 7-9 ore pe noapte, într-un mediu întunecat și răcoros.",
|
||||
"Redu consumul de cofeină și zahăr, care epuizează glandele suprarenale.",
|
||||
"Introdu tehnici de relaxare: meditație, yoga, plimbări în natură. Chiar și 10 minute pe zi fac o diferență.",
|
||||
"Consideră suplimente adaptogene precum Ashwagandha sau Rhodiola, după consultarea unui specialist.",
|
||||
],
|
||||
},
|
||||
C: {
|
||||
title: "HealthType C: Dezechilibru Glicemic și Inflamație",
|
||||
description: "Răspunsurile tale sugerează o posibilă rezistență la insulină și un nivel crescut de inflamație în corp.",
|
||||
recommendations: [
|
||||
"Adoptă o dietă low-carb, bogată în grăsimi sănătoase (avocado, nuci, ulei de măsline) și proteine de calitate.",
|
||||
"Include antrenamente de forță (greutăți, benzi elastice) pentru a îmbunătăți sensibilitatea la insulină.",
|
||||
"Consumă alimente antiinflamatorii: pește gras (somon, sardine), turmeric, ghimbir, fructe de pădure.",
|
||||
"Evită carbohidrații rafinați (pâine albă, paste, patiserie) și uleiurile vegetale procesate (floarea-soarelui, soia).",
|
||||
],
|
||||
},
|
||||
D: {
|
||||
title: "HealthType D: Digestie Compromisă și Sănătate Intestinală Scăzută",
|
||||
description: "Simptomele tale indică probleme la nivelul sistemului digestiv, care pot afecta absorbția nutrienților și starea generală de bine.",
|
||||
recommendations: [
|
||||
"Încearcă o dietă de eliminare (ex: fără gluten și lactate timp de 30 de zile) pentru a identifica posibile intoleranțe.",
|
||||
"Introdu în dietă alimente fermentate (chefir, varză murată) și o sursă de probiotice de calitate.",
|
||||
"Asigură-te că mesteci bine mâncarea și mănânci într-un mediu relaxat, fără grabă.",
|
||||
"Consideră enzime digestive sau oțet de mere diluat în apă înainte de mese pentru a sprijini digestia.",
|
||||
],
|
||||
},
|
||||
E: {
|
||||
title: "HealthType E: Dezechilibru Hormonal (Estrogen/Testosteron)",
|
||||
description: "Datele sugerează un dezechilibru al hormonilor sexuali, care poate cauza o varietate de simptome fizice și emoționale.",
|
||||
recommendations: [
|
||||
"Sprijină detoxifierea ficatului prin consumul de legume crucifere (broccoli, conopidă, varză).",
|
||||
"Asigură un aport adecvat de zinc (semințe de dovleac, carne roșie) și vitamina B6.",
|
||||
"Redu expunerea la xenoestrogeni din plastic, cosmetice și pesticide (alege produse organice și recipiente de sticlă).",
|
||||
"Pentru femei: monitorizează ciclul menstrual și ajustează dieta și antrenamentele în funcție de fazele acestuia.",
|
||||
"Pentru bărbați: optimizează nivelul de testosteron prin antrenamente de forță, somn adecvat și managementul stresului.",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function switchScreen(screenId) {
|
||||
Object.values(screens).forEach(screen => screen.setAttribute('aria-hidden', 'true'));
|
||||
screens[screenId].setAttribute('aria-hidden', 'false');
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
function startTest(sex) {
|
||||
resetState();
|
||||
state.sex = sex;
|
||||
state.currentQuestionIndex = 0;
|
||||
renderQuestion();
|
||||
switchScreen('question');
|
||||
}
|
||||
|
||||
function renderQuestion() {
|
||||
const questions = data[state.sex];
|
||||
const question = questions[state.currentQuestionIndex];
|
||||
|
||||
if (!question) {
|
||||
calculateResult();
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `<h2 class="question-title">${question.text}</h2>`;
|
||||
if (question.help) {
|
||||
html += `<p class="question-help">${question.help}</p>`;
|
||||
}
|
||||
|
||||
if (question.type === 'hw') {
|
||||
html += renderHWInputs();
|
||||
} else {
|
||||
html += '<ul class="option-list">';
|
||||
question.options.forEach((opt, index) => {
|
||||
const inputId = `q${state.currentQuestionIndex}-opt${index}`;
|
||||
html += `
|
||||
<li>
|
||||
<label for="${inputId}" class="option-label">
|
||||
<input type="${question.type}" id="${inputId}" name="answer" value="${opt.value}" data-exclusive="${opt.exclusive || false}">
|
||||
<span>${opt.text}</span>
|
||||
</label>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
questionElements.container.innerHTML = html;
|
||||
updateProgress();
|
||||
restoreAnswer();
|
||||
addOptionListeners();
|
||||
buttons.back.disabled = state.history.length === 0;
|
||||
}
|
||||
|
||||
function renderHWInputs() {
|
||||
return `
|
||||
<div class="input-group">
|
||||
<label for="height">Înălțime</label>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<input type="number" id="height" class="input-field" value="${state.height.value || ''}">
|
||||
<span id="height-unit" class="unit-toggle">${state.height.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="weight">Greutate</label>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<input type="number" id="weight" class="input-field" value="${state.weight.value || ''}">
|
||||
<span id="weight-unit" class="unit-toggle">${state.weight.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function addOptionListeners() {
|
||||
const question = data[state.sex][state.currentQuestionIndex];
|
||||
if (question.type === 'hw') {
|
||||
document.getElementById('height').addEventListener('input', e => state.height.value = e.target.value);
|
||||
document.getElementById('weight').addEventListener('input', e => state.weight.value = e.target.value);
|
||||
document.getElementById('height-unit').addEventListener('click', () => toggleUnit('height'));
|
||||
document.getElementById('weight-unit').addEventListener('click', () => toggleUnit('weight'));
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = questionElements.container.querySelectorAll('.option-label');
|
||||
labels.forEach(label => {
|
||||
label.addEventListener('click', (e) => {
|
||||
const input = label.querySelector('input');
|
||||
if (input.type === 'radio') {
|
||||
labels.forEach(l => l.classList.remove('selected'));
|
||||
label.classList.add('selected');
|
||||
} else { // checkbox
|
||||
label.classList.toggle('selected');
|
||||
if (input.dataset.exclusive === 'true' && input.checked) {
|
||||
// Uncheck others and remove selected class
|
||||
labels.forEach(l => {
|
||||
const i = l.querySelector('input');
|
||||
if (i !== input) {
|
||||
i.checked = false;
|
||||
l.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Uncheck the exclusive option if another is selected
|
||||
const exclusiveOpt = questionElements.container.querySelector('input[data-exclusive="true"]');
|
||||
if (exclusiveOpt) exclusiveOpt.checked = false;
|
||||
questionElements.container.querySelector('input[data-exclusive="true"]').parentElement.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleUnit(type) {
|
||||
if (type === 'height') {
|
||||
const currentUnit = state.height.unit;
|
||||
const currentValue = parseFloat(document.getElementById('height').value);
|
||||
if (currentUnit === 'cm') {
|
||||
state.height.unit = 'ft';
|
||||
if (!isNaN(currentValue)) {
|
||||
document.getElementById('height').value = (currentValue * 0.0328084).toFixed(1);
|
||||
}
|
||||
} else {
|
||||
state.height.unit = 'cm';
|
||||
if (!isNaN(currentValue)) {
|
||||
document.getElementById('height').value = (currentValue * 30.48).toFixed(0);
|
||||
}
|
||||
}
|
||||
document.getElementById('height-unit').textContent = state.height.unit;
|
||||
} else { // weight
|
||||
const currentUnit = state.weight.unit;
|
||||
const currentValue = parseFloat(document.getElementById('weight').value);
|
||||
if (currentUnit === 'kg') {
|
||||
state.weight.unit = 'lbs';
|
||||
if (!isNaN(currentValue)) {
|
||||
document.getElementById('weight').value = (currentValue * 2.20462).toFixed(1);
|
||||
}
|
||||
} else {
|
||||
state.weight.unit = 'lbs';
|
||||
if (!isNaN(currentValue)) {
|
||||
document.getElementById('weight').value = (currentValue / 2.20462).toFixed(1);
|
||||
}
|
||||
}
|
||||
document.getElementById('weight-unit').textContent = state.weight.unit;
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
const questions = data[state.sex];
|
||||
const progress = (state.currentQuestionIndex / (questions.length -1)) * 100;
|
||||
questionElements.progressText.textContent = `Întrebarea ${state.currentQuestionIndex + 1}/${questions.length}`;
|
||||
questionElements.progressFill.style.width = `${progress}%`;
|
||||
}
|
||||
|
||||
function saveAnswer() {
|
||||
const question = data[state.sex][state.currentQuestionIndex];
|
||||
let answerValue;
|
||||
|
||||
if (question.type === 'hw') {
|
||||
const h = parseFloat(document.getElementById('height').value);
|
||||
const w = parseFloat(document.getElementById('weight').value);
|
||||
state.height.value = h;
|
||||
state.weight.value = w;
|
||||
answerValue = { height: h, weight: w };
|
||||
} else {
|
||||
const inputs = questionElements.form.querySelectorAll('input:checked');
|
||||
if (inputs.length === 0) return null; // Validation failed
|
||||
|
||||
if (question.type === 'radio') {
|
||||
answerValue = parseFloat(inputs[0].value);
|
||||
} else { // checkbox
|
||||
answerValue = Array.from(inputs).reduce((sum, input) => sum + parseFloat(input.value), 0);
|
||||
}
|
||||
}
|
||||
|
||||
const existingAnswerIndex = state.answers.findIndex(a => a.questionId === question.id);
|
||||
if (existingAnswerIndex > -1) {
|
||||
state.answers[existingAnswerIndex].answerValue = answerValue;
|
||||
} else {
|
||||
state.answers.push({ questionId: question.id, answerValue });
|
||||
}
|
||||
return answerValue;
|
||||
}
|
||||
|
||||
function restoreAnswer() {
|
||||
const question = data[state.sex][state.currentQuestionIndex];
|
||||
const savedAnswer = state.answers.find(a => a.questionId === question.id);
|
||||
if (!savedAnswer) return;
|
||||
|
||||
const inputs = questionElements.form.querySelectorAll('input');
|
||||
inputs.forEach(input => {
|
||||
if (question.type === 'radio') {
|
||||
if (parseFloat(input.value) === savedAnswer.answerValue) {
|
||||
input.checked = true;
|
||||
input.parentElement.classList.add('selected');
|
||||
}
|
||||
} else { // checkbox
|
||||
// This is tricky for checkboxes as value is a sum.
|
||||
// A simple restore isn't possible without storing individual selections.
|
||||
// For now, we skip checkbox restore to avoid complexity.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateAndGoNext() {
|
||||
const question = data[state.sex][state.currentQuestionIndex];
|
||||
let answerValue = saveAnswer();
|
||||
let validationPassed = true;
|
||||
|
||||
if (question.type === 'hw') {
|
||||
const h = state.height.value;
|
||||
const w = state.weight.value;
|
||||
if (!h || !w || h <= 0 || w <= 0) {
|
||||
questionElements.validation.textContent = 'Te rugăm să introduci valori valide.';
|
||||
validationPassed = false;
|
||||
} else {
|
||||
questionElements.validation.textContent = '';
|
||||
}
|
||||
} else {
|
||||
if (answerValue === null) {
|
||||
questionElements.validation.textContent = 'Te rugăm să selectezi o opțiune.';
|
||||
validationPassed = false;
|
||||
} else {
|
||||
questionElements.validation.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!validationPassed) return;
|
||||
|
||||
state.history.push(state.currentQuestionIndex);
|
||||
|
||||
// Branching logic
|
||||
let nextIndex = state.currentQuestionIndex + 1;
|
||||
if (question.type === 'radio') {
|
||||
const selectedOption = question.options.find(o => o.value === answerValue);
|
||||
if (selectedOption && selectedOption.next) {
|
||||
const nextQuestion = data[state.sex].find(q => q.id === selectedOption.next);
|
||||
if (nextQuestion) {
|
||||
nextIndex = data[state.sex].indexOf(nextQuestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.currentQuestionIndex = nextIndex;
|
||||
renderQuestion();
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (state.history.length > 0) {
|
||||
state.currentQuestionIndex = state.history.pop();
|
||||
renderQuestion();
|
||||
}
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
state = {
|
||||
sex: null,
|
||||
currentQuestionIndex: 0,
|
||||
answers: [],
|
||||
history: [],
|
||||
height: { value: null, unit: 'cm' },
|
||||
weight: { value: null, unit: 'kg' },
|
||||
};
|
||||
}
|
||||
|
||||
function resetTest() {
|
||||
resetState();
|
||||
switchScreen('start');
|
||||
}
|
||||
|
||||
function calculateResult() {
|
||||
let scores = { B: 0, C: 0, D: 0, E: 0 };
|
||||
state.answers.forEach(answer => {
|
||||
const qId = answer.questionId;
|
||||
const val = answer.answerValue;
|
||||
|
||||
if (qId.startsWith('F') || qId.startsWith('M')) scores.E += val;
|
||||
if (['C1', 'C4'].includes(qId)) scores.B += val;
|
||||
if (['C5'].includes(qId)) scores.C += val;
|
||||
if (['C3'].includes(qId)) scores.D += val;
|
||||
if (['C2', 'C6', 'C7', 'C8'].includes(qId)) {
|
||||
scores.B += val * 0.5;
|
||||
scores.C += val * 0.5;
|
||||
scores.D += val * 0.5;
|
||||
}
|
||||
});
|
||||
|
||||
// BMI calculation
|
||||
let h = state.height.value;
|
||||
if (state.height.unit === 'ft') h = h * 30.48; // to cm
|
||||
let w = state.weight.value;
|
||||
if (state.weight.unit === 'lbs') w = w / 2.20462; // to kg
|
||||
const bmi = w / ((h / 100) ** 2);
|
||||
if (bmi > 25) scores.C += 2;
|
||||
if (bmi < 18.5) scores.D += 1;
|
||||
|
||||
const maxScore = Object.entries(scores).reduce((max, entry) => entry[1] > max[1] ? entry : max, [null, -1]);
|
||||
const finalType = maxScore[1] > 2 ? maxScore[0] : 'A';
|
||||
|
||||
displayResult(finalType);
|
||||
}
|
||||
|
||||
function displayResult(type) {
|
||||
const result = results[type];
|
||||
resultElements.title.textContent = result.title;
|
||||
let html = `<div class="result-section"><h3>Descriere</h3><p>${result.description}</p></div>`;
|
||||
html += `<div class="result-section"><h3>Recomandări</h3><ul>`;
|
||||
result.recommendations.forEach(rec => {
|
||||
html += `<li>${rec}</li>`;
|
||||
});
|
||||
html += `</ul></div>`;
|
||||
resultElements.content.innerHTML = html;
|
||||
|
||||
// Prepare print summary
|
||||
let summaryHtml = '<h3>Rezumatul Răspunsurilor</h3><ul>';
|
||||
data[state.sex].forEach(q => {
|
||||
const answer = state.answers.find(a => a.questionId === q.id);
|
||||
if (answer) {
|
||||
summaryHtml += `<li><strong>${q.text}</strong>: `;
|
||||
if (q.type === 'hw') {
|
||||
summaryHtml += `Înălțime: ${state.height.value} ${state.height.unit}, Greutate: ${state.weight.value} ${state.weight.unit}`;
|
||||
} else {
|
||||
// This part is complex to get right without storing more data
|
||||
// For now, just show the score value
|
||||
summaryHtml += `Răspuns (valoare: ${answer.answerValue})`;
|
||||
}
|
||||
summaryHtml += `</li>`;
|
||||
}
|
||||
});
|
||||
summaryHtml += '</ul>';
|
||||
summaryHtml += `<p>Data testului: ${new Date().toLocaleString('ro-RO')}</p>`;
|
||||
resultElements.printSummary.innerHTML = summaryHtml;
|
||||
|
||||
switchScreen('result');
|
||||
}
|
||||
|
||||
// Event Listeners
|
||||
buttons.sex.forEach(button => {
|
||||
button.addEventListener('click', () => startTest(button.dataset.sex));
|
||||
});
|
||||
|
||||
buttons.next.addEventListener('click', validateAndGoNext);
|
||||
buttons.back.addEventListener('click', goBack);
|
||||
buttons.reset.addEventListener('click', resetTest);
|
||||
buttons.print.addEventListener('click', () => window.print());
|
||||
|
||||
// Initial setup
|
||||
switchScreen('start');
|
||||
});
|
||||
408
styles.css
Normal file
408
styles.css
Normal file
@ -0,0 +1,408 @@
|
||||
|
||||
:root {
|
||||
--primary-color: #007bff;
|
||||
--primary-color-dark: #0056b3;
|
||||
--background-color: #f4f7f6;
|
||||
--surface-color: #ffffff;
|
||||
--text-color: #333333;
|
||||
--border-color: #e0e0e0;
|
||||
--danger-color: #dc3545;
|
||||
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--border-radius: 8px;
|
||||
--shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
--transition-speed: 0.3s;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
min-height: 100vh;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.app {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.screen {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background-color: var(--surface-color);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.screen[aria-hidden="false"] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.screen--start {
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
max-width: 500px;
|
||||
margin: 0 0 2.5rem;
|
||||
}
|
||||
|
||||
.sex-select {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sex-card {
|
||||
flex: 1;
|
||||
max-width: 220px;
|
||||
padding: 2rem 1rem;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
transition: all var(--transition-speed) ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sex-card:hover, .sex-card:focus-visible {
|
||||
transform: translateY(-5px);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 8px 20px rgba(0, 123, 255, 0.15);
|
||||
}
|
||||
|
||||
.sex-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: var(--primary-color);
|
||||
transform: scaleX(0);
|
||||
transition: transform var(--transition-speed) ease;
|
||||
}
|
||||
|
||||
.sex-card:hover::after, .sex-card:focus-visible::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.screen--question {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.q-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#progress-text {
|
||||
font-size: 0.9rem;
|
||||
color: #555;
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background-color: var(--primary-color);
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
.nav-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border-radius: var(--border-radius);
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-speed) ease;
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn--primary:hover, .btn--primary:focus-visible {
|
||||
background-color: var(--primary-color-dark);
|
||||
box-shadow: 0 4px 10px rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
background-color: transparent;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.btn--ghost:hover, .btn--ghost:focus-visible {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.question-container {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.question-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
|
||||
.question-help {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin: -1rem 0 1.5rem;
|
||||
border-left: 3px solid var(--border-color);
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.option-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-speed) ease;
|
||||
}
|
||||
|
||||
.option-label:hover {
|
||||
border-color: var(--primary-color);
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.option-label input {
|
||||
margin-right: 0.75rem;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
}
|
||||
|
||||
.option-label.selected {
|
||||
border-color: var(--primary-color);
|
||||
background-color: #e9f2ff;
|
||||
}
|
||||
|
||||
.q-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.validation {
|
||||
color: var(--danger-color);
|
||||
font-weight: 500;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.screen--result {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#result-title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
text-align: left;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.result-section h3 {
|
||||
font-size: 1.3rem;
|
||||
color: var(--primary-color);
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
margin-top: 2.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
padding: 0.8rem 1rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
transition: border-color var(--transition-speed);
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.unit-toggle {
|
||||
margin-left: 1rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
.app {
|
||||
max-width: 100%;
|
||||
}
|
||||
.screen {
|
||||
border-radius: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.screen--start {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
.sex-select {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.sex-card {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
.screen--question, .screen--result {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.q-actions {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
#btn-next {
|
||||
width: 100%;
|
||||
}
|
||||
.validation {
|
||||
margin-right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
padding: 0;
|
||||
}
|
||||
.app {
|
||||
box-shadow: none;
|
||||
max-width: 100%;
|
||||
}
|
||||
.screen--result {
|
||||
display: block !important;
|
||||
padding: 1rem;
|
||||
}
|
||||
.screen, .q-header, .q-actions, .result-actions {
|
||||
display: none;
|
||||
}
|
||||
#result-title, .result-section h3 {
|
||||
color: #000;
|
||||
border-bottom: 2px solid #ccc;
|
||||
}
|
||||
.print-summary {
|
||||
display: block !important;
|
||||
margin-top: 2rem;
|
||||
page-break-before: always;
|
||||
}
|
||||
.print-summary h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.print-summary ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.print-summary {
|
||||
display: none;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user