Initial commit: Move basicstack.org frontend to Forgejo
Migrate the Nuxt 3 + Vue 3 SSR frontend application for basicstack.org to our self-hosted Forgejo instance. This repository will serve as the source for all future development and deployment of the basicstack.org website. The application includes: - Nuxt 3 + Vue 3 SSR setup - Directus CMS integration - Tailwind CSS styling - Kubernetes deployment manifests - Docker containerization Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
a7bd527793
25 changed files with 14778 additions and 0 deletions
2
.env.example
Normal file
2
.env.example
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
DIRECTUS_URL=https://directus.basicstack.de
|
||||
SITE_URL=https://basicstack.org
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
node_modules/
|
||||
.output/
|
||||
.nuxt/
|
||||
.env
|
||||
dist/
|
||||
*.log
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
FROM node:24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:24-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --from=builder /app/.output ./
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server/index.mjs"]
|
||||
176
README.md
Normal file
176
README.md
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
# BasicStack Frontend
|
||||
|
||||
Vue 3 / Nuxt 3 frontend for basicstack.org with Directus CMS integration.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Vue 3** with Composition API
|
||||
- **Nuxt 3** (SSR/SSG, file-based routing)
|
||||
- **TypeScript** for type safety
|
||||
- **Directus SDK** for headless CMS integration
|
||||
- **@nuxt/image** for optimized image handling
|
||||
- **@nuxtjs/sitemap** for SEO
|
||||
|
||||
## Features
|
||||
|
||||
- Responsive mobile-first design
|
||||
- SEO-optimized with meta tags and sitemap
|
||||
- Directus API integration for dynamic content
|
||||
- Static site generation (SSG) ready
|
||||
- TypeScript types for Directus schema
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
basicstack-frontend/
|
||||
├── assets/ # CSS and static assets
|
||||
├── components/ # Vue components
|
||||
│ └── Navigation.vue
|
||||
├── composables/ # Composables
|
||||
│ └── useDirectus.ts # Directus client & API functions
|
||||
├── layouts/ # Nuxt layouts
|
||||
│ └── default.vue
|
||||
├── pages/ # File-based routing
|
||||
│ ├── index.vue
|
||||
│ ├── [slug].vue # Generic content pages
|
||||
│ ├── packages/
|
||||
│ │ ├── index.vue
|
||||
│ │ └── [slug].vue
|
||||
│ └── infrastructure/
|
||||
│ ├── index.vue
|
||||
│ └── [slug].vue
|
||||
├── public/ # Public static files
|
||||
├── .env # Environment variables
|
||||
├── nuxt.config.ts # Nuxt configuration
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Routes
|
||||
|
||||
- `/` - Home page
|
||||
- `/packages` - Software packages listing
|
||||
- `/packages/:slug` - Package detail page
|
||||
- `/infrastructure` - Infrastructure overview
|
||||
- `/infrastructure/:slug` - Infrastructure topic detail
|
||||
- `/:slug` - Generic content pages (About, Contact, etc.)
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+ and npm
|
||||
- Access to Directus instance at https://directus.basicstack.de
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Create .env file (already created)
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env if needed
|
||||
nano .env
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
DIRECTUS_URL=https://directus.basicstack.de
|
||||
SITE_URL=https://basicstack.org
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Start dev server (http://localhost:3000)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
```bash
|
||||
# Build for production (SSR)
|
||||
npm run build
|
||||
|
||||
# Preview production build
|
||||
npm run preview
|
||||
|
||||
# Generate static site (SSG)
|
||||
npm run generate
|
||||
```
|
||||
|
||||
## Directus Integration
|
||||
|
||||
The application connects to Directus using the `@directus/sdk` package. All API calls are made through the `useDirectus()` composable.
|
||||
|
||||
### Collections
|
||||
|
||||
- `software_packages` - Software package listings
|
||||
- `package_screenshots` - Package screenshots
|
||||
- `package_deployment_details` - Deployment information
|
||||
- `package_troubleshooting` - Troubleshooting guides
|
||||
- `infrastructure_content` - Infrastructure documentation
|
||||
- `pages` - Generic content pages
|
||||
- `navigation` - Site navigation menu
|
||||
|
||||
### Example Usage
|
||||
|
||||
```typescript
|
||||
const { getPackages, getPackage, getAssetUrl } = useDirectus()
|
||||
|
||||
// Get all published packages
|
||||
const packages = await getPackages()
|
||||
|
||||
// Get a specific package by slug
|
||||
const pkg = await getPackage('directus')
|
||||
|
||||
// Get asset URL for a Directus file
|
||||
const logoUrl = getAssetUrl(pkg.logo)
|
||||
```
|
||||
|
||||
## Design Integration
|
||||
|
||||
The frontend is scaffolded with basic responsive styling. Corporate design tokens and components from [DEV-171](/DEV/issues/DEV-171) can be integrated as they become available without requiring architectural changes.
|
||||
|
||||
## Deployment
|
||||
|
||||
The application can be deployed as:
|
||||
|
||||
1. **SSR (Node.js server)** - Default mode, best for dynamic content
|
||||
2. **SSG (Static site)** - Pre-rendered pages, best for CDN hosting
|
||||
3. **Hybrid** - Mix of SSR and SSG based on routes
|
||||
|
||||
Recommended deployment targets:
|
||||
- Kubernetes (with Node.js container)
|
||||
- Vercel / Netlify (for SSG)
|
||||
- Any Node.js hosting platform
|
||||
|
||||
## SEO
|
||||
|
||||
- Meta tags configured per page
|
||||
- Sitemap.xml automatically generated
|
||||
- Semantic HTML structure
|
||||
- Open Graph tags ready for social sharing
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Nuxt 3 application scaffolded
|
||||
2. ✅ Directus SDK integrated
|
||||
3. ✅ All required pages/routes created
|
||||
4. ✅ Responsive layout implemented
|
||||
5. ✅ SEO basics in place
|
||||
6. ⏳ Apply corporate design tokens from DEV-171
|
||||
7. ⏳ Deploy to Kubernetes with TLS
|
||||
8. ⏳ Content population via Directus
|
||||
|
||||
## Related Issues
|
||||
|
||||
- [DEV-172](/DEV/issues/DEV-172) - Directus schema (dependency)
|
||||
- [DEV-171](/DEV/issues/DEV-171) - Corporate design (soft dependency)
|
||||
- [DEV-169](/DEV/issues/DEV-169) - Parent issue
|
||||
|
||||
## License
|
||||
|
||||
See LICENSE file in repository root.
|
||||
202
assets/css/main.css
Normal file
202
assets/css/main.css
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
:root {
|
||||
/* Colors - Primary */
|
||||
--color-primary: #FF5501;
|
||||
--color-primary-hover: #E84D00;
|
||||
--color-primary-subtle: #FF6533;
|
||||
|
||||
/* Colors - Surfaces */
|
||||
--color-surface: #FFFFFF;
|
||||
--color-surface-raised: #F8FAFC;
|
||||
--color-surface-overlay: #F1F5F9;
|
||||
|
||||
/* Colors - Borders */
|
||||
--color-border: #E2E8F0;
|
||||
--color-border-strong: #CBD5E1;
|
||||
|
||||
/* Colors - Text */
|
||||
--color-text-primary: #0F172A;
|
||||
--color-text-secondary: #475569;
|
||||
--color-text-placeholder: #94A3B8;
|
||||
--color-text-inverse: #FFFFFF;
|
||||
|
||||
/* Colors - Semantic */
|
||||
--color-success: #16A34A;
|
||||
--color-success-subtle: #F0FDF4;
|
||||
--color-warning: #D97706;
|
||||
--color-warning-subtle: #FFFBEB;
|
||||
--color-error: #DC2626;
|
||||
--color-error-subtle: #FEF2F2;
|
||||
--color-info: #0284C7;
|
||||
--color-info-subtle: #F0F9FF;
|
||||
|
||||
/* Component-friendly aliases */
|
||||
--color-bg: var(--color-surface);
|
||||
--color-bg-secondary: var(--color-surface-raised);
|
||||
--color-text: var(--color-text-primary);
|
||||
--color-text-muted: var(--color-text-secondary);
|
||||
|
||||
/* Spacing */
|
||||
--spacing-1: 4px;
|
||||
--spacing-2: 8px;
|
||||
--spacing-3: 12px;
|
||||
--spacing-4: 16px;
|
||||
--spacing-6: 24px;
|
||||
--spacing-8: 32px;
|
||||
--spacing-12: 48px;
|
||||
--spacing-16: 64px;
|
||||
--spacing-20: 80px;
|
||||
--spacing-24: 96px;
|
||||
|
||||
/* Spacing aliases for components */
|
||||
--space-xs: var(--spacing-2);
|
||||
--space-sm: var(--spacing-3);
|
||||
--space-md: var(--spacing-4);
|
||||
--space-lg: var(--spacing-6);
|
||||
--space-xl: var(--spacing-8);
|
||||
--space-2xl: var(--spacing-12);
|
||||
--space-3xl: var(--spacing-16);
|
||||
|
||||
/* Layout */
|
||||
--nav-height: 64px;
|
||||
--header-height: var(--nav-height);
|
||||
--max-content-width: 1280px;
|
||||
--sidebar-width: 240px;
|
||||
--sidebar-collapsed: 64px;
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: 4px;
|
||||
--radius-base: 6px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
|
||||
--shadow-base: 0 1px 3px rgba(0,0,0,0.1);
|
||||
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
|
||||
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0,0,0,0.1);
|
||||
|
||||
/* Typography */
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Courier New', monospace;
|
||||
--text-xs: 12px;
|
||||
--text-sm: 14px;
|
||||
--text-base: 16px;
|
||||
--text-lg: 18px;
|
||||
--text-xl: 20px;
|
||||
--text-2xl: 24px;
|
||||
}
|
||||
|
||||
/* Dark theme support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-surface: #0F172A;
|
||||
--color-surface-raised: #1E293B;
|
||||
--color-surface-overlay: #334155;
|
||||
--color-text-primary: #F8FAFC;
|
||||
--color-text-secondary: #CBD5E1;
|
||||
--color-text-placeholder: #64748B;
|
||||
--color-border: #334155;
|
||||
--color-border-strong: #475569;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
line-height: 1.6;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
a {
|
||||
color: #FF5501;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #E84D00;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f3f4f6;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #1f2937;
|
||||
color: #f9fafb;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Utility classes */
|
||||
.container {
|
||||
max-width: var(--max-content-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-lg);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: var(--space-sm) var(--space-lg);
|
||||
border-radius: var(--radius-md);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--color-primary-hover);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border-color: var(--color-border-strong);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--color-surface-raised);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
58
components/Navigation.vue
Normal file
58
components/Navigation.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<nav class="navigation" v-if="navigation">
|
||||
<NuxtLink
|
||||
v-for="item in navigation"
|
||||
:key="item.id"
|
||||
:to="getNavLink(item)"
|
||||
class="nav-link"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { getNavigation } = useDirectus()
|
||||
|
||||
const { data: navigation } = await useAsyncData('navigation', () => getNavigation())
|
||||
|
||||
function getNavLink(item: any) {
|
||||
if (item.type === 'custom' && item.custom_route) {
|
||||
return item.custom_route
|
||||
}
|
||||
if (item.type === 'external' && item.external_url) {
|
||||
return item.external_url
|
||||
}
|
||||
if (item.type === 'page' && item.page_id) {
|
||||
return `/${item.page_id}`
|
||||
}
|
||||
return '/'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navigation {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #4b5563;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.router-link-active {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navigation {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
197
composables/useDirectus.ts
Normal file
197
composables/useDirectus.ts
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import { createDirectus, rest, readItems, readItem, readSingleton } from '@directus/sdk'
|
||||
|
||||
type SoftwarePackage = {
|
||||
id: number
|
||||
status: 'published' | 'draft' | 'archived'
|
||||
name: string
|
||||
slug: string
|
||||
short_description: string
|
||||
full_description: string | null
|
||||
logo: string | null
|
||||
website_url: string | null
|
||||
documentation_url: string | null
|
||||
screenshots: PackageScreenshot[]
|
||||
deployment_details: PackageDeploymentDetail[]
|
||||
troubleshooting: PackageTroubleshooting[]
|
||||
}
|
||||
|
||||
type PackageScreenshot = {
|
||||
id: number
|
||||
image: string
|
||||
title: string | null
|
||||
caption: string | null
|
||||
sort: number | null
|
||||
}
|
||||
|
||||
type PackageDeploymentDetail = {
|
||||
id: number
|
||||
title: string
|
||||
content: string | null
|
||||
sort: number | null
|
||||
}
|
||||
|
||||
type PackageTroubleshooting = {
|
||||
id: number
|
||||
title: string
|
||||
problem: string | null
|
||||
solution: string | null
|
||||
sort: number | null
|
||||
}
|
||||
|
||||
type InfrastructureContent = {
|
||||
id: number
|
||||
status: 'published' | 'draft'
|
||||
category: 'hosting' | 'security' | 'monitoring' | 'backup' | 'disaster_recovery'
|
||||
title: string
|
||||
slug: string | null
|
||||
summary: string | null
|
||||
content: string | null
|
||||
}
|
||||
|
||||
type Page = {
|
||||
id: number
|
||||
status: 'published' | 'draft'
|
||||
title: string
|
||||
slug: string
|
||||
content: string | null
|
||||
meta_title: string | null
|
||||
meta_description: string | null
|
||||
}
|
||||
|
||||
type NavigationItem = {
|
||||
id: number
|
||||
status: 'published' | 'draft'
|
||||
label: string
|
||||
type: 'page' | 'external' | 'custom'
|
||||
page_id: number | null
|
||||
external_url: string | null
|
||||
custom_route: string | null
|
||||
parent_id: number | null
|
||||
sort: number | null
|
||||
}
|
||||
|
||||
type SiteSettings = {
|
||||
id: number
|
||||
logo: string | null
|
||||
}
|
||||
|
||||
type Schema = {
|
||||
software_packages: SoftwarePackage[]
|
||||
package_screenshots: PackageScreenshot[]
|
||||
package_deployment_details: PackageDeploymentDetail[]
|
||||
package_troubleshooting: PackageTroubleshooting[]
|
||||
infrastructure_content: InfrastructureContent[]
|
||||
pages: Page[]
|
||||
navigation: NavigationItem[]
|
||||
site_settings: SiteSettings
|
||||
}
|
||||
|
||||
export function useDirectus() {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const client = createDirectus<Schema>(config.public.directusUrl).with(rest())
|
||||
|
||||
async function getPackages() {
|
||||
return client.request(
|
||||
readItems('software_packages', {
|
||||
filter: { status: { _eq: 'published' } },
|
||||
fields: ['*', 'screenshots.*', 'deployment_details.*', 'troubleshooting.*'],
|
||||
sort: ['sort', 'name'],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function getPackage(slug: string) {
|
||||
const items = await client.request(
|
||||
readItems('software_packages', {
|
||||
filter: {
|
||||
_and: [
|
||||
{ status: { _eq: 'published' } },
|
||||
{ slug: { _eq: slug } },
|
||||
]
|
||||
},
|
||||
fields: ['*', 'screenshots.*', 'deployment_details.*', 'troubleshooting.*'],
|
||||
limit: 1,
|
||||
})
|
||||
)
|
||||
return items[0] ?? null
|
||||
}
|
||||
|
||||
async function getInfrastructure() {
|
||||
return client.request(
|
||||
readItems('infrastructure_content', {
|
||||
filter: { status: { _eq: 'published' } },
|
||||
sort: ['category', 'title'],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function getInfrastructureItem(slug: string) {
|
||||
const items = await client.request(
|
||||
readItems('infrastructure_content', {
|
||||
filter: {
|
||||
_and: [
|
||||
{ status: { _eq: 'published' } },
|
||||
{ slug: { _eq: slug } },
|
||||
]
|
||||
},
|
||||
limit: 1,
|
||||
})
|
||||
)
|
||||
return items[0] ?? null
|
||||
}
|
||||
|
||||
async function getPages() {
|
||||
return client.request(
|
||||
readItems('pages', {
|
||||
filter: { status: { _eq: 'published' } },
|
||||
sort: ['sort', 'title'],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function getPage(slug: string) {
|
||||
const items = await client.request(
|
||||
readItems('pages', {
|
||||
filter: {
|
||||
_and: [
|
||||
{ status: { _eq: 'published' } },
|
||||
{ slug: { _eq: slug } },
|
||||
]
|
||||
},
|
||||
limit: 1,
|
||||
})
|
||||
)
|
||||
return items[0] ?? null
|
||||
}
|
||||
|
||||
async function getNavigation() {
|
||||
return client.request(
|
||||
readItems('navigation', {
|
||||
filter: { status: { _eq: 'published' } },
|
||||
sort: ['sort'],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function getSiteSettings() {
|
||||
return client.request(readSingleton('site_settings'))
|
||||
}
|
||||
|
||||
function getAssetUrl(fileId: string) {
|
||||
return `${config.public.directusUrl}/assets/${fileId}`
|
||||
}
|
||||
|
||||
return {
|
||||
client,
|
||||
getPackages,
|
||||
getPackage,
|
||||
getInfrastructure,
|
||||
getInfrastructureItem,
|
||||
getPages,
|
||||
getPage,
|
||||
getNavigation,
|
||||
getSiteSettings,
|
||||
getAssetUrl,
|
||||
}
|
||||
}
|
||||
4
k8s/00-namespace.yaml
Normal file
4
k8s/00-namespace.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: basicstack-web
|
||||
9
k8s/10-configmap.yaml
Normal file
9
k8s/10-configmap.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: basicstack-web-config
|
||||
namespace: basicstack-web
|
||||
data:
|
||||
DIRECTUS_URL: "https://directus.basicstack.de"
|
||||
SITE_URL: "https://basicstack.org"
|
||||
NODE_ENV: "production"
|
||||
64
k8s/20-deployment.yaml
Normal file
64
k8s/20-deployment.yaml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: basicstack-web
|
||||
namespace: basicstack-web
|
||||
labels:
|
||||
app: basicstack-web
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: basicstack-web
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: basicstack-web
|
||||
spec:
|
||||
containers:
|
||||
- name: basicstack-web
|
||||
image: basicstack-web:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: basicstack-web-config
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: basicstack-web
|
||||
namespace: basicstack-web
|
||||
spec:
|
||||
selector:
|
||||
app: basicstack-web
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
name: http
|
||||
50
k8s/30-ingress.yaml
Normal file
50
k8s/30-ingress.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: basicstack-web
|
||||
namespace: basicstack-web
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.middlewares: basicstack-web-redirect@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- basicstack.org
|
||||
- www.basicstack.org
|
||||
secretName: basicstack-org-tls
|
||||
rules:
|
||||
- host: basicstack.org
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: basicstack-web
|
||||
port:
|
||||
number: 80
|
||||
- host: www.basicstack.org
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: basicstack-web
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
# Redirect www to non-www
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: redirect
|
||||
namespace: basicstack-web
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: "^https://www\\.basicstack\\.org/(.*)"
|
||||
replacement: "https://basicstack.org/${1}"
|
||||
permanent: true
|
||||
77
k8s/README.md
Normal file
77
k8s/README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# BasicStack Web — Kubernetes Deployment
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker + buildx
|
||||
- kubectl with access to the basicstack cluster
|
||||
- Container registry (e.g., GitHub Container Registry, Docker Hub)
|
||||
|
||||
## Build & Push Image
|
||||
|
||||
```bash
|
||||
export REGISTRY=ghcr.io/basicstack # or your registry
|
||||
export TAG=$(git rev-parse --short HEAD)
|
||||
|
||||
# Build multi-arch or single-arch
|
||||
docker build -t $REGISTRY/basicstack-web:$TAG -f ../Dockerfile ..
|
||||
|
||||
# Push
|
||||
docker push $REGISTRY/basicstack-web:$TAG
|
||||
```
|
||||
|
||||
## Update Deployment Image
|
||||
|
||||
Edit `20-deployment.yaml` and set the image:
|
||||
|
||||
```yaml
|
||||
image: ghcr.io/basicstack/basicstack-web:abc1234
|
||||
```
|
||||
|
||||
Or use `kubectl set image`:
|
||||
|
||||
```bash
|
||||
kubectl set image deployment/basicstack-web basicstack-web=$REGISTRY/basicstack-web:$TAG \
|
||||
-n basicstack-web
|
||||
```
|
||||
|
||||
## Apply Manifests
|
||||
|
||||
```bash
|
||||
# Initial deploy
|
||||
kubectl apply -f k8s/00-namespace.yaml
|
||||
kubectl apply -f k8s/10-configmap.yaml
|
||||
kubectl apply -f k8s/20-deployment.yaml
|
||||
kubectl apply -f k8s/30-ingress.yaml
|
||||
|
||||
# Wait for rollout
|
||||
kubectl rollout status deployment/basicstack-web -n basicstack-web
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Check pods
|
||||
kubectl get pods -n basicstack-web
|
||||
|
||||
# Check ingress
|
||||
kubectl get ingress -n basicstack-web
|
||||
|
||||
# Test endpoint
|
||||
curl -I https://basicstack.org
|
||||
```
|
||||
|
||||
## TLS
|
||||
|
||||
cert-manager automatically provisions a TLS certificate for `basicstack.org` and `www.basicstack.org` using the `letsencrypt-prod` ClusterIssuer. The certificate is stored in the `basicstack-org-tls` secret.
|
||||
|
||||
DNS for `basicstack.org` must point to the cluster's ingress IP before cert-manager can complete the ACME challenge.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `DIRECTUS_URL` | `https://directus.basicstack.de` | Directus CMS endpoint |
|
||||
| `SITE_URL` | `https://basicstack.org` | Public site URL (for sitemap) |
|
||||
| `NODE_ENV` | `production` | Node environment |
|
||||
|
||||
Change these in `10-configmap.yaml`.
|
||||
200
layouts/default.vue
Normal file
200
layouts/default.vue
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
<template>
|
||||
<div class="layout">
|
||||
<header class="site-header">
|
||||
<div class="container header-inner">
|
||||
<NuxtLink to="/" class="site-logo">
|
||||
<img v-if="logoUrl" :src="logoUrl" alt="BasicStack" class="logo-image" />
|
||||
</NuxtLink>
|
||||
|
||||
<button class="nav-toggle" aria-label="Toggle menu" @click="menuOpen = !menuOpen">
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</button>
|
||||
|
||||
<nav class="site-nav" :class="{ open: menuOpen }">
|
||||
<template v-for="item in topLevelNav" :key="item.id">
|
||||
<NuxtLink
|
||||
v-if="resolveNavHref(item)"
|
||||
:to="resolveNavHref(item)!"
|
||||
class="nav-link"
|
||||
:target="item.type === 'external' ? '_blank' : undefined"
|
||||
@click="menuOpen = false"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-inner">
|
||||
<p class="footer-brand">BasicStack</p>
|
||||
<p class="footer-tagline">Production-ready open source infrastructure</p>
|
||||
<p class="footer-copy">© {{ new Date().getFullYear() }} BasicStack</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { getNavigation, getSiteSettings, getAssetUrl } = useDirectus()
|
||||
const menuOpen = ref(false)
|
||||
|
||||
const { data: navItems } = await useAsyncData('navigation', () => getNavigation())
|
||||
const { data: siteSettings } = await useAsyncData('site-settings', () => getSiteSettings())
|
||||
|
||||
const topLevelNav = computed(() =>
|
||||
(navItems.value ?? []).filter(item => !item.parent_id)
|
||||
)
|
||||
|
||||
const logoUrl = computed(() =>
|
||||
siteSettings.value?.logo ? getAssetUrl(siteSettings.value.logo) : null
|
||||
)
|
||||
|
||||
function resolveNavHref(item: { type: string; external_url: string | null; custom_route: string | null; page_id: number | null }) {
|
||||
if (item.type === 'external') return item.external_url
|
||||
if (item.type === 'custom') return item.custom_route
|
||||
if (item.type === 'page' && item.page_id) return `/${item.page_id}`
|
||||
return null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: var(--color-bg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
height: var(--header-height);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
gap: var(--space-xl);
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
text-decoration: none;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
height: 48px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
padding: var(--space-xs) 0;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.router-link-active {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.nav-toggle span {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: var(--color-text);
|
||||
border-radius: 2px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
background: var(--color-bg-secondary);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: var(--space-2xl) 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 var(--space-xs);
|
||||
}
|
||||
|
||||
.footer-tagline {
|
||||
color: var(--color-text-muted);
|
||||
margin: 0 0 var(--space-md);
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.footer-copy {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nav-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: var(--header-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--color-bg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-direction: column;
|
||||
padding: var(--space-lg);
|
||||
gap: var(--space-md);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.site-nav.open {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
45
nuxt.config.ts
Normal file
45
nuxt.config.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2024-11-01',
|
||||
devtools: { enabled: false },
|
||||
|
||||
modules: [
|
||||
'@nuxt/image',
|
||||
'@nuxtjs/sitemap',
|
||||
],
|
||||
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
directusUrl: process.env.DIRECTUS_URL || 'https://directus.basicstack.de',
|
||||
siteUrl: process.env.SITE_URL || 'https://basicstack.org',
|
||||
}
|
||||
},
|
||||
|
||||
app: {
|
||||
head: {
|
||||
htmlAttrs: { lang: 'en' },
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ name: 'format-detection', content: 'telephone=no' },
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
sitemap: {
|
||||
siteUrl: process.env.SITE_URL || 'https://basicstack.org',
|
||||
},
|
||||
|
||||
nitro: {
|
||||
preset: 'node-server',
|
||||
},
|
||||
|
||||
image: {
|
||||
domains: ['directus.basicstack.de'],
|
||||
},
|
||||
})
|
||||
11946
package-lock.json
generated
Normal file
11946
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
package.json
Normal file
24
package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "basicstack-frontend",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@directus/sdk": "^17.0.0",
|
||||
"@nuxt/image": "^1.10.0",
|
||||
"@nuxtjs/sitemap": "^8.2.2",
|
||||
"nuxt": "^3.16.2",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
52
pages/[slug].vue
Normal file
52
pages/[slug].vue
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<div v-if="page" class="content-page">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>{{ page.title }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container page-body">
|
||||
<div class="prose" v-html="page.content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="not-found container">
|
||||
<h1>Page not found</h1>
|
||||
<NuxtLink to="/" class="btn btn-primary">Back to Home</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const { getPage } = useDirectus()
|
||||
|
||||
const { data: page } = await useAsyncData(`page-${route.params.slug}`, () =>
|
||||
getPage(route.params.slug as string)
|
||||
)
|
||||
|
||||
if (page.value) {
|
||||
useSeoMeta({
|
||||
title: page.value.meta_title ?? `${page.value.title} – BasicStack`,
|
||||
description: page.value.meta_description ?? undefined,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: var(--color-bg-secondary);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--space-2xl) 0;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
padding: var(--space-3xl) var(--space-lg);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
246
pages/index.vue
Normal file
246
pages/index.vue
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
<template>
|
||||
<div>
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h1 class="hero-title">Production-ready open source infrastructure</h1>
|
||||
<p class="hero-sub">
|
||||
BasicStack is a curated collection of self-hosted open source software, deployed and maintained on Hetzner Cloud infrastructure in Germany.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<NuxtLink to="/packages" class="btn btn-primary">Browse Packages</NuxtLink>
|
||||
<NuxtLink to="/infrastructure" class="btn btn-outline">Our Infrastructure</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="packages-section" v-if="packages && packages.length > 0">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Software Packages</h2>
|
||||
<p class="section-sub">Open source tools we run and maintain</p>
|
||||
<div class="packages-grid">
|
||||
<NuxtLink
|
||||
v-for="pkg in packages.slice(0, 6)"
|
||||
:key="pkg.id"
|
||||
:to="`/packages/${pkg.slug}`"
|
||||
class="package-card card"
|
||||
>
|
||||
<img
|
||||
v-if="pkg.logo"
|
||||
:src="getAssetUrl(pkg.logo)"
|
||||
:alt="pkg.name + ' logo'"
|
||||
class="package-logo"
|
||||
/>
|
||||
<div v-else class="package-logo-placeholder">
|
||||
{{ pkg.name[0] }}
|
||||
</div>
|
||||
<h3 class="package-name">{{ pkg.name }}</h3>
|
||||
<p class="package-desc">{{ pkg.short_description }}</p>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="section-cta" v-if="packages.length > 6">
|
||||
<NuxtLink to="/packages" class="btn btn-outline">View all {{ packages.length }} packages</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="empty-state" v-else-if="packages !== null">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Software Packages</h2>
|
||||
<p class="section-sub">Content coming soon...</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="infra-section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Infrastructure</h2>
|
||||
<p class="section-sub">How we run BasicStack</p>
|
||||
<div class="infra-grid" v-if="infrastructure && infrastructure.length > 0">
|
||||
<NuxtLink
|
||||
v-for="item in infrastructure.slice(0, 4)"
|
||||
:key="item.id"
|
||||
:to="item.slug ? `/infrastructure/${item.slug}` : '/infrastructure'"
|
||||
class="infra-card card"
|
||||
>
|
||||
<span class="infra-category badge badge-primary">{{ formatCategory(item.category) }}</span>
|
||||
<h3 class="infra-title">{{ item.title }}</h3>
|
||||
<p class="infra-summary" v-if="item.summary">{{ item.summary }}</p>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="section-cta">
|
||||
<NuxtLink to="/infrastructure" class="btn btn-outline">Learn more about our infrastructure</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { getPackages, getInfrastructure, getAssetUrl } = useDirectus()
|
||||
|
||||
const [{ data: packages }, { data: infrastructure }] = await Promise.all([
|
||||
useAsyncData('home-packages', () => getPackages()),
|
||||
useAsyncData('home-infrastructure', () => getInfrastructure()),
|
||||
])
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
hosting: 'Hosting',
|
||||
security: 'Security',
|
||||
monitoring: 'Monitoring',
|
||||
backup: 'Backup',
|
||||
disaster_recovery: 'Disaster Recovery',
|
||||
}
|
||||
|
||||
function formatCategory(cat: string) {
|
||||
return categoryLabels[cat] ?? cat
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
title: 'BasicStack – Production-ready open source infrastructure',
|
||||
description: 'A curated collection of self-hosted open source software on Hetzner Cloud infrastructure in Germany.',
|
||||
ogTitle: 'BasicStack',
|
||||
ogDescription: 'Production-ready open source infrastructure',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hero {
|
||||
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg) 100%);
|
||||
padding: var(--space-3xl) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
margin-bottom: var(--space-lg);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 600px;
|
||||
margin: 0 auto var(--space-xl);
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.packages-section,
|
||||
.infra-section {
|
||||
padding: var(--space-3xl) 0;
|
||||
}
|
||||
|
||||
.infra-section {
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.875rem;
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.section-sub {
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.package-card {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.package-card:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.package-logo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
object-fit: contain;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.package-logo-placeholder {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.package-desc {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9375rem;
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-cta {
|
||||
text-align: center;
|
||||
margin-top: var(--space-2xl);
|
||||
}
|
||||
|
||||
.infra-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.infra-card {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.infra-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.infra-category {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.infra-title {
|
||||
font-size: 1.125rem;
|
||||
margin: var(--space-sm) 0;
|
||||
}
|
||||
|
||||
.infra-summary {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9375rem;
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
86
pages/infrastructure/[slug].vue
Normal file
86
pages/infrastructure/[slug].vue
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<div v-if="item" class="infra-detail">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<NuxtLink to="/infrastructure" class="breadcrumb">← Infrastructure</NuxtLink>
|
||||
<span class="badge badge-primary">{{ categoryLabels[item.category] ?? item.category }}</span>
|
||||
<h1>{{ item.title }}</h1>
|
||||
<p v-if="item.summary" class="summary">{{ item.summary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container page-body">
|
||||
<div class="prose" v-html="item.content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="not-found container">
|
||||
<h1>Not found</h1>
|
||||
<NuxtLink to="/infrastructure" class="btn btn-primary">Back to Infrastructure</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const { getInfrastructureItem } = useDirectus()
|
||||
|
||||
const { data: item } = await useAsyncData(`infra-${route.params.slug}`, () =>
|
||||
getInfrastructureItem(route.params.slug as string)
|
||||
)
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
hosting: 'Hosting',
|
||||
security: 'Security',
|
||||
monitoring: 'Monitoring',
|
||||
backup: 'Backup',
|
||||
disaster_recovery: 'Disaster Recovery',
|
||||
}
|
||||
|
||||
if (item.value) {
|
||||
useSeoMeta({
|
||||
title: `${item.value.title} – BasicStack`,
|
||||
description: item.value.summary ?? undefined,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: var(--color-bg-secondary);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--space-2xl) 0;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
display: block;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.page-header .badge {
|
||||
display: inline-block;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.summary {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.0625rem;
|
||||
margin: 0;
|
||||
max-width: 65ch;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
padding: var(--space-3xl) var(--space-lg);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
154
pages/infrastructure/index.vue
Normal file
154
pages/infrastructure/index.vue
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div class="infra-page">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>Infrastructure</h1>
|
||||
<p>How BasicStack is hosted, secured, monitored, and maintained</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container page-body">
|
||||
<div v-if="grouped && Object.keys(grouped).length > 0">
|
||||
<section
|
||||
v-for="(items, category) in grouped"
|
||||
:key="category"
|
||||
class="category-section"
|
||||
>
|
||||
<h2 class="category-title">{{ categoryLabels[category] ?? category }}</h2>
|
||||
<div class="infra-grid">
|
||||
<NuxtLink
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:to="item.slug ? `/infrastructure/${item.slug}` : '#'"
|
||||
class="infra-card card"
|
||||
>
|
||||
<h3>{{ item.title }}</h3>
|
||||
<p v-if="item.summary" class="item-summary">{{ item.summary }}</p>
|
||||
<span class="read-more">Read more →</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty">
|
||||
<p>Infrastructure documentation coming soon.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { getInfrastructure } = useDirectus()
|
||||
const { data: infrastructure } = await useAsyncData('infrastructure', () => getInfrastructure())
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
hosting: 'Hosting',
|
||||
security: 'Security & Encryption',
|
||||
monitoring: 'Monitoring',
|
||||
backup: 'Backup',
|
||||
disaster_recovery: 'Disaster Recovery',
|
||||
}
|
||||
|
||||
const categoryOrder = ['hosting', 'security', 'monitoring', 'backup', 'disaster_recovery']
|
||||
|
||||
const grouped = computed(() => {
|
||||
if (!infrastructure.value) return {}
|
||||
const groups: Record<string, typeof infrastructure.value> = {}
|
||||
for (const item of infrastructure.value) {
|
||||
if (!groups[item.category]) groups[item.category] = []
|
||||
groups[item.category].push(item)
|
||||
}
|
||||
const ordered: Record<string, typeof infrastructure.value> = {}
|
||||
for (const cat of categoryOrder) {
|
||||
if (groups[cat]) ordered[cat] = groups[cat]
|
||||
}
|
||||
for (const cat of Object.keys(groups)) {
|
||||
if (!ordered[cat]) ordered[cat] = groups[cat]
|
||||
}
|
||||
return ordered
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: 'Infrastructure – BasicStack',
|
||||
description: 'Learn about how BasicStack infrastructure is hosted, secured, and maintained on Hetzner Cloud.',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: var(--color-bg-secondary);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--space-2xl) 0;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: var(--color-text-muted);
|
||||
margin: var(--space-sm) 0 0;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
}
|
||||
|
||||
.category-section {
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: var(--space-lg);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.infra-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.infra-card {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: box-shadow 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.infra-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.infra-card h3 {
|
||||
font-size: 1.0625rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.item-summary {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9375rem;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.read-more {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-primary);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: var(--space-3xl) 0;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
264
pages/packages/[slug].vue
Normal file
264
pages/packages/[slug].vue
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
<template>
|
||||
<div v-if="pkg" class="package-detail">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<NuxtLink to="/packages" class="breadcrumb">← All Packages</NuxtLink>
|
||||
<div class="package-hero">
|
||||
<img
|
||||
v-if="pkg.logo"
|
||||
:src="getAssetUrl(pkg.logo)"
|
||||
:alt="pkg.name + ' logo'"
|
||||
class="package-logo"
|
||||
/>
|
||||
<div v-else class="package-logo-placeholder">{{ pkg.name[0] }}</div>
|
||||
<div>
|
||||
<h1>{{ pkg.name }}</h1>
|
||||
<p class="short-desc">{{ pkg.short_description }}</p>
|
||||
<div class="package-actions">
|
||||
<a v-if="pkg.website_url" :href="pkg.website_url" target="_blank" rel="noopener" class="btn btn-primary">
|
||||
Website
|
||||
</a>
|
||||
<a v-if="pkg.documentation_url" :href="pkg.documentation_url" target="_blank" rel="noopener" class="btn btn-outline">
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container page-body">
|
||||
<div class="content-grid">
|
||||
<div class="main-content">
|
||||
<!-- Full description -->
|
||||
<section v-if="pkg.full_description" class="content-section">
|
||||
<h2>About {{ pkg.name }}</h2>
|
||||
<div class="prose" v-html="pkg.full_description" />
|
||||
</section>
|
||||
|
||||
<!-- Screenshots -->
|
||||
<section v-if="pkg.screenshots && pkg.screenshots.length > 0" class="content-section">
|
||||
<h2>Screenshots</h2>
|
||||
<div class="screenshots-grid">
|
||||
<figure
|
||||
v-for="screenshot in sortedScreenshots"
|
||||
:key="screenshot.id"
|
||||
class="screenshot"
|
||||
>
|
||||
<img
|
||||
:src="getAssetUrl(screenshot.image)"
|
||||
:alt="screenshot.title ?? pkg.name + ' screenshot'"
|
||||
class="screenshot-img"
|
||||
/>
|
||||
<figcaption v-if="screenshot.title || screenshot.caption" class="screenshot-caption">
|
||||
<strong v-if="screenshot.title">{{ screenshot.title }}</strong>
|
||||
<span v-if="screenshot.caption"> – {{ screenshot.caption }}</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Deployment details -->
|
||||
<section v-if="pkg.deployment_details && pkg.deployment_details.length > 0" class="content-section">
|
||||
<h2>Deployment</h2>
|
||||
<div
|
||||
v-for="detail in sortedDeploymentDetails"
|
||||
:key="detail.id"
|
||||
class="detail-block"
|
||||
>
|
||||
<h3>{{ detail.title }}</h3>
|
||||
<div class="prose" v-html="detail.content" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Troubleshooting -->
|
||||
<section v-if="pkg.troubleshooting && pkg.troubleshooting.length > 0" class="content-section">
|
||||
<h2>Troubleshooting</h2>
|
||||
<div
|
||||
v-for="item in sortedTroubleshooting"
|
||||
:key="item.id"
|
||||
class="trouble-block card"
|
||||
>
|
||||
<h3>{{ item.title }}</h3>
|
||||
<div v-if="item.problem">
|
||||
<h4>Problem</h4>
|
||||
<div class="prose" v-html="item.problem" />
|
||||
</div>
|
||||
<div v-if="item.solution">
|
||||
<h4>Solution</h4>
|
||||
<div class="prose" v-html="item.solution" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="not-found container">
|
||||
<h1>Package not found</h1>
|
||||
<NuxtLink to="/packages" class="btn btn-primary">Back to Packages</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const { getPackage, getAssetUrl } = useDirectus()
|
||||
|
||||
const { data: pkg } = await useAsyncData(`package-${route.params.slug}`, () =>
|
||||
getPackage(route.params.slug as string)
|
||||
)
|
||||
|
||||
const sortedScreenshots = computed(() =>
|
||||
[...(pkg.value?.screenshots ?? [])].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0))
|
||||
)
|
||||
|
||||
const sortedDeploymentDetails = computed(() =>
|
||||
[...(pkg.value?.deployment_details ?? [])].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0))
|
||||
)
|
||||
|
||||
const sortedTroubleshooting = computed(() =>
|
||||
[...(pkg.value?.troubleshooting ?? [])].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0))
|
||||
)
|
||||
|
||||
if (pkg.value) {
|
||||
useSeoMeta({
|
||||
title: `${pkg.value.name} – BasicStack`,
|
||||
description: pkg.value.short_description,
|
||||
ogTitle: pkg.value.name,
|
||||
ogDescription: pkg.value.short_description,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: var(--color-bg-secondary);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--space-2xl) 0;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
display: block;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.package-hero {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-xl);
|
||||
}
|
||||
|
||||
.package-logo {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.package-logo-placeholder {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.package-hero h1 {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.short-desc {
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.package-actions {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
}
|
||||
|
||||
.content-section {
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.content-section h2 {
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: var(--space-md);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.screenshots-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.screenshot-img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.screenshot-caption {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
.detail-block {
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.detail-block h3 {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.trouble-block {
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.trouble-block h3 {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.trouble-block h4 {
|
||||
font-size: 0.9375rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.not-found {
|
||||
padding: var(--space-3xl) var(--space-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.package-hero {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
167
pages/packages/index.vue
Normal file
167
pages/packages/index.vue
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<template>
|
||||
<div class="packages-page">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>Software Packages</h1>
|
||||
<p>Open source software we deploy and maintain on BasicStack infrastructure</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container page-body">
|
||||
<div v-if="packages && packages.length > 0" class="packages-grid">
|
||||
<NuxtLink
|
||||
v-for="pkg in packages"
|
||||
:key="pkg.id"
|
||||
:to="`/packages/${pkg.slug}`"
|
||||
class="package-card card"
|
||||
>
|
||||
<div class="package-header">
|
||||
<img
|
||||
v-if="pkg.logo"
|
||||
:src="getAssetUrl(pkg.logo)"
|
||||
:alt="pkg.name + ' logo'"
|
||||
class="package-logo"
|
||||
/>
|
||||
<div v-else class="package-logo-placeholder">{{ pkg.name[0] }}</div>
|
||||
<div class="package-info">
|
||||
<h2 class="package-name">{{ pkg.name }}</h2>
|
||||
<div class="package-meta">
|
||||
<span v-if="pkg.screenshots && pkg.screenshots.length > 0" class="badge badge-primary">
|
||||
{{ pkg.screenshots.length }} screenshot{{ pkg.screenshots.length !== 1 ? 's' : '' }}
|
||||
</span>
|
||||
<span v-if="pkg.deployment_details && pkg.deployment_details.length > 0" class="badge badge-primary">
|
||||
Deploy docs
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="package-desc">{{ pkg.short_description }}</p>
|
||||
<div class="package-links" v-if="pkg.website_url || pkg.documentation_url">
|
||||
<span v-if="pkg.website_url" class="link-badge">Website</span>
|
||||
<span v-if="pkg.documentation_url" class="link-badge">Docs</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty">
|
||||
<p>No packages found. Content will be added soon.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { getPackages, getAssetUrl } = useDirectus()
|
||||
const { data: packages } = await useAsyncData('packages', () => getPackages())
|
||||
|
||||
useSeoMeta({
|
||||
title: 'Packages – BasicStack',
|
||||
description: 'Browse all open source software packages deployed on BasicStack infrastructure.',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: var(--color-bg-secondary);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--space-2xl) 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.package-card {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.package-card:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.package-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.package-logo {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.package-logo-placeholder {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-size: 1.125rem;
|
||||
margin: 0 0 var(--space-xs);
|
||||
}
|
||||
|
||||
.package-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.package-desc {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9375rem;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.package-links {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.link-badge {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: var(--space-3xl) 0;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
185
public/basic-stack-icon-dark.svg
Normal file
185
public/basic-stack-icon-dark.svg
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="-47 -23 498.30701 498.772"
|
||||
id="svg61"
|
||||
sodipodi:docname="Basic Stack Logo only - dark.svg"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
width="498.30701"
|
||||
height="498.772"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs61" />
|
||||
<sodipodi:namedview
|
||||
id="namedview61"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="2.0513991"
|
||||
inkscape:cx="572.04861"
|
||||
inkscape:cy="246.90466"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2054"
|
||||
inkscape:window-x="3829"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
shape-rendering="crispEdges" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="Hintergund"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<rect
|
||||
style="fill:#000000;stroke-width:0"
|
||||
id="rect3"
|
||||
width="501.1214"
|
||||
height="499.659"
|
||||
x="64.556976"
|
||||
y="356.6033"
|
||||
rx="0.57999998"
|
||||
inkscape:label="Background" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 453.18,666.33 40.04,22.52 a 1.22,1.22 0 0 1 0.62,1.06 v 45.05 a 1.86,1.85 73.3 0 1 -0.83,1.55 q -2.39,1.59 -5.7,3.54 c -25.26,14.93 -54.69,33 -81.01,48.61 q -46.69,27.68 -77.94,46.45 c -2.29,1.37 -2.61,0.7 -4.89,-0.65 q -82.55,-48.8 -163.06,-97.78 a 1.16,1.15 16 0 1 -0.55,-0.98 v -45.54 a 1.56,1.55 -14.1 0 1 0.82,-1.37 l 37.76,-20.66 q 18.29,10.89 36.59,22.02 0.64,0.39 0.78,0.94 L 201.5,710.7 a 0.29,0.29 0 0 0 0,0.51 l 123.53,71.32 a 2.07,2.06 -44.6 0 0 2.05,0.01 l 124.57,-70.52 a 0.54,0.54 0 0 0 0.01,-0.94 l -36.43,-21.43 q 0.19,0.84 0.7,-0.31 a 1.75,1.66 -5.1 0 1 0.66,-0.75 z"
|
||||
id="path60"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
fill="#bcbdc2"
|
||||
d="m 495.32494,592.77371 c -7.49851,7.40425 -17.66591,10.99063 -26.26803,16.84053 -9.59935,5.70086 -19.32999,11.23325 -28.37099,17.80842 -4.56185,1.79769 -9.37199,9.09319 -14.47299,4.81572 -11.60618,-7.37377 -24.69897,-12.2349 -35.89709,-20.26534 -3.76541,-2.66338 3.91027,-4.08058 5.57849,-6.10338 11.49617,-7.18147 22.99234,-14.36296 34.48851,-21.54443 5.23945,1.67707 8.43033,-4.09599 12.92216,-5.48176 5.23898,-0.57634 7.80029,-7.86685 13.56199,-6.5032 6.57122,2.40343 12.08622,6.96457 18.5991,9.52903 6.70961,3.48001 12.95835,7.7804 19.85885,10.90441 z"
|
||||
id="path51"
|
||||
style="stroke-width:1.07587" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 432.60531,585.12531 -40,24.15 -65.74,39.49 a 2.18,2.17 43.7 0 1 -2.17,0.05 l -122.99,-67.31 a 1.09,1.09 0 0 1 -0.57,-0.96 v -27.58 a 1.04,1.04 0 0 0 -0.57,-0.93 l -40.28,-20.96 a 1.78,1.78 0 0 1 -0.96,-1.58 v -8.23 a 1.48,1.5 75.2 0 1 0.77,-1.3 l 164.65,-93.22 a 0.94,0.93 42.8 0 1 0.88,-0.03 l 167.79,82.97 a 0.84,0.83 13.1 0 1 0.47,0.75 v 36.22 a 2.27,2.26 74.5 0 1 -1.09,1.94 z m -29.45,-62.08 a 0.34,0.34 0 0 0 -0.02,-0.6 l -79.92,-41.57 a 0.34,0.34 0 0 0 -0.32,0 l -77.46,43.85 a 0.34,0.34 0 0 0 0.02,0.6 l 79.91,41.57 a 0.34,0.34 0 0 0 0.32,0 z"
|
||||
id="path42" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Vordergurnd"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<path
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke-width:1.02152"
|
||||
d="m 161.49195,688.72314 40.76574,22.37691 35.28045,-20.63649 -38.64642,-23.1228 -38.14777,21.38238"
|
||||
id="path64"
|
||||
inkscape:label="path64" />
|
||||
<path
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke-width:0"
|
||||
d="m 432.14408,699.60808 c -11.12655,-6.5661 -21.57804,-12.7166 -23.22552,-13.66776 -1.64749,-0.95117 -2.90882,-1.80349 -2.80297,-1.89405 0.10586,-0.0906 8.61124,-5.17621 18.90085,-11.30147 l 23.09563,-9.55255 0.91237,0.58928 c 16.24871,10.49456 20.85593,12.56705 31.25752,18.38066 2.88218,1.61089 13.3671,7.13269 13.3671,7.13269 -39.40304,21.19899 -2.49537,1.57031 -40.90928,21.94248 -0.35534,0.18845 -3.59935,-1.59925 -20.5957,-11.62928 z"
|
||||
id="path19"
|
||||
sodipodi:nodetypes="sssscsssss" />
|
||||
<path
|
||||
style="fill:#a2a2a2;fill-opacity:1;stroke-width:0"
|
||||
d="m 325.90902,694.04316 v 49.29137 l 168.9005,-101.68499 0.51704,-48.77433 z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#bcbdc2;fill-opacity:1;stroke-width:0"
|
||||
d="m 326.08137,694.04316 0.17235,49.29137 -166.14294,-98.06569 -0.51704,-47.05086 z"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="187.16635"
|
||||
y="381.46991"
|
||||
transform="rotate(-0.4)"
|
||||
width="9.6999998"
|
||||
height="10.08"
|
||||
rx="0.64999998"
|
||||
id="rect40" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="121.47729"
|
||||
y="397.59161"
|
||||
transform="rotate(-0.1)"
|
||||
width="10.28"
|
||||
height="10.28"
|
||||
rx="0.63999999"
|
||||
id="rect41" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="159.48051"
|
||||
y="415.51767"
|
||||
transform="rotate(-0.3)"
|
||||
width="11.76"
|
||||
height="11.76"
|
||||
rx="0.51999998"
|
||||
id="rect42" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="193.1608"
|
||||
y="443.14157"
|
||||
transform="rotate(0.2)"
|
||||
width="9.3400002"
|
||||
height="9.3400002"
|
||||
rx="0.56999999"
|
||||
id="rect43" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="-9.8500004"
|
||||
y="-9.8500004"
|
||||
transform="rotate(-0.2,131727.77,-38694.058)"
|
||||
width="19.700001"
|
||||
height="19.700001"
|
||||
rx="0.60000002"
|
||||
id="rect44" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="162.21117"
|
||||
y="476.91843"
|
||||
transform="rotate(0.1)"
|
||||
width="18.540001"
|
||||
height="18.540001"
|
||||
rx="0.63999999"
|
||||
id="rect45" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="113.01939"
|
||||
y="495.00061"
|
||||
width="23.200001"
|
||||
height="23.200001"
|
||||
rx="0.57999998"
|
||||
id="rect46" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="127.90707"
|
||||
y="544.4801"
|
||||
transform="rotate(-0.3)"
|
||||
width="20.84"
|
||||
height="20.84"
|
||||
rx="0.56999999"
|
||||
id="rect49" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="163.39401"
|
||||
y="569.77728"
|
||||
transform="rotate(-0.3)"
|
||||
width="19.459999"
|
||||
height="19.459999"
|
||||
rx="0.47"
|
||||
id="rect51" />
|
||||
<path
|
||||
style="fill:#ff6533;fill-opacity:1;stroke-width:0.993896"
|
||||
d="m 159.92165,521.52336 -0.681,9.99616 41.20048,20.68169 0.34049,29.29907 124.28244,67.56019 1.362,-36.19296 -167.86641,-92.37823 z"
|
||||
id="path63" />
|
||||
<path
|
||||
style="fill:#e84d00;fill-opacity:1;stroke-width:0.987968"
|
||||
d="m 326.91058,612.82073 -1.24207,36.51627 168.29577,-99.65189 -0.0835,-37.88137 z"
|
||||
id="path62" />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="m 403.5,523.39 -77.47,43.85 a 0.34,0.34 0 0 1 -0.32,0 L 245.8,525.67 a 0.34,0.34 0 0 1 -0.02,-0.6 l 77.46,-43.85 a 0.34,0.34 0 0 1 0.32,0 l 79.92,41.57 a 0.34,0.34 0 0 1 0.02,0.6 z"
|
||||
id="path45"
|
||||
style="fill:#000000" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
284
public/basic-stack-logo-dark.svg
Normal file
284
public/basic-stack-logo-dark.svg
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="-19 -26 1080.601 501.772"
|
||||
id="svg61"
|
||||
sodipodi:docname="Basic Stack Logo - with text - dark mode.svg"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
width="1080.601"
|
||||
height="501.772"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs61" />
|
||||
<sodipodi:namedview
|
||||
id="namedview61"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="2.0513991"
|
||||
inkscape:cx="572.04861"
|
||||
inkscape:cy="246.90466"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2054"
|
||||
inkscape:window-x="3829"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="Hintergund"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<rect
|
||||
style="fill:#000000;stroke-width:0"
|
||||
id="rect1"
|
||||
width="1086.5754"
|
||||
height="509.8959"
|
||||
x="92.556976"
|
||||
y="351.16592"
|
||||
rx="0.57999998"
|
||||
inkscape:label="Backgorund" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 432.60531,585.12531 -40,24.15 -65.74,39.49 a 2.18,2.17 43.7 0 1 -2.17,0.05 l -122.99,-67.31 a 1.09,1.09 0 0 1 -0.57,-0.96 v -27.58 a 1.04,1.04 0 0 0 -0.57,-0.93 l -40.28,-20.96 a 1.78,1.78 0 0 1 -0.96,-1.58 v -8.23 a 1.48,1.5 75.2 0 1 0.77,-1.3 l 164.65,-93.22 a 0.94,0.93 42.8 0 1 0.88,-0.03 l 167.79,82.97 a 0.84,0.83 13.1 0 1 0.47,0.75 v 36.22 a 2.27,2.26 74.5 0 1 -1.09,1.94 z m -29.45,-62.08 a 0.34,0.34 0 0 0 -0.02,-0.6 l -79.92,-41.57 a 0.34,0.34 0 0 0 -0.32,0 l -77.46,43.85 a 0.34,0.34 0 0 0 0.02,0.6 l 79.91,41.57 a 0.34,0.34 0 0 0 0.32,0 z"
|
||||
id="path42" />
|
||||
<path
|
||||
fill="#bcbdc2"
|
||||
d="m 495.32494,592.77371 c -7.49851,7.40425 -17.66591,10.99063 -26.26803,16.84053 -9.59935,5.70086 -19.32999,11.23325 -28.37099,17.80842 -4.56185,1.79769 -9.37199,9.09319 -14.47299,4.81572 -11.60618,-7.37377 -24.69897,-12.2349 -35.89709,-20.26534 -3.76541,-2.66338 3.91027,-4.08058 5.57849,-6.10338 11.49617,-7.18147 22.99234,-14.36296 34.48851,-21.54443 5.23945,1.67707 8.43033,-4.09599 12.92216,-5.48176 5.23898,-0.57634 7.80029,-7.86685 13.56199,-6.5032 6.57122,2.40343 12.08622,6.96457 18.5991,9.52903 6.70961,3.48001 12.95835,7.7804 19.85885,10.90441 z"
|
||||
id="path51"
|
||||
style="stroke-width:1.07587" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 453.18,666.33 40.04,22.52 a 1.22,1.22 0 0 1 0.62,1.06 v 45.05 a 1.86,1.85 73.3 0 1 -0.83,1.55 q -2.39,1.59 -5.7,3.54 c -25.26,14.93 -54.69,33 -81.01,48.61 q -46.69,27.68 -77.94,46.45 c -2.29,1.37 -2.61,0.7 -4.89,-0.65 q -82.55,-48.8 -163.06,-97.78 a 1.16,1.15 16 0 1 -0.55,-0.98 v -45.54 a 1.56,1.55 -14.1 0 1 0.82,-1.37 l 37.76,-20.66 q 18.29,10.89 36.59,22.02 0.64,0.39 0.78,0.94 L 201.5,710.7 a 0.29,0.29 0 0 0 0,0.51 l 123.53,71.32 a 2.07,2.06 -44.6 0 0 2.05,0.01 l 124.57,-70.52 a 0.54,0.54 0 0 0 0.01,-0.94 l -36.43,-21.43 q 0.19,0.84 0.7,-0.31 a 1.75,1.66 -5.1 0 1 0.66,-0.75 z"
|
||||
id="path60"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Vordergurnd"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<path
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke-width:1.02152"
|
||||
d="m 161.49195,688.72314 40.76574,22.37691 35.28045,-20.63649 -38.64642,-23.1228 -38.14777,21.38238"
|
||||
id="path64"
|
||||
inkscape:label="path64" />
|
||||
<path
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke-width:0"
|
||||
d="m 432.14408,699.60808 c -11.12655,-6.5661 -21.57804,-12.7166 -23.22552,-13.66776 -1.64749,-0.95117 -2.90882,-1.80349 -2.80297,-1.89405 0.10586,-0.0906 8.61124,-5.17621 18.90085,-11.30147 l 23.09563,-9.55255 0.91237,0.58928 c 16.24871,10.49456 20.85593,12.56705 31.25752,18.38066 2.88218,1.61089 13.3671,7.13269 13.3671,7.13269 -39.40304,21.19899 -2.49537,1.57031 -40.90928,21.94248 -0.35534,0.18845 -3.59935,-1.59925 -20.5957,-11.62928 z"
|
||||
id="path19"
|
||||
sodipodi:nodetypes="sssscsssss" />
|
||||
<path
|
||||
style="fill:#a2a2a2;fill-opacity:1;stroke-width:0"
|
||||
d="m 325.90902,694.04316 v 49.29137 l 168.9005,-101.68499 0.51704,-48.77433 z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#bcbdc2;fill-opacity:1;stroke-width:0"
|
||||
d="m 326.08137,694.04316 0.17235,49.29137 -166.14294,-98.06569 -0.51704,-47.05086 z"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="187.16635"
|
||||
y="381.46991"
|
||||
transform="rotate(-0.4)"
|
||||
width="9.6999998"
|
||||
height="10.08"
|
||||
rx="0.64999998"
|
||||
id="rect40" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="121.47729"
|
||||
y="397.59161"
|
||||
transform="rotate(-0.1)"
|
||||
width="10.28"
|
||||
height="10.28"
|
||||
rx="0.63999999"
|
||||
id="rect41" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="159.48051"
|
||||
y="415.51767"
|
||||
transform="rotate(-0.3)"
|
||||
width="11.76"
|
||||
height="11.76"
|
||||
rx="0.51999998"
|
||||
id="rect42" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="193.1608"
|
||||
y="443.14157"
|
||||
transform="rotate(0.2)"
|
||||
width="9.3400002"
|
||||
height="9.3400002"
|
||||
rx="0.56999999"
|
||||
id="rect43" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="-9.8500004"
|
||||
y="-9.8500004"
|
||||
transform="rotate(-0.2,131727.77,-38694.058)"
|
||||
width="19.700001"
|
||||
height="19.700001"
|
||||
rx="0.60000002"
|
||||
id="rect44" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="162.21117"
|
||||
y="476.91843"
|
||||
transform="rotate(0.1)"
|
||||
width="18.540001"
|
||||
height="18.540001"
|
||||
rx="0.63999999"
|
||||
id="rect45" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="113.01939"
|
||||
y="495.00061"
|
||||
width="23.200001"
|
||||
height="23.200001"
|
||||
rx="0.57999998"
|
||||
id="rect46" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="127.90707"
|
||||
y="544.4801"
|
||||
transform="rotate(-0.3)"
|
||||
width="20.84"
|
||||
height="20.84"
|
||||
rx="0.56999999"
|
||||
id="rect49" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="163.39401"
|
||||
y="569.77728"
|
||||
transform="rotate(-0.3)"
|
||||
width="19.459999"
|
||||
height="19.459999"
|
||||
rx="0.47"
|
||||
id="rect51" />
|
||||
<path
|
||||
style="fill:#ff6533;fill-opacity:1;stroke-width:0.993896"
|
||||
d="m 159.92165,521.52336 -0.681,9.99616 41.20048,20.68169 0.34049,29.29907 124.28244,67.56019 1.362,-36.19296 -167.86641,-92.37823 z"
|
||||
id="path63" />
|
||||
<path
|
||||
style="fill:#e84d00;fill-opacity:1;stroke-width:0.987968"
|
||||
d="m 326.91058,612.82073 -1.24207,36.51627 168.29577,-99.65189 -0.0835,-37.88137 z"
|
||||
id="path62" />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="m 403.5,523.39 -77.47,43.85 a 0.34,0.34 0 0 1 -0.32,0 L 245.8,525.67 a 0.34,0.34 0 0 1 -0.02,-0.6 l 77.46,-43.85 a 0.34,0.34 0 0 1 0.32,0 l 79.92,41.57 a 0.34,0.34 0 0 1 0.02,0.6 z"
|
||||
id="path45"
|
||||
style="fill:#000000" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Schrift"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 1147.53,741.13 a 0.35,0.35 0 0 1 -0.32,0.21 h -13.06 a 1.91,1.88 -21.2 0 1 -1.39,-0.6 l -29.51,-31.09 a 2.51,2.49 67.9 0 0 -1.8,-0.77 h -15.54 a 0.69,0.68 0 0 0 -0.69,0.68 v 31.08 a 0.68,0.68 0 0 1 -0.68,0.68 h -9.81 a 0.9,0.89 0 0 1 -0.9,-0.89 v -74.09 a 0.61,0.61 0 0 1 0.61,-0.61 h 10.13 a 0.62,0.61 0 0 1 0.62,0.61 v 30.47 a 0.71,0.7 90 0 0 0.7,0.71 h 15.07 a 2.47,2.46 21.5 0 0 1.8,-0.78 l 28.94,-30.49 a 1.9,1.88 -68.1 0 1 1.37,-0.59 h 12.91 a 0.36,0.36 0 0 1 0.26,0.6 l -32.75,35.93 a 0.95,0.95 0 0 0 -0.02,1.26 q 6.06,7.06 12.89,14.19 9.88,10.3 20.92,22.58 0.44,0.48 0.25,0.91 z"
|
||||
id="path59"
|
||||
inkscape:label="STACK-K" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 947.12,703.46 q 0,-18.5 0.01,-20.09 0.03,-7.17 1.22,-9.62 2.25,-4.65 6.84,-6.9 2.63,-1.3 10.47,-1.27 31.13,0.08 55.9,0.04 a 0.56,0.55 -90 0 1 0.55,0.56 v 10.27 a 0.55,0.55 0 0 1 -0.55,0.56 L 960.25,677 a 1.83,1.82 -90 0 0 -1.82,1.82 q -0.01,12.62 -0.02,24.65 0,12.02 0.01,24.64 a 1.83,1.82 90 0 0 1.82,1.83 l 61.31,-0.01 a 0.55,0.55 0 0 1 0.56,0.56 v 10.27 a 0.56,0.55 90 0 1 -0.55,0.56 q -24.77,-0.04 -55.91,0.04 -7.83,0.02 -10.47,-1.28 -4.59,-2.25 -6.83,-6.9 -1.19,-2.45 -1.22,-9.62 -0.01,-1.59 -0.01,-20.1 z"
|
||||
id="path58"
|
||||
inkscape:label="STACK-C" />
|
||||
<rect
|
||||
fill="#ffffff"
|
||||
x="-26.139999"
|
||||
y="-13.13"
|
||||
transform="rotate(-0.1,395861.22,-490983.29)"
|
||||
width="52.279999"
|
||||
height="26.26"
|
||||
rx="1.29"
|
||||
id="rect60"
|
||||
inkscape:label="STACK-A-V"
|
||||
style="fill:none" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 883.08,714.55 h -50.89 a 0.69,0.68 0 0 0 -0.69,0.68 v 25.4 a 0.68,0.68 0 0 1 -0.68,0.68 h -10.27 a 0.47,0.47 0 0 1 -0.47,-0.46 q -0.15,-28.18 -0.08,-57.05 0.03,-7.73 1.47,-10.39 c 3.49,-6.45 8.68,-8.08 16.5,-7.86 q 4.64,0.13 37.53,0.02 c 9.35,-0.03 14.3,0.57 18.21,8.2 q 1.2,2.34 1.22,9.09 0.12,34.74 -0.03,57.92 a 0.55,0.55 0 0 1 -0.55,0.55 h -10.08 a 0.59,0.59 0 0 1 -0.59,-0.59 v -25.59 a 0.6,0.6 0 0 0 -0.6,-0.6 z m 0.5693,-36.2756 a 1.29,1.29 0 0 0 -1.2923,-1.2878 l -49.6999,0.0868 a 1.29,1.29 0 0 0 -1.2877,1.2922 l 0.0413,23.68 a 1.29,1.29 0 0 0 1.2923,1.2878 l 49.6999,-0.0868 a 1.29,1.29 0 0 0 1.2877,-1.2922 z"
|
||||
id="path57"
|
||||
inkscape:label="STACK-A" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 732.79,665.69 q 11.42,0 36.97,-0.06 a 0.69,0.69 0 0 1 0.69,0.69 v 10.03 a 0.66,0.66 0 0 1 -0.66,0.66 h -30.42 a 0.75,0.75 0 0 0 -0.75,0.75 l -0.15,62.99 a 0.54,0.54 0 0 1 -0.5,0.53 q -0.17,0.01 -5.21,0.01 -5.04,0 -5.22,-0.01 a 0.54,0.54 0 0 1 -0.49,-0.53 l -0.1,-62.99 A 0.75,0.75 0 0 0 726.2,677 l -30.42,-0.02 a 0.66,0.66 0 0 1 -0.66,-0.66 l 0.01,-10.03 a 0.69,0.69 0 0 1 0.69,-0.69 q 25.55,0.08 36.97,0.09 z"
|
||||
id="path56"
|
||||
inkscape:label="STACK-T" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 571.71,722.82 a 0.38,0.38 0 0 1 0.38,-0.38 h 9.97 a 0.73,0.73 0 0 1 0.73,0.73 v 5.09 a 1.66,1.66 0 0 0 1.66,1.66 h 48.88 a 1.9,1.9 0 0 0 1.9,-1.93 q -0.09,-5.57 0.21,-16.92 c 0.07,-2.63 -2.37,-2.35 -4.4,-2.34 q -38.5,0.28 -46.53,0.09 c -7.48,-0.17 -12.82,-7.07 -12.89,-14.35 q -0.05,-4.38 0.01,-10.32 c 0.08,-8.05 0.99,-13.92 8.09,-17.22 q 2.88,-1.34 9.77,-1.34 8.01,-0.01 38.93,0.04 7.02,0.01 9.51,1.15 c 7.17,3.29 9,8.81 8.48,16.59 a 0.7,0.69 -87.8 0 1 -0.69,0.65 h -9.95 a 0.5,0.49 90 0 1 -0.49,-0.5 v -4.66 a 1.88,1.87 0 0 0 -1.88,-1.87 h -48.66 a 1.62,1.62 0 0 0 -1.62,1.62 v 17.02 a 1.78,1.78 0 0 0 1.75,1.78 q 21.17,0.28 40.13,0.06 c 8.27,-0.1 16.04,-0.17 20.26,8.31 q 1.49,3 1.27,8.64 -0.19,4.77 -0.02,14.3 a 6.02,5.95 52.4 0 1 -0.2,1.61 q -2.3,8.8 -10.58,10.76 a 6.75,6.58 37.6 0 1 -1.36,0.16 q -8.76,0.02 -40.48,0.06 -11.17,0.01 -12.69,-0.44 -6.38,-1.87 -8.86,-8.56 c -1.08,-2.9 -0.68,-6.35 -0.63,-9.49 z"
|
||||
id="path55"
|
||||
inkscape:label="STACK-S" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 1077.12,574.44 q 0,13.5 0.01,27.66 a 2.05,2.04 90 0 0 2.04,2.05 h 68.82 a 0.62,0.62 0 0 1 0.62,0.62 v 11.53 a 0.63,0.62 90 0 1 -0.62,0.63 q -27.8,-0.05 -62.75,0.04 -8.79,0.03 -11.75,-1.43 -5.15,-2.53 -7.67,-7.75 -1.34,-2.75 -1.37,-10.8 -0.01,-1.79 -0.01,-22.55 0,-20.77 0.01,-22.56 0.04,-8.05 1.38,-10.8 2.52,-5.22 7.67,-7.74 2.96,-1.46 11.75,-1.43 34.95,0.09 62.75,0.05 a 0.63,0.62 -90 0 1 0.62,0.63 v 11.53 a 0.62,0.62 0 0 1 -0.62,0.62 l -68.82,-0.01 a 2.05,2.04 -90 0 0 -2.05,2.05 q -0.01,14.16 -0.01,27.66 z"
|
||||
id="path49"
|
||||
inkscape:label="BASIC-C"
|
||||
style="fill:#ffffff" />
|
||||
<rect
|
||||
fill="#181817"
|
||||
x="-6.3200002"
|
||||
y="-42.5"
|
||||
transform="rotate(0.1,-328628.93,574236.09)"
|
||||
width="12.64"
|
||||
height="85"
|
||||
rx="0.57999998"
|
||||
id="rect48"
|
||||
inkscape:label="BASIC-I"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 927.04,552.83 a 0.72,0.71 0 0 1 -0.72,-0.71 v -5.25 a 2.15,2.15 0 0 0 -2.15,-2.15 h -55 a 2.38,2.38 0 0 0 -2.38,2.38 v 18.57 a 0.88,0.84 -65 0 0 0.18,0.53 q 1.24,1.64 2.54,1.64 12.34,0 48.61,-0.14 8.5,-0.04 11.55,1.37 5.52,2.54 8.13,8.28 1.37,3.02 1.13,9.77 -0.07,2.16 -0.05,15.66 c 0.01,7.96 -7.27,14.25 -15.14,14.19 q -3.58,-0.02 -51.07,-0.06 -7.14,0 -10.1,-1.42 -4.85,-2.32 -7.13,-7.24 c -1.68,-3.64 -1.31,-7.81 -1.29,-12.31 a 0.4,0.4 0 0 1 0.4,-0.4 h 11.61 a 0.8,0.8 0 0 1 0.8,0.8 v 5.64 a 2.21,2.2 90 0 0 2.2,2.21 h 55.02 a 2.03,2.03 0 0 0 2.03,-2.06 q -0.12,-9.61 0.13,-18.57 c 0.07,-2.45 -1.01,-3.04 -3.33,-3.04 q -51.39,0.05 -52,0.06 c -9,0.09 -16.84,-6.19 -16.83,-15.39 q 0,-6.27 -0.1,-12.34 c -0.15,-9.33 0.85,-15.59 8.81,-19.5 q 2.8,-1.38 10.46,-1.41 29.15,-0.13 47.65,-0.04 4.97,0.02 7.39,0.92 c 6.45,2.37 10.31,7.73 10.36,14.6 q 0.02,3.6 0.03,4.71 a 0.69,0.69 0 0 1 -0.69,0.7 z"
|
||||
id="path48"
|
||||
inkscape:label="BASIC-S"
|
||||
style="fill:#ffffff" />
|
||||
<rect
|
||||
fill="#ffffff"
|
||||
x="-29.33"
|
||||
y="-14.64"
|
||||
transform="rotate(-0.1,320867.39,-432641.39)"
|
||||
width="58.66"
|
||||
height="29.280001"
|
||||
rx="1.67"
|
||||
id="rect50"
|
||||
inkscape:label="BASIC-A-V"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 755.49,531.89 q 3.12,-0.01 26.25,0.05 10.51,0.03 14.43,8.92 1.5,3.41 1.5,10.01 -0.03,63.88 -0.04,65.47 a 0.54,0.54 0 0 1 -0.54,0.54 h -11.67 a 0.49,0.49 0 0 1 -0.49,-0.49 v -28.97 a 0.59,0.58 1.4 0 0 -0.56,-0.58 q -1.48,-0.05 -28.83,-0.03 -27.35,0.02 -28.84,0.08 a 0.59,0.58 -1.4 0 0 -0.55,0.58 l 0.04,28.97 a 0.49,0.49 0 0 1 -0.49,0.49 l -11.67,0.02 a 0.54,0.54 0 0 1 -0.54,-0.54 q -0.01,-1.59 -0.14,-65.47 -0.01,-6.6 1.48,-10.01 3.91,-8.9 14.42,-8.95 23.12,-0.09 26.24,-0.09 z m 29.4073,14.4488 a 1.67,1.67 0 0 0 -1.6729,-1.6671 l -55.3199,0.0966 a 1.67,1.67 0 0 0 -1.6671,1.6729 l 0.0453,25.94 a 1.67,1.67 0 0 0 1.6729,1.6671 l 55.3199,-0.0966 a 1.67,1.67 0 0 0 1.6671,-1.6729 z"
|
||||
id="path47"
|
||||
inkscape:label="BASIC-A"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="m 471.33061,200.43924 a 0.62,0.62 0 0 1 0.62,-0.62 l 54.45,-0.19 a 3.13,3.95 89.8 0 1 3.96,3.12 l 0.06,18.14 a 3.13,3.95 89.8 0 1 -3.94,3.14 l -54.45,0.19 a 0.62,0.62 0 0 1 -0.62,-0.62 z"
|
||||
id="path52"
|
||||
inkscape:label="BASIC-B-V2"
|
||||
transform="translate(113.01939,380.09076)"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="m 584.32,545.67 a 0.86,0.86 0 0 1 0.86,-0.87 l 51.18,-0.17 a 3.96,3.09 -0.2 0 1 3.97,3.07 l 0.06,15.88 a 3.96,3.09 -0.2 0 1 -3.95,3.11 l -51.18,0.17 a 0.86,0.86 0 0 1 -0.86,-0.85 z"
|
||||
id="path50"
|
||||
inkscape:label="BASIC-B-V1"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 651.23,570.47 a 1.06,1.06 0 0 0 0.22,1.34 q 4.54,3.88 4.87,9.86 0.56,10.12 0.08,20.86 -0.26,5.73 -4.64,10.06 c -3.87,3.83 -7.25,4.37 -13.12,4.36 q -33.17,-0.07 -66.47,-0.01 a 0.49,0.48 0 0 1 -0.49,-0.48 v -83.97 a 0.57,0.57 0 0 1 0.57,-0.57 q 51.76,0.03 64.53,-0.06 12.76,-0.09 15.72,11.39 0.69,2.7 0.62,9.4 -0.06,6.32 -0.49,13.34 -0.14,2.35 -1.4,4.48 z m -66.91,-24.8 0.08,20.34 a 0.86,0.86 0 0 0 0.86,0.85 l 51.18,-0.17 a 3.96,3.09 -0.2 0 0 3.95,-3.11 l -0.06,-15.88 a 3.96,3.09 -0.2 0 0 -3.97,-3.07 l -51.18,0.17 a 0.86,0.86 0 0 0 -0.86,0.87 z m 0.03,34.86 0.08,23.16 a 0.62,0.62 0 0 0 0.62,0.62 l 54.45,-0.19 a 3.95,3.13 -0.2 0 0 3.94,-3.14 l -0.06,-18.14 a 3.95,3.13 -0.2 0 0 -3.96,-3.12 l -54.45,0.19 a 0.62,0.62 0 0 0 -0.62,0.62 z"
|
||||
id="path46"
|
||||
inkscape:label="BASIC-B"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
263
public/basic-stack-logo-light.svg
Normal file
263
public/basic-stack-logo-light.svg
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="-19 -26 1080.601 501.772"
|
||||
id="svg61"
|
||||
sodipodi:docname="Basic Stack Logo - with text - light mode.svg"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
width="1080.601"
|
||||
height="501.772"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs61" />
|
||||
<sodipodi:namedview
|
||||
id="namedview61"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="2.0513991"
|
||||
inkscape:cx="572.04861"
|
||||
inkscape:cy="246.90466"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2054"
|
||||
inkscape:window-x="3829"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="Hintergund"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 432.60531,585.12531 -40,24.15 -65.74,39.49 a 2.18,2.17 43.7 0 1 -2.17,0.05 l -122.99,-67.31 a 1.09,1.09 0 0 1 -0.57,-0.96 v -27.58 a 1.04,1.04 0 0 0 -0.57,-0.93 l -40.28,-20.96 a 1.78,1.78 0 0 1 -0.96,-1.58 v -8.23 a 1.48,1.5 75.2 0 1 0.77,-1.3 l 164.65,-93.22 a 0.94,0.93 42.8 0 1 0.88,-0.03 l 167.79,82.97 a 0.84,0.83 13.1 0 1 0.47,0.75 v 36.22 a 2.27,2.26 74.5 0 1 -1.09,1.94 z m -29.45,-62.08 a 0.34,0.34 0 0 0 -0.02,-0.6 l -79.92,-41.57 a 0.34,0.34 0 0 0 -0.32,0 l -77.46,43.85 a 0.34,0.34 0 0 0 0.02,0.6 l 79.91,41.57 a 0.34,0.34 0 0 0 0.32,0 z"
|
||||
id="path42" />
|
||||
<path
|
||||
fill="#bcbdc2"
|
||||
d="m 495.32494,592.77371 c -7.49851,7.40425 -17.66591,10.99063 -26.26803,16.84053 -9.59935,5.70086 -19.32999,11.23325 -28.37099,17.80842 -4.56185,1.79769 -9.37199,9.09319 -14.47299,4.81572 -11.60618,-7.37377 -24.69897,-12.2349 -35.89709,-20.26534 -3.76541,-2.66338 3.91027,-4.08058 5.57849,-6.10338 11.49617,-7.18147 22.99234,-14.36296 34.48851,-21.54443 5.23945,1.67707 8.43033,-4.09599 12.92216,-5.48176 5.23898,-0.57634 7.80029,-7.86685 13.56199,-6.5032 6.57122,2.40343 12.08622,6.96457 18.5991,9.52903 6.70961,3.48001 12.95835,7.7804 19.85885,10.90441 z"
|
||||
id="path51"
|
||||
style="stroke-width:1.07587" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 453.18,666.33 40.04,22.52 a 1.22,1.22 0 0 1 0.62,1.06 v 45.05 a 1.86,1.85 73.3 0 1 -0.83,1.55 q -2.39,1.59 -5.7,3.54 c -25.26,14.93 -54.69,33 -81.01,48.61 q -46.69,27.68 -77.94,46.45 c -2.29,1.37 -2.61,0.7 -4.89,-0.65 q -82.55,-48.8 -163.06,-97.78 a 1.16,1.15 16 0 1 -0.55,-0.98 v -45.54 a 1.56,1.55 -14.1 0 1 0.82,-1.37 l 37.76,-20.66 q 18.29,10.89 36.59,22.02 0.64,0.39 0.78,0.94 L 201.5,710.7 a 0.29,0.29 0 0 0 0,0.51 l 123.53,71.32 a 2.07,2.06 -44.6 0 0 2.05,0.01 l 124.57,-70.52 a 0.54,0.54 0 0 0 0.01,-0.94 l -36.43,-21.43 q 0.19,0.84 0.7,-0.31 a 1.75,1.66 -5.1 0 1 0.66,-0.75 z"
|
||||
id="path60" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Vordergurnd"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<path
|
||||
style="fill:#232f28;fill-opacity:1;stroke-width:1.02152"
|
||||
d="m 161.49195,688.72314 40.76574,22.37691 35.28045,-20.63649 -38.64642,-23.1228 -38.14777,21.38238"
|
||||
id="path64"
|
||||
inkscape:label="path64" />
|
||||
<path
|
||||
style="fill:#232f28;fill-opacity:1;stroke-width:0"
|
||||
d="m 432.14408,699.60808 c -11.12655,-6.5661 -21.57804,-12.7166 -23.22552,-13.66776 -1.64749,-0.95117 -2.90882,-1.80349 -2.80297,-1.89405 0.10586,-0.0906 8.61124,-5.17621 18.90085,-11.30147 l 23.09563,-9.55255 0.91237,0.58928 c 16.24871,10.49456 20.85593,12.56705 31.25752,18.38066 2.88218,1.61089 13.3671,7.13269 13.3671,7.13269 -39.40304,21.19899 -2.49537,1.57031 -40.90928,21.94248 -0.35534,0.18845 -3.59935,-1.59925 -20.5957,-11.62928 z"
|
||||
id="path19"
|
||||
sodipodi:nodetypes="sssscsssss" />
|
||||
<path
|
||||
style="fill:#a2a2a2;fill-opacity:1;stroke-width:0"
|
||||
d="m 325.90902,694.04316 v 49.29137 l 168.9005,-101.68499 0.51704,-48.77433 z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#bcbdc2;fill-opacity:1;stroke-width:0"
|
||||
d="m 326.08137,694.04316 0.17235,49.29137 -166.14294,-98.06569 -0.51704,-47.05086 z"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="187.16635"
|
||||
y="381.46991"
|
||||
transform="rotate(-0.4)"
|
||||
width="9.6999998"
|
||||
height="10.08"
|
||||
rx="0.64999998"
|
||||
id="rect40" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="121.47729"
|
||||
y="397.59161"
|
||||
transform="rotate(-0.1)"
|
||||
width="10.28"
|
||||
height="10.28"
|
||||
rx="0.63999999"
|
||||
id="rect41" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="159.48051"
|
||||
y="415.51767"
|
||||
transform="rotate(-0.3)"
|
||||
width="11.76"
|
||||
height="11.76"
|
||||
rx="0.51999998"
|
||||
id="rect42" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="193.1608"
|
||||
y="443.14157"
|
||||
transform="rotate(0.2)"
|
||||
width="9.3400002"
|
||||
height="9.3400002"
|
||||
rx="0.56999999"
|
||||
id="rect43" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="-9.8500004"
|
||||
y="-9.8500004"
|
||||
transform="rotate(-0.2,131727.77,-38694.058)"
|
||||
width="19.700001"
|
||||
height="19.700001"
|
||||
rx="0.60000002"
|
||||
id="rect44" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="162.21117"
|
||||
y="476.91843"
|
||||
transform="rotate(0.1)"
|
||||
width="18.540001"
|
||||
height="18.540001"
|
||||
rx="0.63999999"
|
||||
id="rect45" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="113.01939"
|
||||
y="495.00061"
|
||||
width="23.200001"
|
||||
height="23.200001"
|
||||
rx="0.57999998"
|
||||
id="rect46" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="127.90707"
|
||||
y="544.4801"
|
||||
transform="rotate(-0.3)"
|
||||
width="20.84"
|
||||
height="20.84"
|
||||
rx="0.56999999"
|
||||
id="rect49" />
|
||||
<rect
|
||||
fill="#ff5501"
|
||||
x="163.39401"
|
||||
y="569.77728"
|
||||
transform="rotate(-0.3)"
|
||||
width="19.459999"
|
||||
height="19.459999"
|
||||
rx="0.47"
|
||||
id="rect51" />
|
||||
<path
|
||||
style="fill:#ff6533;fill-opacity:1;stroke-width:0.993896"
|
||||
d="m 159.92165,521.52336 -0.681,9.99616 41.20048,20.68169 0.34049,29.29907 124.28244,67.56019 1.362,-36.19296 -167.86641,-92.37823 z"
|
||||
id="path63" />
|
||||
<path
|
||||
style="fill:#e84d00;fill-opacity:1;stroke-width:0.987968"
|
||||
d="m 326.91058,612.82073 -1.24207,36.51627 168.29577,-99.65189 -0.0835,-37.88137 z"
|
||||
id="path62" />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="m 403.5,523.39 -77.47,43.85 a 0.34,0.34 0 0 1 -0.32,0 L 245.8,525.67 a 0.34,0.34 0 0 1 -0.02,-0.6 l 77.46,-43.85 a 0.34,0.34 0 0 1 0.32,0 l 79.92,41.57 a 0.34,0.34 0 0 1 0.02,0.6 z"
|
||||
id="path45" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Schrift"
|
||||
transform="translate(-113.01939,-380.09076)">
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 1147.53,741.13 a 0.35,0.35 0 0 1 -0.32,0.21 h -13.06 a 1.91,1.88 -21.2 0 1 -1.39,-0.6 l -29.51,-31.09 a 2.51,2.49 67.9 0 0 -1.8,-0.77 h -15.54 a 0.69,0.68 0 0 0 -0.69,0.68 v 31.08 a 0.68,0.68 0 0 1 -0.68,0.68 h -9.81 a 0.9,0.89 0 0 1 -0.9,-0.89 v -74.09 a 0.61,0.61 0 0 1 0.61,-0.61 h 10.13 a 0.62,0.61 0 0 1 0.62,0.61 v 30.47 a 0.71,0.7 90 0 0 0.7,0.71 h 15.07 a 2.47,2.46 21.5 0 0 1.8,-0.78 l 28.94,-30.49 a 1.9,1.88 -68.1 0 1 1.37,-0.59 h 12.91 a 0.36,0.36 0 0 1 0.26,0.6 l -32.75,35.93 a 0.95,0.95 0 0 0 -0.02,1.26 q 6.06,7.06 12.89,14.19 9.88,10.3 20.92,22.58 0.44,0.48 0.25,0.91 z"
|
||||
id="path59"
|
||||
inkscape:label="STACK-K" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 947.12,703.46 q 0,-18.5 0.01,-20.09 0.03,-7.17 1.22,-9.62 2.25,-4.65 6.84,-6.9 2.63,-1.3 10.47,-1.27 31.13,0.08 55.9,0.04 a 0.56,0.55 -90 0 1 0.55,0.56 v 10.27 a 0.55,0.55 0 0 1 -0.55,0.56 L 960.25,677 a 1.83,1.82 -90 0 0 -1.82,1.82 q -0.01,12.62 -0.02,24.65 0,12.02 0.01,24.64 a 1.83,1.82 90 0 0 1.82,1.83 l 61.31,-0.01 a 0.55,0.55 0 0 1 0.56,0.56 v 10.27 a 0.56,0.55 90 0 1 -0.55,0.56 q -24.77,-0.04 -55.91,0.04 -7.83,0.02 -10.47,-1.28 -4.59,-2.25 -6.83,-6.9 -1.19,-2.45 -1.22,-9.62 -0.01,-1.59 -0.01,-20.1 z"
|
||||
id="path58"
|
||||
inkscape:label="STACK-C" />
|
||||
<rect
|
||||
fill="#ffffff"
|
||||
x="-26.139999"
|
||||
y="-13.13"
|
||||
transform="rotate(-0.1,395861.22,-490983.29)"
|
||||
width="52.279999"
|
||||
height="26.26"
|
||||
rx="1.29"
|
||||
id="rect60"
|
||||
inkscape:label="STACK-A-V" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 883.08,714.55 h -50.89 a 0.69,0.68 0 0 0 -0.69,0.68 v 25.4 a 0.68,0.68 0 0 1 -0.68,0.68 h -10.27 a 0.47,0.47 0 0 1 -0.47,-0.46 q -0.15,-28.18 -0.08,-57.05 0.03,-7.73 1.47,-10.39 c 3.49,-6.45 8.68,-8.08 16.5,-7.86 q 4.64,0.13 37.53,0.02 c 9.35,-0.03 14.3,0.57 18.21,8.2 q 1.2,2.34 1.22,9.09 0.12,34.74 -0.03,57.92 a 0.55,0.55 0 0 1 -0.55,0.55 h -10.08 a 0.59,0.59 0 0 1 -0.59,-0.59 v -25.59 a 0.6,0.6 0 0 0 -0.6,-0.6 z m 0.5693,-36.2756 a 1.29,1.29 0 0 0 -1.2923,-1.2878 l -49.6999,0.0868 a 1.29,1.29 0 0 0 -1.2877,1.2922 l 0.0413,23.68 a 1.29,1.29 0 0 0 1.2923,1.2878 l 49.6999,-0.0868 a 1.29,1.29 0 0 0 1.2877,-1.2922 z"
|
||||
id="path57"
|
||||
inkscape:label="STACK-A" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 732.79,665.69 q 11.42,0 36.97,-0.06 a 0.69,0.69 0 0 1 0.69,0.69 v 10.03 a 0.66,0.66 0 0 1 -0.66,0.66 h -30.42 a 0.75,0.75 0 0 0 -0.75,0.75 l -0.15,62.99 a 0.54,0.54 0 0 1 -0.5,0.53 q -0.17,0.01 -5.21,0.01 -5.04,0 -5.22,-0.01 a 0.54,0.54 0 0 1 -0.49,-0.53 l -0.1,-62.99 A 0.75,0.75 0 0 0 726.2,677 l -30.42,-0.02 a 0.66,0.66 0 0 1 -0.66,-0.66 l 0.01,-10.03 a 0.69,0.69 0 0 1 0.69,-0.69 q 25.55,0.08 36.97,0.09 z"
|
||||
id="path56"
|
||||
inkscape:label="STACK-T" />
|
||||
<path
|
||||
fill="#ff5501"
|
||||
d="m 571.71,722.82 a 0.38,0.38 0 0 1 0.38,-0.38 h 9.97 a 0.73,0.73 0 0 1 0.73,0.73 v 5.09 a 1.66,1.66 0 0 0 1.66,1.66 h 48.88 a 1.9,1.9 0 0 0 1.9,-1.93 q -0.09,-5.57 0.21,-16.92 c 0.07,-2.63 -2.37,-2.35 -4.4,-2.34 q -38.5,0.28 -46.53,0.09 c -7.48,-0.17 -12.82,-7.07 -12.89,-14.35 q -0.05,-4.38 0.01,-10.32 c 0.08,-8.05 0.99,-13.92 8.09,-17.22 q 2.88,-1.34 9.77,-1.34 8.01,-0.01 38.93,0.04 7.02,0.01 9.51,1.15 c 7.17,3.29 9,8.81 8.48,16.59 a 0.7,0.69 -87.8 0 1 -0.69,0.65 h -9.95 a 0.5,0.49 90 0 1 -0.49,-0.5 v -4.66 a 1.88,1.87 0 0 0 -1.88,-1.87 h -48.66 a 1.62,1.62 0 0 0 -1.62,1.62 v 17.02 a 1.78,1.78 0 0 0 1.75,1.78 q 21.17,0.28 40.13,0.06 c 8.27,-0.1 16.04,-0.17 20.26,8.31 q 1.49,3 1.27,8.64 -0.19,4.77 -0.02,14.3 a 6.02,5.95 52.4 0 1 -0.2,1.61 q -2.3,8.8 -10.58,10.76 a 6.75,6.58 37.6 0 1 -1.36,0.16 q -8.76,0.02 -40.48,0.06 -11.17,0.01 -12.69,-0.44 -6.38,-1.87 -8.86,-8.56 c -1.08,-2.9 -0.68,-6.35 -0.63,-9.49 z"
|
||||
id="path55"
|
||||
inkscape:label="STACK-S" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 1077.12,574.44 q 0,13.5 0.01,27.66 a 2.05,2.04 90 0 0 2.04,2.05 h 68.82 a 0.62,0.62 0 0 1 0.62,0.62 v 11.53 a 0.63,0.62 90 0 1 -0.62,0.63 q -27.8,-0.05 -62.75,0.04 -8.79,0.03 -11.75,-1.43 -5.15,-2.53 -7.67,-7.75 -1.34,-2.75 -1.37,-10.8 -0.01,-1.79 -0.01,-22.55 0,-20.77 0.01,-22.56 0.04,-8.05 1.38,-10.8 2.52,-5.22 7.67,-7.74 2.96,-1.46 11.75,-1.43 34.95,0.09 62.75,0.05 a 0.63,0.62 -90 0 1 0.62,0.63 v 11.53 a 0.62,0.62 0 0 1 -0.62,0.62 l -68.82,-0.01 a 2.05,2.04 -90 0 0 -2.05,2.05 q -0.01,14.16 -0.01,27.66 z"
|
||||
id="path49"
|
||||
inkscape:label="BASIC-C" />
|
||||
<rect
|
||||
fill="#181817"
|
||||
x="-6.3200002"
|
||||
y="-42.5"
|
||||
transform="rotate(0.1,-328628.93,574236.09)"
|
||||
width="12.64"
|
||||
height="85"
|
||||
rx="0.57999998"
|
||||
id="rect48"
|
||||
inkscape:label="BASIC-I" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 927.04,552.83 a 0.72,0.71 0 0 1 -0.72,-0.71 v -5.25 a 2.15,2.15 0 0 0 -2.15,-2.15 h -55 a 2.38,2.38 0 0 0 -2.38,2.38 v 18.57 a 0.88,0.84 -65 0 0 0.18,0.53 q 1.24,1.64 2.54,1.64 12.34,0 48.61,-0.14 8.5,-0.04 11.55,1.37 5.52,2.54 8.13,8.28 1.37,3.02 1.13,9.77 -0.07,2.16 -0.05,15.66 c 0.01,7.96 -7.27,14.25 -15.14,14.19 q -3.58,-0.02 -51.07,-0.06 -7.14,0 -10.1,-1.42 -4.85,-2.32 -7.13,-7.24 c -1.68,-3.64 -1.31,-7.81 -1.29,-12.31 a 0.4,0.4 0 0 1 0.4,-0.4 h 11.61 a 0.8,0.8 0 0 1 0.8,0.8 v 5.64 a 2.21,2.2 90 0 0 2.2,2.21 h 55.02 a 2.03,2.03 0 0 0 2.03,-2.06 q -0.12,-9.61 0.13,-18.57 c 0.07,-2.45 -1.01,-3.04 -3.33,-3.04 q -51.39,0.05 -52,0.06 c -9,0.09 -16.84,-6.19 -16.83,-15.39 q 0,-6.27 -0.1,-12.34 c -0.15,-9.33 0.85,-15.59 8.81,-19.5 q 2.8,-1.38 10.46,-1.41 29.15,-0.13 47.65,-0.04 4.97,0.02 7.39,0.92 c 6.45,2.37 10.31,7.73 10.36,14.6 q 0.02,3.6 0.03,4.71 a 0.69,0.69 0 0 1 -0.69,0.7 z"
|
||||
id="path48"
|
||||
inkscape:label="BASIC-S" />
|
||||
<rect
|
||||
fill="#ffffff"
|
||||
x="-29.33"
|
||||
y="-14.64"
|
||||
transform="rotate(-0.1,320867.39,-432641.39)"
|
||||
width="58.66"
|
||||
height="29.280001"
|
||||
rx="1.67"
|
||||
id="rect50"
|
||||
inkscape:label="BASIC-A-V" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 755.49,531.89 q 3.12,-0.01 26.25,0.05 10.51,0.03 14.43,8.92 1.5,3.41 1.5,10.01 -0.03,63.88 -0.04,65.47 a 0.54,0.54 0 0 1 -0.54,0.54 h -11.67 a 0.49,0.49 0 0 1 -0.49,-0.49 v -28.97 a 0.59,0.58 1.4 0 0 -0.56,-0.58 q -1.48,-0.05 -28.83,-0.03 -27.35,0.02 -28.84,0.08 a 0.59,0.58 -1.4 0 0 -0.55,0.58 l 0.04,28.97 a 0.49,0.49 0 0 1 -0.49,0.49 l -11.67,0.02 a 0.54,0.54 0 0 1 -0.54,-0.54 q -0.01,-1.59 -0.14,-65.47 -0.01,-6.6 1.48,-10.01 3.91,-8.9 14.42,-8.95 23.12,-0.09 26.24,-0.09 z m 29.4073,14.4488 a 1.67,1.67 0 0 0 -1.6729,-1.6671 l -55.3199,0.0966 a 1.67,1.67 0 0 0 -1.6671,1.6729 l 0.0453,25.94 a 1.67,1.67 0 0 0 1.6729,1.6671 l 55.3199,-0.0966 a 1.67,1.67 0 0 0 1.6671,-1.6729 z"
|
||||
id="path47"
|
||||
inkscape:label="BASIC-A" />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="m 584.32,545.67 a 0.86,0.86 0 0 1 0.86,-0.87 l 51.18,-0.17 a 3.96,3.09 -0.2 0 1 3.97,3.07 l 0.06,15.88 a 3.96,3.09 -0.2 0 1 -3.95,3.11 l -51.18,0.17 a 0.86,0.86 0 0 1 -0.86,-0.85 z"
|
||||
id="path50"
|
||||
inkscape:label="BASIC-B-V1" />
|
||||
<path
|
||||
fill="#181817"
|
||||
d="m 651.23,570.47 a 1.06,1.06 0 0 0 0.22,1.34 q 4.54,3.88 4.87,9.86 0.56,10.12 0.08,20.86 -0.26,5.73 -4.64,10.06 c -3.87,3.83 -7.25,4.37 -13.12,4.36 q -33.17,-0.07 -66.47,-0.01 a 0.49,0.48 0 0 1 -0.49,-0.48 v -83.97 a 0.57,0.57 0 0 1 0.57,-0.57 q 51.76,0.03 64.53,-0.06 12.76,-0.09 15.72,11.39 0.69,2.7 0.62,9.4 -0.06,6.32 -0.49,13.34 -0.14,2.35 -1.4,4.48 z m -66.91,-24.8 0.08,20.34 a 0.86,0.86 0 0 0 0.86,0.85 l 51.18,-0.17 a 3.96,3.09 -0.2 0 0 3.95,-3.11 l -0.06,-15.88 a 3.96,3.09 -0.2 0 0 -3.97,-3.07 l -51.18,0.17 a 0.86,0.86 0 0 0 -0.86,0.87 z m 0.03,34.86 0.08,23.16 a 0.62,0.62 0 0 0 0.62,0.62 l 54.45,-0.19 a 3.95,3.13 -0.2 0 0 3.94,-3.14 l -0.06,-18.14 a 3.95,3.13 -0.2 0 0 -3.96,-3.12 l -54.45,0.19 a 0.62,0.62 0 0 0 -0.62,0.62 z"
|
||||
id="path46"
|
||||
inkscape:label="BASIC-B" />
|
||||
</g>
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="m 471.33061,200.43924 a 0.62,0.62 0 0 1 0.62,-0.62 l 54.45,-0.19 a 3.13,3.95 89.8 0 1 3.96,3.12 l 0.06,18.14 a 3.13,3.95 89.8 0 1 -3.94,3.14 l -54.45,0.19 a 0.62,0.62 0 0 1 -0.62,-0.62 z"
|
||||
id="path52"
|
||||
inkscape:label="BASIC-B-V2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
Loading…
Add table
Reference in a new issue