feat:Implementar transição em slide para a tela de login com validação aprimorada e notificações via snackbar

- Adicionado componente de slider para transição entre as telas de introdução e login.
- Validação aprimorada dos campos de e-mail e senha, com mensagens de erro..
- Substituídos os componentes do Vuetify por elementos HTML padrão nos campos de entrada.
- IIntroduzido snackbar para exibição de mensagens de erro durante tentativas de login
- Estilos atualizados para melhor responsividade e apelo visual.
This commit is contained in:
flavia-vic 2025-04-29 09:04:26 -04:00
parent bb33d0b20b
commit d60af1cffc
9 changed files with 1213 additions and 648 deletions

14
package-lock.json generated
View File

@ -9,10 +9,12 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"animate.css": "^4.1.1",
"apexcharts": "^3.54.1", "apexcharts": "^3.54.1",
"axios": "^1.8.4", "axios": "^1.8.4",
"dashboard": "file:", "dashboard": "file:",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"gsap": "^3.12.7",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"npm-check-updates": "^17.1.11", "npm-check-updates": "^17.1.11",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
@ -818,6 +820,12 @@
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA=="
}, },
"node_modules/animate.css": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
"integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==",
"license": "MIT"
},
"node_modules/apexcharts": { "node_modules/apexcharts": {
"version": "3.54.1", "version": "3.54.1",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.1.tgz", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.1.tgz",
@ -992,6 +1000,12 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/gsap": {
"version": "3.12.7",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.7.tgz",
"integrity": "sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg==",
"license": "Standard 'no charge' license: https://gsap.com/standard-license. Club GSAP members get more: https://gsap.com/licensing/. Why GreenSock doesn't employ an MIT license: https://gsap.com/why-license/"
},
"node_modules/jwt-decode": { "node_modules/jwt-decode": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",

View File

@ -10,10 +10,12 @@
}, },
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"animate.css": "^4.1.1",
"apexcharts": "^3.54.1", "apexcharts": "^3.54.1",
"axios": "^1.8.4", "axios": "^1.8.4",
"dashboard": "file:", "dashboard": "file:",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"gsap": "^3.12.7",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"npm-check-updates": "^17.1.11", "npm-check-updates": "^17.1.11",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",

View File

@ -93,7 +93,9 @@ const loadHolidayData = async () => {
estado: holidayData.estado || '', estado: holidayData.estado || '',
municipio: holidayData.municipio || '', municipio: holidayData.municipio || '',
parent_id: authStore.userId, parent_id: authStore.userId,
service_instance_id: authStore.service_instance_id || 2 service_instance_id: authStore.service_instance_id || 2,
adicional_he: Number(localHoliday.value.adicional_he), // Garante tipo numérico
recorrente: localHoliday.value.recorrente
}; };
} else if (props.holiday) { } else if (props.holiday) {
// Fallback para os dados recebidos via props // Fallback para os dados recebidos via props

View File

@ -19,10 +19,34 @@
:rules="[v => !!v || 'Data é obrigatória']" required /> :rules="[v => !!v || 'Data é obrigatória']" required />
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-text-field v-model="localHoliday.municipio" label="Municipio" type="municipio" :rules="[rules.required]" clearable required/> <v-text-field v-model="localHoliday.municipio" label="Municipio" type="municipio" />
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-text-field v-model="localHoliday.estado" label="Estado" type="estado" clearable :rules="[rules.required]" required/> <v-text-field v-model="localHoliday.estado" label="Estado" type="estado" clearable />
</v-col>
<v-col cols="12">
<v-switch
v-model="localHoliday.recorrente"
label="Feriado Recorrente (Anual)"
color="primary"
:true-value="true"
:false-value="false"
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model.number="localHoliday.adicional_he"
label="Adicional de HE (%) *"
type="number"
min="0"
max="100"
:rules="[
v => v !== null && v !== undefined || 'Campo obrigatório',
v => (v >= 0 && v <= 100) || 'Valor entre 0 e 100'
]"
required
@input="form?.validate()"
/>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
@ -30,8 +54,13 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn text color="blue-darken-1" @click="$emit('cancel')">Cancelar</v-btn> <v-btn text color="blue-darken-1" @click="$emit('cancel')">Cancelar</v-btn>
<v-btn text class="text-white salvar-btn" :disabled="!formValid" <v-btn
@click="handleSave" :loading="holidayStore.loading"> text
class="text-white salvar-btn"
:disabled="!formValid"
@click="handleSave"
:loading="holidayStore.loading"
>
Salvar Salvar
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -64,6 +93,8 @@ const defaultHoliday = {
date: '', date: '',
estado: '', estado: '',
municipio: '', municipio: '',
recorrente: true,
adicional_he: 0,
parent_id: authStore.userId, parent_id: authStore.userId,
service_instance_id: authStore.service_instance_id || 2 service_instance_id: authStore.service_instance_id || 2
}; };
@ -116,25 +147,26 @@ const closeModal = () => {
// Salvar o novo feriado // Salvar o novo feriado
const handleSave = async (event) => { const handleSave = async (event) => {
if(form.value.validate()){ event.preventDefault();
event.preventDefault(); // Impede o comportamento padrão do botão
// Valida apenas o formulário (sem verificações extras)
const { valid } = await form.value.validate();
if (valid) { // Só depende da validação do formulário
try { try {
const holidayData = { await holidayStore.createHoliday({
...localHoliday.value, ...localHoliday.value,
parent_id: authStore.userId, parent_id: authStore.userId,
service_instance_id: authStore.service_instance_id || 2 service_instance_id: authStore.service_instance_id
}; });
console.log('Criando novo feriado:', holidayData); emit('save');
await holidayStore.createHoliday(holidayData);
emit('save', holidayData);
emit('update:modalValue', false); emit('update:modalValue', false);
resetLocalHoliday(); resetLocalHoliday();
} catch (error) { } catch (error) {
console.error('Erro ao criar feriado:', error); console.error('Erro ao criar feriado:', error);
}} }
}
}; };
// Inicialização do componente // Inicialização do componente

View File

@ -16,8 +16,19 @@ import Holiday from '../views/Holiday.vue'
import Company from '../views/Company.vue' import Company from '../views/Company.vue'
const routes = [ const routes = [
{ path: '/', redirect: '/login' }, {
{ path: '/login', component: Login }, path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
transition: 'slide',
requiresGuest: true
}
},
{ {
path: '/dashboard', path: '/dashboard',
component: Dashboard, component: Dashboard,

View File

@ -100,19 +100,31 @@ export const useHolidayStore = defineStore('holidays', {
const url = '/holiday/'; const url = '/holiday/';
this.loading = true; this.loading = true;
this.error = null; this.error = null;
try { try {
const response = await api.post(url, holiday, { // Garantindo que o objeto holiday tem todos os campos necessários
const holidayData = {
name: holiday.name,
date: holiday.date,
estado: holiday.estado || '',
municipio: holiday.municipio || '',
recorrente: holiday.recorrente !== undefined ? holiday.recorrente : true,
adicional_he: holiday.adicional_he !== undefined ? holiday.adicional_he : 0,
type: holiday.type || '',
service_instance_id: useAuthStore().service_instance_id
};
const response = await api.post(url, holidayData, {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}); });
this.holidays.push(response.data); this.holidays.push(response.data);
return response.data; return response.data;
} catch (error) { } catch (error) {
this.error = error?.response?.data?.message || error.message || 'Erro ao criar feriado'; this.error = error?.response?.data?.message || error.message || 'Erro ao criar feriado';
console.error('Erro ao criar turno', error); console.error('Erro ao criar feriado', error);
throw error; throw error;
} finally { } finally {
this.loading = false; this.loading = false;
@ -133,6 +145,7 @@ export const useHolidayStore = defineStore('holidays', {
date: holiday.date, date: holiday.date,
estado: holiday.estado, estado: holiday.estado,
municipio: holiday.municipio, municipio: holiday.municipio,
type: holiday.type || '',
}); });
const index = this.holidays.findIndex((s) => s.id === id); const index = this.holidays.findIndex((s) => s.id === id);

View File

@ -2,7 +2,6 @@ import { defineStore } from 'pinia';
import api from '../services/api'; import api from '../services/api';
import router from '../routes/router'; import router from '../routes/router';
import { useAuthStore } from './auth'; import { useAuthStore } from './auth';
import { ca } from 'date-fns/locale';
export const useShiftStore = defineStore('shifts', { export const useShiftStore = defineStore('shifts', {
state: () => ({ state: () => ({

File diff suppressed because it is too large Load Diff

View File

@ -1,132 +1,335 @@
<template> <template>
<v-container class="login-container fill-height" fluid> <div class="slider-container">
<v-row no-gutters class="fill-height align-center justify-center"> <!-- Container de slides que se move horizontalmente -->
<v-col cols="12" md="10" class="d-flex align-center justify-center"> <div class="slides-wrapper" :style="slideStyle">
<!-- Retângulo de fundo --> <!-- Slide 1: Tela Inicial -->
<div class="retangulo-box"> <div class="slide intro-slide">
<!-- Formulário (esquerda) --> <div class="content-container">
<div class="login-form-box"> <!-- Card Inicial - Versão Atualizada -->
<h2 class="title">Bem-vindo</h2> <div class="intro-card">
<p class="subtitle">Realize o login em sua conta</p> <div class="intro-content">
<h1 class="intro-title">Olhe Cada Detalhe</h1>
<p class="intro-subtitle">Inteligência artificial trazendo dados para o seu negócio</p>
<v-btn
color="primary"
class="access-btn"
height="44"
@click="slideToLogin"
>
Acessar
</v-btn>
</div>
</div>
</div>
</div>
<!-- Slide 2: Tela de Login -->
<div class="slide login-slide">
<!-- Retângulo de fundo -->
<div class="retangulo-box">
<!-- Formulário de Login -->
<div class="login-form-box">
<h2 class="title">Bem-vindo</h2>
<p class="subtitle">Realize o login em sua conta</p>
<v-form class="form" v-model="valid" ref="loginForm"> <v-form class="form" v-model="valid" ref="loginForm" @submit.prevent="login">
<v-text-field <div class="input-container">
v-model="email" <label for="email">E-mail</label>
label="E-mail" <input
:rules="emailRules" id="email"
required v-model="email"
hide-details="auto" type="text"
class="custom-field" :class="['custom-input', {'input-error': emailError}]"
></v-text-field> @blur="validateEmail"
/>
<div v-if="emailError" class="error-text">{{ emailError }}</div>
</div>
<v-text-field <div class="input-container">
v-model="password" <label for="password">Senha</label>
label="Senha" <input
:rules="passwordRules" id="password"
@keyup.enter="login" v-model="password"
type="password" type="password"
required :class="['custom-input', {'input-error': passwordError}]"
hide-details="auto" @blur="validatePassword"
class="custom-field" @keyup.enter="login"
></v-text-field> />
<div v-if="passwordError" class="error-text">{{ passwordError }}</div>
</div>
<div class="text-right forgot-password"> <div class="text-right forgot-password">
<a href="#">Esqueci a senha</a> <a href="#">Esqueci a senha</a>
</div> </div>
<v-btn <button
block
color="primary"
class="login-btn" class="login-btn"
height="44" :disabled="!isFormValid"
:disabled="!valid" @click.prevent="login"
@click="login"
> >
Entrar Entrar
</v-btn> </button>
</v-form> </v-form>
<div class="register-link"> <div class="register-link">
Não tem cadastro? <a href="#">Cadastre-se aqui</a> Não tem cadastro? <a href="#">Cadastre-se aqui</a>
</div> </div>
</div> </div>
<!-- Círculo (direita) --> <!-- Gráfico/Círculo informativo -->
<div class="circle-container"> <div class="circle-container">
<img src="/image.png" alt="Círculo" class="circle-img" /> <img src="/image.png" alt="Círculo" class="circle-img" />
</div> </div>
</div> </div>
</v-col> </div>
</v-row> </div>
</v-container> </div>
<!-- Snackbar para mensagens de erro/sucesso -->
<v-snackbar
v-model="snackbar"
:color="snackbarColor"
timeout="5000"
position="top"
>
{{ snackbarText }}
<template v-slot:actions>
<v-btn
variant="text"
@click="snackbar = false"
>
Fechar
</v-btn>
</template>
</v-snackbar>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useAuthStore } from '../stores/auth'; // Certifique-se de que o caminho da store está correto import { useAuthStore } from '../stores/auth'; // Certifique-se de que o caminho da store está correto
const router = useRouter(); const router = useRouter();
const authStore = useAuthStore(); const authStore = useAuthStore();
// Estado
const currentSlide = ref(0); // 0 = intro, 1 = login
const email = ref(''); const email = ref('');
const password = ref(''); const password = ref('');
const emailError = ref('');
const passwordError = ref('');
const sliding = ref(false);
const valid = ref(false); const valid = ref(false);
const emailRules = [ // Estado para o Snackbar
(v) => !!v || 'Preencha o campo', // Verifica se está preenchido const snackbar = ref(false);
(v) => const snackbarText = ref('');
/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(v) || /^[a-zA-Z0-9_.-]+$/.test(v) || const snackbarColor = ref('error'); // 'error', 'success', 'info', etc.
'Insira um e-mail válido ou um nome de usuário', // Permite emails ou usernames simples
];
const passwordRules = [(v) => !!v || 'Preencha a senha']; // Validação computada
const isFormValid = computed(() => {
return email.value && password.value && !emailError.value && !passwordError.value;
});
const login = async () => { // Estilo computado para a posição do slider
try { const slideStyle = computed(() => {
await authStore.login(email.value, password.value); // Ação de login no Pinia return {
router.push('/dashboard'); // Redireciona ao dashboard após login bem-sucedido transform: `translateX(-${currentSlide.value * 100}%)`,
} catch (error) { transition: sliding.value ? 'transform 0.8s ease-in-out' : 'none'
alert('Falha ao fazer login. Verifique suas credenciais.'); // Exibe erro em caso de falha };
});
// Funções de validação
const validateEmail = () => {
if (!email.value) {
emailError.value = 'Preencha o campo';
return;
} }
if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(email.value) &&
!/^[a-zA-Z0-9_.-]+$/.test(email.value)) {
emailError.value = 'Insira um e-mail válido ou um nome de usuário';
return;
}
emailError.value = '';
}; };
const validatePassword = () => {
if (!password.value) {
passwordError.value = 'Preencha a senha';
return;
}
passwordError.value = '';
};
// Métodos para navegação de slides - Melhorados
const slideToLogin = () => {
console.log('Deslizando para o login...');
sliding.value = true;
currentSlide.value = 1;
// Adicionado: Garantir que o estado seja atualizado imediatamente
setTimeout(() => {
console.log('Slide atual após timeout:', currentSlide.value);
}, 100);
};
const slideToIntro = () => {
console.log('Deslizando para a introdução...');
sliding.value = true;
currentSlide.value = 0;
// Adicionado: Garantir que o estado seja atualizado imediatamente
setTimeout(() => {
console.log('Slide atual após timeout:', currentSlide.value);
}, 100);
};
// Função de login usando o authStore do Pinia
const login = async () => {
if (!isFormValid.value) {
// Validar formulário novamente
validateEmail();
validatePassword();
return;
}
try {
console.log('Tentando fazer login com:', email.value, password.value);
await authStore.login(email.value, password.value); // Ação de login no Pinia
// Forçar a navegação para o dashboard usando location.href
// em vez de router.push para garantir que saia do sistema de slides
window.location.href = '/dashboard';
// Alternativa: router.push com opção replace
// router.push({ path: '/dashboard', replace: true });
} catch (error) {
console.error('Erro de login:', error);
// Exibe o Snackbar com a mensagem de erro
snackbarText.value = 'Falha ao fazer login. Verifique suas credenciais.';
snackbarColor.value = 'error';
snackbar.value = true;
}
};
</script> </script>
<style scoped>
<style scoped> /* Container principal que mantém todo o conteúdo e controla overflow */
.slider-container {
.login-container { position: relative;
background: linear-gradient(135deg, #2d0f44, #23408e); width: 100vw;
color: #fff; height: 100vh;
overflow: hidden;
} }
/* Wrapper que desliza horizontalmente contendo ambos os slides */
.slides-wrapper {
display: flex;
width: 100vw; /* Duas vezes a largura da viewport - CORRIGIDO */
height: 100%;
will-change: transform; /* Otimiza a animação */
}
/* Cada slide individual ocupa uma viewport completa */
.slide {
width: 100vw;
height: 100vh;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
}
/* Estilos específicos para cada slide */
.intro-slide {
background: linear-gradient(135deg, #2d0f44, #23408e);
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.login-slide {
background: linear-gradient(135deg, #3a1788, #4c82e6);
}
/* Container para o conteúdo centralizado em cada slide */
.content-container {
display: flex;
width: 100%;
max-width: 1200px;
position: relative;
justify-content: flex-start;
align-items: center;
padding: 0 20px;
}
/* Cartão de introdução */
.intro-card {
background-color: rgba(252, 252, 252, 0.1);
padding: 50px 40px;
border-radius: 20px;
width: 500%;
max-width: 650px;
color: white;
margin-left: 10px;
backdrop-filter: blur(10px);
}
.intro-title {
font-size: 120px;
font-weight: 120;
color: white;
margin-bottom: 15px;
line-height: 1.1;
}
.intro-subtitle {
margin-bottom: 30px;
color: rgba(255, 255, 255, 0.85);
font-size: 22px;
line-height: 1.4;
font-size: 40px;
font-weight: 110;
}
.access-btn {
background: linear-gradient(to right, #5189f9, #4447f1) !important;
color: white !important;
font-weight: 600 !important;
text-transform: none !important;
border-radius: 25px !important;
margin-top: 10px !important;
width: 70% !important;
height: 60px !important;
font-size: 18px !important;
letter-spacing: 0.5px !important;
}
/* Formulário de login */
.retangulo-box { .retangulo-box {
background-image: url('/image_ret.png'); background-image: url('/image_ret.png');
background-repeat: no-repeat; background-size: 98%;
background-size: 110%;
background-position: center; background-position: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100%; width: 110%;
max-width: 1200px; max-width: 1500px;
height: 600px; height: 1500px;
padding: 120px; padding: 110px;
box-sizing: border-box; border-radius: 30px;
position: relative;
} }
.login-form-box { .login-form-box {
background-color: white; width: 34%;
padding: 30px; margin-left: 130px;
border-radius: 20px;
width: 100%;
max-width: 360px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
color: #000;
} }
.title { .title {
@ -137,52 +340,247 @@ const login = async () => {
} }
.subtitle { .subtitle {
margin-bottom: 20px; margin-bottom: 30px;
color: #777; color: rgba(255, 255, 255, 0.7);
} }
.custom-field { .form {
margin-bottom: 20px; width: 100%;
}
.input-container {
margin-bottom: 22px;
}
.input-container label {
display: block;
margin-bottom: 6px;
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
}
.custom-input {
width: 100%;
height: 44px;
padding: 0 16px;
background-color: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
color: white;
font-size: 16px;
box-sizing: border-box;
transition: all 0.3s ease;
}
.custom-input:focus {
outline: none;
border-color: #4c9ff0;
background-color: rgba(255, 255, 255, 0.25);
}
.input-error {
border-color: #ff4d4f;
}
.error-text {
color: #ff4d4f;
font-size: 12px;
margin-top: 4px;
} }
.forgot-password { .forgot-password {
margin-bottom: 20px; text-align: right;
margin-bottom: 25px;
font-size: 14px; font-size: 14px;
} }
.login-btn { .login-btn {
background: linear-gradient(to right, #86b6f6, #b8b4f7); width: 100%;
height: 44px;
background: linear-gradient(to right, #3e7bf4, #5658f5);
color: white; color: white;
font-weight: 600; font-weight: 600;
text-transform: none; border: none;
border-radius: 25px; border-radius: 20px;
margin-bottom: 15px; cursor: pointer;
font-size: 16px;
transition: all 0.3s ease;
} }
.login-btn:hover {
background: linear-gradient(to right, #3568d4, #4a4ce5);
}
.register-link { .register-link {
margin-top: 20px; margin-top: 20px;
font-size: 14px; font-size: 14px;
color: #333; color: rgba(255, 255, 255, 0.8);
text-align: center;
} }
.register-link a, .register-link a,
.forgot-password a { .forgot-password a {
color: #4c9ff0; color: #4c9ff0;
text-decoration: none; text-decoration: none;
font-weight: 500;
} }
.circle-container { .register-link a:hover,
flex: 1; .forgot-password a:hover {
text-decoration: underline;
}
.circle-content {
display: flex; display: flex;
justify-content: flex-end; flex-direction: column;
align-items: center; align-items: center;
padding-left: 20px; justify-content: center;
} }
.circle-img { .circle-img {
width: 100%; width: 270%;
max-width: 320px; max-width: 650px;
height: auto; height: auto;
} }
</style> /* Responsividade */
@media (max-width: 960px) {
.content-container {
flex-direction: column;
height: auto;
padding: 40px 20px;
}
.intro-card {
margin: 0 0 30px 0;
max-width: 90%;
}
.retangulo-box {
flex-direction: column;
padding: 40px 20px;
height: auto;
}
.login-form-box,
.circle-container {
width: 90%;
margin: 15px 0;
}
@media (max-width: 1200px) {
.intro-title {
font-size: 80px;
}
.intro-subtitle {
font-size: 28px;
}
.retangulo-box {
padding: 80px;
background-size: 100%;
}
.login-form-box {
margin-left: 50px;
width: 45%;
}
.circle-img {
width: 200%;
}
}
@media (max-width: 960px) {
.slides-wrapper {
flex-direction: column;
transform: none !important;
}
.slide {
width: 100%;
height: auto;
min-height: 100vh;
}
.content-container {
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.intro-card {
margin: 0;
padding: 40px 20px;
width: 90%;
}
.intro-title {
font-size: 50px;
}
.intro-subtitle {
font-size: 22px;
}
.access-btn {
width: 80% !important;
height: 50px !important;
font-size: 16px !important;
}
.retangulo-box {
flex-direction: column;
padding: 40px 20px;
height: auto;
width: 95%;
}
.login-form-box {
width: 90%;
margin: 0;
}
.circle-container {
margin-top: 30px;
}
.circle-img {
width: 100%;
max-width: 300px;
}
}
@media (max-width: 600px) {
.intro-title {
font-size: 36px;
}
.intro-subtitle {
font-size: 18px;
}
.access-btn {
font-size: 14px !important;
height: 44px !important;
}
.title {
font-size: 24px;
}
.subtitle {
font-size: 16px;
}
.login-btn {
font-size: 14px;
height: 44px;
}
}
}
</style>