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:
Paperclip CTO 2026-06-28 12:32:23 +00:00
commit a7bd527793
25 changed files with 14778 additions and 0 deletions

2
.env.example Normal file
View file

@ -0,0 +1,2 @@
DIRECTUS_URL=https://directus.basicstack.de
SITE_URL=https://basicstack.org

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
node_modules/
.output/
.nuxt/
.env
dist/
*.log

17
Dockerfile Normal file
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: basicstack-web

9
k8s/10-configmap.yaml Normal file
View 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
View 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
View 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
View 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
View 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">&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
View 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

File diff suppressed because it is too large Load diff

24
package.json Normal file
View 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
View 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
View 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>

View 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>

View 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
View 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
View 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>

View 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

View 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

View 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