31 Commits
Author SHA1 Message Date
FernandoJVideira b2f5bf2124 added docker-compose 2025-08-22 21:30:41 +01:00
FernandoJVideira 157e5332e3 locale fix 2025-04-19 18:45:21 +01:00
FernandoJVideira babcc20166 Website Overall 2025-04-19 18:12:49 +01:00
FernandoJVideira 30c93820f4 Responsive navbar changes 2025-04-15 14:45:15 +01:00
FernandoJVideira c0b1550609 Contact form errors fix 2024-11-19 19:29:03 +00:00
FernandoJVideira 0a22799555 Fixed typos and added TOS agreement checkbox 2024-10-23 16:10:31 +01:00
FernandoJVideira d73c6b567b Fixed Service Cards Not Bing Centered 2024-08-19 05:12:10 +01:00
FernandoJVideira ce0bcac88f Removed useless footer links 2024-07-17 03:34:43 +01:00
FernandoJVideira 64faec6de8 Email Fix 2024-07-17 01:50:35 +01:00
FernandoJVideira b7be5e5881 I hope I'm done ;-; 2024-07-04 10:36:23 +01:00
FernandoJVideira 5a729bf88d Website Rework & bugfixes 2024-07-03 22:06:33 +01:00
FernandoJVideira b9822eb21e Website overall 2024-07-01 16:40:40 +01:00
Fernando Videira b552bc4d3a Merge branch 'develop' 2024-06-27 19:31:39 +01:00
Fernando Videira fe468885b0 Changed buttons to tab style things in the Services cards container 2024-06-27 19:31:27 +01:00
Fernando Videira 946d16609d ;-; 2024-06-24 17:12:48 +01:00
Fernando Videira 3ee54504cc Fixed Titles 2024-06-24 12:38:46 +01:00
Fernando Videira 4fb64cfb88 Services title change 2024-06-21 18:23:38 +01:00
Fernando Videira 3b7d973d27 Footer text Fix 2024-06-20 03:33:12 +01:00
Fernando Videira f3a3931c51 Email Service Fix 2024-06-20 02:51:35 +01:00
Fernando Videira 3a551728c8 Lil hotfix 2024-06-20 00:46:05 +01:00
Fernando Videira 7732ce80e5 Fixed wrong namespace 2024-06-19 20:18:36 +01:00
Fernando Videira 0cd2bf4c38 Added TOS + Privacy 2024-06-19 19:59:42 +01:00
Fernando Videira a21cca3df9 Typos fix 2024-06-16 05:34:37 +01:00
Fernando Videira 79d1865dc0 Typos fixed 2024-06-16 05:16:43 +01:00
Fernando Videira 13212f6170 Fixed Dockerfile and Loading component 2024-06-16 04:46:47 +01:00
Fernando Videira 598395aebe Fixed Image saturation 2024-06-16 02:59:39 +01:00
Fernando Videira 9bc484e1da Merge branch 'develop' 2024-06-16 02:51:45 +01:00
Fernando Videira b226c82db6 Fixed imports 2024-06-16 02:50:24 +01:00
FernandoJVideira 72915f515d Responsive design fix PT 2024-06-11 21:12:13 +01:00
FernandoJVideira fe01646473 minor fix 2024-06-11 20:22:24 +01:00
FernandoJVideira 95f86f13ba Final touches 2024-06-11 19:16:44 +01:00
40 changed files with 1650 additions and 805 deletions
+2 -3
View File
@@ -1,13 +1,12 @@
FROM node:18-alpine
# Path: /app
WORKDIR /lk-results/
COPY . .
RUN npm install
RUN npm audit fix --force
RUN npm run build
RUN npm install -g serve
CMD ["serve", "-s", "build"]
CMD ["serve", "-s", "dist"]
Executable
BIN
View File
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
version: "3.9"
name: lk-landing-page
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: lk-landing-page
environment:
- NODE_ENV=production
restart: unless-stopped
+820 -542
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -13,6 +13,8 @@
"@emailjs/browser": "^4.3.3",
"@headlessui/react": "^1.7.18",
"@heroicons/react": "^2.1.3",
"caniuse-lite": "^1.0.30001696",
"framer-motion": "^12.7.3",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2",
@@ -22,11 +24,13 @@
"react-dom": "^18.2.0",
"react-i18next": "^14.1.2",
"react-icons": "^5.0.1",
"react-phone-number-input": "^3.4.3",
"react-router-dom": "^6.22.3",
"react-toastify": "^10.0.5",
"sort-by": "^0.0.2"
},
"devDependencies": {
"@types/node": "^22.14.1",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.21.0",
@@ -39,6 +43,6 @@
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"vite": "^5.1.0"
"vite": "^6.3.2"
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"pp": {
"lastUpdated": "Last Updated: 06/19/2024",
"pageTitle": "Privacy Policy",
"introText": "LK Results (\"we\", \"our\", or \"us\") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, and safeguard your information when you visit our website lk-results.com (the \"Site\") and use our contact form.",
"pp1": {
"title": "1. Information We Collect",
"desc": "We may collect personal information that you voluntarily provide when you fill out the contact form on our Site. This includes:\n\nName\nEmail address\nPhone number\nCompany name\nAny other information you choose to provide in your message."
},
"pp2": {
"title": "2. How We Use Your Information",
"desc": "We use the information we collect to:\n\nRespond to your inquiries and provide customer support.\nCommunicate with you about your inquiries or services you have requested.\nImprove our Site and the services we offer.\nMonitor and analyze usage and trends to enhance user experience."
},
"pp3": {
"title": "3. Sharing Your Information",
"desc": "We do not sell or rent your personal information to third parties. We may share your information only in the following circumstances:\n\nWith Service Providers: We may share your information with third-party service providers who assist us in operating our Site or conducting our business, so long as those parties agree to keep this information confidential.\nFor Legal Purposes: We may disclose your information if required by law or in response to valid requests by public authorities (e.g., a court or government agency)."
},
"pp4": {
"title": "4. Security of Your Information",
"desc": "We implement appropriate security measures to protect your personal information from unauthorized access, use, or disclosure. However, please note that no method of transmission over the internet or method of electronic storage is 100% secure, and we cannot guarantee absolute security."
},
"pp5": {
"title": "5. Retention of Your Information",
"desc": "We will retain your personal information for as long as necessary to fulfill the purposes for which it was collected or to comply with legal requirements."
},
"pp6": {
"title": "6. Your Rights",
"desc": "Depending on your location, you may have the right to:\n\nAccess and receive a copy of the personal information we hold about you.\nRequest correction or deletion of your personal information.\nObject to or request the restriction of processing your personal information.\nWithdraw your consent to the use of your personal information, where consent was the basis for processing."
},
"pp7": {
"title": "7. Changes to This Privacy Policy",
"desc": "We may update this Privacy Policy from time to time. Changes will be posted on this page, and the \"Last Updated\" date will be revised. Your continued use of the Site after any changes indicates your acceptance of the new Privacy Policy."
},
"pp8": {
"title": "8. Contact Us",
"desc": "If you have any questions about these Privacy Policy, please contact us at:\n\nLK Results\[email protected]\n(+351) 923-543-517"
}
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"pp": {
"lastUpdated": "Última Atualização: 06/19/2024",
"pageTitle": "Política de Privacidade",
"introText": "LK Results (\"nós\", \"nosso\" ou \"nos\") está comprometido com a proteção da sua privacidade. Esta Política de Privacidade explica como recolhemos, utilizamos e protegemos as suas informações quando visita o nosso website lk-results.com (o \"Site\") e utiliza o nosso formulário de contacto.",
"pp1": {
"title": "1. Informações que Recolhemos",
"desc": "Podemos recolher informações pessoais que você fornece voluntariamente ao preencher o formulário de contacto no nosso Site. Isto inclui:\n\nNome\nEndereço de Email\nNúmero de Telefone\nNome da Empresa\nQualquer outra informação que decida fornecer na sua mensagem."
},
"pp2": {
"title": "2. Como Utilizamos as Suas Informações",
"desc": "Utilizamos as informações que recolhemos para:\n\nResponder às suas perguntas e fornecer suporte ao cliente.\nComunicar consigo sobre as suas questões ou serviços que solicitou.\nMelhorar o nosso Site e os serviços que oferecemos.\nMonitorizar e analisar o uso e as tendências para melhorar a experiência do utilizador."
},
"pp3": {
"title": "3. Partilha das Suas Informações",
"desc": "Não vendemos nem alugamos as suas informações pessoais a terceiros. Podemos partilhar as suas informações apenas nas seguintes circunstâncias:\n\nCom Fornecedores de Serviços: Podemos partilhar as suas informações com fornecedores de serviços terceiros que nos assistem na operação do nosso Site ou na condução do nosso negócio, desde que essas partes concordem em manter essa informação confidencial.\nPara Fins Legais: Podemos divulgar as suas informações se exigido por lei ou em resposta a pedidos válidos de autoridades públicas (por exemplo, um tribunal ou agência governamental)."
},
"pp4": {
"title": "4. Segurança das Suas Informações",
"desc": "Implementamos medidas de segurança apropriadas para proteger as suas informações pessoais contra acesso, uso ou divulgação não autorizados. No entanto, note que nenhum método de transmissão pela internet ou método de armazenamento eletrónico é 100% seguro, e não podemos garantir segurança absoluta."
},
"pp5": {
"title": "5. Retenção das Suas Informações",
"desc": "Reteremos as suas informações pessoais pelo tempo necessário para cumprir os propósitos para os quais foram recolhidas ou para cumprir os requisitos legais."
},
"pp6": {
"title": "6. Os Seus Direitos",
"desc": "Dependendo da sua localização, pode ter o direito de:\n\nAceder e receber uma cópia das informações pessoais que mantemos sobre si.\nSolicitar a correção ou eliminação das suas informações pessoais.\nOpor-se ao processamento ou solicitar a restrição do processamento das suas informações pessoais.\nRetirar o seu consentimento para o uso das suas informações pessoais, quando o consentimento foi a base para o processamento."
},
"pp7": {
"title": "7. Alterações a Esta Política de Privacidade",
"desc": "Podemos atualizar esta Política de Privacidade periodicamente. As alterações serão publicadas nesta página, e a data de \"Última Atualização\" será revista. A sua utilização contínua do Site após quaisquer alterações indica a sua aceitação da nova Política de Privacidade."
},
"pp8": {
"title": "8. Contacte-nos",
"desc": "Se tiver alguma pergunta sobre esta Política de Privacidade, por favor, contacte-nos em:\n\nLK Results\[email protected]\n(+351) 923-543-517"
}
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"tos": {
"lastUpdated": "Last Updated: 06/19/2024",
"pageTitle": "Terms and Conditions",
"introText": "Welcome to LK Results (\"we\", \"our\", or \"us\"). By accessing or using LK Results (the \"Site\"), you agree to comply with and be bound by these Terms and Conditions (\"Terms\"). Please read these Terms carefully. If you do not agree with any part of these Terms, you must not use the Site.",
"tos1": {
"title": "1. Use of the Site",
"desc": "You agree to use the Site and the contact form for lawful purposes only.\nYou must provide accurate and complete information when filling out the contact form.\nYou agree not to use the form to submit any content that is illegal, harmful, or intended to harass or impersonate others."
},
"tos2": {
"title": "2. User Responsibilities",
"desc": "You are responsible for ensuring the security and confidentiality of any personal information you provide.\nYou agree not to use the contact form to send unsolicited commercial messages or spam."
},
"tos3": {
"title": "3. Intellectual Property Rights",
"desc": "All content and materials on the Site, including text, graphics, logos, and images, are the property of LK Results or its licensors. You may not use, reproduce, or distribute any content without our prior written permission."
},
"tos4": {
"title": "4. Limitation of Liability",
"desc": "To the fullest extent permitted by law, LK Results shall not be liable for any damages resulting from your use of the Site or the contact form, including but not limited to direct, indirect, incidental, or consequential damages."
},
"tos5": {
"title": "5. Changes to Terms",
"desc": "We may update these Terms from time to time. Changes will be posted on this page, and the \"Last Updated\" date will be revised. Your continued use of the Site after any changes indicates your acceptance of the new Terms."
},
"tos6": {
"title": "6. Contact Us",
"desc": "If you have any questions about these Terms, please contact us at:\n\nLK Results\[email protected]\n(+351) 923-543-517"
}
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"tos": {
"lastUpdated": "Última Atualização: 19/06/2024",
"pageTitle": "Termos e Condições",
"introText": "Bem-vindo(a) ao LK Results (\"nós\", \"nosso\" ou \"nos\"). Ao aceder ou utilizar LK Results (o \"Site\"), você concorda em cumprir e estar vinculado por estes Termos e Condições (\"Termos\"). Por favor, leia estes Termos cuidadosamente. Se não concordar com qualquer parte destes Termos, não deve utilizar o Site.",
"tos1": {
"title": "1. Utilização do Site",
"desc": "Concorda em utilizar o Site e o formulário de contacto apenas para fins legais.\nDeve fornecer informações precisas e completas ao preencher o formulário de contacto.\nConcorda em não utilizar o formulário para enviar qualquer conteúdo que seja ilegal, prejudicial ou destinado a assediar ou fazer-se passar por outras pessoas."
},
"tos2": {
"title": "2. Responsabilidades do Utilizador",
"desc": "É responsável por garantir a segurança e a confidencialidade de qualquer informação pessoal que forneça.\nConcorda em não utilizar o formulário de contacto para enviar mensagens comerciais não solicitadas ou spam."
},
"tos3": {
"title": "3. Direitos de Propriedade Intelectual",
"desc": "Todo o conteúdo e materiais no Site, incluindo texto, gráficos, logótipos e imagens, são propriedade do LK Results ou dos seus licenciadores. Não pode utilizar, reproduzir ou distribuir qualquer conteúdo sem a nossa permissão prévia por escrito."
},
"tos4": {
"title": "4. Limitação de Responsabilidade",
"desc": "Na medida máxima permitida por lei, LK Results não será responsável por quaisquer danos resultantes da sua utilização do Site ou do formulário de contacto, incluindo, mas não limitado a danos diretos, indiretos, incidentais ou consequenciais."
},
"tos5": {
"title": "5. Alterações aos Termos",
"desc": "Podemos atualizar estes Termos periodicamente. As alterações serão publicadas nesta página, e a data de \"Última Atualização\" será revista. A sua utilização contínua do Site após quaisquer alterações indica a sua aceitação dos novos Termos."
},
"tos6": {
"title": "6. Contacte-nos",
"desc": "Se tiver alguma pergunta sobre estes Termos, por favor, contacte-nos em:\n\nLK Results\[email protected]\n(+351) 923-543-517"
}
}
}
@@ -14,17 +14,14 @@
"part1": "We do all the digital footprint for you,",
"part2": "so you can focus on what's important!"
},
"button": "Yes, I want that!"
"button": "Contact Us"
},
"services": {
"heading": {
"part1": "We've Got Your",
"part2": "Footprint",
"part3": "Bullsh",
"part4": "*",
"part5": "t Covered!"
"part1": "Unlock The Full ",
"part2": "Potential",
"part3": "Of Your Business!"
},
"subheading": "Unlock the full potential of your business with our services.",
"cards": {
"marketing": {
"card1": {
@@ -102,8 +99,8 @@
},
"optimization": {
"heading": {
"part1": "So How Do You Optimize",
"part2": "Your Business?"
"part1": "So How Do I Optimize",
"part2": "My Business?"
},
"cards": {
"card1": {
@@ -123,7 +120,7 @@
"results": {
"heading": {
"part1": "Ok... So What Makes",
"part2": "Us Different?"
"part2": "You Different?"
},
"subheading": "We have helped many businesses grow and expand their digital footprint.",
"cards": {
@@ -153,8 +150,8 @@
"part4": "Analysis"
},
"subheading": {
"part1": "Would you like to know what we could do for you? Fill out the form ad well get",
"part2": "back to you No obligation, no annoying tactics. We wont waste your time."
"part1": "Would you like to know what we could do for you? Fill out the form and well get",
"part2": "back to you. No obligation, no annoying tactics. We wont waste your time."
},
"form": {
"name": "Name",
@@ -162,13 +159,25 @@
"company": "Company",
"phone": "Phone",
"question": "What is your most important question?",
"message": "Message",
"button": "Send"
"reference": "How did you hear about us?",
"button": "Send",
"agreement": "By submitting this form, you agree to our ",
"tos": "Terms of Service",
"and": " and ",
"privacy": "Privacy Policy"
},
"toasts": {
"success": "Your message has been sent successfully!",
"error": "An error occurred while sending your message. Please try again later.",
"empty": "Please fill out all the fields before sending the message."
"empty": "Please fill out all the fields before sending the message.",
"invalidCountryCode": "Please enter a valid country code.",
"invalidEmail": "Please enter a valid email address.",
"invalidPhone": "Please enter a valid phone number."
}
},
"footer": {
"rights": "© 2024 LK Results. All rights reservered.",
"tos": "Terms of Service",
"privacy": "Privacy Policy"
}
}
@@ -7,24 +7,21 @@
},
"hero": {
"heading": {
"part1": "CRESÇA O",
"part2": "SEU NEGÓCIO"
"part1": "CRESCE O",
"part2": "TEU NEGÓCIO"
},
"subheading": {
"part1": "Nós tratamos de toda a sua presença digital,",
"part2": "para que possa concentrar-se no que é importante!"
"part1": "Nós tratamos da tua presença digital,",
"part2": "para que te possas concentrar no que é importante!"
},
"button": "Eu quero isto!"
"button": "Contacta-nos"
},
"services": {
"heading": {
"part1": "Nós Cuidamos Da Sua",
"part2": "Presença",
"part3": "Digital",
"part4": " ",
"part5": "Por Completo!"
"part1": "Desbloqueia Todo O ",
"part2": "Potencial",
"part3": "Do Teu Negócio!"
},
"subheading": "Desbloqueie todo o potencial do seu negócio com os nossos serviços.",
"cards": {
"marketing": {
"card1": {
@@ -108,7 +105,7 @@
"cards": {
"card1": {
"title": "TRATAR DE TUDO SOZINHO?",
"description": "Está ótimo se tens muito pouco em mãos.\n\nMas se já estás bastante ocupado... esta não é uma opção viável."
"description": "É ótimo se tens muito pouco em mãos.\n\nMas se já estás bastante ocupado... esta não é uma opção viável."
},
"card2": {
"title": "NOVA STAFF?",
@@ -116,20 +113,20 @@
},
"card3": {
"title": "CONTRATAR UMA AGÊNCIA?",
"description": "Não tens um orçamento de dezenas de milhares de euros?\n\n Então a tua conta pode acabar a ser gerida pelo estagiário do assistente do assistente...\n\n Não é o cenário ideal."
"description": "Não tens um orçamento de dezenas de milhares de euros?\n\n Então a tua conta pode acabar por ser gerida pelo estagiário do assistente do assistente...\n\n Não é o cenário ideal."
}
}
},
"results": {
"heading": {
"part1": "Ok... Então O Que Nos Torna",
"part1": "Ok... Então O Que Vos Torna",
"part2": "Diferentes?"
},
"subheading": "Ajudamos muitos negócios a crescer e expandir a sua presença digital.",
"cards": {
"card1": {
"title": "GARANTIDO",
"description": "Nós só ganhamos se você ganhar!\nEssa é a base para uma boa\nparceria.\nVocê não vai carregar todo o risco,\nnós vamos compartilhá-lo."
"description": "Nós só ganhamos se tu ganhares!\nEssa é a base para uma boa\nparceria. Não vais carregar todo o risco,\n vamos compartilhá-lo."
},
"card2": {
"title": "RESULTADOS",
@@ -137,38 +134,50 @@
},
"card3": {
"title": "LOCAL",
"description": "Não somos um centro de chamadas anónimo. Somos uma\nempresa local, por isso pode\ncontactar-nos quando precisar."
"description": "Não somos um centro de chamadas anónimo. Somos uma\nempresa local, por isso podes\ncontactar-nos quando precisares."
},
"card4": {
"title": "ESPECIALIZADO",
"description": "Faz-tudo... mestre de nada. A especialização\nfunciona. Trabalhamos com\nindústrias que conhecemos, para que possamos\ngarantir resultados."
"description": "Quem faz tudo, nada faz... A especialização\nfunciona. Trabalhamos com\nindústrias que conhecemos, para que possamos\ngarantir resultados."
}
}
},
"contact": {
"heading": {
"part1": "Contacte-nos para uma",
"part1": "Contacta-nos para uma",
"part2": "Análise",
"part3": "Gratuita",
"part4": ""
},
"subheading": {
"part1": "Gostaria de saber o que podemos fazer por si? Preencha o formulário e nós entraremos",
"part2": "em contacto consigo. Sem obrigações, sem táticas irritantes. Não vamos desperdiçar o seu tempo."
"part1": "Gostarias de saber o que podemos fazer por ti? Preenche o formulário e nós entraremos",
"part2": "em contacto contigo. Sem obrigações, sem táticas irritantes. Não vamos desperdiçar o teu tempo."
},
"form": {
"name": "Nome",
"email": "Email",
"company": "Empresa",
"phone": "Telefone",
"question": "Qual é a sua pergunta mais importante?",
"message": "Mensagem",
"button": "Enviar"
"question": "Qual é a tua pergunta mais importante?",
"reference": "Como nos conheceste?",
"button": "Enviar",
"agreement": "Ao enviar este formulário, concordas com os nossos",
"tos": "Termos de Serviço",
"and": " e ",
"privacy": "Política de Privacidade."
},
"toasts": {
"success": "A sua mensagem foi enviada com sucesso!",
"error": "Ocorreu um erro ao enviar a sua mensagem. Por favor, tente novamente mais tarde.",
"empty": "Por favor, preencha todos os campos antes de enviar a mensagem."
"success": "A tua mensagem foi enviada com sucesso!",
"error": "Ocorreu um erro ao enviar a tua mensagem. Por favor, tenta novamente mais tarde.",
"empty": "Por favor, preenche todos os campos antes de enviar a mensagem.",
"invalidCountryCode": "Por favor, preenche um código de país válido.",
"invalidEmail": "Por favor, preenche um endereço de email válido.",
"invalidPhone": "Por favor, preenche um número de telefone válido."
}
},
"footer": {
"rights": "©2024 Direitos reservados a LK Results",
"tos": "Termos de Serviço",
"privacy": "Política de Privacidade"
}
}
+4
View File
@@ -1,11 +1,15 @@
import { Route, Routes } from "react-router-dom";
import Home from "./routes/Home";
import TOS from "./routes/TOS";
import PP from "./routes/PP";
export default function App() {
return (
<div className="font-baijam min-h-screen text-white">
<Routes>
<Route path="*" element={<Home />} />
<Route path="tos" element={<TOS />} />
<Route path="privacy" element={<PP />} />
</Routes>
</div>
);
+4 -4
View File
@@ -9,11 +9,11 @@ const Card = ({ title, description, image }: IProps) => {
const imgClasses = image ? defaultImgClasses : `${defaultImgClasses} hidden`;
return (
<div className="mt-5 xl:mt-0 flex flex-1 bg-gradient-to-b from-gold-light to-gold-dark p-0.5 shadow-lg">
<div className="w-full font-medium flex-col md:px-4 py-1 bg-gradient-to-br from-custom-bglight to-custom-bgdark">
<div className="glass-premium mt-5 xl:mt-0 flex flex-1 border-2 border-gold-accent shadow-xl rounded-2xl transition-transform duration-300 hover:scale-105 hover:shadow-gold-accent/40">
<div className="w-full font-medium flex-col md:px-4 py-1 bg-gradient-to-br from-custom-bglight to-custom-bgdark rounded-2xl">
<object type="image/svg+xml" data={image} className={imgClasses} aria-label={title}></object>
<h1 className="mt-2 lg:mt-6 text-3xl xl:text-5xl">{title}</h1>
<h2 className="px-2 mb-2 mt-3 italic text-xl xl:text-3xl font-medium text-gray-300">
<h1 className="mt-2 lg:mt-6 text-2xl md:text-3xl xl:text-4xl font-bold text-white drop-shadow-lg break-words truncate max-w-full whitespace-normal">{title}</h1>
<h2 className="results-card-content">
{description.split("\n").map((line, i) => (
<span key={i}>
{line}
+8 -31
View File
@@ -1,38 +1,15 @@
import { FaFacebook, FaLinkedin, FaInstagram } from "react-icons/fa";
import { FaXTwitter } from "react-icons/fa6";
import { useTranslation } from "react-i18next";
const Footer = () => {
const { t } = useTranslation();
return (
<footer className="flex flex-col space-y-10 justify-center m-10">
<div className="flex justify-center space-x-5">
<a
href="https://www.facebook.com/people/LK-Marketing/61556714037746/"
target="_blank"
rel="noopener noreferrer"
>
<FaFacebook className="text-gold-light" size={30} />
</a>
<a
href="https://www.linkedin.com/company/lk-results"
target="_blank"
rel="noopener noreferrer"
>
<FaLinkedin className="text-gold-light" size={30} />
</a>
{/* <a
href="https://instagram.com"
target="_blank"
rel="noopener noreferrer"
>
<FaInstagram className="text-gold-light" size={30} />
</a>
<a href="https://twitter.com" target="_blank" rel="noopener noreferrer">
<FaXTwitter className="text-gold-light" size={30} />
</a> */}
<footer className="flex flex-col items-center justify-center py-6 px-4 mt-10 border-t border-gray-800 bg-transparent">
<div className="flex space-x-6 mb-2">
<a href="/tos" className="text-sm text-gray-400 hover:underline">{t("footer.tos")}</a>
<a href="/privacy" className="text-sm text-gray-400 hover:underline">{t("footer.privacy")}</a>
</div>
<p className="text-center text-gray-400 font-medium">
&copy; 2024 LK Results. All rights reservered.
</p>
<p className="text-xs text-gray-400 text-center">{t("footer.rights")}</p>
</footer>
);
};
+9
View File
@@ -0,0 +1,9 @@
const Loading = () => {
return (
<div className="flex items-center justify-center h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>
</div>
);
};
export default Loading;
+102 -28
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { FaTimes, FaBars } from "react-icons/fa";
import Logo from "../../assets/logo.svg";
@@ -9,6 +9,7 @@ export default () => {
const { t, i18n } = useTranslation();
const [colorChange, setColorChange] = useState(false);
useEffect(() => {
const changeColor = () => {
if (window.scrollY >= 100) {
setColorChange(true);
@@ -19,6 +20,17 @@ export default () => {
window.addEventListener("scroll", changeColor);
const scrollPath = sessionStorage.getItem("scrollPath");
if (scrollPath) {
smoothScroll(scrollPath);
sessionStorage.removeItem("scrollPath");
}
return () => {
window.removeEventListener("scroll", changeColor);
};
}, []);
const navigation = [
{ title: t("navbar.services"), path: "Services" },
{ title: t("navbar.optimization"), path: "Optimization" },
@@ -27,6 +39,19 @@ export default () => {
];
const handleNavigation = (path: string) => {
if (
(window.location.pathname === "/tos" ||
window.location.pathname === "/privacy") &&
navigation.some((navItem) => navItem.path === path)
) {
sessionStorage.setItem("scrollPath", path);
window.location.href = "/";
} else {
smoothScroll(path);
}
};
const smoothScroll = (path: string) => {
const element = document.getElementById(path);
const navbarHeight = 110; // Replace with your navbar's height
if (element) {
@@ -43,55 +68,96 @@ export default () => {
return (
<nav
className={`${
colorChange
? "bg-black bg-opacity-80 transition duration-500 w-full border-0 sticky top-0 z-10"
colorChange || state
? "bg-custom-bglight w-full border-0 sticky top-0 z-10"
: "z-10"
}`}
>
<div className="items-center px-4 max-w-screen-xl mx-auto md:flex md:px-8">
<div className="flex items-center justify-between py-3 md:py-5 md:block">
<div className="items-center px-4 max-w-[1400px] mx-auto w-full flex flex-col lg:flex-row lg:justify-between lg:px-8">
<div className="flex flex-row items-center justify-between w-full py-3 lg:py-5 lg:justify-between">
<a
href="Top"
onClick={(e) => {
e.preventDefault(); // Prevent the default anchor tag behavior
window.scrollTo({ top: 0, behavior: "smooth" }); // Scroll to the top of the page
if (window.location.pathname === "/") {
window.scrollTo({ top: 0, behavior: "smooth" });
} else {
window.location.href = "/";
}
}}
>
<img src={Logo} width={60} height={60} alt="logo" />
</a>
<div className="md:hidden">
{/* Only show hamburger button when menu is closed */}
{!state && (
<div className="lg:hidden">
<button
className="outline-none p-2 rounded-md focus:border-gray-400 focus:border"
onClick={() => setState(!state)}
onClick={() => setState(true)}
>
{state ? (
<FaTimes className="h-6 w-6" />
) : (
<FaBars className="h-6 w-6" />
)}
</button>
</div>
)}
</div>
<div
className={`absolute top-[3.24rem] md:top-0 left-0 w-full z-50 flex flex-col md:flex-row items-center justify-center pb-3 mt-8 md:relative md:bg-transparent md:pb-0 md:mt-0 ${
state
? colorChange
? "block bg-black bg-opacity-80 transition duration-500"
: "block bg-gradient-to-br from-custom-bglight to-custom-bgdark"
: "hidden md:flex"
}`}
>
<ul className="text-text-gold space-y-8 md:ml-auto md:flex md:space-x-6 md:space-y-0 justify-center items-center">
{/* Center nav items for desktop */}
<div className="hidden lg:flex lg:items-center lg:justify-center lg:absolute lg:left-1/2 lg:-translate-x-1/2">
<ul className="flex space-x-6 items-center">
{navigation.map((item, idx) => {
return (
<li key={idx} className="hover:text-white">
<li key={idx} className="text-white hover:text-gold-accent text-center transition-colors duration-200">
<a
href={item.path}
className="block py-2 md:py-0 md:px-4 text-xl font-medium"
className="block py-0 px-4 text-xl font-medium"
onClick={(e) => {
e.preventDefault(); // Prevent the default anchor tag behavior
setState(false); // Close the navigation
handleNavigation(item.path); // Navigate to the section
e.preventDefault();
handleNavigation(item.path);
}}
>
{item.title}
</a>
</li>
);
})}
</ul>
</div>
{/* Language toggle for desktop */}
<div className="hidden lg:flex lg:items-center ml-auto">
<button
className="text-white hover:text-gold-accent transition-colors duration-200"
onClick={() => changeLanguage(i18n.language === "en" ? "pt" : "en")}
>
{i18n.language === "en" ? "PT" : "EN"}
</button>
</div>
{/* Mobile slide-in menu */}
<div
className={`glass-premium fixed top-0 right-0 h-screen w-[70%] z-50 flex flex-col items-center justify-center pt-16 shadow-lg transition-transform duration-300 ease-in-out lg:hidden ${
state ? "transform-none" : "translate-x-full"
}`}
>
<button
className="absolute top-4 left-4 text-text-gold hover:text-white"
onClick={() => setState(false)}
>
<FaTimes className="h-6 w-6" />
</button>
<ul className="space-y-8 text-center justify-center items-center">
{navigation.map((item, idx) => {
return (
<li key={idx} className="text-white hover:text-gold-accent w-full text-center transition-colors duration-200">
<a
href={item.path}
className="block py-2 text-xl font-medium"
onClick={(e) => {
e.preventDefault();
setState(false);
handleNavigation(item.path);
}}
>
{item.title}
@@ -101,12 +167,20 @@ export default () => {
})}
</ul>
<button
className="mt-5 md:mt-0 md:ml-auto"
className="absolute bottom-10 right-10 text-white hover:text-gold-accent transition-colors duration-200"
onClick={() => changeLanguage(i18n.language === "en" ? "pt" : "en")}
>
{i18n.language === "en" ? "PT" : "EN"}
</button>
</div>
{/* Dark overlay behind menu */}
{state && (
<div
className="fixed top-0 left-0 w-full h-screen bg-black bg-opacity-60 z-40 lg:hidden"
onClick={() => setState(false)}
/>
)}
</div>
</nav>
);
@@ -0,0 +1,10 @@
.PhoneInputInput {
background-color: transparent;
border: none;
outline: none;
width: 100%;
}
.PhoneInputCountrySelect {
background-color: #1C1C1C;
}
+100 -24
View File
@@ -1,11 +1,20 @@
import React from "react";
import React, { useState } from "react";
import emailjs from "@emailjs/browser";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { useTranslation } from "react-i18next";
import PhoneInput, {
formatPhoneNumber,
isValidPhoneNumber,
parsePhoneNumber,
} from "react-phone-number-input";
import "react-phone-number-input/style.css";
import "./ContactForm.css";
import PremiumButton from "../../Shared/PremiumButton";
const ContactForm = () => {
const { t } = useTranslation();
const [phone, setPhone] = useState<string | undefined>(undefined);
const notify = () => {
toast.success(t("contact.toasts.success"));
@@ -19,6 +28,19 @@ const ContactForm = () => {
toast.warning(t("contact.toasts.empty"));
};
const validateEmail = (email: string) => {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
};
const invalidEmail = () => {
toast.error(t("contact.toasts.invalidEmail"));
};
const invalidCountryCode = () => {
toast.error(t("contact.toasts.invalidCountryCode"));
};
const sendEmail = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@@ -28,13 +50,41 @@ const ContactForm = () => {
(input as HTMLInputElement).value.trim() === ""
);
if (isEmpty) {
if (isEmpty || !phone) {
if (!phone || !isValidPhoneNumber(phone)) {
toast.error(t("contact.toasts.invalidPhone"));
} else {
emptyForm();
}
return;
}
const email = e.currentTarget.user_email.value;
if (!validateEmail(email)) {
invalidEmail();
return;
}
let parsedPhone = parsePhoneNumber(phone);
let countryCode = parsedPhone?.countryCallingCode;
if (!countryCode) {
invalidCountryCode();
return;
}
// Append phone number to the form
const phoneInput = document.createElement("input");
phoneInput.setAttribute("type", "hidden");
phoneInput.setAttribute(
"value",
`+${countryCode} ${formatPhoneNumber(phone)}`
);
phoneInput.setAttribute("name", "user_phone");
e.currentTarget.appendChild(phoneInput);
emailjs
.sendForm("service_42i3ig6", "template_f30zlog", e.currentTarget, {
.sendForm("service_o7kxotj", "template_f30zlog", e.currentTarget, {
publicKey: "vcSOwPedqkRH3nmr9",
})
.then(
@@ -49,40 +99,48 @@ const ContactForm = () => {
);
e.currentTarget.reset();
setPhone(undefined); // Reset phone state
};
return (
<div className="mt-10 mx-5 lg:mx-0 flex justify-center items-center">
<form onSubmit={sendEmail} className="w-full md:w-auto">
<div className="flex flex-col md:flex-row md:gap-3">
<div className="flex flex-col md:flex-row md:gap-4">
<input
className="form-input"
className="form-input flex-1"
type="text"
name="user_name"
placeholder={t("contact.form.name")}
/>
<input
className="form-input"
className="form-input flex-1"
type="email"
name="user_email"
placeholder={t("contact.form.email")}
/>
</div>
<div className="flex flex-col md:flex-row md:gap-3">
<div className="flex flex-col md:flex-row md:gap-4 md:mt-4">
<input
className="form-input"
className="form-input md:w-1/2"
type="text"
name="user_company"
placeholder={t("contact.form.company")}
/>
<input
className="form-input"
type="number"
name="user_phone"
<div className="md:w-1/2 flex items-center">
<PhoneInput
defaultCountry="PT"
placeholder={t("contact.form.phone")}
className="form-input placeholder-white w-full"
value={phone}
onChange={setPhone}
inputprops={{
name: "user_phone",
required: true,
}}
/>
</div>
<div className="flex flex-col">
</div>
<div className="flex flex-col md:mt-4">
<input
className="form-input"
type="text"
@@ -90,21 +148,39 @@ const ContactForm = () => {
placeholder={t("contact.form.question")}
></input>
<textarea
className="form-input"
name="message"
placeholder={t("contact.form.message")}
rows={6}
></textarea>
<div className="md:mt-4">
<input
className="form-input w-full"
name="user_reference"
placeholder={t("contact.form.reference")}
></input>
</div>
<div className="flex mt-5 mb-10 lg:mt-12 mx-10 lg:mx-20 rounded-xl bg-gradient-to-br from-gold-light to-gold-dark p-0.5 shadow-lg">
<button
type="submit"
className="flex-1 font-medium md:px-8 py-3 text-2xl md:text-3xl bg-gradient-to-br from-custom-bglight to-custom-bgdark rounded-xl hover:bg-transparent hover:from-transparent hover:to-transparent transition duration-500"
<div className="mt-3 md:mt-4">
<div className="flex items-center">
<input
type="checkbox"
id="user_agreement"
name="user_agreement"
required
/>
<label
htmlFor="user_agreement"
className="ml-2 text-xs md:text-sm"
>
{t("contact.form.agreement")}{" "}
<a href="/tos">{t("contact.form.tos")}</a>{" "}
{t("contact.form.and")}{" "}
<a href="/privacy">{t("contact.form.privacy")}</a>
</label>
</div>
</div>
</div>
<div className="flex mx-10 md:mx-40 lg:mx-0 lg:ml-auto mt-5 mb-10 lg:mt-12">
<PremiumButton type="submit" className="sm:mx-40">
{t("contact.form.button")}
</button>
</PremiumButton>
</div>
<ToastContainer
+1 -1
View File
@@ -10,7 +10,7 @@ const ContactPage = () => {
<h1>
{t("contact.heading.part1")} <br />
{t("contact.heading.part2")}{" "}
<span className="gold-text">{t("contact.heading.part3")}</span>{" "}
<span className="gold-text"><span className="gold-text">{t("contact.heading.part3")}</span></span>{" "}
{t("contact.heading.part4")}
</h1>
<h2 className="sub-header-text ">
+28 -18
View File
@@ -1,9 +1,10 @@
import { useTranslation } from "react-i18next";
import PremiumButton from "../../Shared/PremiumButton";
import HeroImg from "../../../assets/HeroImg.svg";
import Stars from "../../../assets/Stars.svg";
const Hero = () => {
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const handleNavigation = (path: string) => {
const element = document.getElementById(path);
@@ -21,15 +22,23 @@ const Hero = () => {
<img className="items-center" src={HeroImg} alt="hero"></img>
</div>
<div className="justify-center items-center lg:w-1/2 xl:w-1/2">
<div className="flex flex-col text-center lg:pt-10 lg:pr-20 lg:text-right 2xl:pr-40">
<div className="flex flex-col text-center pt-4 lg:pt-10 lg:pr-20 lg:text-right">
<img
src={Stars}
alt="hero"
className="md:px-0 ml-auto mr-auto lg:mr-0 lg:text-right"
></img>
<div>
<h1 className="hero-text">{t("hero.heading.part1")}</h1>
<h1 className="hero-text">{t("hero.heading.part2")}</h1>
<h1
className={i18n.language === "en" ? "hero-text" : "hero-text-pt"}
>
{t("hero.heading.part1")}
</h1>
<h1
className={i18n.language === "en" ? "hero-text" : "hero-text-pt"}
>
{t("hero.heading.part2")}
</h1>
<div>
<img
src={HeroImg}
@@ -39,26 +48,27 @@ const Hero = () => {
</div>
</div>
<div className="mx-5 lg:mx-0">
<h2 className="italic text-lg md:text-2xl 2xl:text-3xl mt-4 md:mt-10">
<h2
className={
i18n.language === "en"
? "hero-sub-text mt-4 md:mt-10"
: "hero-sub-text-pt mt-4 md:mt-10"
}
>
{t("hero.subheading.part1")}
</h2>
<h2 className="italic text-lg md:text-2xl 2xl:text-3xl">
<h2
className={
i18n.language === "en" ? "hero-sub-text" : "hero-sub-text-pt"
}
>
{t("hero.subheading.part2")}
</h2>
</div>
<div className="flex mt-5 lg:mt-12 mx-10 lg:mx-0 lg:ml-auto rounded-xl bg-gradient-to-br from-gold-light to-gold-dark p-0.5 shadow-lg">
<a
className="flex w-full"
href="Contact"
onClick={(e) => {
e.preventDefault();
handleNavigation("Contact");
}}
>
<button className="flex-1 font-medium md:px-8 py-3 text-2xl md:text-3xl bg-gradient-to-br from-custom-bglight to-custom-bgdark rounded-xl hover:bg-transparent hover:from-transparent hover:to-transparent transition duration-500">
<div className="flex mt-8 lg:mt-16 mx-10 md:mx-20 lg:mx-0 lg:ml-auto">
<PremiumButton onClick={() => handleNavigation("Contact")} className="sm:mx-40 lg:mx-0">
{t("hero.button")}
</button>
</a>
</PremiumButton>
</div>
</div>
</div>
@@ -1,7 +1,7 @@
import Card from "../../Card";
import yourself from "../../../assets/Yourself.svg"
import staff from "../../../assets/staff.svg"
import agency from "../../../assets/agency.svg"
import staff from "../../../assets/Staff.svg"
import agency from "../../../assets/Agency.svg"
import { useTranslation } from "react-i18next";
const CardContainer = () => {
@@ -2,7 +2,7 @@ import CardContainer from './CardContainer'
import { useTranslation } from 'react-i18next'
const Optimization = () => {
const { t } = useTranslation('optimization')
const { t } = useTranslation()
return (
<div className="header-text">
<h1>
+1 -1
View File
@@ -5,7 +5,7 @@ const ResultsPage = () => {
const { t } = useTranslation();
return (
<div className="header-text">
<div className="header-text p-8 md:p-16 mx-4 my-10 rounded-2xl">
<h1>
<span className="gold-text">"</span>
{t("results.heading.part1")} <br /> {t("results.heading.part2")}
+2 -2
View File
@@ -16,7 +16,7 @@ const Buttons = ({ changeActiveGroup }: props) => {
};
return (
<div className="lg:px-40 flex flex-row 2xl:gap-8 gap-2 mx-2 md:gap-5 mt-10 mb-4 flex-wrap justify-center">
<div className="lg:px-40 mx-5 gap-5 flex flex-row flex-1 2xl:gap-8 md:gap-5 mt-5 lg:mt-10 mb-4 justify-center">
<CardBtn
title="Marketing"
onClick={() => handleClick("markCards")}
@@ -33,7 +33,7 @@ const Buttons = ({ changeActiveGroup }: props) => {
isSelected={selectedButton === "smCards"}
/>
<CardBtn
title="Camera & Edits"
title="Video & Edits"
onClick={() => handleClick("camCards")}
isSelected={selectedButton === "camCards"}
/>
@@ -9,7 +9,7 @@ const CameraCards = () => {
const { t } = useTranslation();
return (
<div className="h-full flex lg:mt-20 flex-col flex-wrap gap-4 xl:flex-row mx-4">
<div className="h-full flex lg:mt-20 flex-col flex-wrap md:flex-row md:mx-4 gap-5">
<Card
title={t("services.cards.media.card1.title")}
description={t("services.cards.media.card1.description")}
@@ -20,19 +20,19 @@ const CameraCards = () => {
title={t("services.cards.media.card2.title")}
description={t("services.cards.media.card2.description")}
imgSrc={CaptureImg}
delay="0.2s"
delay="0.1s"
/>
<Card
title={t("services.cards.media.card3.title")}
description={t("services.cards.media.card3.description")}
imgSrc={EditImg}
delay="0.4s"
delay="0.2s"
/>
<Card
title={t("services.cards.media.card4.title")}
description={t("services.cards.media.card4.description")}
imgSrc={PublishImg}
delay="0.6s"
delay="0.3s"
/>
</div>
);
+4 -10
View File
@@ -6,19 +6,13 @@ interface props {
const CardBtn = ({ title, onClick, isSelected }: props) => {
const buttonClass = isSelected
? "w-full font-bold md:px-4 py-1 text-xs lg:text-sm xl:text-lg bg-gradient-to-br from-custom-bglight to-custom-bgdark"
: "w-full font-medium md:px-4 py-1 text-xs lg:text-sm xl:text-lg bg-gradient-to-br from-custom-bglight to-custom-bgdark text-gray-400 shadow-inner";
const divClass = isSelected
? "flex flex-1 flex-row mt-5 lg:mt-12 bg-gradient-to-b from-gold-light to-gold-dark p-0.5 shadow-lg transform scale-110"
: "flex flex-1 flex-row mt-5 lg:mt-12 bg-gradient-to-b from-gold-light to-gold-dark p-0.5 shadow-lg";
? "w-full font-bold md:px-4 py-1 text-xs lg:text-sm xl:text-xl cursor-pointer"
: "w-full font-medium md:px-4 py-1 text-xs lg:text-sm xl:text-xl text-gray-400 shadow-inner hover:text-text-gold cursor-pointer";
return (
<div className={divClass}>
<button className={buttonClass} onClick={onClick}>
<a className={buttonClass} onClick={onClick}>
{title}
</button>
</div>
</a>
);
};
+2 -2
View File
@@ -1,5 +1,5 @@
import { useState } from "react";
import WesiteCards from "./WesiteCards";
import WesiteCards from "./WebsiteCards";
import Buttons from "./Buttons";
import MarketingCards from "./MarketingCards";
import CameraCards from "./CameraCards";
@@ -31,7 +31,7 @@ const CardGroup = () => {
}
return (
<div className="h-full font-medium md:px-8 py-3 text-2xl md:text-3xl bg-gradient-to-br from-custom-bglight to-custom-bgdark rounded-xl flex flex-col">
<div className="h-full font-medium md:px-8 py-3 text-2xl md:text-3xl flex flex-col">
{ActiveCards && <ActiveCards />}
<Buttons changeActiveGroup={changeActiveGroup} />
</div>
@@ -9,7 +9,7 @@ const MarketingCards = () => {
const { t } = useTranslation();
return (
<div className="h-full flex lg:mt-20 flex-col flex-wrap gap-4 xl:flex-row mx-4">
<div className="h-full flex lg:mt-20 flex-col flex-wrap md:flex-row md:mx-4 gap-5">
<Card
title={t("services.cards.marketing.card1.title")}
description={t("services.cards.marketing.card1.description")}
@@ -20,19 +20,19 @@ const MarketingCards = () => {
title={t("services.cards.marketing.card2.title")}
description={t("services.cards.marketing.card2.description")}
imgSrc={CreateImg}
delay="0.2s"
delay="0.1s"
/>
<Card
title={t("services.cards.marketing.card3.title")}
description={t("services.cards.marketing.card3.description")}
imgSrc={ScaleImg}
delay="0.4s"
delay="0.2s"
/>
<Card
title={t("services.cards.marketing.card4.title")}
description={t("services.cards.marketing.card4.description")}
imgSrc={ExecuteImg}
delay="0.6s"
delay="0.3s"
/>
</div>
);
@@ -8,28 +8,29 @@ interface IProps {
const Card = ({ title, description, imgSrc, delay }: IProps) => {
return (
<div
className="flex flex-col justify-end flex-1 h-full border-solid border shadow-lg bg-cover bg-center text-center"
className="flex flex-col mt-4 lg:mt-0 justify-end flex-1 h-full w-full md:w-96 xl:w-[340px] 2xl:w-[400px] border-solid border bg-cover bg-center text-center shadow-xl rounded-2xl transition-transform duration-300 hover:scale-105 hover:shadow-gold-accent/40"
style={{
backgroundImage: `url(${imgSrc})`,
filter: "saturate(50%)",
animation: `fadeEffect 1s ${delay} forwards`,
opacity: 0, // Add this line
opacity: 0,
}}
>
<div className="lg:my-6">
<div className="glass-premium p-6">
<div className="lg:py-6">
<h2
className="drop-shadow-[0_1.2px_1.2px_rgba(0,0,0,0.8)]
text-3xl font-bold"
className="drop-shadow-[0_1.2px_1.2px_rgba(0,0,0,0.8)] text-3xl font-bold text-white"
>
{title}
</h2>
<p
className="drop-shadow-[0_1.2px_1.2px_rgba(0,0,0,0.8)]
xl:mb-10 font-thin text-xl"
className="drop-shadow-[0_1.2px_1.2px_rgba(0,0,0,0.8)] font-thin text-xl"
>
{description}
</p>
</div>
</div>
</div>
);
};
@@ -2,7 +2,7 @@ import CardGroup from "./CardGroup";
const ServiceCardContainer = () => {
return (
<div className="h-full xl:h-[65vh] 1920:h-[60vh] rounded-xl mx-4 lg:mx-20 xl:mx-40 2xl:mx-60 1920:margin-calc mt-10 bg-gradient-to-br from-gold-light to-gold-dark p-0.5 shadow-lg ">
<div className="h-full xl:h-[65vh] 1920:h-[60vh] rounded-xl mx-4 lg:mx-20 xl:mx-40 2xl:mx-60 1920:margin-calc mt-10 p-0.5 gap-4">
<CardGroup />
</div>
);
+3 -8
View File
@@ -6,11 +6,11 @@ const Services = () => {
const { t } = useTranslation();
return (
<div className="header-text">
<div className="header-text p-8 md:p-16 mx-4 my-10 rounded-2xl">
<h1>
{t("services.heading.part1")}{" "}
{t("services.heading.part1")}
<span className="relative inline-block">
{t("services.heading.part2")}{" "}
{t("services.heading.part2")}
<img
src={Underline}
alt="underline"
@@ -20,12 +20,7 @@ const Services = () => {
</h1>
<h1>
{t("services.heading.part3")}
<span className="gold-text">{t("services.heading.part4")}</span>
{t("services.heading.part5")}
</h1>
<h2 className="sub-header-text">{t("services.subheading")}</h2>
<ServiceCardContainer />
</div>
);
@@ -9,7 +9,7 @@ const SocialMediaCards = () => {
const { t } = useTranslation();
return (
<div className="h-full flex lg:mt-20 flex-col flex-wrap gap-4 xl:flex-row mx-4">
<div className="h-full flex lg:mt-20 flex-col flex-wrap md:flex-row md:mx-4 gap-5">
<Card
title={t("services.cards.social.card1.title")}
description={t("services.cards.social.card1.description")}
@@ -20,19 +20,19 @@ const SocialMediaCards = () => {
title={t("services.cards.social.card2.title")}
description={t("services.cards.social.card2.description")}
imgSrc={ScheduleImg}
delay="0.2s"
delay="0.1s"
/>
<Card
title={t("services.cards.social.card3.title")}
description={t("services.cards.social.card3.description")}
imgSrc={TargetImg}
delay="0.4s"
delay="0.2s"
/>
<Card
title={t("services.cards.social.card4.title")}
description={t("services.cards.social.card4.description")}
imgSrc={ExpandImg}
delay="0.6s"
delay="0.3s"
/>
</div>
);
@@ -9,7 +9,7 @@ const WesiteCards = () => {
const { t } = useTranslation();
return (
<div className="h-full flex lg:mt-20 flex-col flex-wrap gap-4 xl:flex-row mx-4">
<div className="h-full flex lg:mt-20 flex-col flex-wrap md:flex-row md:mx-4 gap-5">
<Card
title={t("services.cards.website.card1.title")}
description={t("services.cards.website.card1.description")}
@@ -20,19 +20,19 @@ const WesiteCards = () => {
title={t("services.cards.website.card2.title")}
description={t("services.cards.website.card2.description")}
imgSrc={DesignImg}
delay="0.2s"
delay="0.1s"
/>
<Card
title={t("services.cards.website.card3.title")}
description={t("services.cards.website.card3.description")}
imgSrc={CodeImg}
delay="0.4s"
delay="0.2s"
/>
<Card
title={t("services.cards.website.card4.title")}
description={t("services.cards.website.card4.description")}
imgSrc={BuildImg}
delay="0.6s"
delay="0.3s"
/>
</div>
);
+23
View File
@@ -0,0 +1,23 @@
import React from "react";
interface PremiumButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
className?: string;
}
const PremiumButton: React.FC<PremiumButtonProps> = ({
children,
className = "",
...props
}) => (
<div className={`gradient-glow-border p-[2px] rounded-full w-full transition-all duration-300 hover:scale-105 ${className}`}>
<button
className={`flex-1 w-full font-medium md:px-8 py-3 text-2xl md:text-3xl rounded-full bg-gradient-to-br from-custom-bglight to-custom-bgdark text-white shadow-lg transition-all duration-300`}
{...props}
>
{children}
</button>
</div>
);
export default PremiumButton;
+14 -3
View File
@@ -13,12 +13,23 @@ i18n
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
ns: ['common', 'TOS'],
defaultNS: 'common',
backend: {
// translation file path
loadPath: "/locales/{{lng}}.json",
loadPath: (lngs: string[], namespaces: string[]) => {
// i18next-http-backend passes arrays for lngs and namespaces
const lng = lngs[0];
const ns = namespaces[0];
return `/locales/${ns}/${lng}.json`;
},
debug: true,
fallbackLng: "en",
},
// 'process.env.NODE_ENV' is replaced at build time by most bundlers (Vite, CRA, Webpack) to control debug mode.
// TypeScript may show a warning about 'process' not being defined. You can safely ignore this if using a modern React toolchain.
// To silence the error in your editor, install Node types: npm install --save-dev @types/node
debug: process.env.NODE_ENV !== "production",
fallbackLng: { default: ["en"] },
supportedLngs: ["en", "pt"],
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
+76 -3
View File
@@ -1,16 +1,70 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Bai+Jamjuree:wght@400;700&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Gradient Glow Border for Hero Button */
.gradient-glow-border {
position: relative;
z-index: 1;
}
.gradient-glow-border::before {
content: '';
position: absolute;
inset: -4px;
border-radius: 9999px;
background: linear-gradient(to bottom right, theme('colors.gold-gradient-light'), theme('colors.gold-gradient-dark'));
filter: blur(8px) brightness(1.18);
opacity: 0.7;
z-index: -1;
transition: filter 0.3s, opacity 0.3s;
}
.gradient-glow-border:hover::before {
filter: blur(10px) brightness(1.7);
opacity: 0.9;
}
body {
@apply bg-gradient-to-br from-custom-bglight to-custom-bgdark;
@apply bg-gradient-to-br from-custom-bglight to-custom-bgdark antialiased min-h-screen text-white;
font-family: 'Inter', sans-serif;
}
.glass-premium {
@apply bg-white bg-opacity-10 backdrop-blur-lg rounded-2xl shadow-lg border border-white border-opacity-20;
}
.underline-gold {
position: relative;
display: inline-block;
}
.underline-gold::after {
content: "";
display: block;
width: 100%;
height: 5px;
margin-top: 0.18em;
background: linear-gradient(90deg, #E6C674 0%, #C28818 60%, #8C6B1F 100%);
border-radius: 2px;
opacity: 0.85;
}
.hero-text {
@apply font-medium text-5xl lg:text-7xl xl:text-8xl;
}
.hero-text-pt {
@apply font-medium text-4xl md:text-5xl lg:text-6xl xl:text-8xl;
}
.hero-sub-text {
@apply italic text-lg md:text-2xl 2xl:text-3xl;
}
.hero-sub-text-pt {
@apply italic text-lg md:text-xl 2xl:text-2xl;
}
.header-text {
@apply mt-20 lg:mt-48 text-3xl md:text-5xl xl:text-7xl text-center;
@@ -21,17 +75,36 @@ body {
}
.form-input {
@apply rounded-xl px-4 py-2 mt-2 bg-gray-bg border-solid border border-gray-300 text-sm md:text-lg;
@apply rounded-xl px-4 py-4 mt-2 bg-gray-bg border-solid border border-gray-300 text-xs md:text-xl;
}
.gold-text {
@apply bg-clip-text text-transparent bg-gradient-to-br from-gold-light to-gold-dark;
@apply bg-clip-text text-transparent bg-gradient-to-br from-gold-gradient-light to-gold-gradient-dark;
}
.fade-effect {
animation: fadeEffect 1s ease-in-out;
}
.results-card-heading {
@apply mt-2 lg:mt-6 text-3xl xl:text-5xl;
}
.results-card-heading-pt {
@apply mt-2 lg:mt-6 text-2xl xl:text-4xl;
}
.results-card-content {
@apply mb-5 mt-3 italic text-lg xl:text-2xl font-medium text-gray-300;
}
.footer-text {
@apply text-gray-400 font-medium text-xs md:text-base
}
.footer-text-pt {
@apply text-gray-400 font-medium text-xs md:text-base
}
@keyframes fadeEffect {
0% {
opacity: 0;
+2 -1
View File
@@ -5,11 +5,12 @@ import "./index.css";
import { BrowserRouter } from "react-router-dom";
import "./i18n";
import Loading from "./Components/Loading.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<React.StrictMode>
<Suspense fallback="...is loading">
<Suspense fallback={<Loading />}>
<App />
</Suspense>
</React.StrictMode>
+72
View File
@@ -0,0 +1,72 @@
import { useTranslation } from "react-i18next";
import Navbar from "../Components/Navbar/Navbar";
import Footer from "../Components/Footer/Footer";
const PP = () => {
const { t } = useTranslation("PP");
return (
<div className="min-h-screen font-inter">
<Navbar />
<div className="container mx-auto px-4 py-20 flex flex-col min-h-[70vh]">
<h1 className="text-4xl md:text-5xl font-extrabold gold-text text-center mb-6 tracking-wide animate-fadein">{t("pp.pageTitle")}</h1>
<p className="mb-2 text-white text-sm text-center animate-fadein">{t("pp.lastUpdated")}</p>
<p className="mb-10 text-white text-base text-center animate-fadein">{t("pp.introText")}</p>
<div className="w-full max-w-5xl mx-auto">
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp1.title")}</h2>
{t("pp.pp1.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4 text-white text-base text-left">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp2.title")}</h2>
{t("pp.pp2.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp3.title")}</h2>
{t("pp.pp3.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp4.title")}</h2>
{t("pp.pp4.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp5.title")}</h2>
{t("pp.pp5.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp6.title")}</h2>
{t("pp.pp6.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp7.title")}</h2>
{t("pp.pp7.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("pp.pp8.title")}</h2>
{t("pp.pp8.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
</div>
</div>
<Footer/>
</div>
);
};
export default PP;
+60
View File
@@ -0,0 +1,60 @@
import { useTranslation } from "react-i18next";
import Navbar from "../Components/Navbar/Navbar";
import Footer from "../Components/Footer/Footer";
const TOS = () => {
const { t } = useTranslation("TOS")
return (
<div className="min-h-screen font-inter">
<Navbar />
<div className="container mx-auto px-4 py-20 flex flex-col min-h-[70vh]">
<h1 className="text-4xl md:text-5xl font-extrabold gold-text text-center mb-6 tracking-wide animate-fadein">{t("tos.pageTitle")}</h1>
<p className="mb-2 text-white text-sm text-center animate-fadein">{t("tos.lastUpdated")}</p>
<p className="mb-10 text-white text-base text-center animate-fadein">{t("tos.introText")}</p>
<div className="w-full max-w-5xl mx-auto">
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("tos.tos1.title")}</h2>
{t("tos.tos1.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4 text-white text-base text-left">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("tos.tos2.title")}</h2>
{t("tos.tos2.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4 text-white text-base text-left">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("tos.tos3.title")}</h2>
{t("tos.tos3.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4 text-white text-base text-left">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("tos.tos4.title")}</h2>
{t("tos.tos4.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4 text-white text-base text-left">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("tos.tos5.title")}</h2>
{t("tos.tos5.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4 text-white text-base text-left">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
<h2 className="text-xl font-bold mb-3 gold-text text-left">{t("tos.tos6.title")}</h2>
{t("tos.tos6.desc").split("\n\n").map((paragraph, index) => (
<p key={index} className="mb-4 text-white text-base text-left">{paragraph.split("\n").map((line, index) => (
<span key={index}>{line}<br/></span>
))}</p>
))}
</div>
</div>
<Footer/>
</div>
);
};
export default TOS;
+14 -10
View File
@@ -3,19 +3,23 @@ export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
screens: {
1920: "1921px",
fontFamily: {
inter: ['Inter', 'sans-serif'],
baijam: ['Bai Jamjuree', 'sans-serif'],
},
colors: {
"custom-bglight": "#1C1C1F",
"custom-bgdark": "#06060B",
"text-gold": "#FACC97",
"gold-light": "#FACC97",
"gold-dark": "#54330F",
"gray-bg": "#1C1C1C",
'gold-accent': '#C28818',
'gold-light': '#E6C674',
'gold-dark': '#8C6B1F',
'gold-gradient-light': '#FACC97',
'gold-gradient-dark': '#54330F',
'custom-bglight': '#1C1C1F',
'custom-bgdark': '#06060B',
'text-gold': '#FACC97',
'gray-bg': '#1C1C1C',
},
fontFamily: {
baijam: ['"Bai Jamjuree"', "sans-serif"],
screens: {
1920: "1921px",
},
backgroundImage: {
"hero-pattern": "url('./src/assets/HeroImg.svg')",