Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/app/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { RouteObject } from 'react-router-dom';
import { ContatoPage, HabilidadesPage, InicioPage, PortifolioPage, SobrePage } from '../pages';
import { ContatoPage, HabilidadesPage, InicioPage, JogoDaVida, PortifolioPage, SobrePage } from '../pages';

const rotas: RouteObject[] = [
{
Expand All @@ -10,6 +10,7 @@ const rotas: RouteObject[] = [
{ path: 'habilidades', element: <HabilidadesPage /> },
{ path: 'portifolio', element: <PortifolioPage /> },
{ path: 'contato', element: <ContatoPage /> },
{ path: 'jogo-da-vida', element: <JogoDaVida /> },
],
},
];
Expand Down
1 change: 1 addition & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { SobrePage } from './sobre/sobre.page';
export { HabilidadesPage } from './habilidades/habilidades.page';
export { PortifolioPage } from './portifolio/portifolio.page';
export { ContatoPage } from './contato/contato.page';
export { JogoDaVida } from './jogo-da-vida/jogo-da-vida';
6 changes: 5 additions & 1 deletion src/pages/inicio/inicio.page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { PaginaLayout } from '../../app/layouts';
import { Menu } from '../../shared/ui';
import './inicio.style.css'
Expand All @@ -20,7 +21,8 @@ const InicioPage: React.FC = () => {
</nav>
</section>

<section className='inicio-secao-direita' aria-label='Ilustração decorativa'>
<section className='inicio-secao-direita'>
<Link className='inicio-link-cubo' to='/jogo-da-vida' aria-label='Abrir o Jogo da Vida de Conway'>
<svg width="320" height="368" viewBox="0 0 270 310" className='inicio--imagem-cubo-impossivel'>
<path d="M135 0L269.5 76.5L270 232L162 171L191 156L243 186V95L163 48V108L135 125V0Z" fill="var(--cor-primaria)"/>
<path d="M27 95L135 155.706V279L55 232L80 216.5L108 232.706V171L54.5 139.706V169.046L27 185V95Z" fill="var(--cor-primaria)"/>
Expand All @@ -29,6 +31,8 @@ const InicioPage: React.FC = () => {
<path d="M0 77L135 0V125L107 109V48L27 95V185L79.5 154.5L108 171L0 232V77Z" fill="var(--cor-destaque)"/>
<path d="M135 155.833L243 95V186L216 170.5V143L162 171V233L190 216.5L217 232L135 279V155.833Z" fill="var(--cor-destaque)"/>
</svg>
<span className='inicio-dica-cubo'>Explorar Jogo da Vida</span>
</Link>
</section>
</div>
</PaginaLayout>
Expand Down
28 changes: 28 additions & 0 deletions src/pages/inicio/inicio.style.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,34 @@
justify-content: center;
}

.inicio-link-cubo {
position: relative;
display: grid;
place-items: center;
color: var(--cor-primaria);
}

.inicio-dica-cubo {
position: absolute;
bottom: -1rem;
padding: .45rem .75rem;
border: 1px solid var(--cor-borda);
border-radius: 99px;
font-size: .68rem;
font-weight: 600;
opacity: 0;
pointer-events: none;
transform: translateY(-.35rem);
transition: opacity var(--transicao), transform var(--transicao);
}

.inicio-link-cubo:hover .inicio-dica-cubo, .inicio-link-cubo:focus-visible .inicio-dica-cubo {
opacity: 1;
transform: translateY(0);
}

.inicio-link-cubo:focus-visible { outline: 2px solid var(--cor-primaria); outline-offset: .5rem; }

.inicio--imagem-cubo-impossivel {
animation: girar-cubo 20s linear infinite;
}
Expand Down
53 changes: 53 additions & 0 deletions src/pages/jogo-da-vida/gameHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export type Grid = boolean[][];

export const GRID_LINHAS = 32;
export const GRID_COLUNAS = 64;

export const criarGrid = (linhas = GRID_LINHAS, colunas = GRID_COLUNAS): Grid => (
Array.from({ length: linhas }, () => Array.from({ length: colunas }, () => false))
);

export const contarCelulasVivas = (grid: Grid): number => (
grid.reduce((total, linha) => total + linha.filter(Boolean).length, 0)
);

export const proximaGeracao = (grid: Grid, bordasConectadas = true): Grid => {
const linhas = grid.length;
const colunas = grid[0]?.length ?? 0;

return grid.map((linha, linhaAtual) => linha.map((viva, colunaAtual) => {
let vizinhasVivas = 0;

for (let deslocamentoLinha = -1; deslocamentoLinha <= 1; deslocamentoLinha += 1) {
for (let deslocamentoColuna = -1; deslocamentoColuna <= 1; deslocamentoColuna += 1) {
if (deslocamentoLinha === 0 && deslocamentoColuna === 0) continue;

let linhaVizinha = linhaAtual + deslocamentoLinha;
let colunaVizinha = colunaAtual + deslocamentoColuna;

if (bordasConectadas) {
linhaVizinha = (linhaVizinha + linhas) % linhas;
colunaVizinha = (colunaVizinha + colunas) % colunas;
}

if (linhaVizinha >= 0 && linhaVizinha < linhas && colunaVizinha >= 0 && colunaVizinha < colunas && grid[linhaVizinha][colunaVizinha]) {
vizinhasVivas += 1;
}
}
}

return viva ? vizinhasVivas === 2 || vizinhasVivas === 3 : vizinhasVivas === 3;
}));
};

export const alternarCelula = (grid: Grid, linha: number, coluna: number, viva?: boolean): Grid => (
grid.map((celulas, indiceLinha) => (
indiceLinha === linha
? celulas.map((celula, indiceColuna) => (indiceColuna === coluna ? viva ?? !celula : celula))
: celulas
))
);

export const gerarGridAleatorio = (linhas = GRID_LINHAS, colunas = GRID_COLUNAS, chanceDeVida = 0.28): Grid => (
Array.from({ length: linhas }, () => Array.from({ length: colunas }, () => Math.random() < chanceDeVida))
);
143 changes: 143 additions & 0 deletions src/pages/jogo-da-vida/jogo-da-vida.style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
.jogo-da-vida-pagina { display: flex; min-height: 100vh; flex-direction: column; }

.jogo-da-vida-topo {
position: relative;
display: flex;
height: 5vh;
align-items: center;
justify-content: space-between;
padding: 0 5vh;
}

.jogo-da-vida-topo > p { font-size: clamp(.72rem, 1.1vw, .95rem); font-weight: 700; letter-spacing: -.03em; }
.jogo-da-vida-topo > p span { color: var(--cor-secundaria); font-weight: 500; }
.jogo-da-vida-marca { position: absolute; top: 50%; left: 50%; width: 1rem; height: 1rem; transform: translate(-50%, -50%) rotate(45deg); background: var(--cor-primaria); }
.jogo-da-vida-marca:focus-visible { outline: 2px solid var(--cor-primaria); outline-offset: .35rem; }

.jogo-da-vida-janela {
display: flex;
width: 100%;
height: 90vh;
gap: clamp(.8rem, 2vw, 1.6rem);
padding: 0 5vh;
}

.jogo-da-vida-menu,
.jogo-da-vida {
border: 4px solid var(--cor-borda);
border-radius: 1.1rem;
background: var(--cor-superficie);
}

.jogo-da-vida-menu {
display: flex;
width: 4rem;
min-width: 4rem;
flex-direction: column;
align-items: center;
padding: 1rem 0;
}

.jogo-da-vida-menu-grupo { display: grid; gap: .7rem; }
.jogo-da-vida-menu-rodape { margin-top: auto; }

.jogo-da-vida-botao-icone {
display: grid;
width: 2.25rem;
height: 2.25rem;
place-items: center;
border: 2px solid transparent;
border-radius: .45rem;
color: var(--cor-secundaria);
font-size: 1.1rem;
transition: background-color var(--transicao), color var(--transicao), border-color var(--transicao), transform var(--transicao);
}

.jogo-da-vida-botao-icone:hover,
.jogo-da-vida-botao-icone.ativo { border-color: var(--cor-borda); color: var(--cor-primaria); background: var(--cor-background); }
.jogo-da-vida-botao-icone:active { transform: scale(.9); }
.jogo-da-vida-botao-icone.destaque { color: var(--cor-texto-claro); background: var(--cor-primaria); }

.jogo-da-vida {
display: flex;
min-width: 0;
min-height: 0;
flex: 1;
flex-direction: column;
overflow: hidden;
}

.jogo-da-vida-estatisticas { display: flex; gap: clamp(1.2rem, 3vw, 3rem); }
.jogo-da-vida-estatisticas div { display: flex; align-items: baseline; gap: .42rem; }
.jogo-da-vida-estatisticas span { color: var(--cor-secundaria); font-size: .56rem; font-weight: 700; letter-spacing: .12em; }
.jogo-da-vida-estatisticas strong { font-size: .82rem; font-variant-numeric: tabular-nums; }

.jogo-da-vida-painel {
width: 100%;
height: 100%;
overflow: hidden;
border-radius: inherit;
}

.jogo-da-vida-tabuleiro-wrap { width: 100%; height: 100%; overflow: auto; }

.jogo-da-vida-tabuleiro {
display: grid;
width: 100%;
min-width: 42rem;
height: 100%;
min-height: 27rem;
grid-template-columns: repeat(var(--colunas), 1fr);
grid-template-rows: repeat(var(--linhas), 1fr);
background-color: color-mix(in srgb, var(--cor-primaria) 30%, transparent);
gap: 1px;
user-select: none;
touch-action: none;
}

.jogo-da-vida-celula { min-width: 0; border-radius: 0; background: var(--cor-superficie); }
.jogo-da-vida-celula:hover { background: var(--cor-secundaria); }
.jogo-da-vida-celula.viva { background: var(--cor-primaria); }
.jogo-da-vida-celula:focus-visible { z-index: 1; outline: 2px solid var(--cor-destaque); outline-offset: -2px; }

.jogo-da-vida-ajuda {
position: fixed;
z-index: 2;
top: 50%;
left: 50%;
width: min(44rem, calc(100vw - 3rem));
max-height: calc(100vh - 3rem);
overflow: auto;
padding: clamp(1.75rem, 4vw, 3.25rem);
border: 4px solid var(--cor-borda);
border-radius: 1.1rem;
box-shadow: .75rem .75rem 0 color-mix(in srgb, var(--cor-primaria) 16%, transparent);
background: var(--cor-superficie);
transform: translate(-50%, -50%);
}

.jogo-da-vida-ajuda > button { position: absolute; top: .7rem; right: .9rem; color: var(--cor-secundaria); font-size: 1.35rem; }
.jogo-da-vida-ajuda h2 { max-width: 28rem; margin: .8rem 0 1rem; font-size: clamp(1.45rem, 3vw, 2.15rem); letter-spacing: -.055em; }
.jogo-da-vida-ajuda-introducao { max-width: 37rem; color: var(--cor-secundaria); font-size: .92rem; line-height: 1.7; }
.jogo-da-vida-ajuda ul { display: grid; gap: .75rem; margin-top: 1.5rem; padding-left: 1.25rem; color: var(--cor-secundaria); font-size: .9rem; line-height: 1.5; }
.jogo-da-vida-ajuda-interacao { margin-top: 2rem; padding-top: 1.5rem; border-top: 2px solid color-mix(in srgb, var(--cor-primaria) 22%, transparent); }
.jogo-da-vida-ajuda-interacao h3 { margin-bottom: .65rem; font-size: .8rem; letter-spacing: .08em; }
.jogo-da-vida-ajuda-interacao p { color: var(--cor-secundaria); font-size: .88rem; line-height: 1.7; }
.jogo-da-vida-ajuda kbd { padding: .14rem .3rem; border: 1px solid var(--cor-borda); border-radius: .25rem; color: var(--cor-primaria); font: 600 .72rem monospace; background: var(--cor-background); }
.jogo-da-vida-sobretitulo { color: var(--cor-secundaria); font-size: .65rem; font-weight: 700; letter-spacing: .12em; }

@media (max-width: 700px) {
.jogo-da-vida-janela { height: 90vh; gap: .6rem; padding: 0 1rem; }
.jogo-da-vida-menu,
.jogo-da-vida { border-width: 3px; border-radius: .8rem; }
.jogo-da-vida-topo { padding: 0 1rem; }
.jogo-da-vida-topo > p { font-size: .65rem; }
.jogo-da-vida-marca { width: .75rem; height: .75rem; }
.jogo-da-vida-menu { width: 3.25rem; min-width: 3.25rem; padding: .8rem 0; }
.jogo-da-vida-botao-icone { width: 2rem; height: 2rem; font-size: .95rem; }
.jogo-da-vida-menu-grupo { gap: .45rem; }
.jogo-da-vida-estatisticas { gap: .65rem; }
.jogo-da-vida-estatisticas span { font-size: .48rem; }
.jogo-da-vida-estatisticas strong { font-size: .68rem; }
.jogo-da-vida-tabuleiro { min-width: 34rem; min-height: 25rem; }
}
Loading
Loading