| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793 |
- <template>
- <div>
- <section
- id="quem-somos"
- class="relative min-h-screen flex flex-col bg-cover lg:bg-left"
- :style="{ backgroundImage: `url(${heroBackground})` }"
- >
- <div class="absolute inset-0 bg-violet-normal/45"/>
- <div class="relative z-10 flex-1 flex items-center">
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full pt-8 pb-20 lg:py-18">
- <div class="flex flex-col lg:flex-row gap-4 lg:gap-12">
- <div class="max-w-2xl justifyBetween">
- <h1
- class="reveal text-h2 text-white font-black mb-10"
- style="font-size: clamp(2rem, 5vw, 3.75rem); font-weight: 900;"
- >
- {{ settings.hero_title }}
- </h1>
- <p class="reveal reveal-delay-1 text-white/90 mb-6 leading-relaxed" style="font-size: clamp(1rem, 1.5vw, 1.125rem); font-weight: 600;">
- {{ settings.hero_subtitle }}
- </p>
- <div class="reveal reveal-delay-2 flex flex-col sm:flex-row gap-4">
- <button
- class="btn-primary"
- @click="scrollTo('associe-se')"
- >
- ASSOCIE-SE AGORA
- </button>
- <button
- class="btn-outline"
- @click="scrollTo('beneficios')"
- >
- CONHEÇA OS BENEFÍCIOS
- </button>
- </div>
- </div>
- <div class="reveal reveal-delay-3 flex flex-row lg:flex-col gap-6 lg:gap-8 shrink-0">
- <div
- v-for="stat in stats"
- :key="stat.key"
- class="flex flex-col items-start gap-1"
- >
- <i :class="`mdi ${stat.icon} text-white/90 text-3xl lg:text-4xl`" />
- <span class="text-white font-bold" style="font-size: clamp(1.25rem, 2.5vw, 1.75rem);">{{ stat.value }}</span>
- <span class="text-white/80 text-sm">{{ stat.label }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </section>
- <section id="beneficios" class="bg-neutral-light py-16 lg:py-24">
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
- <div class="reveal text-center mb-12 lg:mb-16">
- <h2 class="text-h4 text-text" style="font-size: clamp(1.5rem, 3vw, 2.125rem);">
- Benefícios para você
- </h2>
- </div>
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
- <div
- v-for="(benefit, index) in benefits"
- :key="benefit.id"
- :class="`reveal reveal-delay-${index + 1}`"
- class="benefit-card"
- >
- <div class="benefit-icon-wrap">
- <i :class="`mdi ${benefit.icon} text-violet-normal`" style="font-size: 1.5rem;" />
- </div>
- <h3 class="text-subtitle-1 font-semibold text-text mt-4 mb-2">
- {{ benefit.title }}
- </h3>
- <p class="text-body-2 text-text-2 leading-relaxed">
- {{ benefit.description }}
- </p>
- </div>
- </div>
- </div>
- </section>
- <section v-if="hasAbout" id="sobre-nos" class=" py-16 lg:py-24">
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
- <div v-if="aboutCards.length" class="reveal text-center mb-12 lg:mb-16">
- <h2 class="text-h4 text-text" style="font-size: clamp(1.5rem, 3vw, 2.125rem);">
- Quem somos
- </h2>
- </div>
- <div v-if="aboutCards.length" class="grid grid-cols-1 md:grid-cols-3 gap-6 items-stretch">
- <div
- v-for="(card, index) in aboutCards"
- :key="card.title"
- :class="`reveal reveal-delay-${index + 1}`"
- class="benefit-card"
- >
- <div class="flex items-center gap-3 mb-4">
- <div class="benefit-icon-wrap shrink-0">
- <i :class="`mdi ${card.icon} text-violet-normal`" style="font-size: 1.5rem;" />
- </div>
- <h3 class="text-subtitle-1 font-semibold text-text">
- {{ card.title }}
- </h3>
- </div>
- <p
- v-for="(paragraph, paragraphIndex) in card.paragraphs"
- :key="paragraphIndex"
- lang="pt-BR"
- class="text-body-2 text-text-2 text-justify hyphens-auto about-text"
- :class="{ 'mt-3': paragraphIndex > 0 }"
- >
- {{ paragraph }}
- </p>
- </div>
- </div>
- <template v-if="settings.about_history">
- <div
- class="reveal flex items-center justify-center gap-3 mb-10 lg:mb-12"
- :class="{ 'mt-16 lg:mt-24': aboutCards.length }"
- >
- <div class="benefit-icon-wrap shrink-0">
- <i class="mdi mdi-book-open-page-variant-outline text-violet-normal" style="font-size: 1.5rem;" />
- </div>
- <h2 class="text-h4 text-text" style="font-size: clamp(1.5rem, 3vw, 2.125rem);">
- História
- </h2>
- </div>
- <div class="reveal reveal-delay-1 benefit-card history-card">
- <p
- v-for="(paragraph, index) in historyParagraphs"
- :key="index"
- lang="pt-BR"
- class="text-body-1 text-text-2 text-justify hyphens-auto history-text"
- :class="{ 'mt-4': index > 0 }"
- >
- {{ paragraph }}
- </p>
- </div>
- </template>
- </div>
- </section>
- <section v-if="events.length" id="eventos" class="bg-neutral-light py-16 lg:py-24">
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
- <div class="reveal flex items-center justify-center gap-3 mb-12 lg:mb-16">
- <div class="benefit-icon-wrap shrink-0">
- <i class="mdi mdi-calendar-month-outline text-violet-normal" style="font-size: 1.5rem;" />
- </div>
- <h2 class="text-h4 text-text" style="font-size: clamp(1.5rem, 3vw, 2.125rem);">
- Eventos
- </h2>
- </div>
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
- <EventCard
- v-for="(event, index) in events"
- :key="event.id"
- :class="`reveal reveal-delay-${(index % 2) + 1}`"
- :event="event"
- />
- </div>
- </div>
- </section>
- <section id="associe-se" class="bg-violet-light py-16 lg:py-24">
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
- <div class="reveal text-center mb-10 lg:mb-12">
- <h2 class="text-h4 text-text" style="font-size: clamp(1.5rem, 3vw, 2.125rem);">
- Associe-se
- </h2>
- </div>
- <div class="reveal reveal-delay-1 max-w-3xl mx-auto">
- <div class="mb-0">
- <button class="tab-active">
- NOVA ASSOCIAÇÃO
- </button>
- </div>
- <div class="bg-white rounded-b-2xl rounded-tr-2xl shadow-md p-6 lg:p-10">
- <form novalidate @submit.prevent="handleSubmit" >
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-5 mb-6">
- <div class="field-group">
- <label class="field-label">Nome</label>
- <div :class="['field-input-wrap', { 'field-error': errors.nome }]">
- <span class="material-icons field-icon">person</span>
- <input
- v-model="form.nome"
- type="text"
- placeholder="Seu nome"
- class="field-input"
- @blur="onNameBlur('nome')"
- >
- </div>
- <span v-if="errors.nome" class="field-error-text">{{ errors.nome }}</span>
- </div>
- <div class="field-group">
- <label class="field-label">Sobrenome</label>
- <div :class="['field-input-wrap', { 'field-error': errors.sobrenome }]">
- <span class="material-icons field-icon">person</span>
- <input
- v-model="form.sobrenome"
- type="text"
- placeholder="Seu sobrenome"
- class="field-input"
- @blur="onNameBlur('sobrenome')"
- >
- </div>
- <span v-if="errors.sobrenome" class="field-error-text">{{ errors.sobrenome }}</span>
- </div>
- <div class="field-group">
- <label class="field-label">CPF</label>
- <div :class="['field-input-wrap', { 'field-error': errors.cpf }]">
- <span class="material-icons field-icon">badge</span>
- <input
- :value="form.cpf"
- type="text"
- placeholder="000.000.000-00"
- maxlength="14"
- class="field-input"
- @input="handleCPF"
- @blur="onCPFBlur"
- >
- </div>
- </div>
- <div class="field-group">
- <label class="field-label">Crachá</label>
- <div :class="['field-input-wrap', { 'field-error': errors.cracha }]">
- <span class="material-icons field-icon">credit_card</span>
- <input
- v-model="form.cracha"
- type="text"
- placeholder="Número do seu crachá"
- class="field-input"
- >
- </div>
- </div>
- <div class="field-group">
- <label class="field-label">E-mail</label>
- <div :class="['field-input-wrap', { 'field-error': errors.email }]">
- <span class="material-icons field-icon">email</span>
- <input
- v-model="form.email"
- type="email"
- placeholder="nome@conta.com"
- class="field-input"
- @blur="onEmailBlur"
- >
- </div>
- </div>
- <div class="field-group">
- <label class="field-label">Telefone</label>
- <div :class="['field-input-wrap', { 'field-error': errors.telefone }]">
- <span class="material-icons field-icon">phone</span>
- <input
- :value="form.telefone"
- type="tel"
- placeholder="(00) 00000-0000"
- maxlength="15"
- class="field-input"
- @input="handlePhone"
- @blur="onPhoneBlur"
- >
- </div>
- </div>
- <div class="field-group">
- <label class="field-label">Cargo</label>
- <div :class="['field-input-wrap', { 'field-error': errors.cargoId }]">
- <span class="material-icons field-icon">work</span>
- <select v-model="form.cargoId" class="field-input">
- <option :value="null" disabled>Selecione seu cargo</option>
- <option v-for="position in positions" :key="position.id" :value="position.id">
- {{ position.name }}
- </option>
- </select>
- </div>
- </div>
- <div class="field-group">
- <label class="field-label">Setor</label>
- <div :class="['field-input-wrap', { 'field-error': errors.setorId }]">
- <span class="material-icons field-icon">business</span>
- <select v-model="form.setorId" class="field-input">
- <option :value="null" disabled>Selecione seu setor</option>
- <option v-for="sector in sectors" :key="sector.id" :value="sector.id">
- {{ sector.name }}
- </option>
- </select>
- </div>
- </div>
- </div>
- <div class="flex justify-center">
- <button type="submit" class="btn-submit" :disabled="isLoading" :style="isLoading ? 'opacity: 0.7; cursor: not-allowed;' : ''">
- {{ isLoading ? 'ENVIANDO...' : 'ENVIAR SOLICITAÇÃO' }}
- </button>
- </div>
- </form>
- </div>
- </div>
- </div>
- </section>
- <Transition name="toast">
- <div
- v-if="showSuccess"
- class="fixed bottom-6 right-6 z-50 max-w-sm bg-violet-normal text-white px-5 py-4 rounded-xl shadow-xl flex items-start gap-3"
- >
- <span class="material-icons text-xl">check_circle</span>
- <span class="text-sm font-medium">
- Solicitação enviada com sucesso! Assim que a administração aprovar seu cadastro,
- use a opção "Meu Primeiro Acesso" no login para criar sua senha.
- </span>
- </div>
- </Transition>
- <Transition name="toast">
- <div
- v-if="showError"
- class="fixed bottom-6 right-6 z-50 bg-red-600 text-white px-5 py-4 rounded-xl shadow-xl flex items-center gap-3"
- >
- <span class="material-icons text-xl">error_outline</span>
- <span class="text-sm font-medium">{{ errorMessage }}</span>
- </div>
- </Transition>
- </div>
- </template>
- <script setup lang="ts">
- import heroBgFallback from '~/assets/images/imagem_fundo.png'
- useHead({ title: 'Serprati' })
- definePageMeta({ layout: 'default' })
- const { settings, benefits } = useCompanyData()
- const { events } = useEvents()
- const heroBackground = computed(() => settings.value.hero_background_url ?? heroBgFallback)
- const STAT_FALLBACK_ICONS = ['mdi-account-group', 'mdi-handshake', 'mdi-heart-outline']
- const stats = computed(() =>
- [1, 2, 3].map((n, index) => ({
- key: `stat${n}`,
- icon: settings.value[`stat${n}_icon` as keyof typeof settings.value] || STAT_FALLBACK_ICONS[index],
- value: settings.value[`stat${n}_value` as keyof typeof settings.value],
- label: settings.value[`stat${n}_label` as keyof typeof settings.value],
- }))
- )
- const toParagraphs = (raw: string | null): string[] => {
- const content = raw?.trim()
- if (!content) return []
- const paragraphs: string[] = []
- let startNewParagraph = true
- for (const line of content.split(/\r?\n/)) {
- const text = line.trim()
- if (!text) {
- startNewParagraph = true
- continue
- }
- const previous = paragraphs[paragraphs.length - 1]
- const isWrappedLine = !!previous && previous.length >= 60 && !/[.!?:;]$/.test(previous)
- if (!startNewParagraph && isWrappedLine) {
- paragraphs[paragraphs.length - 1] = `${previous} ${text}`
- } else {
- paragraphs.push(text)
- }
- startNewParagraph = false
- }
- return paragraphs
- }
- const aboutCards = computed(() =>
- [
- { title: 'Visão', icon: 'mdi-eye-outline', text: settings.value.about_vision },
- { title: 'Missão', icon: 'mdi-bullseye-arrow', text: settings.value.about_mission },
- { title: 'Valores', icon: 'mdi-heart-outline', text: settings.value.about_values },
- ]
- .filter((card) => !!card.text?.trim())
- .map((card) => ({ ...card, paragraphs: toParagraphs(card.text) }))
- )
- const hasAbout = computed(() => aboutCards.value.length > 0 || !!settings.value.about_history?.trim())
- const historyParagraphs = computed(() => toParagraphs(settings.value.about_history))
- const scrollTo = (id: string) => {
- document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' })
- }
- const form = reactive({
- nome: '',
- sobrenome: '',
- cpf: '',
- cracha: '',
- email: '',
- telefone: '',
- cargoId: null as number | null,
- setorId: null as number | null,
- })
- const formatCPF = (value: string): string => {
- return value
- .replace(/\D/g, '')
- .replace(/(\d{3})(\d)/, '$1.$2')
- .replace(/(\d{3})(\d)/, '$1.$2')
- .replace(/(\d{3})(\d{1,2})$/, '$1-$2')
- .slice(0, 14)
- }
- const validateCPF = (cpf: string): boolean => {
- const d = cpf.replace(/\D/g, '')
- if (d.length !== 11 || /^(\d)\1{10}$/.test(d)) return false
- const calc = (n: number): number => {
- let sum = 0
- for (let i = 0; i < n; i++) sum += parseInt(d.charAt(i)) * (n + 1 - i)
- const rem = sum % 11
- return rem < 2 ? 0 : 11 - rem
- }
- return calc(9) === parseInt(d.charAt(9)) && calc(10) === parseInt(d.charAt(10))
- }
- const validateEmail = (email: string): boolean =>
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
- const formatPhone = (value: string): string =>
- value
- .replace(/\D/g, '')
- .replace(/^(\d{2})(\d)/, '($1) $2')
- .replace(/(\d{5})(\d{1,4})$/, '$1-$2')
- .slice(0, 15)
- const errors = reactive({
- nome: '',
- sobrenome: '',
- cpf: '',
- cracha: '',
- email: '',
- telefone: '',
- cargoId: '',
- setorId: '',
- })
- const clearErrors = () => {
- Object.keys(errors).forEach((key) => { errors[key as keyof typeof errors] = '' })
- }
- const validateRequired = (): boolean => {
- errors.nome = form.nome.trim() ? errors.nome : 'Campo obrigatório'
- errors.sobrenome = form.sobrenome.trim() ? errors.sobrenome : 'Campo obrigatório'
- errors.cracha = form.cracha.trim() ? '' : 'Campo obrigatório'
- errors.cargoId = form.cargoId ? '' : 'Campo obrigatório'
- errors.setorId = form.setorId ? '' : 'Campo obrigatório'
- errors.cpf = form.cpf.trim() ? errors.cpf : 'Campo obrigatório'
- errors.email = form.email.trim() ? errors.email : 'Campo obrigatório'
- errors.telefone = form.telefone.trim() ? errors.telefone : 'Campo obrigatório'
- return !Object.values(errors).some(Boolean)
- }
- const handleCPF = (e: Event) => {
- form.cpf = formatCPF((e.target as HTMLInputElement).value)
- if (errors.cpf) errors.cpf = ''
- }
- const handlePhone = (e: Event) => {
- form.telefone = formatPhone((e.target as HTMLInputElement).value)
- if (errors.telefone) errors.telefone = ''
- }
- const onNameBlur = (field: 'nome' | 'sobrenome') => {
- const value = form[field].trim()
- if(field == 'nome') {
- errors.nome = value && value.length < 3 ? 'Mínimo de 3 caracteres' : ''
- } else if(field == 'sobrenome') {
- errors.sobrenome = value && value.length < 1 ? 'Campo obrigatório' : ''
- }
- }
- const onCPFBlur = () => {
- errors.cpf = form.cpf && !validateCPF(form.cpf) ? 'CPF inválido' : ''
- }
- const onEmailBlur = () => {
- errors.email = form.email && !validateEmail(form.email) ? 'E-mail inválido' : ''
- }
- const onPhoneBlur = () => {
- errors.telefone = form.telefone && form.telefone.replace(/\D/g, '').length < 10 ? 'Telefone inválido' : ''
- }
- watch(() => form.email, () => { if (errors.email) errors.email = '' })
- watch(() => form.nome, () => { if (errors.nome) errors.nome = '' })
- watch(() => form.sobrenome, () => { if (errors.sobrenome) errors.sobrenome = '' })
- watch(() => form.cracha, () => { if (errors.cracha) errors.cracha = '' })
- watch(() => form.cargoId, () => { if (errors.cargoId) errors.cargoId = '' })
- watch(() => form.setorId, () => { if (errors.setorId) errors.setorId = '' })
- const { registerLandingUser, positions, sectors } = useRegisterApi()
- const isLoading = ref(false)
- const showSuccess = ref(false)
- const showError = ref(false)
- const errorMessage = ref('')
- const resetForm = () => {
- Object.assign(form, {
- nome: '',
- sobrenome: '',
- cpf: '',
- cracha: '',
- email: '',
- telefone: '',
- cargoId: null,
- setorId: null,
- })
- clearErrors()
- }
- const handleSubmit = async () => {
- onNameBlur('nome')
- onNameBlur('sobrenome')
- errors.cpf = form.cpf && !validateCPF(form.cpf) ? 'CPF inválido' : ''
- errors.email = form.email && !validateEmail(form.email) ? 'E-mail inválido' : ''
- errors.telefone = form.telefone && form.telefone.replace(/\D/g, '').length < 10 ? 'Telefone inválido' : ''
- if (!validateRequired()) return
- isLoading.value = true
- showError.value = false
- try {
- await registerLandingUser({
- name: form.nome,
- last_name: form.sobrenome,
- cpf: form.cpf,
- registration: form.cracha,
- email: form.email,
- phone: form.telefone,
- position_id: form.cargoId,
- sector_id: form.setorId,
- })
- showSuccess.value = true
- setTimeout(() => { showSuccess.value = false }, 9000)
- resetForm()
- } catch (err: unknown) {
- const error = err as { data?: { errors?: Record<string, string[]>; message?: string } }
- const firstError = error?.data?.errors
- ? Object.values(error.data.errors)[0]?.[0]
- : error?.data?.message
- errorMessage.value = firstError || 'Erro ao enviar solicitação. Tente novamente.'
- showError.value = true
- setTimeout(() => { showError.value = false }, 5000)
- } finally {
- isLoading.value = false
- }
- }
- onMounted(() => {
- const observer = new IntersectionObserver(
- (entries) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting) {
- entry.target.classList.add('is-visible')
- observer.unobserve(entry.target)
- }
- })
- },
- { threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
- )
- document.querySelectorAll('.reveal').forEach((el) => observer.observe(el))
- })
- </script>
- <style scoped>
- @media (max-width: 1023px) {
- #quem-somos {
- background-position: 8% 65%;
- }
- }
- .btn-primary {
- background: linear-gradient(90deg, #4d1658 0%, #8b30a5 100%);
- color: #ffffff;
- font-family: "Nunito", sans-serif;
- font-weight: 800;
- font-size: 0.875rem;
- letter-spacing: 1px;
- padding: 0.75rem 1.75rem;
- border-radius: 0.5rem;
- transition: opacity 0.2s;
- cursor: pointer;
- white-space: nowrap;
- }
- .btn-primary:hover {
- opacity: 0.88;
- }
- .btn-outline {
- background-color: rgba(102, 29, 117, 0.18);
- color: #ffffff;
- font-family: "Nunito", sans-serif;
- font-weight: 700;
- font-size: 0.875rem;
- letter-spacing: 1px;
- padding: 0.75rem 1.75rem;
- border-radius: 0.5rem;
- border: 2px solid #a855c8;
- transition: background-color 0.2s, border-color 0.2s;
- cursor: pointer;
- white-space: nowrap;
- }
- .btn-outline:hover {
- background-color: rgba(102, 29, 117, 0.32);
- border-color: #ffffff;
- }
- .benefit-card {
- background-color: #ffffff;
- border-radius: 1rem;
- padding: 1.75rem;
- box-shadow: 0 6px 24px rgba(102, 29, 117, 0.14);
- transition: box-shadow 0.2s, transform 0.2s;
- }
- .benefit-card:hover {
- box-shadow: 0 10px 36px rgba(102, 29, 117, 0.22);
- transform: translateY(-2px);
- }
- .history-card {
- padding: 2rem 1.75rem;
- }
- @media (min-width: 1024px) {
- .history-card {
- padding: 2.5rem 3rem;
- }
- }
- .about-text {
- line-height: 1.7;
- }
- .history-text {
- font-size: clamp(0.9375rem, 1.05vw, 1.0625rem);
- line-height: 1.85;
- }
- .benefit-icon-wrap {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- background-color: #f0e8f1;
- border-radius: 0.75rem;
- }
- .tab-active {
- background-color: #661d75;
- color: #ffffff;
- font-family: "Nunito", sans-serif;
- font-weight: 600;
- font-size: 0.75rem;
- letter-spacing: 1.25px;
- padding: 0.625rem 1.5rem;
- border-radius: 0.5rem 0.5rem 0 0;
- cursor: default;
- }
- .field-group {
- display: flex;
- flex-direction: column;
- gap: 0.375rem;
- }
- .field-label {
- font-family: "Nunito", sans-serif;
- font-size: 0.8125rem;
- font-weight: 600;
- color: #505050;
- }
- .field-input-wrap {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- border: 1.5px solid #C0C0C0;
- border-radius: 0.5rem;
- padding: 0.5rem 0.75rem;
- background-color: #fefefe;
- transition: border-color 0.2s;
- }
- .field-input-wrap:focus-within {
- border-color: #661d75;
- }
- .field-input-wrap.field-error {
- border-color: #dc2626;
- }
- .field-error-text {
- font-family: "Nunito", sans-serif;
- font-size: 0.75rem;
- color: #dc2626;
- }
- .field-icon {
- font-size: 1.125rem;
- color: #b6b6b6;
- flex-shrink: 0;
- }
- .field-input {
- flex: 1;
- border: none;
- outline: none;
- background: transparent;
- font-family: "Nunito", sans-serif;
- font-size: 0.875rem;
- color: #161616;
- }
- .field-input::placeholder {
- color: #b6b6b6;
- }
- .btn-submit {
- background-color: #661d75;
- color: #ffffff;
- font-family: "Nunito", sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- letter-spacing: 1.25px;
- padding: 0.875rem 3rem;
- border-radius: 0.5rem;
- transition: background-color 0.2s;
- cursor: pointer;
- }
- .btn-submit:hover {
- background-color: #5c1a69;
- }
- .toast-enter-active,
- .toast-leave-active {
- transition: opacity 0.4s ease, transform 0.4s ease;
- }
- .toast-enter-from,
- .toast-leave-to {
- opacity: 0;
- transform: translateY(12px);
- }
- .justifyBetween {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- }
- </style>
|