diff --git a/index.html b/index.html index c5c6f9e..cf72387 100644 --- a/index.html +++ b/index.html @@ -1,15 +1,29 @@ + + + + + + + + - - - - - - DevAlissu | Portfolio - - - -
- - - - \ No newline at end of file + + + + + + + + + + + + + DevAlissu | Portfolio + + + +
+ + + diff --git a/public/projects/aleam-ceap.jpg b/public/projects/aleam-ceap.jpg new file mode 100644 index 0000000..d46aa7c Binary files /dev/null and b/public/projects/aleam-ceap.jpg differ diff --git a/public/projects/aleam-ceap.png b/public/projects/aleam-ceap.png deleted file mode 100644 index f195e9e..0000000 Binary files a/public/projects/aleam-ceap.png and /dev/null differ diff --git a/public/projects/aleam-csv.jpg b/public/projects/aleam-csv.jpg new file mode 100644 index 0000000..9258b47 Binary files /dev/null and b/public/projects/aleam-csv.jpg differ diff --git a/public/projects/aleam-csv.png b/public/projects/aleam-csv.png deleted file mode 100644 index 86f385e..0000000 Binary files a/public/projects/aleam-csv.png and /dev/null differ diff --git a/public/projects/aleam-gastos.jpg b/public/projects/aleam-gastos.jpg new file mode 100644 index 0000000..da82c68 Binary files /dev/null and b/public/projects/aleam-gastos.jpg differ diff --git a/public/projects/aleam-gastos.png b/public/projects/aleam-gastos.png deleted file mode 100644 index 286d7d2..0000000 Binary files a/public/projects/aleam-gastos.png and /dev/null differ diff --git a/public/projects/aleam-home.jpg b/public/projects/aleam-home.jpg new file mode 100644 index 0000000..e2201f1 Binary files /dev/null and b/public/projects/aleam-home.jpg differ diff --git a/public/projects/aleam-home.png b/public/projects/aleam-home.png deleted file mode 100644 index a4557d8..0000000 Binary files a/public/projects/aleam-home.png and /dev/null differ diff --git a/public/projects/aleam-vencimentos.jpg b/public/projects/aleam-vencimentos.jpg new file mode 100644 index 0000000..3c02581 Binary files /dev/null and b/public/projects/aleam-vencimentos.jpg differ diff --git a/public/projects/aleam-vencimentos.png b/public/projects/aleam-vencimentos.png deleted file mode 100644 index e05109a..0000000 Binary files a/public/projects/aleam-vencimentos.png and /dev/null differ diff --git a/public/projects/aleam-vlibras.jpg b/public/projects/aleam-vlibras.jpg new file mode 100644 index 0000000..e60209f Binary files /dev/null and b/public/projects/aleam-vlibras.jpg differ diff --git a/public/projects/aleam-vlibras.png b/public/projects/aleam-vlibras.png deleted file mode 100644 index 81120fb..0000000 Binary files a/public/projects/aleam-vlibras.png and /dev/null differ diff --git a/public/projects/biofogo-camadas.jpg b/public/projects/biofogo-camadas.jpg new file mode 100644 index 0000000..5090974 Binary files /dev/null and b/public/projects/biofogo-camadas.jpg differ diff --git a/public/projects/biofogo-camadas.png b/public/projects/biofogo-camadas.png deleted file mode 100644 index 2f235e0..0000000 Binary files a/public/projects/biofogo-camadas.png and /dev/null differ diff --git a/public/projects/biofogo-dark.jpg b/public/projects/biofogo-dark.jpg new file mode 100644 index 0000000..f1623d6 Binary files /dev/null and b/public/projects/biofogo-dark.jpg differ diff --git a/public/projects/biofogo-dark.png b/public/projects/biofogo-dark.png deleted file mode 100644 index 508edab..0000000 Binary files a/public/projects/biofogo-dark.png and /dev/null differ diff --git a/public/projects/biofogo-mapa.jpg b/public/projects/biofogo-mapa.jpg new file mode 100644 index 0000000..d0b8134 Binary files /dev/null and b/public/projects/biofogo-mapa.jpg differ diff --git a/public/projects/biofogo-mapa.png b/public/projects/biofogo-mapa.png deleted file mode 100644 index 96b32e5..0000000 Binary files a/public/projects/biofogo-mapa.png and /dev/null differ diff --git a/public/projects/biofogo-monitoramento.jpg b/public/projects/biofogo-monitoramento.jpg new file mode 100644 index 0000000..af64769 Binary files /dev/null and b/public/projects/biofogo-monitoramento.jpg differ diff --git a/public/projects/biofogo-monitoramento.png b/public/projects/biofogo-monitoramento.png deleted file mode 100644 index 847979d..0000000 Binary files a/public/projects/biofogo-monitoramento.png and /dev/null differ diff --git a/public/projects/biofogo-previsao.jpg b/public/projects/biofogo-previsao.jpg new file mode 100644 index 0000000..4b60f80 Binary files /dev/null and b/public/projects/biofogo-previsao.jpg differ diff --git a/public/projects/biofogo-previsao.png b/public/projects/biofogo-previsao.png deleted file mode 100644 index 322de33..0000000 Binary files a/public/projects/biofogo-previsao.png and /dev/null differ diff --git a/public/projects/biofogo-status.jpg b/public/projects/biofogo-status.jpg new file mode 100644 index 0000000..9f829fa Binary files /dev/null and b/public/projects/biofogo-status.jpg differ diff --git a/public/projects/biofogo-status.png b/public/projects/biofogo-status.png deleted file mode 100644 index 0f71339..0000000 Binary files a/public/projects/biofogo-status.png and /dev/null differ diff --git a/public/projects/codebot-saas.jpg b/public/projects/codebot-saas.jpg new file mode 100644 index 0000000..774acd3 Binary files /dev/null and b/public/projects/codebot-saas.jpg differ diff --git a/public/projects/codebot-saas.png b/public/projects/codebot-saas.png deleted file mode 100644 index caec913..0000000 Binary files a/public/projects/codebot-saas.png and /dev/null differ diff --git a/public/projects/drawnomes-celebracao.jpg b/public/projects/drawnomes-celebracao.jpg new file mode 100644 index 0000000..9665389 Binary files /dev/null and b/public/projects/drawnomes-celebracao.jpg differ diff --git a/public/projects/drawnomes-celebracao.png b/public/projects/drawnomes-celebracao.png deleted file mode 100644 index d720539..0000000 Binary files a/public/projects/drawnomes-celebracao.png and /dev/null differ diff --git a/public/projects/drawnomes-home.jpg b/public/projects/drawnomes-home.jpg new file mode 100644 index 0000000..23b7ff0 Binary files /dev/null and b/public/projects/drawnomes-home.jpg differ diff --git a/public/projects/drawnomes-home.png b/public/projects/drawnomes-home.png deleted file mode 100644 index 5049086..0000000 Binary files a/public/projects/drawnomes-home.png and /dev/null differ diff --git a/public/projects/drawnomes-passo1.jpg b/public/projects/drawnomes-passo1.jpg new file mode 100644 index 0000000..001445f Binary files /dev/null and b/public/projects/drawnomes-passo1.jpg differ diff --git a/public/projects/drawnomes-passo1.png b/public/projects/drawnomes-passo1.png deleted file mode 100644 index 4bce3a3..0000000 Binary files a/public/projects/drawnomes-passo1.png and /dev/null differ diff --git a/public/projects/drawnomes-passo1b.jpg b/public/projects/drawnomes-passo1b.jpg new file mode 100644 index 0000000..4c7c89f Binary files /dev/null and b/public/projects/drawnomes-passo1b.jpg differ diff --git a/public/projects/drawnomes-passo1b.png b/public/projects/drawnomes-passo1b.png deleted file mode 100644 index 4f28335..0000000 Binary files a/public/projects/drawnomes-passo1b.png and /dev/null differ diff --git a/public/projects/drawnomes-passo2.jpg b/public/projects/drawnomes-passo2.jpg new file mode 100644 index 0000000..fa2e7ac Binary files /dev/null and b/public/projects/drawnomes-passo2.jpg differ diff --git a/public/projects/drawnomes-passo2.png b/public/projects/drawnomes-passo2.png deleted file mode 100644 index 89d8699..0000000 Binary files a/public/projects/drawnomes-passo2.png and /dev/null differ diff --git a/public/projects/drawnomes-passo4.jpg b/public/projects/drawnomes-passo4.jpg new file mode 100644 index 0000000..2679862 Binary files /dev/null and b/public/projects/drawnomes-passo4.jpg differ diff --git a/public/projects/drawnomes-passo4.png b/public/projects/drawnomes-passo4.png deleted file mode 100644 index 0e72284..0000000 Binary files a/public/projects/drawnomes-passo4.png and /dev/null differ diff --git a/public/projects/drawnomes-passo5.jpg b/public/projects/drawnomes-passo5.jpg new file mode 100644 index 0000000..13499ab Binary files /dev/null and b/public/projects/drawnomes-passo5.jpg differ diff --git a/public/projects/drawnomes-passo5.png b/public/projects/drawnomes-passo5.png deleted file mode 100644 index 07061f0..0000000 Binary files a/public/projects/drawnomes-passo5.png and /dev/null differ diff --git a/public/projects/drawnomes-passo6.jpg b/public/projects/drawnomes-passo6.jpg new file mode 100644 index 0000000..ddbfa76 Binary files /dev/null and b/public/projects/drawnomes-passo6.jpg differ diff --git a/public/projects/drawnomes-passo6.png b/public/projects/drawnomes-passo6.png deleted file mode 100644 index 8a7b108..0000000 Binary files a/public/projects/drawnomes-passo6.png and /dev/null differ diff --git a/public/projects/drawnomes-passo7.jpg b/public/projects/drawnomes-passo7.jpg new file mode 100644 index 0000000..8cb9870 Binary files /dev/null and b/public/projects/drawnomes-passo7.jpg differ diff --git a/public/projects/drawnomes-passo7.png b/public/projects/drawnomes-passo7.png deleted file mode 100644 index 61513f2..0000000 Binary files a/public/projects/drawnomes-passo7.png and /dev/null differ diff --git a/public/projects/drawnomes-resultado.jpg b/public/projects/drawnomes-resultado.jpg new file mode 100644 index 0000000..b058979 Binary files /dev/null and b/public/projects/drawnomes-resultado.jpg differ diff --git a/public/projects/drawnomes-resultado.png b/public/projects/drawnomes-resultado.png deleted file mode 100644 index 01d25d3..0000000 Binary files a/public/projects/drawnomes-resultado.png and /dev/null differ diff --git a/public/projects/drawnomes-sorteio.jpg b/public/projects/drawnomes-sorteio.jpg new file mode 100644 index 0000000..5b59dbc Binary files /dev/null and b/public/projects/drawnomes-sorteio.jpg differ diff --git a/public/projects/drawnomes-sorteio.png b/public/projects/drawnomes-sorteio.png deleted file mode 100644 index 593cc71..0000000 Binary files a/public/projects/drawnomes-sorteio.png and /dev/null differ diff --git a/public/projects/emprestimo-1.jpg b/public/projects/emprestimo-1.jpg index 967a288..fc00ce0 100644 Binary files a/public/projects/emprestimo-1.jpg and b/public/projects/emprestimo-1.jpg differ diff --git a/public/projects/emprestimo-10.jpg b/public/projects/emprestimo-10.jpg index bed9849..f444c44 100644 Binary files a/public/projects/emprestimo-10.jpg and b/public/projects/emprestimo-10.jpg differ diff --git a/public/projects/emprestimo-11.jpg b/public/projects/emprestimo-11.jpg index 2c323c9..1ff5cb0 100644 Binary files a/public/projects/emprestimo-11.jpg and b/public/projects/emprestimo-11.jpg differ diff --git a/public/projects/emprestimo-2.jpg b/public/projects/emprestimo-2.jpg index 724fd7f..e234876 100644 Binary files a/public/projects/emprestimo-2.jpg and b/public/projects/emprestimo-2.jpg differ diff --git a/public/projects/emprestimo-3.jpg b/public/projects/emprestimo-3.jpg index 2d215a9..bdd1c36 100644 Binary files a/public/projects/emprestimo-3.jpg and b/public/projects/emprestimo-3.jpg differ diff --git a/public/projects/emprestimo-4.jpg b/public/projects/emprestimo-4.jpg index 6cf72bd..4c892ee 100644 Binary files a/public/projects/emprestimo-4.jpg and b/public/projects/emprestimo-4.jpg differ diff --git a/public/projects/emprestimo-5.jpg b/public/projects/emprestimo-5.jpg index dc2711e..bb0bfe9 100644 Binary files a/public/projects/emprestimo-5.jpg and b/public/projects/emprestimo-5.jpg differ diff --git a/public/projects/emprestimo-6.jpg b/public/projects/emprestimo-6.jpg index b662442..faa8148 100644 Binary files a/public/projects/emprestimo-6.jpg and b/public/projects/emprestimo-6.jpg differ diff --git a/public/projects/emprestimo-7.jpg b/public/projects/emprestimo-7.jpg index 4436d79..c79b286 100644 Binary files a/public/projects/emprestimo-7.jpg and b/public/projects/emprestimo-7.jpg differ diff --git a/public/projects/emprestimo-8.jpg b/public/projects/emprestimo-8.jpg index 46356e1..7d04d78 100644 Binary files a/public/projects/emprestimo-8.jpg and b/public/projects/emprestimo-8.jpg differ diff --git a/public/projects/emprestimo-9.jpg b/public/projects/emprestimo-9.jpg index 0ccff9c..e7eeb21 100644 Binary files a/public/projects/emprestimo-9.jpg and b/public/projects/emprestimo-9.jpg differ diff --git a/public/projects/horto-adote.jpg b/public/projects/horto-adote.jpg new file mode 100644 index 0000000..02de88e Binary files /dev/null and b/public/projects/horto-adote.jpg differ diff --git a/public/projects/horto-adote.png b/public/projects/horto-adote.png deleted file mode 100644 index bf78309..0000000 Binary files a/public/projects/horto-adote.png and /dev/null differ diff --git a/public/projects/horto-camera.jpg b/public/projects/horto-camera.jpg new file mode 100644 index 0000000..fecc251 Binary files /dev/null and b/public/projects/horto-camera.jpg differ diff --git a/public/projects/horto-camera.png b/public/projects/horto-camera.png deleted file mode 100644 index dd9c98a..0000000 Binary files a/public/projects/horto-camera.png and /dev/null differ diff --git a/public/projects/horto-catalogo.jpg b/public/projects/horto-catalogo.jpg new file mode 100644 index 0000000..a180361 Binary files /dev/null and b/public/projects/horto-catalogo.jpg differ diff --git a/public/projects/horto-catalogo.png b/public/projects/horto-catalogo.png deleted file mode 100644 index 0060cb6..0000000 Binary files a/public/projects/horto-catalogo.png and /dev/null differ diff --git a/public/projects/horto-gerencie.jpg b/public/projects/horto-gerencie.jpg new file mode 100644 index 0000000..e0c7085 Binary files /dev/null and b/public/projects/horto-gerencie.jpg differ diff --git a/public/projects/horto-gerencie.png b/public/projects/horto-gerencie.png deleted file mode 100644 index 0406159..0000000 Binary files a/public/projects/horto-gerencie.png and /dev/null differ diff --git a/public/projects/horto-home.jpg b/public/projects/horto-home.jpg new file mode 100644 index 0000000..c818f1c Binary files /dev/null and b/public/projects/horto-home.jpg differ diff --git a/public/projects/horto-home.png b/public/projects/horto-home.png deleted file mode 100644 index 861fb0c..0000000 Binary files a/public/projects/horto-home.png and /dev/null differ diff --git a/public/projects/horto-mudas.jpg b/public/projects/horto-mudas.jpg new file mode 100644 index 0000000..ce552b1 Binary files /dev/null and b/public/projects/horto-mudas.jpg differ diff --git a/public/projects/horto-mudas.png b/public/projects/horto-mudas.png deleted file mode 100644 index 71cc55e..0000000 Binary files a/public/projects/horto-mudas.png and /dev/null differ diff --git a/public/projects/horto-nome.jpg b/public/projects/horto-nome.jpg new file mode 100644 index 0000000..050f9d5 Binary files /dev/null and b/public/projects/horto-nome.jpg differ diff --git a/public/projects/horto-nome.png b/public/projects/horto-nome.png deleted file mode 100644 index cd712d7..0000000 Binary files a/public/projects/horto-nome.png and /dev/null differ diff --git a/public/projects/horto-ola.jpg b/public/projects/horto-ola.jpg new file mode 100644 index 0000000..7df3c34 Binary files /dev/null and b/public/projects/horto-ola.jpg differ diff --git a/public/projects/horto-ola.png b/public/projects/horto-ola.png deleted file mode 100644 index 618ca0f..0000000 Binary files a/public/projects/horto-ola.png and /dev/null differ diff --git a/public/projects/horto-protinho.jpg b/public/projects/horto-protinho.jpg new file mode 100644 index 0000000..c18bf93 Binary files /dev/null and b/public/projects/horto-protinho.jpg differ diff --git a/public/projects/horto-protinho.png b/public/projects/horto-protinho.png deleted file mode 100644 index 268ba7e..0000000 Binary files a/public/projects/horto-protinho.png and /dev/null differ diff --git a/public/projects/jungle-logic.jpg b/public/projects/jungle-logic.jpg index 9d8bc24..19ae1a5 100644 Binary files a/public/projects/jungle-logic.jpg and b/public/projects/jungle-logic.jpg differ diff --git a/public/projects/melo-contas.jpg b/public/projects/melo-contas.jpg new file mode 100644 index 0000000..42e8096 Binary files /dev/null and b/public/projects/melo-contas.jpg differ diff --git a/public/projects/melo-contas.png b/public/projects/melo-contas.png deleted file mode 100644 index 43cf461..0000000 Binary files a/public/projects/melo-contas.png and /dev/null differ diff --git a/public/projects/melo-vendas.jpg b/public/projects/melo-vendas.jpg new file mode 100644 index 0000000..b8867a3 Binary files /dev/null and b/public/projects/melo-vendas.jpg differ diff --git a/public/projects/melo-vendas.png b/public/projects/melo-vendas.png deleted file mode 100644 index 9aa3bbf..0000000 Binary files a/public/projects/melo-vendas.png and /dev/null differ diff --git a/public/projects/ms-sefaz.jpg b/public/projects/ms-sefaz.jpg new file mode 100644 index 0000000..0993fec Binary files /dev/null and b/public/projects/ms-sefaz.jpg differ diff --git a/public/projects/ms-sefaz.png b/public/projects/ms-sefaz.png deleted file mode 100644 index c311689..0000000 Binary files a/public/projects/ms-sefaz.png and /dev/null differ diff --git a/public/projects/nansen-dashboard.jpg b/public/projects/nansen-dashboard.jpg new file mode 100644 index 0000000..0ae2522 Binary files /dev/null and b/public/projects/nansen-dashboard.jpg differ diff --git a/public/projects/nansen-dashboard.png b/public/projects/nansen-dashboard.png deleted file mode 100644 index 9309686..0000000 Binary files a/public/projects/nansen-dashboard.png and /dev/null differ diff --git a/public/projects/nansen-faturamento.jpg b/public/projects/nansen-faturamento.jpg new file mode 100644 index 0000000..0bb8b06 Binary files /dev/null and b/public/projects/nansen-faturamento.jpg differ diff --git a/public/projects/nansen-faturamento.png b/public/projects/nansen-faturamento.png deleted file mode 100644 index 0f58151..0000000 Binary files a/public/projects/nansen-faturamento.png and /dev/null differ diff --git a/public/projects/nansen-home.jpg b/public/projects/nansen-home.jpg new file mode 100644 index 0000000..fa476c2 Binary files /dev/null and b/public/projects/nansen-home.jpg differ diff --git a/public/projects/nansen-home.png b/public/projects/nansen-home.png deleted file mode 100644 index 4351f2a..0000000 Binary files a/public/projects/nansen-home.png and /dev/null differ diff --git a/public/projects/nansen-login.jpg b/public/projects/nansen-login.jpg new file mode 100644 index 0000000..74868a0 Binary files /dev/null and b/public/projects/nansen-login.jpg differ diff --git a/public/projects/nansen-login.png b/public/projects/nansen-login.png deleted file mode 100644 index 0a0070c..0000000 Binary files a/public/projects/nansen-login.png and /dev/null differ diff --git a/public/projects/nansen-missoes-lista.jpg b/public/projects/nansen-missoes-lista.jpg new file mode 100644 index 0000000..d7cb0ef Binary files /dev/null and b/public/projects/nansen-missoes-lista.jpg differ diff --git a/public/projects/nansen-missoes-lista.png b/public/projects/nansen-missoes-lista.png deleted file mode 100644 index f85e306..0000000 Binary files a/public/projects/nansen-missoes-lista.png and /dev/null differ diff --git a/public/projects/nansen-missoes.jpg b/public/projects/nansen-missoes.jpg new file mode 100644 index 0000000..3f3f816 Binary files /dev/null and b/public/projects/nansen-missoes.jpg differ diff --git a/public/projects/nansen-missoes.png b/public/projects/nansen-missoes.png deleted file mode 100644 index fd0eece..0000000 Binary files a/public/projects/nansen-missoes.png and /dev/null differ diff --git a/public/projects/sistema-melo.jpg b/public/projects/sistema-melo.jpg new file mode 100644 index 0000000..72d63fb Binary files /dev/null and b/public/projects/sistema-melo.jpg differ diff --git a/public/projects/sistema-melo.png b/public/projects/sistema-melo.png deleted file mode 100644 index e839bb2..0000000 Binary files a/public/projects/sistema-melo.png and /dev/null differ diff --git a/public/projects/sprint-tools-1.jpg b/public/projects/sprint-tools-1.jpg new file mode 100644 index 0000000..165b1ad Binary files /dev/null and b/public/projects/sprint-tools-1.jpg differ diff --git a/public/projects/sprint-tools-1.png b/public/projects/sprint-tools-1.png deleted file mode 100644 index acbc68b..0000000 Binary files a/public/projects/sprint-tools-1.png and /dev/null differ diff --git a/public/projects/yamaha-dashboard.jpg b/public/projects/yamaha-dashboard.jpg new file mode 100644 index 0000000..78a22eb Binary files /dev/null and b/public/projects/yamaha-dashboard.jpg differ diff --git a/public/projects/yamaha-dashboard.png b/public/projects/yamaha-dashboard.png deleted file mode 100644 index 8002ecd..0000000 Binary files a/public/projects/yamaha-dashboard.png and /dev/null differ diff --git a/src/app/routes.tsx b/src/app/routes.tsx index 7e9fbee..b73f0c1 100644 --- a/src/app/routes.tsx +++ b/src/app/routes.tsx @@ -1,10 +1,12 @@ +import { lazy } from 'react'; import { createBrowserRouter } from 'react-router'; import { Layout } from '../shared/components/layout'; -import { HomePage } from '../features/home'; -import { AboutPage } from '../features/about'; -import { ProjectsPage } from '../features/projects'; -import { ContactPage } from '../features/contact'; -import { NotFoundPage } from '../features/not-found'; + +const HomePage = lazy(() => import('../features/home').then((m) => ({ default: m.HomePage }))); +const AboutPage = lazy(() => import('../features/about').then((m) => ({ default: m.AboutPage }))); +const ProjectsPage = lazy(() => import('../features/projects').then((m) => ({ default: m.ProjectsPage }))); +const ContactPage = lazy(() => import('../features/contact').then((m) => ({ default: m.ContactPage }))); +const NotFoundPage = lazy(() => import('../features/not-found').then((m) => ({ default: m.NotFoundPage }))); export const router = createBrowserRouter([ { diff --git a/src/features/about/components/TabItem.tsx b/src/features/about/components/TabItem.tsx index 25e6614..e430cc2 100644 --- a/src/features/about/components/TabItem.tsx +++ b/src/features/about/components/TabItem.tsx @@ -13,10 +13,19 @@ interface TabItemProps { export function TabItem({ tab, isActive, canClose, onSelect, onClose }: TabItemProps) { return (
onSelect(tab)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onSelect(tab); + } + }} + className={`group relative flex items-center gap-2 px-4 py-3 border-r border-[#314158] cursor-pointer shrink-0 transition-colors focus-visible:outline-2 focus-visible:outline-[#ffb86a] focus-visible:outline-offset-[-2px] ${ isActive ? 'text-[#f8fafc]' : 'text-[#90a1b9] hover:text-[#f8fafc]' }`} - onClick={() => onSelect(tab)} > {isActive && (
@@ -30,7 +39,8 @@ export function TabItem({ tab, isActive, canClose, onSelect, onClose }: TabItemP e.stopPropagation(); onClose(tab); }} - className="opacity-0 group-hover:opacity-100 hover:text-[#f8fafc] transition-opacity ml-1" + aria-label={`Fechar ${TAB_LABELS[tab]}`} + className="opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:text-[#f8fafc] transition-opacity ml-1" > diff --git a/src/features/contact/components/ContactForm.tsx b/src/features/contact/components/ContactForm.tsx index 4ceb112..2c56260 100644 --- a/src/features/contact/components/ContactForm.tsx +++ b/src/features/contact/components/ContactForm.tsx @@ -72,7 +72,7 @@ export function ContactForm({ formData, formErrors, onChange, onSubmit }: Contac diff --git a/src/features/contact/hooks/useContactForm.ts b/src/features/contact/hooks/useContactForm.ts index 6ce0c35..5292973 100644 --- a/src/features/contact/hooks/useContactForm.ts +++ b/src/features/contact/hooks/useContactForm.ts @@ -29,9 +29,15 @@ export function useContactForm() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); + if (formStatus === 'success') return; - if (!EMAIL_REGEX.test(formData.email)) { - setFormErrors({ email: 'email invalido' }); + const errors: ContactFormErrors = {}; + if (!formData.name.trim()) errors.name = 'nome obrigatorio'; + if (!EMAIL_REGEX.test(formData.email)) errors.email = 'email invalido'; + if (!formData.message.trim()) errors.message = 'mensagem obrigatoria'; + + if (Object.keys(errors).length > 0) { + setFormErrors(errors); setFormStatus('error'); return; } diff --git a/src/features/contact/types/index.ts b/src/features/contact/types/index.ts index b517452..55bd51e 100644 --- a/src/features/contact/types/index.ts +++ b/src/features/contact/types/index.ts @@ -5,7 +5,9 @@ export interface ContactFormData { } export interface ContactFormErrors { + name?: string; email?: string; + message?: string; } export type ContactFormStatus = 'idle' | 'error' | 'success'; diff --git a/src/features/home/HomePage.tsx b/src/features/home/HomePage.tsx index 7d765a2..8adf45f 100644 --- a/src/features/home/HomePage.tsx +++ b/src/features/home/HomePage.tsx @@ -1,4 +1,8 @@ -import { SnakeGame } from '../snake-game'; +import { lazy, Suspense } from 'react'; + +const SnakeGame = lazy(() => + import('../snake-game').then((m) => ({ default: m.SnakeGame })), +); export function HomePage() { return ( @@ -41,7 +45,9 @@ export function HomePage() {
- + }> + +
diff --git a/src/features/projects/components/ImageLightbox.tsx b/src/features/projects/components/ImageLightbox.tsx index 9f925ba..2cda9dc 100644 --- a/src/features/projects/components/ImageLightbox.tsx +++ b/src/features/projects/components/ImageLightbox.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { X, ChevronLeft, ChevronRight } from 'lucide-react'; interface ImageLightboxProps { @@ -9,16 +9,12 @@ interface ImageLightboxProps { export function ImageLightbox({ images, startIndex, onClose }: ImageLightboxProps) { const [index, setIndex] = useState(startIndex); + const indexRef = useRef(index); + indexRef.current = index; - const prev = useCallback(() => { - setIndex((i) => (i > 0 ? i - 1 : images.length - 1)); - }, [images.length]); - - const next = useCallback(() => { - setIndex((i) => (i < images.length - 1 ? i + 1 : 0)); - }, [images.length]); - - const close = useCallback(() => onClose(index), [onClose, index]); + const prev = () => setIndex((i) => (i > 0 ? i - 1 : images.length - 1)); + const next = () => setIndex((i) => (i < images.length - 1 ? i + 1 : 0)); + const close = () => onClose(indexRef.current); useEffect(() => { const handleKey = (e: KeyboardEvent) => { @@ -29,16 +25,21 @@ export function ImageLightbox({ images, startIndex, onClose }: ImageLightboxProp }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); - }, [close, prev, next]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return (
@@ -50,7 +51,8 @@ export function ImageLightbox({ images, startIndex, onClose }: ImageLightboxProp {images.length > 1 && ( @@ -66,7 +68,8 @@ export function ImageLightbox({ images, startIndex, onClose }: ImageLightboxProp {images.length > 1 && ( diff --git a/src/features/projects/components/ProjectCard.tsx b/src/features/projects/components/ProjectCard.tsx index da909d7..03222e2 100644 --- a/src/features/projects/components/ProjectCard.tsx +++ b/src/features/projects/components/ProjectCard.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { ExternalLink, Lock } from 'lucide-react'; import type { Project } from '../types'; @@ -7,7 +8,7 @@ interface ProjectCardProps { onSelect: (project: Project) => void; } -export function ProjectCard({ project, index, onSelect }: ProjectCardProps) { +export const ProjectCard = memo(function ProjectCard({ project, index, onSelect }: ProjectCardProps) { return (
@@ -20,7 +21,7 @@ export function ProjectCard({ project, index, onSelect }: ProjectCardProps) {
{project.image ? (
- {project.title} + {project.title}
) : (
@@ -70,7 +71,7 @@ export function ProjectCard({ project, index, onSelect }: ProjectCardProps) {
@@ -78,4 +79,4 @@ export function ProjectCard({ project, index, onSelect }: ProjectCardProps) {
); -} +}); diff --git a/src/features/projects/components/ProjectModal.tsx b/src/features/projects/components/ProjectModal.tsx index ffb3395..8b45f90 100644 --- a/src/features/projects/components/ProjectModal.tsx +++ b/src/features/projects/components/ProjectModal.tsx @@ -20,6 +20,11 @@ export function ProjectModal({ project, onClose }: ProjectModalProps) { ? [project.image] : []; + // reset slide index when project changes to avoid out-of-bounds + useEffect(() => { + setActiveSlide(0); + }, [project.id]); + useEffect(() => { const handleEsc = (e: KeyboardEvent) => { if (lightboxOpen) return; @@ -84,14 +89,15 @@ export function ProjectModal({ project, onClose }: ProjectModalProps) {
@@ -104,13 +110,16 @@ export function ProjectModal({ project, onClose }: ProjectModalProps) { {allImages.length > 0 ? (
{/* Active slide */} -
setLightboxOpen(true)} > {`${project.title} {allImages.length > 1 && ( @@ -118,15 +127,18 @@ export function ProjectModal({ project, onClose }: ProjectModalProps) { {activeSlide + 1} / {allImages.length}
)} -
+ {/* Thumbnails */} {allImages.length > 1 && (
{allImages.map((img, i) => ( -
{`thumb -
+ ))}
)} diff --git a/src/features/projects/constants/index.ts b/src/features/projects/constants/index.ts index 0ade286..2896ec3 100644 --- a/src/features/projects/constants/index.ts +++ b/src/features/projects/constants/index.ts @@ -7,14 +7,14 @@ export const PROJECTS: Project[] = [ tag: '_nansen-iot', description: 'Plataforma de monitoramento industrial IoT e gestao de energia com dashboards em tempo real.', details: 'Plataforma de gestao industrial IoT para monitoramento de equipamentos, linhas de producao e consumo energetico. Modulos:\n\n- Dashboard principal com metricas em tempo real\n- Gestao de produtos, equipamentos e setores produtivos\n- Registro e monitoramento de dispositivos IoT (NansenIC, NansenSensor)\n- Gerenciamento de linhas de producao\n- Faturamento energetico com dashboards comparativos e analise de consumo\n- Sistema de missoes com dashboard dedicado\n- Gestao de usuarios com controle de acesso\n- Internacionalizacao (i18n) multi-idioma\n- Loja integrada e sistema de quizzes', - image: '/projects/nansen-login.png', + image: '/projects/nansen-login.jpg', images: [ - '/projects/nansen-login.png', - '/projects/nansen-home.png', - '/projects/nansen-missoes.png', - '/projects/nansen-dashboard.png', - '/projects/nansen-faturamento.png', - '/projects/nansen-missoes-lista.png', + '/projects/nansen-login.jpg', + '/projects/nansen-home.jpg', + '/projects/nansen-missoes.jpg', + '/projects/nansen-dashboard.jpg', + '/projects/nansen-faturamento.jpg', + '/projects/nansen-missoes-lista.jpg', ], technologies: ['React', 'TypeScript', 'Tailwind', 'Vite', 'Ant Design', 'Zustand', 'Recharts', 'Axios', 'React Hook Form', 'React Router', 'React Toastify', 'Framer Motion'], repoUrl: 'https://github.com/Nansen-NGE/NansenWeb', @@ -26,14 +26,14 @@ export const PROJECTS: Project[] = [ tag: '_ia-fogobio', description: 'Plataforma de deteccao e monitoramento de focos de incendio com IA e dados de satelite NASA.', details: 'Plataforma de inteligencia para deteccao e monitoramento de focos de incendio utilizando dados de satelite NASA FIRMS (VIIRS - NOAA-20, NOAA-21, SNPP). Tres componentes:\n\n- Frontend: mapa interativo com MapLibre GL, modulos de Planejamento, Monitoramento e Previsao. Camadas de tipos de solo, predicao ML (accuracy > 94.3%), focos de incendio e NDVI vegetacao. Sidebar com ferramentas, filtros e dados. Temas claro/escuro, bandeiras PT/EN/ES, exportacao CSV/Excel. Status bar com coordenadas, escala, fonte INPE/NASA e timestamp do ML.\n\n- Backend API (FastAPI): pipeline ETL para extracao, transformacao e carga de dados FIRMS. PostgreSQL com SQLAlchemy, processamento com Pandas/NumPy, clustering de focos.\n\n- Modulo ML: modelo de predicao de incendios com Scikit-learn, previsao de 7-14 dias, deteccao de areas de risco por bioma e tipo de solo.', - image: '/projects/biofogo-mapa.png', + image: '/projects/biofogo-mapa.jpg', images: [ - '/projects/biofogo-mapa.png', - '/projects/biofogo-previsao.png', - '/projects/biofogo-monitoramento.png', - '/projects/biofogo-dark.png', - '/projects/biofogo-camadas.png', - '/projects/biofogo-status.png', + '/projects/biofogo-mapa.jpg', + '/projects/biofogo-previsao.jpg', + '/projects/biofogo-monitoramento.jpg', + '/projects/biofogo-dark.jpg', + '/projects/biofogo-camadas.jpg', + '/projects/biofogo-status.jpg', ], technologies: ['React', 'TypeScript', 'Tailwind', 'Vite', 'shadcn/ui', 'MapLibre GL', 'Recharts', 'React Hook Form', 'FastAPI', 'Python', 'PostgreSQL'], repoUrl: 'https://github.com/IA-FogoBio-IFAM/biofogo-ia', @@ -45,8 +45,8 @@ export const PROJECTS: Project[] = [ tag: '_prototipo-yamaha', description: 'Prototipo de interface com React usando PrimeReact, Ant Design e graficos interativos.', details: 'Prototipo de dashboard industrial para gestao de equipamentos e inspecoes. Modulos de: cadastro e listagem de equipamentos, registro de inspecoes com formularios complexos, dashboard com graficos interativos, sistema de login. Construido com tres bibliotecas de UI (PrimeReact, Ant Design, shadcn/ui), internacionalizacao com i18next e animacoes com Framer Motion.', - image: '/projects/yamaha-dashboard.png', - images: ['/projects/yamaha-dashboard.png'], + image: '/projects/yamaha-dashboard.jpg', + images: ['/projects/yamaha-dashboard.jpg'], technologies: ['React', 'Tailwind', 'TypeScript', 'Vite', 'shadcn/ui', 'PrimeReact', 'Ant Design', 'Zustand', 'Recharts', 'Axios', 'React Toastify', 'Framer Motion'], repoUrl: 'https://github.com/DevAlissu/prototipo-yamaha', liveUrl: 'https://prototipo-yamaha.vercel.app', @@ -57,7 +57,7 @@ export const PROJECTS: Project[] = [ tag: '_ms-sefaz', description: 'Microsservico de integracao com a SEFAZ para consulta e gestao de NF-e.', details: 'Microsservico Python com FastAPI para integracao completa com a SEFAZ. Tres modulos de endpoints:\n\n- SefazNFe-Completo: consulta NF-e completa com salvamento em multiplas tabelas, parsing de XML, download de XML e DANFE (PDF), status do servico e estatisticas do banco.\n\n- SefazNFe-Compativel: versao simplificada para consulta e teste de extracao de dados, download de XML/DANFE e teste de salvamento em banco.\n\n- Scheduler-Automacao: agendamento automatico de consultas via NSU, forcamento de consulta imediata, manifestacao de ciencia de operacao de NF-e.\n\nAutenticacao com certificados digitais PKCS12, validacao com Pydantic, servidor ASGI com Uvicorn. Deploy isolado via Docker como parte do ecossistema Sistema Melo.', - image: '/projects/ms-sefaz.png', + image: '/projects/ms-sefaz.jpg', technologies: ['FastAPI', 'Python'], repoUrl: 'https://github.com/Sistema-Melo/microsservice_sefaz', isPrivate: true, @@ -68,8 +68,8 @@ export const PROJECTS: Project[] = [ tag: '_sprint-tools', description: 'Ferramenta de gestao de sprints com retrospectiva, timer e board colaborativo.', details: 'Ferramenta de gestao agil com board de retrospectiva colaborativo em tempo real. Colunas configuradas: "O que foi legal", "O que poderia ter sido melhor", "Sugestoes de melhoria", "O que devemos manter", "O que devemos abandonar" e "Reconhecimento". Funcionalidades de: adicionar cards anonimos, timer de sessao, revelar cards simultaneamente, resetar board. Sistema multi-usuario com contagem de participantes online.', - image: '/projects/sprint-tools-1.png', - images: ['/projects/sprint-tools-1.png'], + image: '/projects/sprint-tools-1.jpg', + images: ['/projects/sprint-tools-1.jpg'], technologies: ['React', 'Tailwind', 'TypeScript', 'Vite', 'Zustand', 'React Router'], repoUrl: 'https://github.com/DevAlissu/sprint-tools', liveUrl: 'http://sprintpoker.duckdns.org:8000', @@ -81,7 +81,7 @@ export const PROJECTS: Project[] = [ tag: '_codebot-saas', description: 'Plataforma SaaS de code review automatizado com IA para equipes via Discord.', details: 'Plataforma SaaS multi-tenant de code review automatizado com IA. Monorepo Turborepo com 4 pacotes:\n\n- API (Fastify + Drizzle ORM + PostgreSQL): rotas de auth, tenants, webhooks GitHub, estatisticas de reviews e configuracoes. Schema com planos (starter/pro/enterprise), niveis de rigor de review (relaxed/normal/strict), guias de review por tipo (frontend/backend/mobile) e tracking de tokens de IA.\n\n- Painel Web (React + Vite): login com OAuth Discord/GitHub, dashboard de metricas de PRs revisados, gerenciamento de equipe e membros, configuracao de guias de review e settings do bot.\n\n- Bot Discord: revisor de codigo automatico que analisa PRs, respeita personalidade configuravel, limite de respostas por thread, VIP users e senior authors.\n\n- Shared: tipos e utilidades compartilhados entre pacotes.\n\nAutenticacao OAuth2 via Arctic com sessoes seguras via Oslo.', - image: '/projects/codebot-saas.png', + image: '/projects/codebot-saas.jpg', technologies: ['React', 'Tailwind', 'TypeScript', 'Vite', 'Fastify', 'Drizzle', 'PostgreSQL', 'Node.js'], repoUrl: 'https://github.com/DevAlissu/codebot-saas', isPrivate: true, @@ -115,14 +115,14 @@ export const PROJECTS: Project[] = [ tag: '_clone-aleam', description: 'Clone institucional da Assembleia Legislativa do AM com acessibilidade.', details: 'Recriacao do portal da Assembleia Legislativa do Amazonas com foco em acessibilidade (integracao VLibras). Paginas de: home institucional, CEAP (cota parlamentar) com tabelas e filtros, vencimentos com graficos Recharts. Notificacoes com React Toastify, animacoes com Framer Motion e estado com Zustand.', - image: '/projects/aleam-ceap.png', + image: '/projects/aleam-ceap.jpg', images: [ - '/projects/aleam-ceap.png', - '/projects/aleam-home.png', - '/projects/aleam-vlibras.png', - '/projects/aleam-gastos.png', - '/projects/aleam-vencimentos.png', - '/projects/aleam-csv.png', + '/projects/aleam-ceap.jpg', + '/projects/aleam-home.jpg', + '/projects/aleam-vlibras.jpg', + '/projects/aleam-gastos.jpg', + '/projects/aleam-vencimentos.jpg', + '/projects/aleam-csv.jpg', ], technologies: ['React', 'TypeScript', 'Vite', 'Zustand', 'Recharts', 'Axios', 'React Toastify', 'Framer Motion'], repoUrl: 'https://github.com/DevAlissu/clone_ALEAM', @@ -145,11 +145,11 @@ export const PROJECTS: Project[] = [ tag: '_sistema-melo', description: 'Sistema corporativo com Next.js, autenticacao, relatorios PDF/Excel e multiplos bancos.', details: 'ERP corporativo completo com modulos de: cadastros (clientes, fornecedores, produtos, marcas, bancos, vendedores), financeiro (contas a pagar/receber, faturamento, transferencias), compras (nova compra, historico, dashboard), estoque, vendas e relatorios. Controle de acesso por filiais com grupos de permissoes e perfis. Geracao de relatorios em PDF e Excel. Integracao dual-database com PostgreSQL (Sequelize + Prisma) e Oracle. Multi-filial com selecao de filial no login.', - image: '/projects/sistema-melo.png', + image: '/projects/sistema-melo.jpg', images: [ - '/projects/sistema-melo.png', - '/projects/melo-vendas.png', - '/projects/melo-contas.png', + '/projects/sistema-melo.jpg', + '/projects/melo-vendas.jpg', + '/projects/melo-contas.jpg', ], technologies: ['React', 'Next.js', 'Tailwind', 'TypeScript', 'shadcn/ui', 'Axios', 'React Hook Form', 'Framer Motion', 'Prisma', 'Sequelize', 'PostgreSQL', 'Node.js'], repoUrl: 'https://github.com/DevAlissu/Sistema_Melo', @@ -171,19 +171,19 @@ export const PROJECTS: Project[] = [ tag: '_drawnomes', description: 'Plataforma de sorteio de nomes e amigo secreto com lista de desejos e chat em grupo.', details: 'Plataforma completa de sorteio de nomes para celebracoes (Pascoa, Natal, Aniversario, Dia dos Namorados, Ano Novo). Fluxo guiado em 8 passos:\n\n1. Identificacao do organizador\n2. Adicionar participantes\n3. Selecao do tipo de celebracao\n4. Definir data do evento\n5. Definir valor sugerido de presente (R$25 a R$100)\n6. Mensagem personalizada para o grupo\n7. Revisao e criacao\n8. Sorteio automatico\n\nFuncionalidades pos-sorteio: painel da celebracao com membros, lista de desejos com imagens de presentes, localizador de presentes por genero/idade, chat em grupo em tempo real, compartilhamento por link de convite, confirmacao de participantes.', - image: '/projects/drawnomes-home.png', + image: '/projects/drawnomes-home.jpg', images: [ - '/projects/drawnomes-home.png', - '/projects/drawnomes-passo1.png', - '/projects/drawnomes-passo1b.png', - '/projects/drawnomes-passo2.png', - '/projects/drawnomes-passo4.png', - '/projects/drawnomes-passo5.png', - '/projects/drawnomes-passo6.png', - '/projects/drawnomes-passo7.png', - '/projects/drawnomes-celebracao.png', - '/projects/drawnomes-sorteio.png', - '/projects/drawnomes-resultado.png', + '/projects/drawnomes-home.jpg', + '/projects/drawnomes-passo1.jpg', + '/projects/drawnomes-passo1b.jpg', + '/projects/drawnomes-passo2.jpg', + '/projects/drawnomes-passo4.jpg', + '/projects/drawnomes-passo5.jpg', + '/projects/drawnomes-passo6.jpg', + '/projects/drawnomes-passo7.jpg', + '/projects/drawnomes-celebracao.jpg', + '/projects/drawnomes-sorteio.jpg', + '/projects/drawnomes-resultado.jpg', ], technologies: ['React', 'Tailwind', 'TypeScript', 'Vite', 'shadcn/ui', 'Zustand', 'React Query', 'Recharts', 'React Hook Form', 'Axios', 'React Router'], repoUrl: 'https://github.com/DevAlissu/drawnomes', @@ -196,17 +196,17 @@ export const PROJECTS: Project[] = [ tag: '_horto-app', description: 'App mobile de adocao e cuidado de mudas nativas do Amazonas com identificacao por IA.', details: 'Aplicativo mobile para o Horto Municipal de Manaus, desenvolvido com Flutter. Funcionalidades:\n\n- Onboarding ilustrado: adote mudas, diversidade de especies nativas (Assistacia, Acerola, Boldo, Pupunha), gerenciamento de mudas adotadas, identificacao de plantas por camera.\n\n- Cadastro personalizado com mascote "Protinho".\n\n- Catalogo de mudas por categoria: Ornamentais (Coleus, Beldroega), Frutiferas, Medicinais, com fotos reais das plantas.\n\n- Sistema de adocao com controle de tempo e quantidade.\n\n- Identificacao de plantas via camera do celular usando visao computacional (modelo treinado com Python/TensorFlow).\n\n- Gestao de plantas adotadas com descricao e acompanhamento.', - image: '/projects/horto-catalogo.png', + image: '/projects/horto-catalogo.jpg', images: [ - '/projects/horto-catalogo.png', - '/projects/horto-adote.png', - '/projects/horto-mudas.png', - '/projects/horto-gerencie.png', - '/projects/horto-camera.png', - '/projects/horto-ola.png', - '/projects/horto-nome.png', - '/projects/horto-protinho.png', - '/projects/horto-home.png', + '/projects/horto-catalogo.jpg', + '/projects/horto-adote.jpg', + '/projects/horto-mudas.jpg', + '/projects/horto-gerencie.jpg', + '/projects/horto-camera.jpg', + '/projects/horto-ola.jpg', + '/projects/horto-nome.jpg', + '/projects/horto-protinho.jpg', + '/projects/horto-home.jpg', ], technologies: ['Flutter', 'Python'], repoUrl: 'https://github.com/DevAlissu/horto-app', diff --git a/src/features/snake-game/SnakeGame.tsx b/src/features/snake-game/SnakeGame.tsx index 069bc0e..d8c947a 100644 --- a/src/features/snake-game/SnakeGame.tsx +++ b/src/features/snake-game/SnakeGame.tsx @@ -17,12 +17,14 @@ export function SnakeGame({ className = '' }: SnakeGameProps) { const { status, score, food, gridSize, difficulty, mode, leaderboard, actions } = useSnakeGame(); + const { setDirection, startGame } = actions; + const handleDirectionClick = useCallback( - (direction: Direction) => actions.setDirection(direction), - [actions.setDirection], + (direction: Direction) => setDirection(direction), + [setDirection], ); - const handleStartClick = useCallback(() => actions.startGame(), [actions.startGame]); + const handleStartClick = useCallback(() => startGame(), [startGame]); const isPlaying = status === 'playing' || status === 'paused'; diff --git a/src/features/snake-game/components/GameCanvas.tsx b/src/features/snake-game/components/GameCanvas.tsx index 58d9a49..8835a0c 100644 --- a/src/features/snake-game/components/GameCanvas.tsx +++ b/src/features/snake-game/components/GameCanvas.tsx @@ -5,14 +5,14 @@ import { gridToSvg, getSnakeDimensions } from '../utils/grid'; import { DIFFICULTY_SPEEDS } from '../constants'; interface GameCanvasProps { - food: Position; + food: Position | null; gridSize: number; } export const GameCanvas = memo(function GameCanvas({ food, gridSize }: GameCanvasProps) { const pathRef = useRef(null); const { snakeStrokeWidth } = getSnakeDimensions(gridSize); - const foodPos = gridToSvg(food.x, food.y, gridSize); + const foodPos = food ? gridToSvg(food.x, food.y, gridSize) : null; useEffect(() => { let animId: number; @@ -93,11 +93,13 @@ export const GameCanvas = memo(function GameCanvas({ food, gridSize }: GameCanva fill="none" /> - - - - - + {foodPos && ( + + + + + + )}
diff --git a/src/features/snake-game/store/useGameStore.ts b/src/features/snake-game/store/useGameStore.ts index 24d02e8..49ef72e 100644 --- a/src/features/snake-game/store/useGameStore.ts +++ b/src/features/snake-game/store/useGameStore.ts @@ -9,38 +9,57 @@ import { MAX_LEADERBOARD_ENTRIES, } from '../constants'; -function generateRandomFood(snake: Position[]): Position { - let newFood: Position; - do { - newFood = { - x: Math.floor(Math.random() * GRID_SIZE), - y: Math.floor(Math.random() * GRID_SIZE), - }; - } while (snake.some((segment) => segment.x === newFood.x && segment.y === newFood.y)); - return newFood; +function generateRandomFood(snake: Position[]): Position | null { + const totalCells = GRID_SIZE * GRID_SIZE; + if (snake.length >= totalCells) return null; + + const availableCells: Position[] = []; + for (let x = 0; x < GRID_SIZE; x++) { + for (let y = 0; y < GRID_SIZE; y++) { + if (!snake.some((seg) => seg.x === x && seg.y === y)) { + availableCells.push({ x, y }); + } + } + } + return availableCells[Math.floor(Math.random() * availableCells.length)] ?? null; } function getStoredHighScore(): number { if (typeof window === 'undefined') return 0; - return parseInt(localStorage.getItem('snakeHighScore') || '0'); + try { + return parseInt(localStorage.getItem('snakeHighScore') || '0'); + } catch { + return 0; + } } function saveHighScore(score: number) { - if (typeof window !== 'undefined') { + if (typeof window === 'undefined') return; + try { localStorage.setItem('snakeHighScore', score.toString()); + } catch { + // localStorage full or disabled } } function getStoredLeaderboard(): LeaderboardEntry[] { if (typeof window === 'undefined') return []; - const stored = localStorage.getItem('snakeLeaderboard'); - if (!stored) return []; - return JSON.parse(stored) as LeaderboardEntry[]; + try { + const stored = localStorage.getItem('snakeLeaderboard'); + if (!stored) return []; + const parsed = JSON.parse(stored); + return Array.isArray(parsed) ? (parsed as LeaderboardEntry[]) : []; + } catch { + return []; + } } function saveLeaderboard(entries: LeaderboardEntry[]) { - if (typeof window !== 'undefined') { + if (typeof window === 'undefined') return; + try { localStorage.setItem('snakeLeaderboard', JSON.stringify(entries)); + } catch { + // localStorage full or disabled } } @@ -91,7 +110,7 @@ export const useGameStore = create((set, get) => ({ }, resumeGame: () => { - if (get().status === 'paused') set({ status: 'playing' }); + if (get().status === 'paused') set({ status: 'playing', lastTickTime: performance.now() }); }, gameOver: () => { @@ -196,12 +215,12 @@ export const useGameStore = create((set, get) => ({ const newSnake = [newHead, ...snake]; const tickTime = performance.now(); - if (newHead.x === food.x && newHead.y === food.y) { + if (food && newHead.x === food.x && newHead.y === food.y) { const { score, highScore } = get(); const newScore = score + 1; const newFood = generateRandomFood(newSnake); - if (mode === 'casual' && newScore >= FOOD_TO_WIN_CASUAL) { + if (!newFood || (mode === 'casual' && newScore >= FOOD_TO_WIN_CASUAL)) { const newHighScore = Math.max(newScore, highScore); if (newHighScore > highScore) saveHighScore(newHighScore); set({ diff --git a/src/features/snake-game/types/index.ts b/src/features/snake-game/types/index.ts index 84f085c..fcb2a08 100644 --- a/src/features/snake-game/types/index.ts +++ b/src/features/snake-game/types/index.ts @@ -28,7 +28,7 @@ export interface GameState { direction: Direction; nextDirection: Direction; directionQueue: Direction[]; - food: Position; + food: Position | null; gridSize: number; difficulty: Difficulty; mode: GameMode; diff --git a/src/shared/components/layout/Header.tsx b/src/shared/components/layout/Header.tsx index cbd9ebc..69df065 100644 --- a/src/shared/components/layout/Header.tsx +++ b/src/shared/components/layout/Header.tsx @@ -59,7 +59,9 @@ export function Header() { diff --git a/src/shared/components/layout/Layout.tsx b/src/shared/components/layout/Layout.tsx index 2708726..e9bc877 100644 --- a/src/shared/components/layout/Layout.tsx +++ b/src/shared/components/layout/Layout.tsx @@ -1,3 +1,4 @@ +import { Suspense } from 'react'; import { Outlet, useLocation } from 'react-router'; import { Header } from './Header'; import { Footer } from './Footer'; @@ -16,7 +17,9 @@ export function Layout() {
- + + +
diff --git a/src/styles/fonts.css b/src/styles/fonts.css index 40111ea..f8f30eb 100644 --- a/src/styles/fonts.css +++ b/src/styles/fonts.css @@ -1 +1 @@ -@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&family=Buenard:wght@400;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&family=Buenard:wght@400&display=swap'); diff --git a/src/styles/theme.css b/src/styles/theme.css index 014dbee..9b833a1 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -53,3 +53,18 @@ .animate-tab-fade-in { animation: tab-fade-in 0.2s ease-out; } + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + .animate-tab-fade-in { + animation: none; + } +}