snapport 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Guilherme Godoy
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Guilherme Godoy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/README.md CHANGED
@@ -1,130 +1,130 @@
1
- <p align="center">
2
- <a href="./README.pt-br.md">Leia isto em Português</a>
3
- </p>
4
-
5
- <p align="center">
6
- <img src="https://img.shields.io/npm/dm/snapport?style=for-the-badge&logo=npm" alt="NPM Downloads">
7
- <img src="https://img.shields.io/npm/v/snapport?style=for-the-badge" alt="NPM Version">
8
- <img src="https://img.shields.io/github/license/guilhermegodoydev/snapport?style=for-the-badge" alt="License">
9
- <img src="https://img.shields.io/bundlephobia/min/snapport?style=for-the-badge" alt="Bundle Size">
10
- </p>
11
-
12
- # Snap-Port 🚀
13
-
14
- **Snap-Port** is a zero-dependency **TypeScript** library designed to automate the display of GitHub projects on personal websites or portfolios.
15
-
16
- The core philosophy is to use GitHub as your **Single Source of Truth**: by simply tagging your repositories, the library handles fetching, processing, caching, and rendering data, eliminating manual updates to your portfolio's code.
17
-
18
- ---
19
-
20
- ## 🛠 Technical Features
21
-
22
- ### 1. Project Selection & Stack Control
23
- Snap-Port offers full control over what is displayed and how technologies are categorized:
24
-
25
- - **Discovery Tag**: By default, the library looks for repositories tagged with `port`, but you can define any custom tag during initialization.
26
- - **Stack Filters (Topics)**: For automatic filters and search to work correctly, list your technologies (e.g., `react`, `nodejs`, `css`) in your GitHub repository topics.
27
- - **Why avoid automatic "Language"?**: Snap-Port ignores the default GitHub `language` field to let you highlight the actual stack. This prevents a React project from being labeled merely as "HTML" or "JavaScript" due to build files, ensuring the filter reflects the real project stack.
28
-
29
- ### 2. Intelligent Image Management
30
- Since the GitHub API does not return direct preview image links, Snap-Port implements an **automatic generation logic**:
31
-
32
- To ensure each project has its own image, follow these rules:
33
- - **Preview File**: Create a file named `preview.png` in the root of your repository.
34
- - **Note**: The filename must be exactly `preview.png` (case-sensitive).
35
-
36
- If the file is missing or a loading error occurs (such as Rate Limits), the library executes a **cascading fallback strategy**:
37
- 1. **GitHub Open Graph**: Attempts to load the dynamic social card generated by GitHub.
38
- 2. **Safety Placeholder**: If the request is blocked, it generates a neutral card with the project name via `placehold.co`.
39
-
40
- ### 3. Integrated UI Components
41
- - **Search Bar**: Real-time text filtering (name, description, and topics).
42
- - **Filter Carousel**: Dynamic carousel based on repository topics.
43
- - **Project Cards (16:9 Layout)**: Responsive cards with technology badges and action buttons (Code and Deploy).
44
-
45
- ---
46
-
47
- ## 💡 Best Practices
48
-
49
- - **Image Aspect Ratio**: To avoid cropping, save your `preview.png` files in **16:9** aspect ratio (e.g., 1280x720px).
50
- - **Live Demo (Deploy)**: The "Acessar" (Visit) button only appears if the **"Homepage"** field is filled in your GitHub repository settings.
51
-
52
- ---
53
-
54
- ## 📦 Installation & Integration
55
-
56
- ### Via NPM
57
- ```bash
58
- npm install snapport
59
- ```
60
-
61
- ### Via CDN (Direct HTML)
62
- Use ``type="module"`` for modern ES module compatibility.
63
-
64
- ```html
65
- <!-- 1. Library Styles -->
66
- <link rel="stylesheet" href="https://cdn.jsdelivr.net">
67
-
68
- <!-- 2. Logic and Initialization -->
69
- <script type="module">
70
- import { initPortfolio } from 'https://cdn.jsdelivr.net';
71
-
72
- initPortfolio('your-username', {
73
- tag: 'port', // Optional: defaults to 'port'
74
- searchContainer: 'id-search', // Search container ID
75
- filtersContainer: 'id-filters', // Filters container ID
76
- projectsContainer: 'id-projects' // Projects grid container ID
77
- });
78
- </script>
79
- ```
80
-
81
- ---
82
-
83
- ## 🎨 Customization (CSS Variables)
84
- Adapt the UI to your theme by overriding these variables in your global CSS:
85
-
86
- ```css
87
- :root {
88
- --ghp-accent: #333; /* Accent color (buttons/icons) */
89
- --ghp-bg: #ffffff; /* Card background */
90
- --ghp-text: #333; /* Main headings and text */
91
- --ghp-text-light: #666; /* Descriptions and secondary text */
92
- --ghp-border: rgba(226, 226, 228, 0.8); /* Borders */
93
- --ghp-shadow: rgba(0, 0, 0, 0.1); /* Card shadows */
94
- }
95
- ```
96
-
97
- ---
98
-
99
- ## ⚙️ Performance & Advanced Usage
100
-
101
- ### Custom Template Injection
102
- Keep the search/cache logic but use your own design:
103
-
104
- ```javascript
105
- initPortfolio('your-username', {
106
- projectsContainer: 'id-projects',
107
- customCardTemplate: (repo) => `
108
- <div class="my-custom-card">
109
- <h4>${repo.name}</h4>
110
- <p>${repo.description}</p>
111
- <a href="${repo.htmlUrl}">View Source</a>
112
- </div>
113
- `
114
- });
115
- ```
116
-
117
- ### Cache & Stability
118
- Data is stored via localStorage for better performance:
119
-
120
- - Persistence: Data cached for up to 2 hours.
121
- - Isolation: Cache is separated by GitHub username.
122
-
123
- ---
124
-
125
- > **Note:**
126
- > This is an independent open-source project. Feel free to contribute! If you find a bug or have a feature idea, opening an Issue or a Pull Request is the best way to help.
127
- >
128
- > To learn how to collaborate with the code, please check our [**Contributing Guide**](./CONTRIBUTING.md).
129
-
130
- **Author**: Guilherme Godoy (@guilhermegodoydev)
1
+ <p align="center">
2
+ <a href="./README.pt-br.md">Leia isto em Português</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="https://img.shields.io/npm/dm/snapport?style=for-the-badge&logo=npm" alt="NPM Downloads">
7
+ <img src="https://img.shields.io/npm/v/snapport?style=for-the-badge" alt="NPM Version">
8
+ <img src="https://img.shields.io/github/license/guilhermegodoydev/snapport?style=for-the-badge" alt="License">
9
+ <img src="https://img.shields.io/bundlephobia/min/snapport?style=for-the-badge" alt="Bundle Size">
10
+ </p>
11
+
12
+ # Snap-Port 🚀
13
+
14
+ **Snap-Port** is a zero-dependency **TypeScript** library designed to automate the display of GitHub projects on personal websites or portfolios.
15
+
16
+ The core philosophy is to use GitHub as your **Single Source of Truth**: by simply tagging your repositories, the library handles fetching, processing, caching, and rendering data, eliminating manual updates to your portfolio's code.
17
+
18
+ ---
19
+
20
+ ## 🛠 Technical Features
21
+
22
+ ### 1. Project Selection & Stack Control
23
+ Snap-Port offers full control over what is displayed and how technologies are categorized:
24
+
25
+ - **Discovery Tag**: By default, the library looks for repositories tagged with `port`, but you can define any custom tag during initialization.
26
+ - **Stack Filters (Topics)**: For automatic filters and search to work correctly, list your technologies (e.g., `react`, `nodejs`, `css`) in your GitHub repository topics.
27
+ - **Why avoid automatic "Language"?**: Snap-Port ignores the default GitHub `language` field to let you highlight the actual stack. This prevents a React project from being labeled merely as "HTML" or "JavaScript" due to build files, ensuring the filter reflects the real project stack.
28
+
29
+ ### 2. Intelligent Image Management
30
+ Since the GitHub API does not return direct preview image links, Snap-Port implements an **automatic generation logic**:
31
+
32
+ To ensure each project has its own image, follow these rules:
33
+ - **Preview File**: Create a file named `preview.png` in the root of your repository.
34
+ - **Note**: The filename must be exactly `preview.png` (case-sensitive).
35
+
36
+ If the file is missing or a loading error occurs (such as Rate Limits), the library executes a **cascading fallback strategy**:
37
+ 1. **GitHub Open Graph**: Attempts to load the dynamic social card generated by GitHub.
38
+ 2. **Safety Placeholder**: If the request is blocked, it generates a neutral card with the project name via `placehold.co`.
39
+
40
+ ### 3. Integrated UI Components
41
+ - **Search Bar**: Real-time text filtering (name, description, and topics).
42
+ - **Filter Carousel**: Dynamic carousel based on repository topics.
43
+ - **Project Cards (16:9 Layout)**: Responsive cards with technology badges and action buttons (Code and Deploy).
44
+
45
+ ---
46
+
47
+ ## 💡 Best Practices
48
+
49
+ - **Image Aspect Ratio**: To avoid cropping, save your `preview.png` files in **16:9** aspect ratio (e.g., 1280x720px).
50
+ - **Live Demo (Deploy)**: The "Acessar" (Visit) button only appears if the **"Homepage"** field is filled in your GitHub repository settings.
51
+
52
+ ---
53
+
54
+ ## 📦 Installation & Integration
55
+
56
+ ### Via NPM
57
+ ```bash
58
+ npm install snapport
59
+ ```
60
+
61
+ ### Via CDN (Direct HTML)
62
+ Use ``type="module"`` for modern ES module compatibility.
63
+
64
+ ```html
65
+ <!-- 1. Library Styles -->
66
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net">
67
+
68
+ <!-- 2. Logic and Initialization -->
69
+ <script type="module">
70
+ import { initPortfolio } from 'https://cdn.jsdelivr.net';
71
+
72
+ initPortfolio('your-username', {
73
+ tag: 'port', // Optional: defaults to 'port'
74
+ searchContainer: 'id-search', // Search container ID
75
+ filtersContainer: 'id-filters', // Filters container ID
76
+ projectsContainer: 'id-projects' // Projects grid container ID
77
+ });
78
+ </script>
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 🎨 Customization (CSS Variables)
84
+ Adapt the UI to your theme by overriding these variables in your global CSS:
85
+
86
+ ```css
87
+ :root {
88
+ --ghp-accent: #333; /* Accent color (buttons/icons) */
89
+ --ghp-bg: #ffffff; /* Card background */
90
+ --ghp-text: #333; /* Main headings and text */
91
+ --ghp-text-light: #666; /* Descriptions and secondary text */
92
+ --ghp-border: rgba(226, 226, 228, 0.8); /* Borders */
93
+ --ghp-shadow: rgba(0, 0, 0, 0.1); /* Card shadows */
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## ⚙️ Performance & Advanced Usage
100
+
101
+ ### Custom Template Injection
102
+ Keep the search/cache logic but use your own design:
103
+
104
+ ```javascript
105
+ initPortfolio('your-username', {
106
+ projectsContainer: 'id-projects',
107
+ customCardTemplate: (repo) => `
108
+ <div class="my-custom-card">
109
+ <h4>${repo.name}</h4>
110
+ <p>${repo.description}</p>
111
+ <a href="${repo.htmlUrl}">View Source</a>
112
+ </div>
113
+ `
114
+ });
115
+ ```
116
+
117
+ ### Cache & Stability
118
+ Data is stored via localStorage for better performance:
119
+
120
+ - Persistence: Data cached for up to 2 hours.
121
+ - Isolation: Cache is separated by GitHub username.
122
+
123
+ ---
124
+
125
+ > **Note:**
126
+ > This is an independent open-source project. Feel free to contribute! If you find a bug or have a feature idea, opening an Issue or a Pull Request is the best way to help.
127
+ >
128
+ > To learn how to collaborate with the code, please check our [**Contributing Guide**](./CONTRIBUTING.md).
129
+
130
+ **Author**: Guilherme Godoy (@guilhermegodoydev)
package/README.pt-br.md CHANGED
@@ -1,137 +1,137 @@
1
- <p align="center">
2
- <a href="./README.md">Read this in English</a>
3
- </p>
4
-
5
- <p align="center">
6
- <img src="https://img.shields.io/npm/dm/snapport?style=for-the-badge&logo=npm" alt="NPM Downloads">
7
- <img src="https://img.shields.io/npm/v/snapport?style=for-the-badge" alt="NPM Version">
8
- <img src="https://img.shields.io/github/license/guilhermegodoydev/snapport?style=for-the-badge" alt="License">
9
- <img src="https://img.shields.io/bundlephobia/min/snapport?style=for-the-badge" alt="Bundle Size">
10
- </p>
11
-
12
- # Snap-Port 🚀
13
-
14
- O **Snap-Port** é uma biblioteca desenvolvida em **TypeScript**, sem dependências externas, projetada para automatizar a exibição de projetos do GitHub em sites pessoais ou portfólios.
15
-
16
- A proposta central é utilizar o GitHub como **fonte única de verdade:** ao marcar seus repositórios com a tag escolhida, a biblioteca se encarrega de buscar, tratar, aplicar cache e renderizar os dados, eliminando a manutenção manual no código do seu site.
17
-
18
- ---
19
-
20
- ## 🛠 Funcionalidades Técnicas
21
-
22
- ### 1. Seleção de Projetos e Controle de Stacks
23
- O Snap-Port oferece controle total sobre o que é exibido e como as tecnologias são categorizadas:
24
-
25
- - **Tag de Descoberta**: Por padrão, a biblioteca busca repositórios com a tag ``port``, mas você pode definir qualquer outra tag no momento da inicialização.
26
- - **Filtros por Stacks (Topics):** Para que os filtros automáticos e a barra de busca funcionem corretamente, você deve listar as tecnologias (ex: ``react``, ``nodejs``, ``css``) nos topics do seu repositório no GitHub.
27
- - **Por que não usar a "Language" automática?** A lib ignora o campo ``language`` do GitHub para permitir que você decida quais ferramentas quer destacar. Isso evita que um projeto de React seja classificado apenas como "HTML" ou "JavaScript" devido ao volume de arquivos gerados por ferramentas de build, garantindo que o filtro reflita a stack real do projeto.
28
-
29
- ### 2. Gestão Inteligente de Imagens
30
- Como a API do GitHub não retorna links diretos de imagens de preview, o Snap-Port utiliza uma lógica de **geração automática** integrada aos componentes de UI.
31
-
32
- Para que cada projeto tenha sua própria imagem, siga estas regras:
33
-
34
- - **Arquivo de Preview:** Você deve criar um arquivo chamado ``preview.png`` na raiz do seu repositório.
35
- - **Importante:** O nome deve ser exatamente preview.png (letras minúsculas), pois o GitHub diferencia maiúsculas de minúsculas (*case-sensitive*).
36
-
37
- Caso o arquivo não exista ou ocorra algum erro de carregamento (como *Rate Limits*), a lib executa uma **estratégia de fallback em cascata:**
38
-
39
- - **GitHub Open Graph:** Tenta carregar o card dinâmico gerado pelo próprio GitHub.
40
- - **Placeholder de Segurança:** Se o GitHub bloquear a requisição, gera um card neutro contendo o nome do projeto via placehold.co.
41
-
42
- ### 3. Componentes de UI Integrados
43
-
44
- - **Search Bar:** Filtro textual em tempo real (nome, descrição e tópicos).
45
- - **Filter** Carousel: Carrossel dinâmico baseado nos tópicos definidos nos repositórios.
46
- - **Project Cards (Layout 16:9):** Cards responsivos com badges de tecnologia e botões de ação (Código e Deploy).
47
-
48
- ---
49
-
50
- ## 💡 Dicas para um melhor Resultado
51
-
52
- - **Proporção de Imagem:** Para que as imagens não fiquem com partes cortadas nos cards, salve seus arquivos ``preview.png`` na proporção **16:9** (ex: 1280x720px).
53
- - **Link de Acesso (Deploy):** O botão "Acessar" só aparecerá se o campo **"Homepage"** estiver preenchido nas configurações do seu repositório no GitHub.
54
-
55
- ---
56
-
57
- ## 📦 Instalação e Integração
58
-
59
- ### Via NPM
60
-
61
- ```bash
62
- npm install snapport
63
- ```
64
-
65
- ### Via CDN (Direto no HTML)
66
- Se você preferir não usar gerenciadores de pacotes, pode importar os arquivos diretamente de um CDN. Recomendamos o uso de type="module" para melhor compatibilidade com o padrão moderno da biblioteca.
67
-
68
- ```html
69
- <!-- 1. Estilos da Biblioteca -->
70
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/snapport/dist/snap-port.css">
71
-
72
- <!-- 2. Lógica e Inicialização -->
73
- <script type="module">
74
- // Importação do módulo oficial ES
75
- import { initPortfolio } from 'https://cdn.jsdelivr.net/npm/snapport/dist/snap-port.js';
76
-
77
- initPortfolio('seu-usuario', {
78
- tag: 'port', // Opcional: padrão é 'port'
79
- searchContainer: 'id-search', // ID do container da busca
80
- filtersContainer: 'id-filters', // ID do container dos filtros
81
- projectsContainer: 'id-projects' // ID do container do grid
82
- });
83
- </script>
84
- ```
85
-
86
- ---
87
-
88
- ## 🎨 Personalização Visual (CSS Variables)
89
- Se você utiliza o layout padrão da biblioteca, pode adaptar as cores e o estilo ao seu tema sem modificar o código interno. O Snap-Port utiliza **Variáveis CSS** que podem ser facilmente sobrescritas no seu arquivo global:
90
-
91
- ```css
92
- :root {
93
- --ghp-accent: #333; /* Cor de destaque (botões e ícones) */
94
- --ghp-bg: #ffffff; /* Fundo dos cards */
95
- --ghp-text: #333; /* Título e textos principais */
96
- --ghp-text-light: #666; /* Descrições e textos secundários */
97
- --ghp-border: rgba(226, 226, 228, 0.8); /* Bordas */
98
- --ghp-shadow: rgba(0, 0, 0, 0.1); /* Sombras dos cards */
99
- }
100
- ```
101
-
102
- ---
103
-
104
- ## ⚙️ Customização e Performance
105
-
106
- ### Injeção de Template Customizado
107
- Mantenha a inteligência de busca e cache, mas use seu próprio design:
108
-
109
- ```javascript
110
- initPortfolio('seu-usuario', {
111
- searchContainer: 'id-search',
112
- filtersContainer: 'id-filters',
113
- projectsContainer: 'id-projects',
114
- customCardTemplate: (repo) => `
115
- <div class="meu-card-personalizado">
116
- <h4>${repo.name}</h4>
117
- <p>${repo.description}</p>
118
- <a href="${repo.htmlUrl}">Ver código</a>
119
- </div>
120
- `
121
- });
122
- ```
123
-
124
- ### Cache e Estabilidade
125
- A biblioteca utiliza localStorage para garantir performance:
126
-
127
- - **Persistência:** Dados armazenados por até 2 horas.
128
- - **Isolamento:** Cache separado por usuário do GitHub.
129
-
130
- ---
131
-
132
- > **Nota sobre Manutenção:**
133
- > Este é um projeto de código aberto mantido de forma independente. Sinta-se à vontade para contribuir! Se encontrar um bug ou tiver uma ideia de funcionalidade, abrir uma **Issue** ou um **Pull Request** é a melhor forma de ajudar o projeto a crescer.
134
- >
135
- > Para entender como colaborar com o código, consulte o nosso [**Guia de Contribuição**](./CONTRIBUTING.md).
136
-
137
- **Autor**: Guilherme Godoy (@guilhermegodoydev)
1
+ <p align="center">
2
+ <a href="./README.md">Read this in English</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="https://img.shields.io/npm/dm/snapport?style=for-the-badge&logo=npm" alt="NPM Downloads">
7
+ <img src="https://img.shields.io/npm/v/snapport?style=for-the-badge" alt="NPM Version">
8
+ <img src="https://img.shields.io/github/license/guilhermegodoydev/snapport?style=for-the-badge" alt="License">
9
+ <img src="https://img.shields.io/bundlephobia/min/snapport?style=for-the-badge" alt="Bundle Size">
10
+ </p>
11
+
12
+ # Snap-Port 🚀
13
+
14
+ O **Snap-Port** é uma biblioteca desenvolvida em **TypeScript**, sem dependências externas, projetada para automatizar a exibição de projetos do GitHub em sites pessoais ou portfólios.
15
+
16
+ A proposta central é utilizar o GitHub como **fonte única de verdade:** ao marcar seus repositórios com a tag escolhida, a biblioteca se encarrega de buscar, tratar, aplicar cache e renderizar os dados, eliminando a manutenção manual no código do seu site.
17
+
18
+ ---
19
+
20
+ ## 🛠 Funcionalidades Técnicas
21
+
22
+ ### 1. Seleção de Projetos e Controle de Stacks
23
+ O Snap-Port oferece controle total sobre o que é exibido e como as tecnologias são categorizadas:
24
+
25
+ - **Tag de Descoberta**: Por padrão, a biblioteca busca repositórios com a tag ``port``, mas você pode definir qualquer outra tag no momento da inicialização.
26
+ - **Filtros por Stacks (Topics):** Para que os filtros automáticos e a barra de busca funcionem corretamente, você deve listar as tecnologias (ex: ``react``, ``nodejs``, ``css``) nos topics do seu repositório no GitHub.
27
+ - **Por que não usar a "Language" automática?** A lib ignora o campo ``language`` do GitHub para permitir que você decida quais ferramentas quer destacar. Isso evita que um projeto de React seja classificado apenas como "HTML" ou "JavaScript" devido ao volume de arquivos gerados por ferramentas de build, garantindo que o filtro reflita a stack real do projeto.
28
+
29
+ ### 2. Gestão Inteligente de Imagens
30
+ Como a API do GitHub não retorna links diretos de imagens de preview, o Snap-Port utiliza uma lógica de **geração automática** integrada aos componentes de UI.
31
+
32
+ Para que cada projeto tenha sua própria imagem, siga estas regras:
33
+
34
+ - **Arquivo de Preview:** Você deve criar um arquivo chamado ``preview.png`` na raiz do seu repositório.
35
+ - **Importante:** O nome deve ser exatamente preview.png (letras minúsculas), pois o GitHub diferencia maiúsculas de minúsculas (*case-sensitive*).
36
+
37
+ Caso o arquivo não exista ou ocorra algum erro de carregamento (como *Rate Limits*), a lib executa uma **estratégia de fallback em cascata:**
38
+
39
+ - **GitHub Open Graph:** Tenta carregar o card dinâmico gerado pelo próprio GitHub.
40
+ - **Placeholder de Segurança:** Se o GitHub bloquear a requisição, gera um card neutro contendo o nome do projeto via placehold.co.
41
+
42
+ ### 3. Componentes de UI Integrados
43
+
44
+ - **Search Bar:** Filtro textual em tempo real (nome, descrição e tópicos).
45
+ - **Filter** Carousel: Carrossel dinâmico baseado nos tópicos definidos nos repositórios.
46
+ - **Project Cards (Layout 16:9):** Cards responsivos com badges de tecnologia e botões de ação (Código e Deploy).
47
+
48
+ ---
49
+
50
+ ## 💡 Dicas para um melhor Resultado
51
+
52
+ - **Proporção de Imagem:** Para que as imagens não fiquem com partes cortadas nos cards, salve seus arquivos ``preview.png`` na proporção **16:9** (ex: 1280x720px).
53
+ - **Link de Acesso (Deploy):** O botão "Acessar" só aparecerá se o campo **"Homepage"** estiver preenchido nas configurações do seu repositório no GitHub.
54
+
55
+ ---
56
+
57
+ ## 📦 Instalação e Integração
58
+
59
+ ### Via NPM
60
+
61
+ ```bash
62
+ npm install snapport
63
+ ```
64
+
65
+ ### Via CDN (Direto no HTML)
66
+ Se você preferir não usar gerenciadores de pacotes, pode importar os arquivos diretamente de um CDN. Recomendamos o uso de type="module" para melhor compatibilidade com o padrão moderno da biblioteca.
67
+
68
+ ```html
69
+ <!-- 1. Estilos da Biblioteca -->
70
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/snapport/dist/snap-port.css">
71
+
72
+ <!-- 2. Lógica e Inicialização -->
73
+ <script type="module">
74
+ // Importação do módulo oficial ES
75
+ import { initPortfolio } from 'https://cdn.jsdelivr.net/npm/snapport/dist/snap-port.js';
76
+
77
+ initPortfolio('seu-usuario', {
78
+ tag: 'port', // Opcional: padrão é 'port'
79
+ searchContainer: 'id-search', // ID do container da busca
80
+ filtersContainer: 'id-filters', // ID do container dos filtros
81
+ projectsContainer: 'id-projects' // ID do container do grid
82
+ });
83
+ </script>
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 🎨 Personalização Visual (CSS Variables)
89
+ Se você utiliza o layout padrão da biblioteca, pode adaptar as cores e o estilo ao seu tema sem modificar o código interno. O Snap-Port utiliza **Variáveis CSS** que podem ser facilmente sobrescritas no seu arquivo global:
90
+
91
+ ```css
92
+ :root {
93
+ --ghp-accent: #333; /* Cor de destaque (botões e ícones) */
94
+ --ghp-bg: #ffffff; /* Fundo dos cards */
95
+ --ghp-text: #333; /* Título e textos principais */
96
+ --ghp-text-light: #666; /* Descrições e textos secundários */
97
+ --ghp-border: rgba(226, 226, 228, 0.8); /* Bordas */
98
+ --ghp-shadow: rgba(0, 0, 0, 0.1); /* Sombras dos cards */
99
+ }
100
+ ```
101
+
102
+ ---
103
+
104
+ ## ⚙️ Customização e Performance
105
+
106
+ ### Injeção de Template Customizado
107
+ Mantenha a inteligência de busca e cache, mas use seu próprio design:
108
+
109
+ ```javascript
110
+ initPortfolio('seu-usuario', {
111
+ searchContainer: 'id-search',
112
+ filtersContainer: 'id-filters',
113
+ projectsContainer: 'id-projects',
114
+ customCardTemplate: (repo) => `
115
+ <div class="meu-card-personalizado">
116
+ <h4>${repo.name}</h4>
117
+ <p>${repo.description}</p>
118
+ <a href="${repo.htmlUrl}">Ver código</a>
119
+ </div>
120
+ `
121
+ });
122
+ ```
123
+
124
+ ### Cache e Estabilidade
125
+ A biblioteca utiliza localStorage para garantir performance:
126
+
127
+ - **Persistência:** Dados armazenados por até 2 horas.
128
+ - **Isolamento:** Cache separado por usuário do GitHub.
129
+
130
+ ---
131
+
132
+ > **Nota sobre Manutenção:**
133
+ > Este é um projeto de código aberto mantido de forma independente. Sinta-se à vontade para contribuir! Se encontrar um bug ou tiver uma ideia de funcionalidade, abrir uma **Issue** ou um **Pull Request** é a melhor forma de ajudar o projeto a crescer.
134
+ >
135
+ > Para entender como colaborar com o código, consulte o nosso [**Guia de Contribuição**](./CONTRIBUTING.md).
136
+
137
+ **Autor**: Guilherme Godoy (@guilhermegodoydev)
@@ -0,0 +1,40 @@
1
+ export declare function getPortProjects(username: string, tag?: string): Promise<SanitizedRepo[]>;
2
+
3
+ export declare function initPortfolio(username: string, config?: PortfolioConfig): Promise<PortfolioResponse>;
4
+
5
+ export declare interface PortfolioConfig {
6
+ tag?: string;
7
+ searchContainer: string;
8
+ filtersContainer: string;
9
+ projectsContainer: string;
10
+ customCardTemplate?: (repo: SanitizedRepo) => string;
11
+ }
12
+
13
+ export declare interface PortfolioResponse {
14
+ projects?: any[];
15
+ status: 'success' | 'error';
16
+ message?: string;
17
+ }
18
+
19
+ export declare function renderFilters(projects: SanitizedRepo[], containerElement: HTMLElement | string): void;
20
+
21
+ export declare function renderProjects(projects: SanitizedRepo[], containerElement: HTMLElement | string, username?: string, customTemplate?: (repo: SanitizedRepo) => string): void;
22
+
23
+ export declare function renderSearchBar(containerElement: HTMLElement | string): void;
24
+
25
+ export declare interface SanitizedRepo {
26
+ id: number;
27
+ name: string;
28
+ description: string | null;
29
+ htmlUrl: string;
30
+ topics: string[];
31
+ deployUrl: string | null;
32
+ }
33
+
34
+ export declare interface TechInfo {
35
+ icon: string;
36
+ name: string;
37
+ color: string;
38
+ }
39
+
40
+ export { }
@@ -0,0 +1 @@
1
+ :root{--ghp-accent: #333;--ghp-bg: #ffffff;--ghp-border: rgba(226, 226, 228, .8);--ghp-shadow: rgba(0, 0, 0, .1);--ghp-text: #333;--ghp-text-light: #666}.ghp-search-input,.ghp-filter-btn,.ghp-card-links a{transition:all .2s ease}.ghp-search-container{position:relative;margin:0 15px 20px;max-width:400px}.ghp-search-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:#888;pointer-events:none}.ghp-search-input{width:100%;padding:10px 12px 10px 38px;border:1px solid var(--ghp-border);border-radius:8px;font-size:14px;outline:none}.ghp-search-input:focus{border-color:var(--ghp-accent);box-shadow:0 0 0 3px #0000000d}.ghp-filters-content{display:flex;gap:12px;padding:10px 15px;margin-bottom:25px;overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;scrollbar-width:none}.ghp-filters-content::-webkit-scrollbar{display:none}.ghp-filter-btn{scroll-snap-align:start;flex:0 0 auto;display:flex;align-items:center;gap:8px;background-color:var(--ghp-bg);padding:8px 16px;border:solid 2px #e1e4e8;border-radius:30px;cursor:pointer;font-family:inherit;font-weight:500}.ghp-filter-btn img,.ghp-filter-btn svg{width:18px;height:18px;object-fit:contain}.ghp-filter-btn p{margin:0;font-size:14px}@media(max-width:600px){.ghp-filters-content{gap:8px;padding:8px}}.ghp-filter-btn:hover{transform:translateY(-3px);border-color:var(--tech-color, var(--ghp-accent));color:var(--tech-color, var(--ghp-accent));box-shadow:0 4px 8px var(--ghp-shadow)}.ghp-filter-btn.active{background-color:var(--tech-color, var(--ghp-accent));color:#fff;border-color:var(--tech-color, var(--ghp-accent))}.ghp-filter-btn.active img{filter:brightness(0) invert(1)}.ghp-projects-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:25px;padding:15px;align-items:start}@media(max-width:1024px){.ghp-projects-grid{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}}.ghp-project-card{background-color:var(--ghp-bg);border-radius:12px;border:solid 1px var(--ghp-border);box-shadow:0 2px 5px var(--ghp-shadow);display:flex;flex-direction:column;transition:transform .2s ease;height:100%;min-height:220px}.ghp-img-container,.ghp-card-img{border-radius:8px 8px 0 0}.ghp-img-container{width:100%;aspect-ratio:16 / 9;background-color:#161b22;border-bottom:1px solid var(--ghp-border);overflow:hidden}.ghp-card-img{width:100%;height:100%;object-fit:cover;object-position:center;display:block}.ghp-card-content{display:flex;flex-direction:column;justify-content:space-between;flex:1;padding:20px}.ghp-skeleton-card{height:220px;width:100%;border:1px solid transparent;box-sizing:border-box}.ghp-project-card:hover{transform:translateY(-5px)}.ghp-project-card h3{margin:0 0 10px;font-size:18px}.ghp-project-card p{font-size:14px;color:var(--ghp-text-light);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;line-height:1.5}.ghp-card-links{display:flex;gap:10px}.ghp-card-links a{text-decoration:none;padding:6px 14px;color:var(--ghp-text);font-size:13px;font-weight:500;border:solid 1px var(--ghp-border);border-radius:20px;background-color:#f6f8fa}.ghp-card-links a:hover{background-color:var(--ghp-accent);color:#fff;border-color:var(--ghp-accent)}.ghp-skeleton{background:linear-gradient(90deg,#f0f0f0 25%,#e6e6e6,#f0f0f0 75%);background-size:200% 100%;animation:ghp-loading 1.5s infinite ease-in-out;border-radius:4px}@keyframes ghp-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.ghp-skeleton-card{height:200px;width:100%;border-radius:12px}
@@ -0,0 +1,191 @@
1
+ const u = (t) => ({
2
+ id: t.id,
3
+ name: t.name ?? "Projeto sem nome",
4
+ description: t.description ?? "Sem descrição disponível",
5
+ htmlUrl: t.html_url,
6
+ topics: Array.isArray(t.topics) ? t.topics : [],
7
+ deployUrl: t.homepage ?? null
8
+ });
9
+ function f(t) {
10
+ try {
11
+ Object.keys(localStorage).forEach((e) => {
12
+ e.startsWith("gh_projects_") && !e.includes(t) && localStorage.removeItem(e);
13
+ });
14
+ } catch (e) {
15
+ console.warn("Erro ao limpar caches antigos:", e);
16
+ }
17
+ }
18
+ async function y(t, e = "port") {
19
+ if (!t)
20
+ return console.error("GitHubPortfolio: Username é obrigatório."), [];
21
+ const o = `gh_projects_${t}`;
22
+ try {
23
+ f(t);
24
+ const r = localStorage.getItem(o);
25
+ if (r) {
26
+ const { data: l, timestamp: p } = JSON.parse(r);
27
+ if (Date.now() - p < 72e5)
28
+ return l;
29
+ }
30
+ const n = encodeURIComponent(`user:${t} topic:${e}`), c = await fetch(`https://api.github.com/search/repositories?q=${n}&sort=updated&order=desc`);
31
+ if (!c.ok)
32
+ throw new Error(`GitHub API error: ${c.status}`);
33
+ const a = ((await c.json()).items || []).map(u), i = {
34
+ data: a,
35
+ timestamp: Date.now()
36
+ };
37
+ return localStorage.setItem(o, JSON.stringify(i)), a;
38
+ } catch (r) {
39
+ console.error(`GitHubPortfolio: Erro ao buscar dados de ${t}:`, r);
40
+ const n = localStorage.getItem(o);
41
+ return n ? (console.warn("GitHubPortfolio: Usando cache expirado devido a erro de rede."), JSON.parse(n).data) : [];
42
+ }
43
+ }
44
+ const v = {
45
+ react: { name: "React", icon: "react", color: "61DAFB" },
46
+ vue: { name: "Vue.js", icon: "vuedotjs", color: "4FC08D" },
47
+ angular: { name: "Angular", icon: "angular", color: "DD0031" },
48
+ nextjs: { name: "Next.js", icon: "nextdotjs", color: "000000" },
49
+ typescript: { name: "TypeScript", icon: "typescript", color: "3178C6" },
50
+ javascript: { name: "JavaScript", icon: "javascript", color: "F7DF1E" },
51
+ html5: { name: "HTML5", icon: "html5", color: "E34F26" },
52
+ css3: { name: "CSS3", icon: "css3", color: "1572B6" },
53
+ sass: { name: "Sass", icon: "sass", color: "CC6699" },
54
+ "tailwind-css": { name: "Tailwind", icon: "tailwindcss", color: "06B6D4" },
55
+ bootstrap: { name: "Bootstrap", icon: "bootstrap", color: "7952B3" },
56
+ "styled-components": { name: "Styled Components", icon: "styledcomponents", color: "DB7093" },
57
+ figma: { name: "Figma", icon: "figma", color: "F24E1E" },
58
+ nodejs: { name: "Node.js", icon: "nodedotjs", color: "339933" },
59
+ express: { name: "Express", icon: "express", color: "000000" },
60
+ nestjs: { name: "NestJS", icon: "nestjs", color: "E0234E" },
61
+ python: { name: "Python", icon: "python", color: "3776AB" },
62
+ java: { name: "Java", icon: "openjdk", color: "007396" },
63
+ php: { name: "PHP", icon: "php", color: "777BB4" },
64
+ postgresql: { name: "PostgreSQL", icon: "postgresql", color: "4169E1" },
65
+ mongodb: { name: "MongoDB", icon: "mongodb", color: "47A248" },
66
+ mysql: { name: "MySQL", icon: "mysql", color: "4479A1" },
67
+ firebase: { name: "Firebase", icon: "firebase", color: "FFCA28" },
68
+ prisma: { name: "Prisma", icon: "prisma", color: "2D3748" },
69
+ docker: { name: "Docker", icon: "docker", color: "2496ED" },
70
+ git: { name: "Git", icon: "git", color: "F05032" },
71
+ jest: { name: "Jest", icon: "jest", color: "C21325" },
72
+ "github-actions": { name: "GitHub Actions", icon: "githubactions", color: "2088FF" }
73
+ }, d = (t) => typeof t == "string" ? document.getElementById(t) : t, g = (t, e = null) => {
74
+ if (!e)
75
+ return `
76
+ <button class="ghp-filter-btn active" data-topic="all">
77
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
78
+ <rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/>
79
+ <rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>
80
+ </svg>
81
+ <p>Todos</p>
82
+ </button>
83
+ `;
84
+ const o = `https://cdn.simpleicons.org/${e.icon}/${e.color}`;
85
+ return `
86
+ <button class="ghp-filter-btn" data-topic="${t}" style="--tech-color: #${e.color}">
87
+ <img src="${o}" alt="${e.name}" loading="lazy">
88
+ <p>${e.name}</p>
89
+ </button>
90
+ `;
91
+ }, j = (t, e) => {
92
+ const o = `https://raw.githubusercontent.com/${e}/${t.name}/main/preview.png`, r = `https://opengraph.githubassets.com/${e}/${t.name}`, n = `https://placehold.co/640x360?text=${encodeURIComponent(t.name)}`, c = t.deployUrl ? `<a href="${t.deployUrl}" target="_blank" rel="noopener noreferrer">Acessar</a>` : "", s = t.htmlUrl ? `<a href="${t.htmlUrl}" target="_blank" rel="noopener noreferrer">Github</a>` : "";
93
+ return `
94
+ <div class="ghp-project-card">
95
+ <div class="ghp-img-container">
96
+ <img
97
+ src="${o}"
98
+ alt="Preview do projeto ${t.name}"
99
+ class="ghp-card-img"
100
+ loading="lazy"
101
+ onerror="if (this.src.includes('preview.png')) { this.src='${r}'; } else { this.onerror=null; this.src='${n}'; }"
102
+ >
103
+ </div>
104
+ <div class="ghp-card-content">
105
+ <div>
106
+ <h3>${t.name}</h3>
107
+ <p title="${t.description}">${t.description}</p>
108
+ </div>
109
+ <div class="ghp-card-links">
110
+ ${s}
111
+ ${c}
112
+ </div>
113
+ </div>
114
+ </div>
115
+ `;
116
+ };
117
+ function C(t, e = 6) {
118
+ const o = d(t);
119
+ o && (o.className = "ghp-projects-grid", o.innerHTML = Array(e).fill('<div class="ghp-project-card ghp-skeleton ghp-skeleton-card"></div>').join(""));
120
+ }
121
+ function w(t, e) {
122
+ const o = d(e);
123
+ if (!o) return;
124
+ const r = t.flatMap((s) => s.topics || []), c = [...new Set(r)].reduce((s, a) => {
125
+ const i = v[a];
126
+ return i ? s + g(a, i) : s;
127
+ }, g("all"));
128
+ o.innerHTML = `<div class="ghp-filters-content">${c}</div>`;
129
+ }
130
+ function m(t, e, o = "", r) {
131
+ const n = d(e);
132
+ if (n) {
133
+ if (n.className = "ghp-projects-grid", n.style.minHeight = "auto", t.length === 0) {
134
+ n.innerHTML = '<p class="ghp-empty-msg">Nenhum projeto encontrado.</p>';
135
+ return;
136
+ }
137
+ n.innerHTML = t.map((c) => r ? r(c) : j(c, o)).join("");
138
+ }
139
+ }
140
+ function $(t) {
141
+ const e = d(t);
142
+ e && (e.innerHTML = `
143
+ <div class="ghp-search-container">
144
+ <svg class="ghp-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
145
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
146
+ </svg>
147
+ <input type="text" id="gh-port-search" class="ghp-search-input" placeholder="Buscar projeto..." autocomplete="off">
148
+ </div>
149
+ `);
150
+ }
151
+ function b(t, e, o, r, n) {
152
+ const c = document.getElementById(t);
153
+ c && c.addEventListener("click", (s) => {
154
+ const i = s.target.closest(".ghp-filter-btn");
155
+ if (!i) return;
156
+ const l = i.dataset.topic, p = l === "all" ? e : e.filter((h) => h.topics.includes(l));
157
+ m(p, o, r, n), c.querySelectorAll(".ghp-filter-btn").forEach((h) => h.classList.remove("active")), i.classList.add("active");
158
+ });
159
+ }
160
+ function E(t, e, o, r) {
161
+ const n = document.getElementById("gh-port-search");
162
+ n && n.addEventListener("input", (c) => {
163
+ const a = c.target.value.toLowerCase(), i = t.filter(
164
+ (l) => l.name.toLowerCase().includes(a) || (l.description || "").toLowerCase().includes(a) || l.topics.some((p) => p.toLowerCase().includes(a))
165
+ );
166
+ m(i, e, o, r);
167
+ });
168
+ }
169
+ async function S(t, e = {
170
+ searchContainer: "search-cont",
171
+ filtersContainer: "filters-cont",
172
+ projectsContainer: "projects-cont"
173
+ }) {
174
+ try {
175
+ const o = e.tag || "port";
176
+ $(e.searchContainer), C(e.projectsContainer, 6);
177
+ const r = await y(t, o);
178
+ return w(r, e.filtersContainer), m(r, e.projectsContainer, t, e.customCardTemplate), b(e.filtersContainer, r, e.projectsContainer, t, e.customCardTemplate), E(r, e.projectsContainer, t, e.customCardTemplate), { projects: r, status: "success" };
179
+ } catch (o) {
180
+ console.error("Erro ao inicializar portfólio:", o);
181
+ const r = document.getElementById(e.projectsContainer);
182
+ return r && (r.innerHTML = '<p class="ghp-empty-msg">Erro ao carregar projetos.</p>'), { status: "error", message: o.message };
183
+ }
184
+ }
185
+ export {
186
+ y as getPortProjects,
187
+ S as initPortfolio,
188
+ w as renderFilters,
189
+ m as renderProjects,
190
+ $ as renderSearchBar
191
+ };
@@ -0,0 +1,43 @@
1
+ (function(s,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(s=typeof globalThis<"u"?globalThis:s||self,h(s.SnapPort={}))})(this,(function(s){"use strict";const C=e=>({id:e.id,name:e.name??"Projeto sem nome",description:e.description??"Sem descrição disponível",htmlUrl:e.html_url,topics:Array.isArray(e.topics)?e.topics:[],deployUrl:e.homepage??null});function b(e){try{Object.keys(localStorage).forEach(t=>{t.startsWith("gh_projects_")&&!t.includes(e)&&localStorage.removeItem(t)})}catch(t){console.warn("Erro ao limpar caches antigos:",t)}}async function f(e,t="port"){if(!e)return console.error("GitHubPortfolio: Username é obrigatório."),[];const o=`gh_projects_${e}`;try{b(e);const r=localStorage.getItem(o);if(r){const{data:p,timestamp:d}=JSON.parse(r);if(Date.now()-d<72e5)return p}const n=encodeURIComponent(`user:${e} topic:${t}`),c=await fetch(`https://api.github.com/search/repositories?q=${n}&sort=updated&order=desc`);if(!c.ok)throw new Error(`GitHub API error: ${c.status}`);const a=((await c.json()).items||[]).map(C),l={data:a,timestamp:Date.now()};return localStorage.setItem(o,JSON.stringify(l)),a}catch(r){console.error(`GitHubPortfolio: Erro ao buscar dados de ${e}:`,r);const n=localStorage.getItem(o);return n?(console.warn("GitHubPortfolio: Usando cache expirado devido a erro de rede."),JSON.parse(n).data):[]}}const w={react:{name:"React",icon:"react",color:"61DAFB"},vue:{name:"Vue.js",icon:"vuedotjs",color:"4FC08D"},angular:{name:"Angular",icon:"angular",color:"DD0031"},nextjs:{name:"Next.js",icon:"nextdotjs",color:"000000"},typescript:{name:"TypeScript",icon:"typescript",color:"3178C6"},javascript:{name:"JavaScript",icon:"javascript",color:"F7DF1E"},html5:{name:"HTML5",icon:"html5",color:"E34F26"},css3:{name:"CSS3",icon:"css3",color:"1572B6"},sass:{name:"Sass",icon:"sass",color:"CC6699"},"tailwind-css":{name:"Tailwind",icon:"tailwindcss",color:"06B6D4"},bootstrap:{name:"Bootstrap",icon:"bootstrap",color:"7952B3"},"styled-components":{name:"Styled Components",icon:"styledcomponents",color:"DB7093"},figma:{name:"Figma",icon:"figma",color:"F24E1E"},nodejs:{name:"Node.js",icon:"nodedotjs",color:"339933"},express:{name:"Express",icon:"express",color:"000000"},nestjs:{name:"NestJS",icon:"nestjs",color:"E0234E"},python:{name:"Python",icon:"python",color:"3776AB"},java:{name:"Java",icon:"openjdk",color:"007396"},php:{name:"PHP",icon:"php",color:"777BB4"},postgresql:{name:"PostgreSQL",icon:"postgresql",color:"4169E1"},mongodb:{name:"MongoDB",icon:"mongodb",color:"47A248"},mysql:{name:"MySQL",icon:"mysql",color:"4479A1"},firebase:{name:"Firebase",icon:"firebase",color:"FFCA28"},prisma:{name:"Prisma",icon:"prisma",color:"2D3748"},docker:{name:"Docker",icon:"docker",color:"2496ED"},git:{name:"Git",icon:"git",color:"F05032"},jest:{name:"Jest",icon:"jest",color:"C21325"},"github-actions":{name:"GitHub Actions",icon:"githubactions",color:"2088FF"}},m=e=>typeof e=="string"?document.getElementById(e):e,y=(e,t=null)=>{if(!t)return`
2
+ <button class="ghp-filter-btn active" data-topic="all">
3
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
4
+ <rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/>
5
+ <rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>
6
+ </svg>
7
+ <p>Todos</p>
8
+ </button>
9
+ `;const o=`https://cdn.simpleicons.org/${t.icon}/${t.color}`;return`
10
+ <button class="ghp-filter-btn" data-topic="${e}" style="--tech-color: #${t.color}">
11
+ <img src="${o}" alt="${t.name}" loading="lazy">
12
+ <p>${t.name}</p>
13
+ </button>
14
+ `},$=(e,t)=>{const o=`https://raw.githubusercontent.com/${t}/${e.name}/main/preview.png`,r=`https://opengraph.githubassets.com/${t}/${e.name}`,n=`https://placehold.co/640x360?text=${encodeURIComponent(e.name)}`,c=e.deployUrl?`<a href="${e.deployUrl}" target="_blank" rel="noopener noreferrer">Acessar</a>`:"",i=e.htmlUrl?`<a href="${e.htmlUrl}" target="_blank" rel="noopener noreferrer">Github</a>`:"";return`
15
+ <div class="ghp-project-card">
16
+ <div class="ghp-img-container">
17
+ <img
18
+ src="${o}"
19
+ alt="Preview do projeto ${e.name}"
20
+ class="ghp-card-img"
21
+ loading="lazy"
22
+ onerror="if (this.src.includes('preview.png')) { this.src='${r}'; } else { this.onerror=null; this.src='${n}'; }"
23
+ >
24
+ </div>
25
+ <div class="ghp-card-content">
26
+ <div>
27
+ <h3>${e.name}</h3>
28
+ <p title="${e.description}">${e.description}</p>
29
+ </div>
30
+ <div class="ghp-card-links">
31
+ ${i}
32
+ ${c}
33
+ </div>
34
+ </div>
35
+ </div>
36
+ `};function S(e,t=6){const o=m(e);o&&(o.className="ghp-projects-grid",o.innerHTML=Array(t).fill('<div class="ghp-project-card ghp-skeleton ghp-skeleton-card"></div>').join(""))}function v(e,t){const o=m(t);if(!o)return;const r=e.flatMap(i=>i.topics||[]),c=[...new Set(r)].reduce((i,a)=>{const l=w[a];return l?i+y(a,l):i},y("all"));o.innerHTML=`<div class="ghp-filters-content">${c}</div>`}function u(e,t,o="",r){const n=m(t);if(n){if(n.className="ghp-projects-grid",n.style.minHeight="auto",e.length===0){n.innerHTML='<p class="ghp-empty-msg">Nenhum projeto encontrado.</p>';return}n.innerHTML=e.map(c=>r?r(c):$(c,o)).join("")}}function j(e){const t=m(e);t&&(t.innerHTML=`
37
+ <div class="ghp-search-container">
38
+ <svg class="ghp-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
39
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
40
+ </svg>
41
+ <input type="text" id="gh-port-search" class="ghp-search-input" placeholder="Buscar projeto..." autocomplete="off">
42
+ </div>
43
+ `)}function E(e,t,o,r,n){const c=document.getElementById(e);c&&c.addEventListener("click",i=>{const l=i.target.closest(".ghp-filter-btn");if(!l)return;const p=l.dataset.topic,d=p==="all"?t:t.filter(g=>g.topics.includes(p));u(d,o,r,n),c.querySelectorAll(".ghp-filter-btn").forEach(g=>g.classList.remove("active")),l.classList.add("active")})}function B(e,t,o,r){const n=document.getElementById("gh-port-search");n&&n.addEventListener("input",c=>{const a=c.target.value.toLowerCase(),l=e.filter(p=>p.name.toLowerCase().includes(a)||(p.description||"").toLowerCase().includes(a)||p.topics.some(d=>d.toLowerCase().includes(a)));u(l,t,o,r)})}async function P(e,t={searchContainer:"search-cont",filtersContainer:"filters-cont",projectsContainer:"projects-cont"}){try{const o=t.tag||"port";j(t.searchContainer),S(t.projectsContainer,6);const r=await f(e,o);return v(r,t.filtersContainer),u(r,t.projectsContainer,e,t.customCardTemplate),E(t.filtersContainer,r,t.projectsContainer,e,t.customCardTemplate),B(r,t.projectsContainer,e,t.customCardTemplate),{projects:r,status:"success"}}catch(o){console.error("Erro ao inicializar portfólio:",o);const r=document.getElementById(t.projectsContainer);return r&&(r.innerHTML='<p class="ghp-empty-msg">Erro ao carregar projetos.</p>'),{status:"error",message:o.message}}}s.getPortProjects=f,s.initPortfolio=P,s.renderFilters=v,s.renderProjects=u,s.renderSearchBar=j,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "snapport",
3
- "private": false,
4
- "version": "1.0.0",
5
- "type": "module",
6
- "files": [
7
- "dist"
8
- ],
9
- "main": "./dist/snap-port.umd.cjs",
10
- "module": "./dist/snap-port.js",
11
- "types": "./dist/index.d.ts",
12
- "exports": {
13
- ".": {
14
- "types": "./dist/index.d.ts",
15
- "import": "./dist/snap-port.js",
16
- "require": "./dist/snap-port.umd.cjs"
17
- },
18
- "./style.css": "./dist/snap-port.css"
19
- },
20
- "scripts": {
21
- "dev": "vite",
22
- "build": "tsc && vite build",
23
- "preview": "vite preview"
24
- },
25
- "devDependencies": {
26
- "@types/node": "^25.2.2",
27
- "typescript": "~5.9.3",
28
- "vite": "^7.2.4",
29
- "vite-plugin-dts": "^4.5.4"
30
- },
31
- "license": "MIT",
32
- "author": "Guilherme Godoy"
33
- }
1
+ {
2
+ "name": "snapport",
3
+ "private": false,
4
+ "version": "1.0.1",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/snap-port.umd.cjs",
10
+ "module": "./dist/snap-port.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/snap-port.js",
16
+ "require": "./dist/snap-port.umd.cjs"
17
+ },
18
+ "./style.css": "./dist/snap-port.css"
19
+ },
20
+ "scripts": {
21
+ "dev": "vite",
22
+ "build": "tsc && vite build",
23
+ "preview": "vite preview"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.2.2",
27
+ "typescript": "~5.9.3",
28
+ "vite": "^7.2.4",
29
+ "vite-plugin-dts": "^4.5.4"
30
+ },
31
+ "license": "MIT",
32
+ "author": "Guilherme Godoy"
33
+ }