nex-app 0.1.0
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/README.md +74 -0
- package/cli/create.js +116 -0
- package/cli/fallback.js +96 -0
- package/generator/agent-installer.js +33 -0
- package/generator/cursor-integration.js +202 -0
- package/generator/index.js +141 -0
- package/generator/package-manager.js +40 -0
- package/generator/template-engine.js +18 -0
- package/lib/marketplace-client.js +75 -0
- package/package.json +55 -0
- package/server/index.js +53 -0
- package/server/routes.js +89 -0
- package/templates/api-only/README.md.tpl +30 -0
- package/templates/api-only/package.json.tpl +20 -0
- package/templates/api-only/src/index.js.tpl +9 -0
- package/templates/blank/.nex-core/config.yaml.tpl +14 -0
- package/templates/blank/README.md.tpl +48 -0
- package/templates/blank/package.json.tpl +18 -0
- package/templates/blank/src/index.js.tpl +9 -0
- package/templates/full-stack/README.md.tpl +33 -0
- package/templates/full-stack/package.json.tpl +20 -0
- package/templates/full-stack/src/client/index.html.tpl +12 -0
- package/templates/full-stack/src/server/index.js.tpl +9 -0
- package/ui/assets/app.js +255 -0
- package/ui/assets/styles.css +416 -0
- package/ui/index.html +113 -0
- package/utils/browser.js +40 -0
- package/utils/environment.js +42 -0
- package/utils/port-finder.js +37 -0
- package/utils/validation.js +44 -0
package/ui/assets/app.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript da UI do Installer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
let currentStep = 1
|
|
6
|
+
const totalSteps = 4
|
|
7
|
+
let selectedAgents = []
|
|
8
|
+
let allAgents = []
|
|
9
|
+
|
|
10
|
+
// Inicialização
|
|
11
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
12
|
+
await loadAgents()
|
|
13
|
+
setupEventListeners()
|
|
14
|
+
updateStepVisibility()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Carregar agentes do Marketplace
|
|
18
|
+
async function loadAgents() {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch('/api/agents/search')
|
|
21
|
+
const data = await response.json()
|
|
22
|
+
allAgents = data.agents || []
|
|
23
|
+
renderAgentResults(allAgents)
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Erro ao carregar agentes:', error)
|
|
26
|
+
document.getElementById('agent-results').innerHTML =
|
|
27
|
+
'<p class="error">Erro ao carregar agentes. Continuando sem agentes...</p>'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Setup de event listeners
|
|
32
|
+
function setupEventListeners() {
|
|
33
|
+
// Navegação de steps
|
|
34
|
+
document.getElementById('next-btn').addEventListener('click', nextStep)
|
|
35
|
+
document.getElementById('prev-btn').addEventListener('click', prevStep)
|
|
36
|
+
|
|
37
|
+
// Busca de agentes
|
|
38
|
+
document.getElementById('agent-search').addEventListener('input', handleAgentSearch)
|
|
39
|
+
|
|
40
|
+
// Submit do formulário
|
|
41
|
+
document.getElementById('installer-form').addEventListener('submit', handleSubmit)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Navegação entre steps
|
|
45
|
+
function nextStep() {
|
|
46
|
+
if (validateCurrentStep()) {
|
|
47
|
+
if (currentStep < totalSteps) {
|
|
48
|
+
currentStep++
|
|
49
|
+
updateStepVisibility()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function prevStep() {
|
|
55
|
+
if (currentStep > 1) {
|
|
56
|
+
currentStep--
|
|
57
|
+
updateStepVisibility()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Validar step atual
|
|
62
|
+
function validateCurrentStep() {
|
|
63
|
+
const currentStepEl = document.querySelector(`.step[data-step="${currentStep}"]`)
|
|
64
|
+
|
|
65
|
+
// Verificar se o step existe
|
|
66
|
+
if (!currentStepEl) {
|
|
67
|
+
console.warn(`Step ${currentStep} não encontrado`)
|
|
68
|
+
return true // Permite avançar se step não existe
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const requiredInputs = currentStepEl.querySelectorAll('input[required], select[required]')
|
|
72
|
+
|
|
73
|
+
for (const input of requiredInputs) {
|
|
74
|
+
if (!input.value.trim()) {
|
|
75
|
+
input.focus()
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Atualizar visibilidade dos steps
|
|
84
|
+
function updateStepVisibility() {
|
|
85
|
+
// Esconder todos os steps
|
|
86
|
+
document.querySelectorAll('.step').forEach(step => {
|
|
87
|
+
step.classList.remove('active')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Mostrar step atual
|
|
91
|
+
const currentStepEl = document.querySelector(`.step[data-step="${currentStep}"]`)
|
|
92
|
+
if (currentStepEl) {
|
|
93
|
+
currentStepEl.classList.add('active')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Atualizar botões
|
|
97
|
+
document.getElementById('prev-btn').disabled = currentStep === 1
|
|
98
|
+
document.getElementById('next-btn').classList.toggle('hidden', currentStep === totalSteps)
|
|
99
|
+
document.getElementById('submit-btn').classList.toggle('hidden', currentStep !== totalSteps)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Busca de agentes
|
|
103
|
+
function handleAgentSearch(e) {
|
|
104
|
+
const query = e.target.value.toLowerCase()
|
|
105
|
+
const filtered = allAgents.filter(agent =>
|
|
106
|
+
agent.name.toLowerCase().includes(query) ||
|
|
107
|
+
agent.tagline.toLowerCase().includes(query) ||
|
|
108
|
+
agent.agent_id.toLowerCase().includes(query)
|
|
109
|
+
)
|
|
110
|
+
renderAgentResults(filtered)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Renderizar resultados de agentes
|
|
114
|
+
function renderAgentResults(agents) {
|
|
115
|
+
const container = document.getElementById('agent-results')
|
|
116
|
+
|
|
117
|
+
if (agents.length === 0) {
|
|
118
|
+
container.innerHTML = '<p class="empty">Nenhum agente encontrado</p>'
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
container.innerHTML = agents.map(agent => {
|
|
123
|
+
const isSelected = selectedAgents.includes(agent.agent_id)
|
|
124
|
+
return `
|
|
125
|
+
<div class="agent-card ${isSelected ? 'selected' : ''}" data-agent-id="${agent.agent_id}">
|
|
126
|
+
<label>
|
|
127
|
+
<input
|
|
128
|
+
type="checkbox"
|
|
129
|
+
${isSelected ? 'checked' : ''}
|
|
130
|
+
onchange="toggleAgent('${agent.agent_id}')"
|
|
131
|
+
>
|
|
132
|
+
<div class="agent-info">
|
|
133
|
+
<h4>${agent.icon} ${agent.name}</h4>
|
|
134
|
+
<p>${agent.tagline}</p>
|
|
135
|
+
<small>${agent.total_installs || 0} instalações • ⭐ ${agent.average_rating || 'N/A'}/5</small>
|
|
136
|
+
</div>
|
|
137
|
+
</label>
|
|
138
|
+
</div>
|
|
139
|
+
`
|
|
140
|
+
}).join('')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Toggle de agente
|
|
144
|
+
window.toggleAgent = function(agentId) {
|
|
145
|
+
const index = selectedAgents.indexOf(agentId)
|
|
146
|
+
if (index > -1) {
|
|
147
|
+
selectedAgents.splice(index, 1)
|
|
148
|
+
} else {
|
|
149
|
+
selectedAgents.push(agentId)
|
|
150
|
+
}
|
|
151
|
+
updateSelectedAgents()
|
|
152
|
+
renderAgentResults(allAgents.filter(a =>
|
|
153
|
+
document.getElementById('agent-search').value === '' ||
|
|
154
|
+
a.name.toLowerCase().includes(document.getElementById('agent-search').value.toLowerCase())
|
|
155
|
+
))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Atualizar lista de agentes selecionados
|
|
159
|
+
function updateSelectedAgents() {
|
|
160
|
+
const container = document.getElementById('selected-list')
|
|
161
|
+
|
|
162
|
+
if (selectedAgents.length === 0) {
|
|
163
|
+
container.innerHTML = '<p class="empty">Nenhum agente selecionado</p>'
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
container.innerHTML = selectedAgents.map(agentId => {
|
|
168
|
+
const agent = allAgents.find(a => a.agent_id === agentId)
|
|
169
|
+
if (!agent) return ''
|
|
170
|
+
return `
|
|
171
|
+
<div class="selected-agent">
|
|
172
|
+
<span>${agent.icon} ${agent.name}</span>
|
|
173
|
+
<button type="button" onclick="toggleAgent('${agentId}')" class="remove-btn">×</button>
|
|
174
|
+
</div>
|
|
175
|
+
`
|
|
176
|
+
}).join('')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Submit do formulário
|
|
180
|
+
async function handleSubmit(e) {
|
|
181
|
+
e.preventDefault()
|
|
182
|
+
|
|
183
|
+
// Esconder formulário e mostrar progresso
|
|
184
|
+
document.getElementById('installer-form').classList.add('hidden')
|
|
185
|
+
document.getElementById('progress').classList.remove('hidden')
|
|
186
|
+
|
|
187
|
+
const formData = new FormData(e.target)
|
|
188
|
+
const data = {
|
|
189
|
+
template: formData.get('template'),
|
|
190
|
+
agents: selectedAgents,
|
|
191
|
+
packageManager: formData.get('packageManager'),
|
|
192
|
+
cursorIntegration: formData.get('cursorIntegration') === 'on',
|
|
193
|
+
options: {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const response = await fetch('/api/generate', {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'application/json'
|
|
201
|
+
},
|
|
202
|
+
body: JSON.stringify(data)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const result = await response.json()
|
|
206
|
+
|
|
207
|
+
if (result.success) {
|
|
208
|
+
showSuccess(result)
|
|
209
|
+
} else {
|
|
210
|
+
showError(result.error || 'Erro ao gerar projeto')
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
showError(error.message)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Mostrar sucesso
|
|
218
|
+
function showSuccess(result) {
|
|
219
|
+
document.getElementById('progress').classList.add('hidden')
|
|
220
|
+
document.getElementById('success').classList.remove('hidden')
|
|
221
|
+
|
|
222
|
+
const formData = new FormData(document.getElementById('installer-form'))
|
|
223
|
+
const packageManager = formData.get('packageManager')
|
|
224
|
+
const projectPath = result.path || 'diretório atual'
|
|
225
|
+
|
|
226
|
+
document.getElementById('success-message').innerHTML = `
|
|
227
|
+
<p>NEX inicializado em: <code>${projectPath}</code></p>
|
|
228
|
+
<p class="hint">NEX foi inicializado no diretório atual</p>
|
|
229
|
+
`
|
|
230
|
+
|
|
231
|
+
document.getElementById('next-steps-commands').textContent = `${packageManager} install
|
|
232
|
+
${packageManager} start
|
|
233
|
+
|
|
234
|
+
# Para instalar agentes:
|
|
235
|
+
nex agent install <agent-id>
|
|
236
|
+
|
|
237
|
+
# Para ver agentes disponíveis:
|
|
238
|
+
nex agent list --all`
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Mostrar erro
|
|
242
|
+
function showError(message) {
|
|
243
|
+
document.getElementById('progress').classList.add('hidden')
|
|
244
|
+
document.getElementById('progress-message').textContent = message
|
|
245
|
+
document.getElementById('progress').classList.remove('hidden')
|
|
246
|
+
document.getElementById('progress').classList.add('error')
|
|
247
|
+
|
|
248
|
+
// Adicionar botão para voltar
|
|
249
|
+
const progressEl = document.getElementById('progress')
|
|
250
|
+
progressEl.innerHTML += `
|
|
251
|
+
<button onclick="location.reload()" class="btn btn-primary" style="margin-top: 1rem;">
|
|
252
|
+
Tentar Novamente
|
|
253
|
+
</button>
|
|
254
|
+
`
|
|
255
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estilos do Installer Web
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
* {
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
13
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
14
|
+
min-height: 100vh;
|
|
15
|
+
padding: 2rem;
|
|
16
|
+
color: #333;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.container {
|
|
20
|
+
max-width: 800px;
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
background: white;
|
|
23
|
+
border-radius: 12px;
|
|
24
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
25
|
+
padding: 2rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
header {
|
|
29
|
+
text-align: center;
|
|
30
|
+
margin-bottom: 2rem;
|
|
31
|
+
padding-bottom: 1.5rem;
|
|
32
|
+
border-bottom: 2px solid #f0f0f0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
header h1 {
|
|
36
|
+
font-size: 2rem;
|
|
37
|
+
color: #667eea;
|
|
38
|
+
margin-bottom: 0.5rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
header p {
|
|
42
|
+
color: #666;
|
|
43
|
+
font-size: 1.1rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.step {
|
|
47
|
+
display: none;
|
|
48
|
+
animation: fadeIn 0.3s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.step.active {
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes fadeIn {
|
|
56
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
57
|
+
to { opacity: 1; transform: translateY(0); }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.step h2 {
|
|
61
|
+
font-size: 1.5rem;
|
|
62
|
+
margin-bottom: 1.5rem;
|
|
63
|
+
color: #333;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.step .subtitle {
|
|
67
|
+
color: #666;
|
|
68
|
+
margin-bottom: 1rem;
|
|
69
|
+
font-size: 0.95rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
input[type="text"],
|
|
73
|
+
input[type="email"],
|
|
74
|
+
select {
|
|
75
|
+
width: 100%;
|
|
76
|
+
padding: 0.75rem;
|
|
77
|
+
border: 2px solid #e0e0e0;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
font-size: 1rem;
|
|
80
|
+
transition: border-color 0.3s;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
input[type="text"]:focus,
|
|
84
|
+
select:focus {
|
|
85
|
+
outline: none;
|
|
86
|
+
border-color: #667eea;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.hint {
|
|
90
|
+
display: block;
|
|
91
|
+
margin-top: 0.5rem;
|
|
92
|
+
color: #999;
|
|
93
|
+
font-size: 0.85rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Template Grid */
|
|
97
|
+
.template-grid {
|
|
98
|
+
display: grid;
|
|
99
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
100
|
+
gap: 1rem;
|
|
101
|
+
margin-top: 1rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.template-card {
|
|
105
|
+
display: block;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.template-card input[type="radio"] {
|
|
110
|
+
display: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.card-content {
|
|
114
|
+
padding: 1.5rem;
|
|
115
|
+
border: 2px solid #e0e0e0;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
transition: all 0.3s;
|
|
118
|
+
text-align: center;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.template-card input[type="radio"]:checked + .card-content {
|
|
122
|
+
border-color: #667eea;
|
|
123
|
+
background: #f0f4ff;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.card-content h3 {
|
|
127
|
+
font-size: 1.2rem;
|
|
128
|
+
margin-bottom: 0.5rem;
|
|
129
|
+
color: #333;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.card-content p {
|
|
133
|
+
color: #666;
|
|
134
|
+
font-size: 0.9rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Agent Search */
|
|
138
|
+
.agent-search {
|
|
139
|
+
margin-bottom: 1.5rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.agent-results {
|
|
143
|
+
max-height: 300px;
|
|
144
|
+
overflow-y: auto;
|
|
145
|
+
margin-bottom: 1.5rem;
|
|
146
|
+
border: 1px solid #e0e0e0;
|
|
147
|
+
border-radius: 8px;
|
|
148
|
+
padding: 1rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.agent-card {
|
|
152
|
+
padding: 1rem;
|
|
153
|
+
border: 1px solid #e0e0e0;
|
|
154
|
+
border-radius: 8px;
|
|
155
|
+
margin-bottom: 0.5rem;
|
|
156
|
+
transition: all 0.3s;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.agent-card:hover {
|
|
160
|
+
border-color: #667eea;
|
|
161
|
+
background: #f9f9f9;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.agent-card.selected {
|
|
165
|
+
border-color: #667eea;
|
|
166
|
+
background: #f0f4ff;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.agent-card label {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
gap: 1rem;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.agent-card input[type="checkbox"] {
|
|
177
|
+
width: 20px;
|
|
178
|
+
height: 20px;
|
|
179
|
+
cursor: pointer;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.agent-info h4 {
|
|
183
|
+
font-size: 1.1rem;
|
|
184
|
+
margin-bottom: 0.25rem;
|
|
185
|
+
color: #333;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.agent-info p {
|
|
189
|
+
color: #666;
|
|
190
|
+
font-size: 0.9rem;
|
|
191
|
+
margin-bottom: 0.25rem;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.agent-info small {
|
|
195
|
+
color: #999;
|
|
196
|
+
font-size: 0.85rem;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.selected-agents {
|
|
200
|
+
margin-top: 1.5rem;
|
|
201
|
+
padding-top: 1.5rem;
|
|
202
|
+
border-top: 1px solid #e0e0e0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.selected-agents h3 {
|
|
206
|
+
font-size: 1.1rem;
|
|
207
|
+
margin-bottom: 1rem;
|
|
208
|
+
color: #333;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.selected-list {
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-wrap: wrap;
|
|
214
|
+
gap: 0.5rem;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.selected-agent {
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 0.5rem;
|
|
221
|
+
padding: 0.5rem 1rem;
|
|
222
|
+
background: #f0f4ff;
|
|
223
|
+
border: 1px solid #667eea;
|
|
224
|
+
border-radius: 20px;
|
|
225
|
+
font-size: 0.9rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.remove-btn {
|
|
229
|
+
background: #667eea;
|
|
230
|
+
color: white;
|
|
231
|
+
border: none;
|
|
232
|
+
border-radius: 50%;
|
|
233
|
+
width: 20px;
|
|
234
|
+
height: 20px;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
font-size: 1rem;
|
|
237
|
+
line-height: 1;
|
|
238
|
+
padding: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.remove-btn:hover {
|
|
242
|
+
background: #5568d3;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Checkbox Label */
|
|
246
|
+
.checkbox-label {
|
|
247
|
+
display: flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
gap: 0.75rem;
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
padding: 1rem;
|
|
252
|
+
border: 2px solid #e0e0e0;
|
|
253
|
+
border-radius: 8px;
|
|
254
|
+
transition: all 0.3s;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.checkbox-label:hover {
|
|
258
|
+
border-color: #667eea;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.checkbox-label input[type="checkbox"] {
|
|
262
|
+
width: 20px;
|
|
263
|
+
height: 20px;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Actions */
|
|
268
|
+
.actions {
|
|
269
|
+
display: flex;
|
|
270
|
+
justify-content: space-between;
|
|
271
|
+
margin-top: 2rem;
|
|
272
|
+
padding-top: 2rem;
|
|
273
|
+
border-top: 2px solid #f0f0f0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.btn {
|
|
277
|
+
padding: 0.75rem 1.5rem;
|
|
278
|
+
border: none;
|
|
279
|
+
border-radius: 8px;
|
|
280
|
+
font-size: 1rem;
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
transition: all 0.3s;
|
|
283
|
+
font-weight: 500;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.btn-primary {
|
|
287
|
+
background: #667eea;
|
|
288
|
+
color: white;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.btn-primary:hover {
|
|
292
|
+
background: #5568d3;
|
|
293
|
+
transform: translateY(-2px);
|
|
294
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.btn-secondary {
|
|
298
|
+
background: #f0f0f0;
|
|
299
|
+
color: #333;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.btn-secondary:hover:not(:disabled) {
|
|
303
|
+
background: #e0e0e0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.btn:disabled {
|
|
307
|
+
opacity: 0.5;
|
|
308
|
+
cursor: not-allowed;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.hidden {
|
|
312
|
+
display: none !important;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Progress */
|
|
316
|
+
.progress {
|
|
317
|
+
text-align: center;
|
|
318
|
+
padding: 3rem;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.spinner {
|
|
322
|
+
width: 50px;
|
|
323
|
+
height: 50px;
|
|
324
|
+
border: 4px solid #f0f0f0;
|
|
325
|
+
border-top-color: #667eea;
|
|
326
|
+
border-radius: 50%;
|
|
327
|
+
animation: spin 1s linear infinite;
|
|
328
|
+
margin: 0 auto 1rem;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@keyframes spin {
|
|
332
|
+
to { transform: rotate(360deg); }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.progress p {
|
|
336
|
+
font-size: 1.1rem;
|
|
337
|
+
color: #333;
|
|
338
|
+
margin-bottom: 0.5rem;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.progress.error {
|
|
342
|
+
color: #e74c3c;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Success */
|
|
346
|
+
.success {
|
|
347
|
+
text-align: center;
|
|
348
|
+
padding: 2rem;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.success h2 {
|
|
352
|
+
color: #27ae60;
|
|
353
|
+
margin-bottom: 1rem;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.next-steps {
|
|
357
|
+
margin-top: 2rem;
|
|
358
|
+
text-align: left;
|
|
359
|
+
background: #f9f9f9;
|
|
360
|
+
padding: 1.5rem;
|
|
361
|
+
border-radius: 8px;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.next-steps h3 {
|
|
365
|
+
margin-bottom: 1rem;
|
|
366
|
+
color: #333;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.next-steps pre {
|
|
370
|
+
background: #2d2d2d;
|
|
371
|
+
color: #f8f8f2;
|
|
372
|
+
padding: 1rem;
|
|
373
|
+
border-radius: 4px;
|
|
374
|
+
overflow-x: auto;
|
|
375
|
+
font-family: 'Courier New', monospace;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.empty {
|
|
379
|
+
color: #999;
|
|
380
|
+
font-style: italic;
|
|
381
|
+
text-align: center;
|
|
382
|
+
padding: 2rem;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.error {
|
|
386
|
+
color: #e74c3c;
|
|
387
|
+
text-align: center;
|
|
388
|
+
padding: 1rem;
|
|
389
|
+
background: #fee;
|
|
390
|
+
border-radius: 8px;
|
|
391
|
+
margin: 1rem 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* Responsive */
|
|
395
|
+
@media (max-width: 600px) {
|
|
396
|
+
body {
|
|
397
|
+
padding: 1rem;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.container {
|
|
401
|
+
padding: 1.5rem;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.template-grid {
|
|
405
|
+
grid-template-columns: 1fr;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.actions {
|
|
409
|
+
flex-direction: column;
|
|
410
|
+
gap: 0.5rem;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.btn {
|
|
414
|
+
width: 100%;
|
|
415
|
+
}
|
|
416
|
+
}
|