Construí o layout do site do Space Jam de 1996 com CSS moderno 🤯
Tags: css, braziliandevs, tutorial, webdev
Published: 2023-11-02 03:10:56

## Sumário

- [Marcação inicial](#marcacao)
- [CSS base](#base)
- [O container principal](#container)
- [A constelação](#constelation)
- [Finalmente](#final)

<br />

---

<br/>

## Marcação inicial <a id="marcacao" href="#marcacao">&#128279;</a>


O site possui basicamente a galáxia como uma espécie de navegação e um footer contendo os links externos. Como boa prática, coloquei o conteúdo dentro de um `<main>` landmark e encapsulei a navegação e o footer dentro do contexto dele.

```html
  <body>
    <main class="layout">
      <nav class="space"></nav>
      <footer class="footer"></footer>
    </main>
  </body>
```

&nbsp;

A navegação é basicamente uma lista não ordenada de links. Dessa forma o leitor de tela irá anunciar a entrada e a quantidade de itens ao acessar a lista e anunciar a saída quando a pessoa usuária mover o foco pra outro lugar.

As imagens tem texto alternativo que são lidos como conteúdo textual dos links!

```html
<ul>
  <li>
    <a href="" class="space__link">
      <img
        src="./assets/p-pressbox.gif"
        alt="press box shuttle"
        class="space__img" />
    </a>
  </li>
</ul>
```

<br />

---

<br/>

## CSS base <a id="base" href="#base">&#128279;</a>

No `index.css` criei duas camadas - a de _reset_ que irá sobrescrever o CSS padrão do navegador (_user-agent stylesheet_) e a _default_ que receberá todo código diretamente relacionado à aplicação. Isso faz com que o código da aplicação jamais seja inferior que o do reset em especificidade, mas também cria uma estrutura que pode comportar ainda mais camadas.

```css
@layer reset, default;

@import "reset.css" layer(reset);
```

&nbsp;

No arquivo de reset, apenas o necessário dado o contexto do projeto. Em aplicações mais robustas talvez eu optasse por um normalizer.

```css
* {
  margin: 0; padding: 0;
  box-sizing: border-box;
}

img {
  height: auto;
  max-width: 100%;
  vertical-align: middle;
  object-fit: contain;
}

body {
  min-block-size: 100vh;
  background-image: url("./assets/bg_stars.gif");
}

ul { list-style-type: none; }
```

**O que tá rolando nesse CSS?**
1. **\*** - Zera a `margin` e o `padding` padrão de todos elementos, e com o `box-sizing: border-box` o box-model passa a não incluir o `padding` na soma na hora de definir a altura e largura dos elementos.

2. **img** - `max-width: 100%` e `height: auto` fazem com que a imagem aja de forma fluida mas mantendo o _aspect-ratio_. `vertical-align: middle` remove aquele espacinho branco esquisito na parte de baixo da imagem, e `object-fit: contain` faz com que a figura se redimensione pra caber completamente no container, mas se mantendo totalmente visível.

3. **body** - `min-block-size: 100vh` faz com que o site **seja no mínimo do tamanho da tela toda**.

<br />

---

<br/>

## O container principal <a id="container" href="#container">&#128279;</a>

O layout é basicamente composto por 3 classes - `.layout` que  coloca o site no centro da tela de forma responsiva, a `.space` que cria o grid em que os planetas vão se encaixar e a `.space__item` que define o alinhamento e posição dos itens do grid.

Vamos começar com o `.layout`.

![Grid de 3 colunas contendo o conteúdo no centro](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/givv8tvxhtz0emk9sdsa.png)

&nbsp;

Essa classe é aplicada no `<main>`, como `<nav>` e `<footer>` são seus únicos filhos, criei um grid de 3 colunas e empurrei os seus dois filhos diretos pra segunda coluna, a do meio. O grid foi construído de forma que quando a largura da página for menor que 600px ele ocupará 100% da largura tela. Como o 1fr se refere a uma fração do espaço restante, como não restará espaço, seu valor será zero, dando a ilusão de que só  há uma coluna.

```css
.layout {
  display: grid;
  grid-template-columns: 1fr min(600px, 100%) 1fr;
  place-content: center;
  height: 100vh;
 & > * { grid-column: 2; }
}
```

> 💡 O `&` nesse código não é Sass, o CSS tem suporte pra nesting nos principais navegadores, não é o suficiente pra ser usado em produção, mas já podemos brincar em projetos como esse. Caso queira saber mais, [leia esse artigo do chrome sobre esse assunto, em inglês](https://developer.chrome.com/articles/css-nesting/).

&nbsp;

O conceito é um pouco avançado, escrevi um artigo bem visual sobre esse padrão de layout em grid:

{% embed https://dev.to/lixeletto/padroes-de-layout-avancados-com-css-33ki %}

&nbsp;

<br />

---

<br/>

## A constelação <a id="constelation" href="#constelation">&#128279;</a>

A área em que fica a galáxia do space-jam e que é uma table no original é um grid com 5 colunas e 4 linhas. O valor de cada célula usa `fr` pra que a distribuição de espaço nunca seja exata, mas sempre proporcional ao espaço disponível. Isso faz com que a constelação se mantenha íntegra mesmo em _viewports_ muito pequenos.

```css
.space {
  display: grid;
  grid-template-columns: 0.5fr 0.75fr 1fr 0.5fr 0.75fr;
  grid-template-rows: 1fr 0.5fr 0.5fr 1fr;
  width: 100%;
  aspect-ratio: 1 / 1;
}
```

&nbsp;

O resultado é um grid bastante complexo:

![Grid com 5 colunas e 4 linhas contendo os planetas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1t03r8qm1rd9q99ms23.png)

&nbsp;

À partir disso, eu podia criar várias classes pra cada item desse grid com `:nth-child` e definir uma `grid-area` e um alinhamento pra cada um, mas escolhi uma abordagem diferenciada:

```css
.space__item {
  --size: auto;
  display: grid;
  place-items: center;
  height: var(--size);
  width: var(--size);

  align-self: var(--align, center);
  justify-self: var(--justify, center);
  grid-area: var(--area);

  aspect-ratio: 1 / 1;
}
```

&nbsp;

Pense nessas variáveis como _props_ ou como argumentos de uma função em Javascript. A `width` e o `height` podem ser customizados pela variável `--size`, mas se não forem, o valor padrão é `auto`, o mesmo vale pros alinhamentos. Dessa forma, ao invés de criar uma dúzia de classes pra estilizar cada planeta, eu transferi essa responsabilidade para o template.

```html
<li
  class="space__item"
  style="
    --area: 1 / 1 / 2 / 3;
    --align: end;
    --justify: end"
>
  <a href="" class="space__link">
    <img
     src="./assets/p-pressbox.gif"
     alt="press box shuttle"
     class="space__img" />
   </a>
</li>

<!-- outros elementos -->

```

&nbsp;

Dessa forma, cada elemento no HTML passaria como  "props" sua própria orientação e alinhamento para a mesma classe no CSS. Se pensar, tem a mesma manutenabilidade e legibilidade de uma classe Tailwind.

Como a galáxia faz um circulo bem decente, apenas com os alinhamentos do grid não foi possível alinhar de forma circular os planetas nas extremidades.

![Site com os planetas desalinhados](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/44cacwd7m0gwx4a6ujb8.png)

&nbsp;

Pra isso, houve um ajuste em planetas específicos:

```css
.space__link:nth-child(3) {
  transform: translateY(12vmin);
}

.space__link:nth-child(10) {
  transform: translateY(-6vmin);
}
```

O `vmin` é calculado pelo tamanho do menor eixo de viewport, os valores foram definidos na base do teste. O `vmin` foi escolhido pois eu precisava de uma unidade que diminuísse o espaçamento do translate proporcionalmente ao tamanho da tela quando esse diminuísse.

Exemplo do layout em 320px:

| Com o ajuste no `transform` | Sem o ajuste no `transform` |
| --- | --- |
| ![Com o ajuste no transform](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhhdlhyuz8zrk2axqjgn.png) | ![Sem o ajuste no transform](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g068ax9azfptytuqoli3.png) |

<br />

---

<br/>

## Finalmente <a id="final" href="#final">&#128279;</a>

Fiquei super feliz que foi possível construir esse layout usando apenas 98 linhas de CSS e um template bem enxuto. Com Sass, seriam 81 linhas. Mas não é só uma questão de contagem de linhas, mas usar o CSS de forma a extrair o máximo do seu potencial de forma robusta e sem desperdício de recursos.

{% details O código em scss, se tiver curiosidade %}

```scss
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:where(img) {
  height: auto;
  max-width: 100%;
  vertical-align: middle;
  object-fit: contain;
}

:where(body) {
  min-block-size: 100vh;
  background-image: url("./assets/bg_stars.gif");
}

:where(ul) { list-style-type: none; }

.layout {
  display: grid;
  grid-template-columns: 1fr min(600px, 100%) 1fr;
  place-content: center;
  height: 100vh;
  & > * { grid-column: 2; }
}

.space {
  width: 100%;
  & ul {
    display: grid;
    grid-template-columns: 0.5fr 0.75fr 1fr 0.5fr 0.75fr;
    grid-template-rows: 1fr 0.5fr 0.5fr 1fr;
    aspect-ratio: 1 / 1;
  }

  &__item {
    --size: auto;
    display: grid;
    place-items: center;
    height: var(--size);
    width: var(--size);

    align-self: var(--align, center);
    justify-self: var(--justify, center);
    grid-area: var(--area);

    aspect-ratio: 1 / 1;

    &:nth-child(3) { transform: translateY(12vmin); }
    &:nth-child(10) { transform: translateY(-6vmin); }
  }
}

.footer {
  display: grid;
  justify-items: center;
  gap: 2ch;
  width: 100%;
  color: rgb(255, 76, 76);
  &__link {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 2ch;
  }

  &__link-item {
    flex-grow: 2;
    text-align: center;
    a { color: inherit; }
  }

  &__text {
    max-width: 40ch;
    text-align: center;
    line-height: 150%;
  }
}

```
{% enddetails %}

&nbsp;

Sites antigos assim são uma ótima referência de estudos, principalmente porque naquela época o propósito da web era outro que não meramente comercial, permitindo com que pessoas transbordassem liberdade criativa nos seus lotes na internet.

Se quiser ver o site online e inspecionar o código, basta acessar o link abaixo.
{% embed https://space-jam-clone.netlify.app/ %}.