The codebase that powers boop.cat boop.cat
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make footer and api docs page mobile friendly

scanash00 eae4a36f b6a2278d

+198 -238
+83 -238
client/src/pages/ApiDocs.jsx
··· 121 121 The boop.cat API allows you to manage sites and trigger deployments programmatically. This is useful for CI/CD 122 122 pipelines, GitHub Actions, and custom deployment workflows. 123 123 </div> 124 - <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}> 125 - <div 126 - style={{ 127 - padding: '8px 16px', 128 - background: 'var(--bg-secondary)', 129 - borderRadius: 8, 130 - fontSize: 13 131 - }} 132 - > 124 + <div className="api-stats-grid"> 125 + <div className="api-stat-card"> 133 126 <strong>Base URL:</strong> <code>{baseUrl}/api/v1</code> 134 127 </div> 135 - <div 136 - style={{ 137 - padding: '8px 16px', 138 - background: 'var(--bg-secondary)', 139 - borderRadius: 8, 140 - fontSize: 13 141 - }} 142 - > 128 + <div className="api-stat-card"> 143 129 <strong>Rate Limit:</strong> 100 requests / 15 minutes 144 130 </div> 145 131 </div> ··· 197 183 </div> 198 184 199 185 {endpoints.map((endpoint, i) => ( 200 - <div 201 - key={i} 202 - style={{ 203 - borderBottom: i < endpoints.length - 1 ? '1px solid var(--border)' : 'none', 204 - paddingBottom: i < endpoints.length - 1 ? 24 : 0, 205 - marginBottom: i < endpoints.length - 1 ? 24 : 0 206 - }} 207 - > 208 - <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 8 }}> 186 + <div key={i} className="api-endpoint-item"> 187 + <div className="api-endpoint-header"> 209 188 <span 189 + className="api-method-badge" 210 190 style={{ 211 - padding: '4px 10px', 212 - borderRadius: 6, 213 - fontSize: 12, 214 - fontWeight: 700, 215 - fontFamily: 'monospace', 216 191 background: endpoint.method === 'POST' ? 'rgba(34, 197, 94, 0.15)' : 'rgba(59, 130, 246, 0.15)', 217 192 color: endpoint.method === 'POST' ? '#22c55e' : '#3b82f6' 218 193 }} 219 194 > 220 195 {endpoint.method} 221 196 </span> 222 - <code style={{ fontSize: 14, fontWeight: 500 }}>{endpoint.path}</code> 197 + <code className="api-path">{endpoint.path}</code> 223 198 </div> 224 - <div className="muted" style={{ marginBottom: 12 }}> 199 + <div className="muted" style={{ marginBottom: 16 }}> 225 200 {endpoint.description} 226 201 </div> 227 202 228 - <div style={{ marginBottom: 12 }}> 229 - <div 230 - style={{ 231 - display: 'flex', 232 - justifyContent: 'space-between', 233 - alignItems: 'center', 234 - marginBottom: 6 235 - }} 236 - > 203 + <div style={{ marginBottom: 16 }}> 204 + <div className="api-example-header"> 237 205 <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>Example Request</span> 238 - <button 239 - className="iconBtn" 240 - style={{ 241 - width: 28, 242 - height: 28, 243 - borderRadius: '50%', 244 - background: 'var(--bg-secondary)', 245 - border: 'none' 246 - }} 247 - onClick={() => copyToClipboard(endpoint.example, `example-${i}`)} 248 - > 206 + <button className="api-copy-btn" onClick={() => copyToClipboard(endpoint.example, `example-${i}`)}> 249 207 {copiedIndex === `example-${i}` ? <Check size={14} /> : <Copy size={14} />} 250 208 </button> 251 209 </div> 252 - <pre 253 - style={{ 254 - background: 'var(--bg-tertiary, #1e1e1e)', 255 - padding: 16, 256 - borderRadius: 8, 257 - fontFamily: 'monospace', 258 - fontSize: 12, 259 - color: 'var(--code-text, #e0e0e0)', 260 - overflowX: 'auto', 261 - margin: 0, 262 - whiteSpace: 'pre-wrap', 263 - wordBreak: 'break-word' 264 - }} 265 - > 266 - {endpoint.example} 267 - </pre> 210 + <pre className="api-code-block">{endpoint.example}</pre> 268 211 </div> 269 212 270 213 <div> 271 - <div 272 - style={{ 273 - display: 'flex', 274 - justifyContent: 'space-between', 275 - alignItems: 'center', 276 - marginBottom: 6 277 - }} 278 - > 214 + <div className="api-example-header"> 279 215 <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>Example Response</span> 280 - <button 281 - className="iconBtn" 282 - style={{ 283 - width: 28, 284 - height: 28, 285 - borderRadius: '50%', 286 - background: 'var(--bg-secondary)', 287 - border: 'none' 288 - }} 289 - onClick={() => copyToClipboard(endpoint.response, `response-${i}`)} 290 - > 216 + <button className="api-copy-btn" onClick={() => copyToClipboard(endpoint.response, `response-${i}`)}> 291 217 {copiedIndex === `response-${i}` ? <Check size={14} /> : <Copy size={14} />} 292 218 </button> 293 219 </div> 294 - <pre 295 - style={{ 296 - background: 'var(--bg-tertiary, #1e1e1e)', 297 - padding: 16, 298 - borderRadius: 8, 299 - fontFamily: 'monospace', 300 - fontSize: 12, 301 - color: 'var(--code-text, #e0e0e0)', 302 - overflowX: 'auto', 303 - margin: 0 304 - }} 305 - > 306 - {endpoint.response} 307 - </pre> 220 + <pre className="api-code-block">{endpoint.response}</pre> 308 221 </div> 309 222 </div> 310 223 ))} ··· 320 233 Here's an example workflow to trigger a deployment on every push to main: 321 234 </div> 322 235 323 - <div style={{ position: 'relative' }}> 324 - <button 325 - className="iconBtn" 326 - style={{ 327 - position: 'absolute', 328 - top: 8, 329 - right: 8, 330 - width: 28, 331 - height: 28, 332 - borderRadius: '50%', 333 - background: 'var(--bg-secondary)', 334 - border: 'none' 335 - }} 336 - onClick={() => 337 - copyToClipboard( 338 - `name: Deploy to boop.cat 236 + <div> 237 + <div className="api-example-header"> 238 + <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}> 239 + .github/workflows/deploy.yml 240 + </span> 241 + <button 242 + className="api-copy-btn" 243 + onClick={() => 244 + copyToClipboard( 245 + `name: Deploy to boop.cat 339 246 340 247 on: 341 248 push: ··· 350 257 curl -X POST \\ 351 258 -H "Authorization: Bearer \${{ secrets.BOOP_CAT_API_KEY }}" \\ 352 259 ${baseUrl}/api/v1/sites/YOUR_SITE_ID/deploy?wait=true`, 353 - 'github-actions' 354 - ) 355 - } 356 - > 357 - {copiedIndex === 'github-actions' ? <Check size={14} /> : <Copy size={14} />} 358 - </button> 359 - <pre 360 - style={{ 361 - background: 'var(--bg-tertiary, #1e1e1e)', 362 - padding: 16, 363 - borderRadius: 8, 364 - fontFamily: 'monospace', 365 - fontSize: 12, 366 - color: 'var(--code-text, #e0e0e0)', 367 - overflowX: 'auto', 368 - margin: 0 369 - }} 370 - > 260 + 'github-actions' 261 + ) 262 + } 263 + > 264 + {copiedIndex === 'github-actions' ? <Check size={14} /> : <Copy size={14} />} 265 + </button> 266 + </div> 267 + <pre className="api-code-block"> 371 268 {`name: Deploy to boop.cat 372 269 373 270 on: ··· 402 299 For GitLab CI/CD, add this to your <code>.gitlab-ci.yml</code>: 403 300 </div> 404 301 405 - <div style={{ position: 'relative' }}> 406 - <button 407 - className="iconBtn" 408 - style={{ 409 - position: 'absolute', 410 - top: 8, 411 - right: 8, 412 - width: 28, 413 - height: 28, 414 - borderRadius: '50%', 415 - background: 'var(--bg-secondary)', 416 - border: 'none' 417 - }} 418 - onClick={() => 419 - copyToClipboard( 420 - `deploy: 302 + <div> 303 + <div className="api-example-header"> 304 + <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>.gitlab-ci.yml</span> 305 + <button 306 + className="api-copy-btn" 307 + onClick={() => 308 + copyToClipboard( 309 + `deploy: 421 310 stage: deploy 422 311 image: curlimages/curl 423 312 script: 424 313 - curl -X POST -H "Authorization: Bearer $BOOP_CAT_API_KEY" ${baseUrl}/api/v1/sites/YOUR_SITE_ID/deploy?wait=true 425 314 only: 426 315 - main`, 427 - 'gitlab-ci' 428 - ) 429 - } 430 - > 431 - {copiedIndex === 'gitlab-ci' ? <Check size={14} /> : <Copy size={14} />} 432 - </button> 433 - <pre 434 - style={{ 435 - background: 'var(--bg-tertiary, #1e1e1e)', 436 - padding: 16, 437 - borderRadius: 8, 438 - fontFamily: 'monospace', 439 - fontSize: 12, 440 - color: 'var(--code-text, #e0e0e0)', 441 - overflowX: 'auto', 442 - margin: 0 443 - }} 444 - > 316 + 'gitlab-ci' 317 + ) 318 + } 319 + > 320 + {copiedIndex === 'gitlab-ci' ? <Check size={14} /> : <Copy size={14} />} 321 + </button> 322 + </div> 323 + <pre className="api-code-block"> 445 324 {`deploy: 446 325 stage: deploy 447 326 image: curlimages/curl ··· 470 349 A simple Node.js script to trigger deployments: 471 350 </div> 472 351 473 - <div style={{ position: 'relative' }}> 474 - <button 475 - className="iconBtn" 476 - style={{ 477 - position: 'absolute', 478 - top: 8, 479 - right: 8, 480 - width: 28, 481 - height: 28, 482 - borderRadius: '50%', 483 - background: 'var(--bg-secondary)', 484 - border: 'none' 485 - }} 486 - onClick={() => 487 - copyToClipboard( 488 - `const siteId = 'YOUR_SITE_ID'; 352 + <div> 353 + <div className="api-example-header"> 354 + <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>deploy.js</span> 355 + <button 356 + className="api-copy-btn" 357 + onClick={() => 358 + copyToClipboard( 359 + `const siteId = 'YOUR_SITE_ID'; 489 360 const apiKey = process.env.BOOP_CAT_API_KEY; 490 361 491 362 async function deploy() { ··· 501 372 } 502 373 503 374 deploy().catch(console.error);`, 504 - 'node-script' 505 - ) 506 - } 507 - > 508 - {copiedIndex === 'node-script' ? <Check size={14} /> : <Copy size={14} />} 509 - </button> 510 - <pre 511 - style={{ 512 - background: 'var(--bg-tertiary, #1e1e1e)', 513 - padding: 16, 514 - borderRadius: 8, 515 - fontFamily: 'monospace', 516 - fontSize: 12, 517 - color: 'var(--code-text, #e0e0e0)', 518 - overflowX: 'auto', 519 - margin: 0 520 - }} 521 - > 375 + 'node-script' 376 + ) 377 + } 378 + > 379 + {copiedIndex === 'node-script' ? <Check size={14} /> : <Copy size={14} />} 380 + </button> 381 + </div> 382 + <pre className="api-code-block"> 522 383 {`const siteId = 'YOUR_SITE_ID'; 523 384 const apiKey = process.env.BOOP_CAT_API_KEY; 524 385 ··· 549 410 For Tangled.org (Spindle), create only <code>.tangled/workflows/deploy.yml</code>: 550 411 </div> 551 412 552 - <div style={{ position: 'relative' }}> 553 - <button 554 - className="iconBtn" 555 - style={{ 556 - position: 'absolute', 557 - top: 8, 558 - right: 8, 559 - width: 28, 560 - height: 28, 561 - borderRadius: '50%', 562 - background: 'var(--bg-secondary)', 563 - border: 'none' 564 - }} 565 - onClick={() => 566 - copyToClipboard( 567 - `when: 413 + <div> 414 + <div className="api-example-header"> 415 + <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}> 416 + .tangled/workflows/deploy.yml 417 + </span> 418 + <button 419 + className="api-copy-btn" 420 + onClick={() => 421 + copyToClipboard( 422 + `when: 568 423 - event: ["push"] 569 424 branch: ["main"] 570 425 ··· 583 438 curl -X POST \\ 584 439 -H "Authorization: Bearer $BOOP_CAT_API_KEY" \\ 585 440 ${baseUrl}/api/v1/sites/YOUR_SITE_ID/deploy?wait=true`, 586 - 'tangled-ci' 587 - ) 588 - } 589 - > 590 - {copiedIndex === 'tangled-ci' ? <Check size={14} /> : <Copy size={14} />} 591 - </button> 592 - <pre 593 - style={{ 594 - background: 'var(--bg-tertiary, #1e1e1e)', 595 - padding: 16, 596 - borderRadius: 8, 597 - fontFamily: 'monospace', 598 - fontSize: 12, 599 - color: 'var(--code-text, #e0e0e0)', 600 - overflowX: 'auto', 601 - margin: 0 602 - }} 603 - > 441 + 'tangled-ci' 442 + ) 443 + } 444 + > 445 + {copiedIndex === 'tangled-ci' ? <Check size={14} /> : <Copy size={14} />} 446 + </button> 447 + </div> 448 + <pre className="api-code-block"> 604 449 {`when: 605 450 - event: ["push"] 606 451 branch: ["main"]
+115
client/src/styles.css
··· 1251 1251 opacity: 0.6; 1252 1252 } 1253 1253 1254 + @media (max-width: 640px) { 1255 + .footer-inner { 1256 + flex-direction: column; 1257 + gap: 20px; 1258 + text-align: center; 1259 + } 1260 + 1261 + .footer-links { 1262 + flex-wrap: wrap; 1263 + justify-content: center; 1264 + gap: 16px 24px; 1265 + } 1266 + } 1267 + 1254 1268 .content-section { 1255 1269 animation: fadeInUp 0.6s ease-out backwards; 1256 1270 } ··· 4281 4295 width: 100%; 4282 4296 } 4283 4297 } 4298 + 4299 + /* ApiDocs Responsive Styles */ 4300 + .api-stats-grid { 4301 + display: flex; 4302 + gap: 12px; 4303 + flex-wrap: wrap; 4304 + } 4305 + 4306 + .api-stat-card { 4307 + padding: 8px 16px; 4308 + background: var(--bg-secondary); 4309 + border-radius: 8px; 4310 + font-size: 13px; 4311 + } 4312 + 4313 + .api-endpoint-item { 4314 + border-bottom: 1px solid var(--border); 4315 + padding-bottom: 24px; 4316 + margin-bottom: 24px; 4317 + } 4318 + 4319 + .api-endpoint-item:last-child { 4320 + border-bottom: none; 4321 + padding-bottom: 0; 4322 + margin-bottom: 0; 4323 + } 4324 + 4325 + .api-endpoint-header { 4326 + display: flex; 4327 + align-items: center; 4328 + gap: 12px; 4329 + margin-bottom: 8px; 4330 + flex-wrap: wrap; 4331 + } 4332 + 4333 + .api-method-badge { 4334 + padding: 4px 10px; 4335 + border-radius: 6px; 4336 + font-size: 12px; 4337 + font-weight: 700; 4338 + font-family: monospace; 4339 + } 4340 + 4341 + .api-path { 4342 + font-size: 14px; 4343 + font-weight: 500; 4344 + word-break: break-all; 4345 + } 4346 + 4347 + .api-example-header { 4348 + display: flex; 4349 + justify-content: space-between; 4350 + align-items: center; 4351 + margin-bottom: 6px; 4352 + } 4353 + 4354 + .api-code-block { 4355 + background: var(--bg-tertiary, #1e1e1e); 4356 + padding: 16px; 4357 + border-radius: 8px; 4358 + font-family: monospace; 4359 + font-size: 12px; 4360 + color: var(--code-text, #e0e0e0); 4361 + overflow-x: auto; 4362 + margin: 0; 4363 + white-space: pre-wrap; 4364 + word-break: break-word; 4365 + } 4366 + 4367 + .api-copy-btn { 4368 + width: 28px; 4369 + height: 28px; 4370 + border-radius: 50%; 4371 + background: var(--bg-secondary); 4372 + border: none; 4373 + display: flex; 4374 + align-items: center; 4375 + justify-content: center; 4376 + cursor: pointer; 4377 + color: var(--card-text); 4378 + transition: background 0.2s; 4379 + } 4380 + 4381 + .api-copy-btn:hover { 4382 + background: var(--sidebar-hover); 4383 + } 4384 + 4385 + @media (max-width: 600px) { 4386 + .api-stats-grid { 4387 + flex-direction: column; 4388 + gap: 8px; 4389 + } 4390 + 4391 + .api-stat-card { 4392 + width: 100%; 4393 + } 4394 + 4395 + .api-example-header { 4396 + margin-bottom: 8px; 4397 + } 4398 + }