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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ Notable changes to this project are recorded here. The format follows

## [Unreleased]

### Changed

- The theme is a thin layer over the VitePress default theme: one
`style.css`, Lucide icons in the sidebar, and the same header geometry on
every page. `ApiCard` is a plain key/description list.
- Each home page is the stock VitePress hero with a pointer-following
`HomeSpotlight` behind it and a screenshot of the portal below.

### Removed

- `HeroSection` and `SpotlightBackground`, and the `theme.css`, `sidebar.css`
and `api-docs-responsive.css` stylesheets.

## [0.1.0] - 2026-11-05

### Added
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,22 @@ raw spec ──► hydrate ► fix ► validate ──► docs/public/openapi.<l

- **Guides** are Markdown in `docs/{en,pt,es,fr}/`. Each locale has its own nav and sidebar in `docs/.vitepress/config.ts`.
- **API reference**: `docs/.vitepress/components/ScalarApi.vue` embeds Scalar and picks the spec from the page language (`en` → `openapi.en.json`, `pt` → `openapi.pt-BR.json`, ...). Pass `spec-url` to override.
- **Theme**: Scalar runs with `theme: 'none'` and its CSS variables are mapped onto VitePress's, so the reference follows the site's colors, fonts and dark mode. The palette lives in `docs/.vitepress/theme/theme.css`.
- **Components**: `ApiCard` (key/description tables used in the guides), `HeroSection` + `SpotlightBackground` (landing page).
- **Theme**: Scalar runs with `theme: 'none'` and its CSS variables are mapped onto VitePress's, so the reference follows the site's colors, fonts and dark mode. The brand color and fonts live in `docs/.vitepress/theme/style.css`, a thin layer over the VitePress default theme.
- **Components**: `ApiCard` (key/description tables used in the guides), Each locale's home page is the stock VitePress hero, configured in `docs/<lang>/index.md`, with `HomeSpotlight` (a glow that follows the pointer) behind it.
- **Language routing**: `/` (`docs/index.md`) sends the browser to `/pt/`, `/es/` or `/fr/` based on its language, and to `/en/` otherwise. Without JavaScript it shows a list of the four languages.

## Project structure

```text
docs/
├── .vitepress/
│ ├── components/ ApiCard, HeroSection, ScalarApi, SpotlightBackground
│ ├── theme/ theme.css (palette), style.css, api-docs-responsive.css, sidebar.css, fonts.css
│ ├── components/ ApiCard, HomeSpotlight, ScalarApi
│ ├── theme/ style.css (brand, fonts, surfaces), fonts.css
│ └── config.ts locales, nav, sidebars
├── public/
│ ├── openapi.{en,pt-BR,es,fr}.json one spec per language
│ ├── preview.png hero image (also used above)
│ ├── preview.png screenshot used above
│ ├── preview-{1080,1761}.webp the same image on the home pages
│ └── favicon.svg, logo-{light,dark}.svg
├── en/ pt/ es/ fr/ intro, authentication, rate-limits, examples, pagination, errors, api-reference
└── index.md language redirect
Expand All @@ -68,8 +69,8 @@ docs/

1. Replace `docs/public/openapi.*.json` with your specs (one per language, or point every locale at the same file in `SPEC_BY_LANG` inside `ScalarApi.vue`).
2. Rewrite the guide pages in `docs/<lang>/` and adjust the sidebars in `docs/.vitepress/config.ts`.
3. Change the brand block in `docs/.vitepress/theme/theme.css` and the logos in `docs/public/`.
4. Take a new `docs/public/preview.png` (the hero shows it inside a MacBook frame: screenshot at 1440×932 and composite it into the screen area).
3. Change the brand block in `docs/.vitepress/theme/style.css` and the logos in `docs/public/`.
4. Replace `docs/public/preview.png` and the two `preview-*.webp` files that the home pages show (1761×1075, a laptop frame with a 1440×932 screenshot composited into the screen).
5. Deploy: any static host works. The live demo runs on Cloudflare Workers Static Assets: `pnpm run deploy` builds the site and runs `wrangler deploy` (config in `wrangler.jsonc`).

## Contributing
Expand Down
15 changes: 8 additions & 7 deletions README.pt-br.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,22 @@ spec crua ──► hydrate ► fix ► validate ──► docs/public/openapi.<

- **Guias** são Markdown em `docs/{en,pt,es,fr}/`. Cada idioma tem nav e sidebar próprios em `docs/.vitepress/config.ts`.
- **Referência da API**: `docs/.vitepress/components/ScalarApi.vue` embute o Scalar e escolhe a spec pelo idioma da página (`pt` → `openapi.pt-BR.json`, `en` → `openapi.en.json`, ...). Use `spec-url` para sobrescrever.
- **Tema**: o Scalar roda com `theme: 'none'` e suas variáveis CSS são mapeadas para as do VitePress, então a referência segue cores, fontes e modo escuro do site. A paleta fica em `docs/.vitepress/theme/theme.css`.
- **Componentes**: `ApiCard` (tabelas chave/descrição dos guias), `HeroSection` + `SpotlightBackground` (página inicial).
- **Tema**: o Scalar roda com `theme: 'none'` e suas variáveis CSS são mapeadas para as do VitePress, então a referência segue cores, fontes e modo escuro do site. A cor da marca e as fontes ficam em `docs/.vitepress/theme/style.css`, uma camada fina sobre o tema padrão do VitePress.
- **Componentes**: `ApiCard` (tabelas chave/descrição dos guias), A página inicial de cada idioma é o hero padrão do VitePress, configurado em `docs/<lang>/index.md`, com o `HomeSpotlight` (um brilho que segue o ponteiro) por trás.
- **Idioma**: `/` (`docs/index.md`) leva o navegador para `/pt/`, `/es/` ou `/fr/` conforme o idioma dele, e para `/en/` nos demais casos. Sem JavaScript, mostra a lista dos quatro idiomas.

## Estrutura do projeto

```text
docs/
├── .vitepress/
│ ├── components/ ApiCard, HeroSection, ScalarApi, SpotlightBackground
│ ├── theme/ theme.css (paleta), style.css, api-docs-responsive.css, sidebar.css, fonts.css
│ ├── components/ ApiCard, HomeSpotlight, ScalarApi
│ ├── theme/ style.css (marca, fontes, superfícies), fonts.css
│ └── config.ts idiomas, nav, sidebars
├── public/
│ ├── openapi.{en,pt-BR,es,fr}.json uma spec por idioma
│ ├── preview.png imagem da página inicial (também usada acima)
│ ├── preview.png screenshot usado acima
│ ├── preview-{1080,1761}.webp a mesma imagem nas páginas iniciais
│ └── favicon.svg, logo-{light,dark}.svg
├── en/ pt/ es/ fr/ intro, authentication, rate-limits, examples, pagination, errors, api-reference
└── index.md redirecionamento de idioma
Expand All @@ -68,8 +69,8 @@ docs/

1. Substitua `docs/public/openapi.*.json` pelas suas specs (uma por idioma, ou aponte todos os idiomas para o mesmo arquivo em `SPEC_BY_LANG`, dentro de `ScalarApi.vue`).
2. Reescreva os guias em `docs/<lang>/` e ajuste as sidebars em `docs/.vitepress/config.ts`.
3. Troque o bloco de marca em `docs/.vitepress/theme/theme.css` e os logos em `docs/public/`.
4. Gere um novo `docs/public/preview.png` (a página inicial mostra a imagem dentro de um MacBook: print em 1440×932 composto na área da tela).
3. Troque o bloco de marca em `docs/.vitepress/theme/style.css` e os logos em `docs/public/`.
4. Troque o `docs/public/preview.png` e os dois `preview-*.webp` das páginas iniciais (1761×1075, um notebook com um print de 1440×932 composto na tela).
5. Publique em qualquer host estático. A demo roda no Cloudflare Workers Static Assets: `pnpm run deploy` gera o site e roda `wrangler deploy` (configuração em `wrangler.jsonc`).

## Contribuindo
Expand Down
227 changes: 51 additions & 176 deletions docs/.vitepress/components/ApiCard.vue
Original file line number Diff line number Diff line change
@@ -1,199 +1,74 @@
<script setup lang="ts">
defineProps<{
title?: string
items: { key: string; description: string }[]
}>()
</script>

<template>
<div class="api-macos-window">
<div class="api-macos-header">
<div class="macos-buttons">
<div class="macos-btn macos-btn-red"></div>
<div class="macos-btn macos-btn-yellow"></div>
<div class="macos-btn macos-btn-green"></div>
<figure class="api-card">
<figcaption v-if="title" class="api-card-title">{{ title }}</figcaption>
<dl>
<div v-for="item in items" :key="item.key" class="api-card-row">
<dt>{{ item.key }}</dt>
<!-- Descriptions are authored in the docs and may contain <code>. -->
<dd v-html="item.description" />
</div>
<span class="api-macos-title">{{ title }}</span>
</div>
<div class="api-macos-content">
<div
v-for="(item, index) in items"
:key="index"
class="api-macos-row"
>
<span class="api-macos-key" :class="item.color">{{ item.key }}</span>
<span class="api-macos-value" v-html="item.description"></span>
</div>
</div>
</div>
</dl>
</figure>
</template>

<script setup lang="ts">
interface ApiCardItem {
key: string
description: string
color?: string
}

withDefaults(defineProps<{ title?: string; items: ApiCardItem[] }>(), {
title: 'request.headers'
})
</script>

<style scoped>
.api-macos-window {
background: var(--api-macos-bg, rgba(255,255,255,0.7));
border-radius: 14px;
box-shadow: 0 6px 32px 0 rgba(28,28,28,0.16), 0 1.5px 3px 0 rgba(28,28,30,0.05);
overflow: hidden;
backdrop-filter: blur(24px) saturate(160%);
-webkit-backdrop-filter: blur(24px) saturate(160%);
margin: 1.5rem 0;
animation: fadeUp 0.5s cubic-bezier(.42,0,.58,1);
transition: box-shadow 0.2s cubic-bezier(.42,0,.58,1);
.api-card {
margin: 16px 0;
border-radius: 8px;
box-shadow: var(--ring);
overflow: clip;
}

.api-card-title {
padding: 10px 16px;
border-bottom: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
font-family: var(--vp-font-family-mono);
font-size: 13px;
color: var(--vp-c-text-2);
}

.api-macos-window:hover {
box-shadow: 0 12px 36px 0 rgba(28,28,28,0.24), 0 2px 8px 0 rgba(28,28,30,0.09);
dl {
margin: 0;
}

.api-macos-header {
display: flex;
align-items: center;
gap: 12px;
padding: 18px 24px 14px 18px;
background: linear-gradient(
120deg,
rgba(240,241,246,0.60) 0%,
rgba(255,255,255,0.35) 100%
);
position: relative;
z-index: 1;
box-shadow: 0 1px 0 0 rgba(28,28,30,0.03);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
.api-card-row {
display: grid;
grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
gap: 4px 24px;
padding: 12px 16px;
}

.macos-buttons {
display: flex;
gap: 8px;
margin-right: 8px;
.api-card-row + .api-card-row {
border-top: 1px solid var(--vp-c-divider);
}

.macos-btn {
width: 11px;
height: 11px;
border-radius: 50%;
box-shadow: 0 0.5px 1.5px rgba(0,0,0,0.07);
border: 0.5px solid rgba(0,0,0,0.04);
transition: transform 0.13s cubic-bezier(.42,0,.58,1);
position: relative;
cursor: default;
}
.macos-btn:hover {
transform: scale(1.11);
}
.macos-btn-red { background: linear-gradient(120deg,#fd594b 60%,#ea3323 100%);}
.macos-btn-yellow { background: linear-gradient(120deg,#fdbd41 60%,#f8e36b 100%);}
.macos-btn-green { background: linear-gradient(120deg,#31c150 60%,#28b846 100%);}

.api-macos-title {
dt {
font-family: var(--vp-font-family-mono);
font-size: 14px;
color: var(--vp-c-text-1, #222);
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
font-weight: 500;
letter-spacing: -0.01em;
margin-left: 8px;
opacity: 0.75;
text-shadow: 0 1px 1.5px rgba(255,255,255,0.18);
user-select: text;
transition: color 0.15s;
}

.api-macos-content {
font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
background: transparent;
}

.api-macos-row {
display: flex;
gap: 18px;
padding: 14px 22px 14px 22px;
align-items: flex-start;
border-bottom: 1px solid rgba(40,40,50,0.05);
transition: background 0.2s, transform 0.18s cubic-bezier(.42,0,.58,1);
position: relative;
min-height: 36px;
}

.api-macos-row:last-child {
border-bottom: none;
}

.api-macos-row:hover {
background: rgba(120,140,255,0.04);
transform: translateY(-1.5px) scale(1.01);
}

.api-macos-key {
font-weight: 600;
min-width: 135px;
flex-shrink: 0;
font-size: 13px;
letter-spacing: -0.01em;
user-select: text;
font-family: inherit;
color: var(--vp-c-text-1);
overflow-wrap: anywhere;
}
.api-macos-key.blue { color: #006fff; }
.api-macos-key.purple { color: #7158e2; }
.api-macos-key.pink { color: #ff2d92; }
.api-macos-key.red { color: #ff3b30; }
.api-macos-key.green { color: #28b846; }

.api-macos-value {
color: var(--vp-c-text-2, #444);
dd {
margin: 0;
font-size: 15px;
line-height: 1.6;
font-size: 13px;
user-select: text;
font-family: inherit;
color: var(--vp-c-text-2);
text-wrap: pretty;
}

@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
@media (max-width: 640px) {
.api-card-row {
grid-template-columns: minmax(0, 1fr);
}
}

/* Dark mode */
:root.dark .api-macos-window {
--api-macos-bg: rgba(32,34,38,0.72);
background: var(--api-macos-bg);
box-shadow: 0 8px 34px 0 rgba(20,20,22,0.28), 0 2px 8px 0 rgba(20,20,24,0.18);
}
:root.dark .api-macos-header {
background: linear-gradient(120deg,rgba(38,41,49,0.82) 0%,rgba(26,27,32,0.56) 100%);
box-shadow: 0 1px 0 0 rgba(190,190,200,0.035);
}
:root.dark .api-macos-title {
color: #eee;
text-shadow: 0 1px 2px rgba(0,0,0,0.22);
}
:root.dark .api-macos-row {
border-bottom: 1px solid rgba(220,220,230,0.04);
}
:root.dark .api-macos-row:hover {
background: rgba(60,80,140,0.06);
}
:root.dark .api-macos-key.blue { color: #0a84ff; }
:root.dark .api-macos-key.purple { color: #5e5ce6; }
:root.dark .api-macos-key.pink { color: #ff375f; }
:root.dark .api-macos-key.red { color: #ff453a; }
:root.dark .api-macos-key.green { color: #28cd41; }

/* Responsive */
@media (max-width: 768px) {
.api-macos-window { margin: 1rem 0; border-radius: 9px; }
.api-macos-header { padding: 13px 12px 10px 13px; }
.api-macos-row { padding: 12px 13px; flex-direction: column; gap: 5px; }
.api-macos-key { min-width: auto; }
}
</style>
Loading
Loading