cdrdpyj/src/components/forms/DynamicForm.vue

752 lines
28 KiB
Vue

<script setup>
import { ref, reactive, computed, onMounted, watch } from "vue";
import { Icon } from "@iconify/vue";
import { createTranslator } from "../../i18n";
const props = defineProps({
locale: String,
formConfigUrl: { type: String, required: false, default: "/forms/formulario-inscripcion.json" }
});
const tl = createTranslator(props.locale);
const config = ref(null);
const configError = ref(false);
const currentStep = ref(0);
const formData = reactive({});
const isSubmitting = ref(false);
const isSuccess = ref(false);
const isError = ref(false);
const errorMsg = ref("");
const reglamentoScrollRef = ref(null);
const reglamentoHTML = ref("");
const reglamentoLoading = ref(false);
const reglamentoScrolled = ref(false);
const reglamentoFetchAttempted = ref(false);
const totalSteps = computed(() => config.value?.steps?.length ?? 0);
const currentStepData = computed(() => config.value?.steps?.[currentStep.value] ?? null);
const isFirstStep = computed(() => currentStep.value === 0);
const isLastStep = computed(() => currentStep.value === totalSteps.value - 1);
const stepErrors = reactive({});
const fetchConfig = async () => {
isSuccess.value = false;
try {
const res = await fetch(props.formConfigUrl);
if (!res.ok) throw new Error("Failed to load form config");
const json = await res.json();
config.value = json;
json.steps.forEach((step, si) => {
step.fields.forEach((field) => {
if (field.type === "checkbox") {
formData[field.key] = [];
} else {
formData[field.key] = "";
}
});
});
} catch {
configError.value = true;
}
};
onMounted(fetchConfig);
const validateStep = (stepIndex) => {
const step = config.value?.steps?.[stepIndex];
if (!step) return true;
let valid = true;
if (stepIndex === 0 && !reglamentoScrolled.value) {
valid = false;
}
step.fields.forEach((field) => {
if (field.showWhen) {
const sourceVal = formData[field.showWhen.field];
const sourceField = findField(field.showWhen.field);
const conditionMet = sourceField?.type === "checkbox"
? Array.isArray(sourceVal) && sourceVal.includes(field.showWhen.value)
: sourceVal === field.showWhen.value;
if (!conditionMet) return;
}
const val = formData[field.key];
if (field.type === "checkbox") {
const requiredOptions = field.options?.filter(o => o.required)?.map(o => o.value) || [];
if (field.required) {
if (requiredOptions.length > 0) {
const allRequiredSelected = requiredOptions.every(optVal => val?.includes(optVal));
if (!allRequiredSelected) {
stepErrors[field.key] = true;
valid = false;
} else {
stepErrors[field.key] = false;
}
} else {
if (!val || val.length === 0) {
stepErrors[field.key] = true;
valid = false;
} else {
stepErrors[field.key] = false;
}
}
}
}
if (field.required && field.type !== "checkbox") {
if (!val || val.trim() === "") {
stepErrors[field.key] = true;
valid = false;
} else {
stepErrors[field.key] = false;
}
}
if (field.type === "email" && val && val.trim() !== "") {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(val.trim())) {
stepErrors[field.key] = true;
valid = false;
} else {
stepErrors[field.key] = false;
}
}
if (field.type === "checkbox" && field.levels && Array.isArray(val)) {
val.forEach((optVal) => {
if (!formData[`${field.key}_nivel_${optVal}`]) {
stepErrors[`${field.key}_nivel_${optVal}`] = true;
valid = false;
} else {
stepErrors[`${field.key}_nivel_${optVal}`] = false;
}
});
}
});
return valid;
};
const nextStep = () => {
if (!validateStep(currentStep.value)) return;
if (currentStep.value < totalSteps.value - 1) {
currentStep.value++;
}
};
const prevStep = () => {
if (currentStep.value > 0) {
currentStep.value--;
}
};
const goToStep = (index) => {
if (index < currentStep.value) {
currentStep.value = index;
return;
}
for (let i = 0; i < index; i++) {
if (!validateStep(i)) return;
}
currentStep.value = index;
};
const handleCheckboxChange = (key, value, checked) => {
if (checked) {
formData[key].push(value);
} else {
formData[key] = formData[key].filter((v) => v !== value);
delete formData[`${key}_nivel_${value}`];
}
stepErrors[key] = false;
};
const buildPayload = () => {
const responses = [];
config.value.steps.forEach((step, si) => {
step.fields.forEach((field) => {
const value = formData[field.key];
if (value !== "" && !(Array.isArray(value) && value.length === 0)) {
responses.push({
key: field.key,
value: value,
section_id: `step_${si}`
});
if (field.levels && Array.isArray(value)) {
value.forEach((optVal) => {
const levelKey = `${field.key}_nivel_${optVal}`;
const levelVal = formData[levelKey];
if (levelVal) {
responses.push({
key: levelKey,
value: levelVal,
section_id: `step_${si}`
});
}
});
}
}
});
});
return { formData: { ...formData }, responses };
};
const handleSubmit = async () => {
for (let i = 0; i < totalSteps.value; i++) {
if (!validateStep(i)) {
currentStep.value = i;
return;
}
}
isSubmitting.value = true;
isSuccess.value = false;
isError.value = false;
errorMsg.value = "";
try {
const payload = buildPayload();
const res = await fetch("/api/formulario/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
if (data.success) {
isSuccess.value = true;
} else {
throw new Error(data.message || "Error al enviar el formulario");
}
} catch (error) {
isError.value = true;
errorMsg.value = error.message || "Error al enviar el formulario";
} finally {
isSubmitting.value = false;
}
};
const getFieldError = (key) => stepErrors[key] || false;
const suggestionsCache = ref({});
const showSuggestions = reactive({});
const loadSuggestions = async (source) => {
if (suggestionsCache.value[source]) return;
try {
const res = await fetch(source);
suggestionsCache.value[source] = await res.json();
} catch {}
};
const filteredSuggestions = (field) => {
const val = formData[field.key]?.toLowerCase().trim() || "";
if (!val) return [];
const list = suggestionsCache.value[field.source] || [];
return list.filter(item => item.label.toLowerCase().includes(val));
};
const selectSuggestion = (field, item) => {
formData[field.key] = item.value;
showSuggestions[field.key] = false;
stepErrors[field.key] = false;
};
const onAutocompleteFocus = (field) => {
loadSuggestions(field.source);
showSuggestions[field.key] = true;
};
const onAutocompleteBlur = (field) => {
setTimeout(() => { showSuggestions[field.key] = false; }, 200);
};
const getSelectOptions = (field) => {
loadSuggestions(field.source);
return suggestionsCache.value[field.source] || [];
};
const findField = (key) => {
if (!config.value) return null;
for (const step of config.value.steps) {
const found = step.fields.find((f) => f.key === key);
if (found) return found;
}
return null;
};
const shouldShow = (field) => {
if (!field.showWhen) return true;
const sourceField = findField(field.showWhen.field);
const value = formData[field.showWhen.field];
if (!value) return false;
if (sourceField?.type === "checkbox") {
return Array.isArray(value) && value.includes(field.showWhen.value);
}
return value === field.showWhen.value;
};
const fetchReglamento = async () => {
if (reglamentoFetchAttempted.value) return;
reglamentoFetchAttempted.value = true;
reglamentoScrolled.value = false;
reglamentoLoading.value = true;
reglamentoHTML.value = "";
try {
const locales = [props.locale, "es"];
let html = null;
for (const l of locales) {
const res = await fetch(`/reglamento/${l}.html`);
if (res.ok) { html = await res.text(); break; }
}
reglamentoHTML.value = html || "<p>Error al cargar el reglamento</p>";
} catch {
reglamentoHTML.value = "<p>Error al cargar el reglamento</p>";
} finally {
reglamentoLoading.value = false;
}
};
const onReglamentoScroll = () => {
const el = reglamentoScrollRef.value;
if (!el) return;
const threshold = 10;
if (el.scrollTop + el.clientHeight >= el.scrollHeight - threshold) {
reglamentoScrolled.value = true;
}
};
watch(currentStep, (step) => {
if (step === 0) {
fetchReglamento();
} else {
reglamentoFetchAttempted.value = false;
}
}, { immediate: true });
const getFieldIcon = (field) => {
const k = field.key;
if (k === "nombre" || k === "emergencia_nombre") return "ph:user";
if (k === "apellido") return "ph:identification-card";
if (k === "documentos") return "ph:cardholder";
if (k === "correo" || k === "emergencia_correo") return "ph:envelope";
if (k === "reglamento_firma") return "ph:signature";
if (k === "fecha_nacimiento") return "ph:calendar";
if (k === "nacionalidad" || k === "pais") return "ph:flag";
if (k === "direccion_completa") return "ph:house";
if (k === "ciudad") return "ph:city";
if (k === "estado" || k === "postal") return "ph:map-pin";
if (k === "telefono" || k === "whatsapp" || k === "emergencia_telefono") return "ph:phone";
if (k === "profesion") return "ph:briefcase";
if (k === "lugar_trabajo_actual") return "ph:buildings";
if (k === "nivel_academico") return "ph:graduation-cap";
if (k === "idioma_otro") return "ph:translate";
if (k === "areas_otra") return "ph:plus-circle";
if (k === "emergencia_parentesco") return "ph:users-three";
if (field.type === "phone") return "ph:phone";
if (field.type === "email") return "ph:envelope";
if (field.type === "textarea") return "ph:note-pencil";
if (field.type === "date") return "ph:calendar";
return "ph:newspaper-clip";
};
const getFieldInputType = (field) => {
if (field.type === "email") return "email";
if (field.type === "phone") return "tel";
if (field.type === "date") return "date";
return "text";
};
</script>
<template>
<div v-if="configError" class="text-center py-12 text-red-500">
{{ tl("form.config_error") }}
</div>
<div v-else-if="!config" class="flex justify-center py-12">
<span class="w-8 h-8 border-2 border-[#22523F] font-secondary border-t-transparent rounded-full animate-spin"></span>
</div>
<template v-else>
<div v-if="!isSuccess" class="w-full max-w-3xl mx-auto md:px-4 sm:px-0">
<div class="flex flex-wrap sm:flex-nowrap items-center justify-center gap-x-1 md:gap-x-2 gap-y-2 sm:gap-y-3 mb-4 sm:mb-6 lg:mb-8">
<template v-for="(step, index) in config.steps" :key="index">
<button
type="button"
@click="goToStep(index)"
class="flex flex-col lg:flex-row items-center gap-1 text-sm transition-colors text-center"
:class="index === currentStep ? 'text-[#EBE6D2] font-bold' : 'text-[#EBE6D2]/60 hover:text-[#EBE6D2]'"
>
<span
class="flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold border-2 transition-colors shrink-0"
:class="index === currentStep
? 'bg-[#CBA16A] text-[#22523F] border-[#CBA16A]'
: index < currentStep
? 'bg-[#EBE6D2] text-[#22523F] border-[#EBE6D2]'
: 'border-[#EBE6D2]/50 text-[#EBE6D2]/60'"
>
{{ index < currentStep ? '✓' : index + 1 }}
</span>
<span class="text-[10px] lg:text-xs leading-tight max-w-[60px] md:max-w-[70px] lg:max-w-none font-secondary">{{ tl(step.label) }}</span>
</button>
<span v-if="index < config.steps.length - 1" class="w-4 md:w-6 lg:w-8 h-px bg-[#EBE6D2]/30 shrink-0 hidden sm:inline"></span>
</template>
</div>
<form @submit.prevent="handleSubmit" class="bg-[#EBE6D2] p-5 sm:p-8 lg:p-10 rounded-none">
<h3 class="text-lg sm:text-xl font-bold text-[#22523F] mb-4 sm:mb-6 text-center font-secondary">
{{ tl(currentStepData.label) }}
</h3>
<div v-if="currentStep === 0" class="mb-4 sm:mb-6">
<div v-if="reglamentoLoading" class="flex justify-center py-8">
<span class="w-8 h-8 border-2 border-[#22523F] border-t-transparent rounded-full animate-spin"></span>
</div>
<div v-else>
<div
ref="reglamentoScrollRef"
class="md:max-h-[720px] max-h-[500px] overflow-y-auto border scrollbar-thin scrollbar-thumb-[#22523F] border-gray-300 p-4 bg-white reglamento-content"
@scroll="onReglamentoScroll"
>
<div v-html="reglamentoHTML"></div>
</div>
<div class="flex items-center gap-2 mt-3">
<Icon
:icon="reglamentoScrolled ? 'ph:check-circle-fill' : 'ph:arrow-down'"
:class="reglamentoScrolled ? 'text-green-600' : 'text-gray-400'"
class="text-lg"
/>
<span :class="reglamentoScrolled ? 'text-green-700 font-medium' : 'text-gray-500'" class="text-sm font-secondary">
{{ reglamentoScrolled ? tl("form.reglamento_leido") : tl("form.reglamento_scroll_needed") }}
</span>
</div>
<div v-if="!reglamentoScrolled" class="bg-amber-50 border-l-4 border-amber-400 text-amber-800 p-3 mt-3 text-sm flex items-start gap-2">
<Icon icon="ph:info" class="text-lg mt-0.5 shrink-0" />
<span class="font-secondary">{{ tl("form.reglamento_aviso") }}</span>
</div>
</div>
</div>
<div class="grid gap-4 sm:gap-5" :class="{ 'grid-cols-1': !currentStepData.column || currentStepData.column === 1, 'md:grid-cols-2': currentStepData.column === 2, 'md:grid-cols-3': currentStepData.column === 3 }">
<template v-for="field in currentStepData.fields" :key="field.key">
<div v-if="shouldShow(field)" class="flex flex-col gap-1.5" :class="{ 'md:col-span-2': field.colspan === 2 && currentStepData.column > 1, 'md:col-span-3': field.colspan === 3 && currentStepData.column > 1 }">
<span class="text-[#22523F] font-secondary font-medium text-sm leading-tight">
{{ tl(field.label) }}
<span v-if="field.required" class="text-red-500 ml-0.5">*</span>
</span>
<div v-if="field.readonly" class="py-2.5 px-3 bg-gray-100 border border-gray-200 text-[#6B7280] rounded-xl min-h-[2.5rem] flex items-center text-sm">
{{ field.valueFrom ? (formData[field.valueFrom] || "—") : (formData[field.key] || "—") }}
</div>
<label
v-else-if="field.type === 'text' || field.type === 'email' || field.type === 'phone'"
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
>
<Icon :icon="getFieldIcon(field)" class="shrink-0 opacity-50 text-gray-400 text-lg" />
<input
class="grow text-[#1a1a1a]"
:type="getFieldInputType(field)"
v-model="formData[field.key]"
:placeholder="tl(field.placeholder || field.label)"
@input="stepErrors[field.key] = false"
/>
</label>
<label
v-else-if="field.type === 'date'"
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
>
<Icon icon="ph:calendar" class="shrink-0 opacity-50 text-gray-400 text-lg" />
<input
class="grow text-[#1a1a1a]"
type="date"
v-model="formData[field.key]"
@change="stepErrors[field.key] = false"
/>
</label>
<div v-else-if="field.type === 'select'" class="relative">
<label
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
>
<Icon :icon="getFieldIcon(field)" class="shrink-0 opacity-50 text-gray-400 text-lg" />
<select
class="grow text-[#1a1a1a] bg-transparent"
v-model="formData[field.key]"
@change="stepErrors[field.key] = false"
>
<option value="" disabled>{{ tl("form.select_placeholder", { label: tl(field.label) }) }}</option>
<option v-for="item in getSelectOptions(field)" :key="item.value" :value="item.value">
{{ item.label }}
</option>
</select>
</label>
</div>
<div v-else-if="field.type === 'autocomplete'" class="relative">
<label
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
>
<Icon :icon="getFieldIcon(field)" class="shrink-0 opacity-50 text-gray-400 text-lg" />
<input
class="grow text-[#1a1a1a]"
type="text"
v-model="formData[field.key]"
:placeholder="tl(field.placeholder || field.label)"
autocomplete="off"
@input="stepErrors[field.key] = false; showSuggestions[field.key] = true"
@focus="onAutocompleteFocus(field)"
@blur="onAutocompleteBlur(field)"
/>
</label>
<ul
v-if="showSuggestions[field.key] && filteredSuggestions(field).length"
class="absolute left-0 right-0 z-50 bg-white shadow-lg rounded-b-xl border border-gray-200 border-t-0 max-h-[200px] overflow-y-auto"
>
<li
v-for="item in filteredSuggestions(field)"
:key="item.value"
class="px-4 py-2.5 cursor-pointer text-sm text-[#1a1a1a] hover:bg-[#22523F]/10 transition-colors"
@mousedown.prevent="selectSuggestion(field, item)"
>
{{ item.label }}
</li>
</ul>
</div>
<div v-else-if="field.type === 'textarea'" class="relative">
<Icon icon="ph:note-pencil" class="absolute left-3 top-3.5 text-gray-400 opacity-50 shrink-0 pointer-events-none text-lg" />
<textarea
class="textarea w-full bg-white text-[#1a1a1a] pl-10 rounded-xl resize-none min-h-[6rem] focus:outline-2 focus:outline-[#22523F] focus:outline-offset-[-1px]"
:class="{ 'border-red-500 focus:outline-red-500': getFieldError(field.key) }"
v-model="formData[field.key]"
:placeholder="tl(field.placeholder || field.label)"
rows="4"
@input="stepErrors[field.key] = false"
/>
</div>
<div v-else-if="field.type === 'radio'" class="flex flex-wrap gap-x-5 gap-y-1">
<label
v-for="opt in field.options"
:key="opt.value"
class="flex items-center gap-2 cursor-pointer p-1 -ml-1 rounded-lg hover:bg-[#22523F]/5 transition-colors"
>
<input
type="radio"
:name="field.key"
:value="opt.value"
v-model="formData[field.key]"
class="radio radio-sm border-gray-300 checked:border-[#4A8C6F] checked:bg-[#4A8C6F]"
@change="stepErrors[field.key] = false"
/>
<span class="text-[#1a1a1a] text-sm">{{ tl(opt.label) }}</span>
</label>
</div>
<div v-else-if="field.type === 'checkbox'" class="flex flex-col gap-1">
<div
v-for="opt in field.options"
:key="opt.value"
class="flex flex-col"
>
<label
class="flex items-center gap-3 cursor-pointer p-1.5 rounded-lg transition-colors"
:class="currentStep === 0 && !reglamentoScrolled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-[#22523F]/5'"
>
<input
type="checkbox"
:value="opt.value"
:disabled="currentStep === 0 && !reglamentoScrolled"
class="checkbox checkbox-sm border-gray-300 checked:border-[#4A8C6F] checked:bg-[#4A8C6F]"
:checked="formData[field.key]?.includes(opt.value)"
@change="handleCheckboxChange(field.key, opt.value, $event.target.checked)"
/>
<span class="text-[#1a1a1a] text-sm">{{ tl(opt.label) }}</span>
</label>
<div
v-if="formData[field.key]?.includes(opt.value) && field.levels"
class="flex flex-wrap gap-x-4 gap-y-1 ml-9 mt-1"
>
<label
v-for="lvl in field.levels.options"
:key="lvl.value"
class="flex items-center gap-1.5 cursor-pointer text-sm"
>
<input
type="radio"
:name="`${field.key}_nivel_${opt.value}`"
:value="lvl.value"
v-model="formData[`${field.key}_nivel_${opt.value}`]"
class="radio radio-sm border-gray-300 checked:border-[#4A8C6F] checked:bg-[#4A8C6F]"
/>
<span class="text-[#1a1a1a]">{{ tl(lvl.label) }}</span>
</label>
<span
v-if="getFieldError(`${field.key}_nivel_${opt.value}`)"
class="text-red-500 text-xs"
>
{{ tl("form.required") }}
</span>
</div>
</div>
</div>
<p
v-if="getFieldError(field.key) && (field.type !== 'checkbox' || currentStep > 0)"
class="text-xs text-red-500 flex items-center gap-1 mt-0.5"
>
<Icon icon="ph:warning-circle" class="text-sm shrink-0" />
{{ tl("form.required") }}
</p>
</div>
</template>
</div>
<div v-if="isError && errorMsg" class="text-red-600 text-sm text-center mt-4">
{{ errorMsg }}
</div>
<div class="border-t border-gray-300/30 mt-6 sm:mt-8 mb-4 sm:mb-6"></div>
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3">
<div class="flex justify-center sm:hidden">
<span class="text-xs text-gray-500 font-secondary font-medium">
{{ tl("form.step_counter", { current: currentStep + 1, total: totalSteps }) }}
</span>
</div>
<div class="flex justify-between items-center w-full sm:contents">
<button
v-if="!isFirstStep"
type="button"
@click="prevStep"
class="btn bg-white border border-gray-200 text-gray-500 hover:bg-gray-50 hover:text-gray-700"
>
<Icon icon="ph:arrow-left" class="text-lg" />
{{ tl("form.back") }}
</button>
<div v-else></div>
<div class="flex items-center gap-3">
<span class="hidden sm:inline text-xs text-gray-500 font-secondary font-medium whitespace-nowrap">
{{ tl("form.step_counter", { current: currentStep + 1, total: totalSteps }) }}
</span>
<div class="relative group">
<button
v-if="!isLastStep"
type="button"
@click="nextStep"
:disabled="currentStep === 0 && !reglamentoScrolled"
class="btn rounded-none text-sm sm:text-base px-3 sm:px-4"
:class="currentStep === 0 && !reglamentoScrolled
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-[#22523F] text-white hover:bg-[#1a3d2f]'"
>
{{ tl("form.next") }}
<Icon icon="ph:arrow-right" class="text-base sm:text-lg" />
</button>
<div
v-if="currentStep === 0 && !reglamentoScrolled"
class="absolute bottom-full mb-1 sm:mb-2 left-1/2 -translate-x-1/2 whitespace-nowrap bg-gray-800 text-white text-xs rounded px-3 py-1.5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10 shadow-lg"
>
{{ tl("form.reglamento_tooltip") }}
</div>
</div>
<button
v-if="isLastStep"
type="submit"
:disabled="isSubmitting"
class="btn rounded-none px-3 sm:px-5 transition-colors text-xs sm:text-sm leading-tight min-w-0"
:class="isSubmitting
? 'bg-gray-400 text-white cursor-not-allowed'
: 'bg-[#22523F] text-white hover:bg-[#1a3d2f]'"
>
<span v-if="isSubmitting" class="flex items-center gap-1 sm:gap-2">
<span class="w-3 h-3 sm:w-4 sm:h-4 border-2 border-white border-t-transparent rounded-full animate-spin shrink-0"></span>
<span class="truncate">{{ tl("form.sending") }}</span>
</span>
<span v-else class="truncate">
{{ tl(config.submit_label) }}
</span>
</button>
</div>
</div>
</div>
</form>
</div>
<div v-else class="w-full max-w-3xl mx-auto md:px-4 sm:px-0">
<div class="bg-[#EBE6D2] p-8 sm:p-12 rounded-none text-center">
<img
src="/img/logo-metalico.webp"
alt="Logo Centro del Reino de Paz y Justicia"
class="w-20 h-20 sm:w-28 sm:h-28 mx-auto mb-6"
/>
<h2 class="text-xl sm:text-2xl font-bold text-[#22523F] font-secondary mb-4">
{{ tl("form.success_title") }}
</h2>
<p class="text-[#1a1a1a]/80 text-sm sm:text-base max-w-lg mx-auto mb-8 leading-relaxed">
{{ tl("form.success_info") }}
</p>
<div class="bg-[#22523F]/5 border border-[#22523F]/10 rounded-xl p-5 sm:p-6 text-left text-sm text-gray-600 max-w-lg mx-auto mb-8">
<div class="flex items-start gap-3">
<Icon icon="ph:info" class="text-[#22523F] text-lg mt-0.5 shrink-0" />
<p>{{ tl("form.success_detail") }}</p>
</div>
</div>
<a :href="`/${props.locale}`" class="btn bg-[#22523F] text-white hover:bg-[#1a3d2f] rounded-none px-8">
{{ tl("form.back_home") }}
</a>
</div>
</div>
</template>
</template>
<style>
.reglamento-content h2 {
color: #22523F;
font-size: 1.25rem;
font-weight: 700;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.reglamento-content h2:first-of-type {
margin-top: 0;
}
.reglamento-content p {
color: #374151;
line-height: 1.625;
margin-bottom: 0.75rem;
}
.reglamento-content ul {
list-style: disc;
padding-left: 1.5rem;
margin-bottom: 0.75rem;
}
.reglamento-content li {
color: #374151;
line-height: 1.625;
margin-bottom: 0.25rem;
}
.reglamento-content h1 {
color: #22523F;
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.25rem;
}
.reglamento-content > p:first-of-type {
color: #6B7280;
font-size: 0.875rem;
margin-bottom: 1.5rem;
}
</style>