1
0
Fork 0
forked from swablab/website

initial commit

This commit is contained in:
ndsboy 2025-02-22 16:59:06 +00:00
commit 4bca29d3c3
63 changed files with 5684 additions and 0 deletions

View file

@ -0,0 +1,18 @@
{
"name": "swabsite",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers-community/features/deno": {}
},
"containerUser": "vscode",
"containerEnv": {
"ASTRO_TELEMETRY_DISABLED": "true"
},
"postStartCommand": "deno task install",
"runArgs": ["--userns=keep-id", "--security-opt=label=disable"],
"customizations": {
"vscode": {
"extensions": ["astro-build.astro-vscode"]
}
}
}

View file

@ -0,0 +1,54 @@
name: Build Astro
on: [push, pull_request, workflow_dispatch]
env:
ASTRO_TELEMETRY_DISABLED: true
jobs:
check:
name: 🧪 Astro check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
ls ${{ github.workspace }}
#- uses: denoland/setup-deno@v2
# with:
# deno-version: v2.x
#- run: deno task install
#- run: deno task check
#deploy:
# name: 🚢 Deploy
# if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
# runs-on: ubuntu-latest
# needs: [check]
# steps:
# - uses: actions/checkout@v4
#
# - uses: denoland/setup-deno@v2
# with:
# deno-version: v2.x
# - run: deno task install
# - run: deno task build
#
# - name: build containerfile
# id: build-image
# uses: redhat-actions/buildah-build@v2
# with:
# image: website
# tags: latest
# containerfiles: |
# ./Containerfile
#
# - name: Push to registry
# uses: redhat-actions/push-to-registry@v2
# with:
# image: ${{ steps.build-image.outputs.image }}
# tags: ${{ steps.build-image.outputs.tags }}
# username: ${{ secrets.REGISTRY_USER }}
# password: ${{ secrets.REGISTRY_PASSWORD }}
# registry: ${{ secrets.REGISTRY }}
#
# - name: Trigger deployment
# run: curl -X POST ${{ secrets.DEPLOY_WEBHOOK }}

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.astro
.DS_Store
.env
.env.production
dist
node_modules

6
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"recommendations": [
"astro-build.astro-vscode",
"denoland.vscode-deno"
]
}

9
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"[astro]": {
"editor.defaultFormatter": "astro-build.astro-vscode"
},
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
"editor.formatOnSave": true
}

36
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,36 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "install/update",
"command": "deno task install",
"type": "shell",
"problemMatcher": []
},
{
"label": "dev",
"command": "deno task dev",
"type": "shell",
"problemMatcher": []
},
{
"label": "check",
"command": "deno task check",
"type": "shell",
"problemMatcher": []
},
{
"label": "preview",
"command": "deno task preview",
"type": "shell",
"problemMatcher": [],
"dependsOn": ["build"]
},
{
"label": "build",
"command": "deno task build",
"type": "shell",
"problemMatcher": []
}
]
}

6
Containerfile Normal file
View file

@ -0,0 +1,6 @@
FROM ghcr.io/swablab/documents:latest AS documents
FROM docker.io/nginxinc/nginx-unprivileged:latest
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY ./dist /usr/share/nginx/html
COPY --from=documents / /usr/share/nginx/html/docs

14
LICENSE Normal file
View file

@ -0,0 +1,14 @@
BSD Zero Clause License
Copyright (c) 2023 swablab e.V.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

16
README.md Normal file
View file

@ -0,0 +1,16 @@
# swablab.de
[![Build Astro CI](https://github.com/swablab/website/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/swablab/website/actions/workflows/main.yml)
These are the source files for the page published at https://swablab.de.
Powered by [Astro](https://astro.build)
# Installation
We recommend [deno](https://deno.com/) as the package manager. To install all
required packages, execute `deno task install`.
Afterwards, you can develop with `deno task dev` and build with
`deno task build`. It is recommended to use VSCode as your editor, as all tasks
can be executed easily within VSCode.

16
astro.config.ts Normal file
View file

@ -0,0 +1,16 @@
import sitemap from "@astrojs/sitemap"
import tailwind from "@astrojs/tailwind"
import { defineConfig, passthroughImageService } from "astro/config"
export default defineConfig({
site: "https://swablab.de",
base: "/",
server: {
host: true,
},
integrations: [tailwind(), sitemap()],
image: {
domains: ["directus.swablab.de", "files.mastodon.social"],
service: passthroughImageService(),
},
})

8
deno.json Normal file
View file

@ -0,0 +1,8 @@
{
"fmt": {
"options": {
"semiColons": false,
"indentWidth": 2
}
}
}

3023
deno.lock generated Normal file

File diff suppressed because it is too large Load diff

27
nginx.conf Normal file
View file

@ -0,0 +1,27 @@
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html index.htm;
include /etc/nginx/mime.types;
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
internal;
}
location /discord {
return 301 https://discord.gg/sZbmJdGkMQ;
}
location / {
try_files $uri $uri/index.html =404;
}
}

31
package.json Normal file
View file

@ -0,0 +1,31 @@
{
"name": "@swablab/website",
"private": true,
"scripts": {
"install": "deno install",
"check": "astro check",
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/sitemap": "^3.2.1",
"@astrojs/tailwind": "^5.1.3",
"@fontsource-variable/ubuntu-sans": "^5.1.0",
"@iconify-json/ph": "^1.2.1",
"@iconify/tailwind": "^1.1.3",
"@tailwindcss/typography": "^0.5.15",
"@types/alpinejs": "^3.13.11",
"alpinejs": "^3.14.6",
"astro": "^5.0.3",
"daisyui": "^4.12.14",
"tailwindcss": "^3.4.16",
"typescript": "^5.7.2"
},
"prettier": {
"tabWidth": 2,
"semi": false,
"endOfLine": "lf"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

49
public/logo.svg Normal file
View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1080 1080" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2.5;">
<g transform="matrix(1,0,0,1,-6881,0)">
<g id="Strukture" transform="matrix(1,0,0,1,133,-1134)">
<rect x="6748" y="1134" width="1080" height="1080" style="fill:none;"/>
<g transform="matrix(1.24487,0,0,1.24487,-1784.64,-411.945)">
<g transform="matrix(1.24839,0,0,1.24839,-749.242,-387.363)">
<g transform="matrix(1,0,0,1,-849.728,-3.45885)">
<path d="M7260.8,1855.8L7347.85,1876.46" style="fill:none;stroke:rgb(243,243,243);stroke-width:18.77px;"/>
</g>
<g transform="matrix(1,0,0,1,-847.9,0)">
<path d="M7276.11,1906.48L7323.66,1917.98" style="fill:none;stroke:rgb(243,243,243);stroke-width:18.77px;stroke-linejoin:miter;"/>
</g>
<g transform="matrix(1,0,0,1,2474.6,-129.288)">
<circle cx="3963.5" cy="1737.5" r="126.3" style="fill:none;stroke:rgb(243,243,243);stroke-width:18.77px;stroke-miterlimit:1.5;"/>
</g>
<g transform="matrix(1,0,0,1,2474.6,-129.288)">
<path d="M4021.69,1896.02C4032.02,1892.93 4043.63,1887.14 4052.61,1880.39L4089.23,1906.33C4105.61,1894.13 4120.13,1879.61 4132.33,1863.23L4106.39,1826.61C4116.21,1810.87 4123.36,1793.6 4127.55,1775.53L4171.78,1767.98C4174.74,1747.77 4174.74,1727.23 4171.78,1707.02L4127.55,1699.48C4123.36,1681.4 4116.21,1664.13 4106.39,1648.39L4132.33,1611.77C4120.13,1595.39 4105.61,1580.87 4089.23,1568.67L4052.61,1594.61C4036.87,1584.79 4019.6,1577.64 4001.53,1573.45L3993.98,1529.22C3973.77,1526.26 3953.23,1526.26 3933.02,1529.22L3925.47,1573.45C3907.4,1577.64 3890.13,1584.79 3874.39,1594.61L3837.77,1568.67C3821.39,1580.87 3806.87,1595.39 3794.67,1611.77L3820.61,1648.39C3810.79,1664.13 3803.64,1681.4 3799.45,1699.48L3755.22,1707.02C3752.26,1727.23 3752.26,1747.77 3755.22,1767.98L3799.45,1775.53C3803.64,1793.6 3810.79,1810.87 3820.61,1826.61L3794.67,1863.23C3806.87,1879.61 3821.39,1894.13 3837.77,1906.33L3874.39,1880.39C3883.06,1884.99 3896.11,1891.64 3904.67,1893.84L3921.92,1925.54L4026.94,1949.95" style="fill:none;stroke:rgb(243,243,243);stroke-width:18.77px;stroke-miterlimit:1.5;"/>
</g>
</g>
<g transform="matrix(0.418992,0,0,0.466271,4240.37,-631.785)">
<g transform="matrix(1.22943,0,0,1.10477,-1501.56,-441.15)">
<path d="M7070.03,4727.19C7062.51,4730.97 7054.03,4733.09 7045.06,4733.09C7014.28,4733.09 6989.29,4708.1 6989.29,4677.32C6989.29,4646.54 7014.28,4621.55 7045.06,4621.55C7070.73,4621.55 7092.37,4638.93 7098.85,4662.55C7081.1,4674.79 7069.46,4695.26 7069.46,4718.42C7069.46,4721.39 7069.65,4724.32 7070.03,4727.19Z" style="fill:rgb(243,243,243);"/>
</g>
<g transform="matrix(1.22943,0,0,1.10477,-1501.56,-441.15)">
<path d="M7102.94,4776.9L7102.91,4780.95C7102.91,4780.95 7080.42,4787.8 7063.75,4804.47C7052.4,4815.81 7032.47,4841.04 7032.47,4874.18C7032.47,4899.4 7032.47,4879.97 7032.47,4879.97C6988.12,4878.21 6952.83,4866.44 6952.83,4866.44C6952.83,4866.44 6952.83,4842.95 6952.83,4816.79C6952.83,4798.22 6960.21,4780.42 6973.34,4767.29C6986.46,4754.16 7004.27,4746.79 7022.83,4746.79C7037.39,4746.79 7052.73,4746.79 7067.28,4746.79C7070.18,4746.79 7073.07,4746.97 7075.92,4747.32C7081.76,4759.69 7091.23,4770 7102.94,4776.9Z" style="fill:rgb(243,243,243);"/>
</g>
</g>
<g transform="matrix(0.418992,0,0,0.466271,4240.37,-631.785)">
<g transform="matrix(-1.22943,0,0,1.10477,16049,-441.15)">
<path d="M7102.94,4776.9L7102.91,4780.95C7102.91,4780.95 7080.42,4787.8 7063.75,4804.47C7052.4,4815.81 7032.47,4841.04 7032.47,4874.18C7032.47,4899.4 7032.47,4879.97 7032.47,4879.97C6988.12,4878.21 6952.83,4866.44 6952.83,4866.44C6952.83,4866.44 6952.83,4842.95 6952.83,4816.79C6952.83,4798.22 6960.21,4780.42 6973.34,4767.29C6986.46,4754.16 7004.27,4746.79 7022.83,4746.79C7037.39,4746.79 7052.73,4746.79 7067.28,4746.79C7070.18,4746.79 7073.07,4746.97 7075.92,4747.32C7081.76,4759.69 7091.23,4770 7102.94,4776.9Z" style="fill:rgb(243,243,243);"/>
</g>
<g transform="matrix(1.22943,0,0,1.10477,-1501.56,-441.15)">
<path d="M7175.79,4662.61C7182.25,4638.96 7203.91,4621.55 7229.6,4621.55C7260.38,4621.55 7285.37,4646.54 7285.37,4677.32C7285.37,4708.1 7260.38,4733.09 7229.6,4733.09C7220.59,4733.09 7212.07,4730.95 7204.54,4727.15C7204.9,4724.29 7205.09,4721.38 7205.09,4718.42C7205.09,4695.29 7193.49,4674.86 7175.79,4662.61Z" style="fill:rgb(243,243,243);"/>
</g>
</g>
<g transform="matrix(0.418992,0,0,0.466271,4240.37,-631.785)">
<g transform="matrix(1.29452,0,0,1.16326,-3371.22,3043.8)">
<circle cx="8222.71" cy="1485.33" r="52.965" style="fill:rgb(243,243,243);"/>
</g>
<g transform="matrix(1.22943,0,0,0.993178,-2937.04,3274.16)">
<path d="M8397.09,1687.61C8397.09,1630.95 8355.81,1585.02 8304.87,1585.02C8304.87,1585.02 8304.87,1585.02 8304.87,1585.02C8253.94,1585.02 8212.65,1630.95 8212.65,1687.61C8212.65,1705.3 8212.65,1718.12 8212.65,1718.12C8212.65,1718.12 8255.69,1733.08 8304.87,1733.08C8354.06,1733.08 8397.09,1718.12 8397.09,1718.12L8397.09,1687.61Z" style="fill:rgb(243,243,243);"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6 KiB

50
public/logo_color.svg Normal file
View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1080 1080" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2.5;">
<g transform="matrix(1,0,0,1,-2281.57,-1257.11)">
<g id="Lightmode-no-text" serif:id="Lightmode no text" transform="matrix(1,0,0,1,-4466.43,123.108)">
<rect x="6748" y="1134" width="1080" height="1080" style="fill:none;"/>
<g transform="matrix(1.24839,0,0,1.24839,-749.242,-387.363)">
<g transform="matrix(1,0,0,1,-849.9,42.4178)">
<path d="M7200.48,1795.34C7204.27,1796.68 7207.24,1799.68 7208.55,1803.48C7214.69,1821.27 7232.49,1872.93 7241.19,1898.18C7244.23,1907 7251.7,1913.55 7260.84,1915.4C7279.24,1919.13 7310.73,1925.52 7330.82,1929.6C7341.98,1931.86 7353.33,1926.66 7358.9,1916.72C7366.21,1903.67 7376.32,1885.62 7383.58,1872.65C7389.62,1861.86 7393.28,1849.91 7394.31,1837.59C7394.99,1829.36 7395.8,1819.69 7396.49,1811.36C7397.42,1800.24 7406.61,1791.61 7417.77,1791.39C7431.82,1791.12 7448.27,1790.8 7448.27,1790.8C7473.78,1772.72 7496.06,1750.44 7514.14,1724.92L7506.91,1678.37C7513.47,1665.76 7518.92,1652.6 7523.2,1639.04L7561.23,1611.24C7566.49,1580.41 7566.49,1548.91 7561.23,1518.08L7523.2,1490.27C7518.92,1476.72 7513.47,1463.56 7506.91,1450.94L7514.14,1404.39C7496.06,1378.87 7473.78,1356.6 7448.27,1338.52L7401.72,1345.75C7389.1,1339.19 7375.94,1333.74 7362.39,1329.45L7334.58,1291.43C7303.75,1286.17 7272.25,1286.17 7241.42,1291.43L7213.61,1329.45C7200.06,1333.74 7186.9,1339.19 7174.28,1345.75L7127.73,1338.52C7102.22,1356.6 7079.94,1378.87 7061.86,1404.39L7069.09,1450.94C7062.53,1463.56 7057.08,1476.72 7052.8,1490.27L7014.77,1518.08C7009.51,1548.91 7009.51,1580.41 7014.77,1611.24L7052.8,1639.04C7057.08,1652.6 7062.53,1665.76 7069.09,1678.37L7061.86,1724.92C7079.94,1750.44 7102.22,1772.72 7127.73,1790.8L7174.28,1783.57C7184.89,1789.08 7190.81,1792 7200.48,1795.34ZM7288,1439.49C7357.71,1439.49 7414.3,1496.09 7414.3,1565.79C7414.3,1635.5 7357.71,1692.09 7288,1692.09C7218.29,1692.09 7161.7,1635.5 7161.7,1565.79C7161.7,1496.09 7218.29,1439.49 7288,1439.49Z" style="fill:rgb(163,255,241);"/>
</g>
<g transform="matrix(1,0,0,1,-849.728,-3.45885)">
<path d="M7260.8,1855.8L7347.85,1876.46" style="fill:none;stroke:rgb(61,61,61);stroke-width:22.36px;"/>
</g>
<g transform="matrix(1,0,0,1,-847.9,0)">
<path d="M7276.11,1906.48L7323.66,1917.98" style="fill:none;stroke:rgb(61,61,61);stroke-width:22.36px;stroke-linejoin:miter;"/>
</g>
<g transform="matrix(1,0,0,1,2474.6,-129.288)">
<circle cx="3963.5" cy="1737.5" r="126.3" style="fill:none;stroke:rgb(61,61,61);stroke-width:22.31px;stroke-miterlimit:1.5;"/>
</g>
<g transform="matrix(1,0,0,1,2474.6,-129.288)">
<path d="M4021.69,1896.02C4032.02,1892.93 4043.63,1887.14 4052.61,1880.39L4089.23,1906.33C4105.61,1894.13 4120.13,1879.61 4132.33,1863.23L4106.39,1826.61C4116.21,1810.87 4123.36,1793.6 4127.55,1775.53L4171.78,1767.98C4174.74,1747.77 4174.74,1727.23 4171.78,1707.02L4127.55,1699.48C4123.36,1681.4 4116.21,1664.13 4106.39,1648.39L4132.33,1611.77C4120.13,1595.39 4105.61,1580.87 4089.23,1568.67L4052.61,1594.61C4036.87,1584.79 4019.6,1577.64 4001.53,1573.45L3993.98,1529.22C3973.77,1526.26 3953.23,1526.26 3933.02,1529.22L3925.47,1573.45C3907.4,1577.64 3890.13,1584.79 3874.39,1594.61L3837.77,1568.67C3821.39,1580.87 3806.87,1595.39 3794.67,1611.77L3820.61,1648.39C3810.79,1664.13 3803.64,1681.4 3799.45,1699.48L3755.22,1707.02C3752.26,1727.23 3752.26,1747.77 3755.22,1767.98L3799.45,1775.53C3803.64,1793.6 3810.79,1810.87 3820.61,1826.61L3794.67,1863.23C3806.87,1879.61 3821.39,1894.13 3837.77,1906.33L3874.39,1880.39C3883.06,1884.99 3896.11,1891.64 3904.67,1893.84L3921.92,1925.54L4026.94,1949.95" style="fill:none;stroke:rgb(61,61,61);stroke-width:22.31px;stroke-miterlimit:1.5;"/>
</g>
</g>
<g transform="matrix(0.418992,0,0,0.466271,4240.37,-631.785)">
<g transform="matrix(1.22943,0,0,1.10477,-1501.56,-441.15)">
<path d="M7070.03,4727.19C7062.51,4730.97 7054.03,4733.09 7045.06,4733.09C7014.28,4733.09 6989.29,4708.1 6989.29,4677.32C6989.29,4646.54 7014.28,4621.55 7045.06,4621.55C7070.73,4621.55 7092.37,4638.93 7098.85,4662.55C7081.1,4674.79 7069.46,4695.26 7069.46,4718.42C7069.46,4721.39 7069.65,4724.32 7070.03,4727.19Z" style="fill:rgb(102,102,102);"/>
</g>
<g transform="matrix(1.22943,0,0,1.10477,-1501.56,-441.15)">
<path d="M7102.94,4776.9L7102.91,4780.95C7102.91,4780.95 7080.42,4787.8 7063.75,4804.47C7052.4,4815.81 7032.47,4841.04 7032.47,4874.18C7032.47,4899.4 7032.47,4879.97 7032.47,4879.97C6988.12,4878.21 6952.83,4866.44 6952.83,4866.44C6952.83,4866.44 6952.83,4842.95 6952.83,4816.79C6952.83,4798.22 6960.21,4780.42 6973.34,4767.29C6986.46,4754.16 7004.27,4746.79 7022.83,4746.79C7037.39,4746.79 7052.73,4746.79 7067.28,4746.79C7070.18,4746.79 7073.07,4746.97 7075.92,4747.32C7081.76,4759.69 7091.23,4770 7102.94,4776.9Z" style="fill:rgb(102,102,102);"/>
</g>
</g>
<g transform="matrix(0.418992,0,0,0.466271,4240.37,-631.785)">
<g transform="matrix(-1.22943,0,0,1.10477,16049,-441.15)">
<path d="M7102.94,4776.9L7102.91,4780.95C7102.91,4780.95 7080.42,4787.8 7063.75,4804.47C7052.4,4815.81 7032.47,4841.04 7032.47,4874.18C7032.47,4899.4 7032.47,4879.97 7032.47,4879.97C6988.12,4878.21 6952.83,4866.44 6952.83,4866.44C6952.83,4866.44 6952.83,4842.95 6952.83,4816.79C6952.83,4798.22 6960.21,4780.42 6973.34,4767.29C6986.46,4754.16 7004.27,4746.79 7022.83,4746.79C7037.39,4746.79 7052.73,4746.79 7067.28,4746.79C7070.18,4746.79 7073.07,4746.97 7075.92,4747.32C7081.76,4759.69 7091.23,4770 7102.94,4776.9Z" style="fill:rgb(102,102,102);"/>
</g>
<g transform="matrix(1.22943,0,0,1.10477,-1501.56,-441.15)">
<path d="M7175.79,4662.61C7182.25,4638.96 7203.91,4621.55 7229.6,4621.55C7260.38,4621.55 7285.37,4646.54 7285.37,4677.32C7285.37,4708.1 7260.38,4733.09 7229.6,4733.09C7220.59,4733.09 7212.07,4730.95 7204.54,4727.15C7204.9,4724.29 7205.09,4721.38 7205.09,4718.42C7205.09,4695.29 7193.49,4674.86 7175.79,4662.61Z" style="fill:rgb(102,102,102);"/>
</g>
</g>
<g transform="matrix(0.418992,0,0,0.466271,4240.37,-631.785)">
<g transform="matrix(1.29452,0,0,1.16326,-3371.22,3043.8)">
<circle cx="8222.71" cy="1485.33" r="52.965" style="fill:rgb(128,128,128);"/>
</g>
<g transform="matrix(1.22943,0,0,0.993178,-2937.04,3274.16)">
<path d="M8397.09,1687.6C8397.09,1660.4 8387.38,1634.31 8370.08,1615.07C8352.79,1595.83 8329.33,1585.02 8304.88,1585.02L8304.87,1585.02C8280.41,1585.02 8256.95,1595.83 8239.66,1615.07C8222.37,1634.31 8212.65,1660.4 8212.65,1687.6L8212.65,1718.12C8212.65,1718.12 8255.69,1733.08 8304.87,1733.08C8354.06,1733.08 8397.09,1718.12 8397.09,1718.12L8397.09,1687.6Z" style="fill:rgb(128,128,128);"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
public/opengraph.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

1
public/robots.txt Normal file
View file

@ -0,0 +1 @@
User-agent: *

5
public/test/me Normal file
View file

@ -0,0 +1,5 @@
{
"data": {
"id": "user1"
}
}

7
public/test/presence Normal file
View file

@ -0,0 +1,7 @@
{
"data": {
"id": 1,
"last": "2024-05-08T18:00:00",
"next": "2024-05-08T18:00:00"
}
}

25
public/test/task Normal file
View file

@ -0,0 +1,25 @@
{
"data": {
"id": "task",
"status": "in_progress",
"title": "title 1",
"date_due": "2024-01-01T12:00:00",
"labels": [
"label1"
],
"description": "description 1",
"priority": "high",
"responsibles": [
{
"directus_users_id": {
"id": "user1"
}
},
{
"directus_users_id": {
"id": "user2"
}
}
]
}
}

37
public/test/tasks_general Normal file
View file

@ -0,0 +1,37 @@
{
"data": [
{
"id": "task",
"status": "in_progress",
"title": "title1",
"date_due": "2024-01-01T12:00:00",
"labels": [
"label1"
],
"description": "description1",
"priority": "high",
"responsibles": [
{
"directus_users_id": {
"id": "user1"
}
},
{
"directus_users_id": {
"id": "user2"
}
}
]
},
{
"id": "task",
"status": "in_progress",
"title": "title2",
"date_due": null,
"labels": [],
"description": "description2",
"priority": "medium",
"responsibles": []
}
]
}

12
public/test/users Normal file
View file

@ -0,0 +1,12 @@
{
"data": [
{
"id": "user1",
"first_name": "first_name1"
},
{
"id": "user2",
"first_name": "first_name2"
}
]
}

41
src/components/Card.astro Normal file
View file

@ -0,0 +1,41 @@
---
import DirectusImg from "./DirectusImg.astro"
export interface Props {
name: string
image: string
link?: string
small?: boolean
}
const { name, image, link, small } = Astro.props
---
<div class="card md:card-side bg-base-300 text-left">
<figure class="md:flex-none">
<DirectusImg
src={image}
widths={[500]}
alt=""
format="webp"
class="h-12 md:h-full w-full object-cover"
class:list={{
"md:w-48": !small,
"md:w-32": small,
}}
/>
</figure>
<div class="card-body">
{
link ? (
<a class="card-title link link-primary" href={link}>
{name}
</a>
) : (
<span class="card-title">{name}</span>
)
}
<p>
<slot />
</p>
</div>
</div>

View file

@ -0,0 +1,16 @@
---
import Link from "./Link.astro"
import Section from "./Section.astro"
import Text from "./Text.astro"
---
<Section title="Noch Fragen?" jumpId="questions">
<Text>
Für weitere Fragen stehen wir dir jederzeit unter
<Link text="info@swablab.de" href="mailto:info@swablab.de" />
oder per Telefon unter
<Link text="+49 15679 232971" href="tel:+4915679232971" />
zur Verfügung.
</Text>
<slot />
</Section>

View file

@ -0,0 +1,32 @@
---
import { getImage } from "astro:assets"
export interface Props {
src: string
widths: number[]
format: "webp" | "svg"
alt: string
class?: string
onload?: string
}
const { src, widths, format, ...attrs }: Props = Astro.props
const images = await Promise.all(
widths.map(
async (width) =>
await getImage({
src: `https://directus.swablab.de/assets/${src}?format=${format}&width=${width}`,
inferSize: true,
format,
}),
),
)
---
<img
src={images[0].src}
srcset={widths.length > 1
? widths.map((w, i) => `${images[i].src} ${w}w`).join(",")
: null}
{...attrs}
/>

View file

@ -0,0 +1,32 @@
---
import DirectusImg from "./DirectusImg.astro"
---
<div class="hero h-screen">
<DirectusImg
src="1f836313-ee59-4840-8da7-5cc6cb874a61"
widths={[256]}
format="webp"
class="absolute w-full h-full object-cover blur-sm"
alt=""
/>
<DirectusImg
src="1f836313-ee59-4840-8da7-5cc6cb874a61"
widths={[3840, 1920, 1080, 720]}
format="webp"
class="absolute w-full h-full object-cover transition-opacity ease-in duration-500 opacity-0"
onload="this.style.opacity = 1"
alt=""
/>
<div class="hero-content">
<div
class="card rounded-none border border-white max-w-xl bg-base-100/80 shadow-[0_0_50px_rgba(255,255,255,0.8)]"
>
<div class="card-body text-center">
<slot />
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,77 @@
---
import { getImage } from "astro:assets"
export interface Props {
images: string[]
}
const { images } = Astro.props
const urls = (
await Promise.all(
images.map(
async (image) =>
await getImage({
src: `https://directus.swablab.de/assets/${image}?format=webp&width=800`,
inferSize: true,
format: "webp",
}),
),
)
).map((url) => url.src)
---
<div class="relative">
<span
class="absolute -right-8 top-1/2 animate-pulse -translate-y-1/2 icon-[ph--caret-right-duotone] w-8 h-8 md:hidden"
>
</span>
<div
class="absolute justify-between transform -translate-y-1/2 left-5 right-5 top-1/2 hidden md:flex"
>
<span
id="gallery-left"
class="icon-[ph--arrow-circle-left-duotone] w-16 h-16 cursor-pointer"
>
</span>
<span
id="gallery-right"
class="icon-[ph--arrow-circle-right-duotone] w-16 h-16 cursor-pointer"
>
</span>
</div>
<div
id="gallery"
class="carousel carousel-center w-full p-4 space-x-4 bg-neutral rounded-box snap-none h-64 md:h-auto"
>
{urls.map((url) => <img src={url} class="w-[800px] rounded" alt="" />)}
</div>
</div>
<script>
const gallery = document.querySelector("#gallery")
let pos = 0
let lastClick = 0
function scroll(step: number, auto: boolean) {
if (globalThis.outerWidth < 768) return // disabled on mobile
if (auto && Date.now() - lastClick < 10000) return // disable auto scrolling if user clicked
pos = (pos + step + 5) % 5
gallery?.scrollTo({
left: pos * 816 - 32,
behavior: "smooth",
})
if (!auto) lastClick = Date.now()
}
setInterval(() => {
scroll(+1, true)
}, 5000)
document.querySelector("#gallery-left")?.addEventListener("click", () => {
scroll(-1, false)
})
document.querySelector("#gallery-right")?.addEventListener("click", () => {
scroll(+1, false)
})
</script>

View file

@ -0,0 +1,9 @@
---
export interface Props {
text: string
href: string
}
const { text, href }: Props = Astro.props
---
<a class="link link-primary" href={href}>{text}</a>

10
src/components/List.astro Normal file
View file

@ -0,0 +1,10 @@
---
export interface Props {
items: string[]
}
const { items }: Props = Astro.props
---
<ul class="text-left">
{items.map((item) => <li>- {item}</li>)}
</ul>

22
src/components/Map.astro Normal file
View file

@ -0,0 +1,22 @@
---
import DirectusImg from "./DirectusImg.astro"
import Link from "./Link.astro"
---
<p>Friedrichstraße 17, 72250 Freudenstadt</p>
<a
class="m-auto max-w-2xl"
href="https://www.openstreetmap.org/node/12119224010"
>
<DirectusImg
src="ec61a1b5-3bbb-4458-b244-1be144e46747"
widths={[800]}
format="webp"
class="rounded-box aspect-video object-cover"
alt="Karte zur Werkstatt"
/>
</a>
<Link
text="Karte öffnen"
href="https://www.openstreetmap.org/node/12119224010"
/>

View file

@ -0,0 +1,94 @@
---
import { getImage } from "astro:assets"
import { formatDate } from "../helper"
type Post = {
content: string
url: string
created_at: string
media_attachments: {
id: string
preview_url: string
description: string
}[]
emojis: {
shortcode: string
url: string
}[]
}
const posts: Post[] = await (
await fetch(
"https://mastodon.social/api/v1/accounts/112282328572683277/statuses?exclude_replies=true&limit=5",
)
).json()
const attachments = await Promise.all(
posts.map(async (post) => {
if (post.media_attachments.length == 0) return null
return await getImage({
src: post.media_attachments[0].preview_url,
inferSize: true,
})
}),
)
const emojis = await Promise.all(
posts
.flatMap((post) => post.emojis)
.map(async (emoji) => {
return {
shortcode: emoji.shortcode,
...(await getImage({
src: emoji.url,
inferSize: true,
})),
}
}),
)
function replaceEmojis(content: string) {
for (const emoji of emojis) {
content = content.replaceAll(
`:${emoji.shortcode}:`,
`<img src="${emoji.src}" class="w-[1em] h-[1em] inline-block" />`,
)
}
return content
}
---
{
posts.map((post, i) => (
<div class="card md:card-side bg-base-300 shadow-xl">
{attachments[i] == null ? null : (
<figure class="max-h-64 md:flex-none md:max-h-none md:max-w-64 md:min-h-full">
<a class="w-full" href={post.url}>
<img
class="w-full md:h-full object-cover"
src={attachments[i]?.src}
alt={post.media_attachments[0].description}
/>
</a>
</figure>
)}
<div class="card-body text-left">
<h2 class="card-title justify-between">
<a href={post.url}>@swablab</a>
<time class="text-xs opacity-50">
{formatDate(post.created_at, "long")}
</time>
</h2>
<div set:html={replaceEmojis(post.content)} />
</div>
</div>
))
}
<a
class="link link-primary"
href="https://mastodon.social/@swablab"
rel="noopener noreferrer me"
target="_blank"
>
Mehr auf Mastodon
</a>

View file

@ -0,0 +1,63 @@
---
import { directus } from "../helper"
import DirectusImg from "./DirectusImg.astro"
type Member = {
firstname: string
image?: string
}
const members = await directus<Member[]>("items/members?sort=firstname")
---
<div class="flex flex-col sm:flex-row justify-evenly gap-6">
<div class="flex flex-col items-center">
<span class="icon-[ph--users-three-duotone] w-[100px] h-[100px]"></span>
<p>{members.length} Mitglieder</p>
</div>
<div class="flex flex-col items-center">
<span class="icon-[ph--house-line-duotone] w-[100px] h-[100px]"></span>
<p>1 Werkstatt</p>
</div>
<div class="flex flex-col items-center">
<span class="icon-[ph--lightbulb-duotone] w-[100px] h-[100px]"></span>
<p>&infin; Ideen</p>
</div>
</div>
<div
class="flex flex-row flex-wrap gap-6 justify-evenly sm:justify-center text-base pt-8"
>
{
members.map((member) => (
<div>
<DirectusImg
src={
member.image != null
? member.image
: "a8f48962-9f0e-40e6-abd2-e932aa9dea2e"
}
widths={[200]}
format="webp"
alt={"Profilbild von " + member.firstname}
class="rounded-full w-[100px] h-[100px]"
/>
<p>{member.firstname}</p>
</div>
))
}
<div>
<div>
<DirectusImg
src="6920d1f2-feeb-4eb3-a066-988b3f60a6d9"
widths={[200]}
format="webp"
alt="Du?"
class="rounded-full w-[100px] h-[100px]"
/>
</div>
<p>Du?</p>
</div>
</div>

17
src/components/QA.astro Normal file
View file

@ -0,0 +1,17 @@
---
export interface Props {
question: string
}
const { question }: Props = Astro.props
---
<div class="chat chat-start">
<div class="chat-bubble chat-bubble-primary">
{question}
</div>
</div>
<div class="chat chat-end">
<div class="chat-bubble">
<slot />
</div>
</div>

View file

@ -0,0 +1,17 @@
---
export interface Props {
title: string
jumpId: string
}
const { title, jumpId }: Props = Astro.props
---
<div class="text-center p-8 space-y-8 even:bg-base-200">
<a hidden class="block invisible relative -top-20" id={jumpId}></a>
<div class="text-3xl md:text-5xl">
<a href={"#" + jumpId}>{title}</a>
</div>
<div class="max-w-4xl mx-auto flex flex-col gap-4">
<slot />
</div>
</div>

View file

@ -0,0 +1,26 @@
---
import { directus } from "../helper"
import DirectusImg from "./DirectusImg.astro"
type Sponsor = {
name: string
logo: string
}
const sponsors = await directus<Sponsor[]>("items/sponsors?sort=order")
---
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{
sponsors.map((svg) => (
<div class="flex justify-center rounded-box p-4 bg-white">
<DirectusImg
widths={[200]}
src={svg.logo}
format="svg"
class="h-16"
alt={svg.name}
/>
</div>
))
}
</div>

View file

@ -0,0 +1,10 @@
---
export interface Props {
items: string[]
}
const { items }: Props = Astro.props
---
<ul class="steps steps-vertical">
{items.map((item) => <li class="!text-left step step-primary">{item}</li>)}
</ul>

View file

@ -0,0 +1,3 @@
<p class="text-left">
<slot />
</p>

View file

@ -0,0 +1,19 @@
---
export interface Props {
items: [string, string][]
}
const { items }: Props = Astro.props
---
<ul class="timeline timeline-vertical timeline-compact">
{
items.map((item) => (
<li>
<div class="timeline-start font-bold">{item[0]}</div>
<div class="timeline-middle icon-[ph--circle-duotone]" />
<div class="timeline-end text-left">{item[1]}</div>
<hr />
</li>
))
}
</ul>

2
src/env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

75
src/helper.ts Normal file
View file

@ -0,0 +1,75 @@
const env = (import.meta as unknown as {
env: {
DEV: boolean
SSR: boolean
}
}).env
export async function directus<T>(
path: string,
method: string = "GET",
body: string | null = null,
): Promise<T> {
return await fetch(
env.DEV && !env.SSR
? `/test/${path.split("/").at(-1)}`
: `https://directus.swablab.de/${path}`,
{
method,
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body,
},
)
.then((res) => res.json())
.then((res) => {
if (res.errors) throw res.errors[0].message
return res.data
})
}
export function formatDate(
date: string | number | Date | undefined,
style: "short" | "medium" | "long",
) {
if (date == null) return ""
return Intl.DateTimeFormat("de-DE", {
dateStyle: style,
}).format(new Date(date))
}
export function formatTime(date: string | number | Date | undefined) {
if (date == null) return ""
return Intl.DateTimeFormat("de-DE", {
timeStyle: "short",
}).format(new Date(date))
}
export const documents = {
"3DDruckAGB": "/docs/3d-druck-agb.pdf",
Beitragsordnung: "/docs/beitragsordnung.pdf",
Beleg: "/docs/beleg.pdf",
Datenschutzhinweise: "/docs/datenschutz.pdf",
Haftungsausschluss: "/docs/haftungsausschluss.pdf",
Mitgliedsantrag: "/docs/mitgliedsantrag.pdf",
Satzung: "/docs/satzung.pdf",
"Werkstatt-AGB": "/docs/werkstatt-agb.pdf",
"Werkstatt-Regeln": "/docs/werkstatt-regeln.pdf",
}
export type Task = {
id: string | undefined
title: string
status: string
date_due?: string
priority: string
description: string
responsibles: {
directus_users_id: {
id: string
first_name: string
}
}[]
}

43
src/layouts/Base.astro Normal file
View file

@ -0,0 +1,43 @@
---
import "../style.css";
export interface Props {
title?: string;
description?: string;
}
const { title } = Astro.props;
const titleFull = title == null ? "swablab e.V." : title + " | swablab e.V.";
const description =
"swablab e.V. in Freudenstadt - die offene Werkstatt für alle ambitionierten Hobby-Schreiner, Bastler, Tüftler, Elektroniker und vieles mehr.";
---
<html lang="de-DE" class="scroll-smooth">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content={description} />
<meta property="og:title" content={titleFull} />
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:image" content="https://swablab.de/opengraph.png" />
<meta property="og:description" content={description} />
<meta property="og:locale" content="de_DE" />
<title>{titleFull}</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<script
is:inline
defer
data-domain="swablab.de"
src="https://analytics.swablab.de/js/plausible.outbound-links.js"
></script>
</head>
<body class="min-h-screen flex flex-col">
<slot />
</body>
</html>

120
src/layouts/Footer.astro Normal file
View file

@ -0,0 +1,120 @@
---
import { documents } from "../helper"
const year = new Date().getFullYear()
function entry(name: string, icon: string, link: string) {
return { name, icon, link }
}
const social = [
entry(
"Mastodon",
"icon-[ph--mastodon-logo-duotone]",
"https://mastodon.social/@swablab",
),
entry(
"Instagram",
"icon-[ph--instagram-logo-duotone]",
"https://www.instagram.com/swablab/",
),
entry(
"Discord",
"icon-[ph--discord-logo-duotone]",
"https://swablab.de/discord",
),
entry(
"Printables",
"icon-[ph--cube-duotone]",
"https://www.printables.com/social/103546-swablab-ev/about",
),
entry(
"GitHub",
"icon-[ph--github-logo-duotone]",
"https://github.com/swablab",
),
]
const downloads = [
entry("Satzung", "icon-[ph--file-pdf-duotone]", documents.Satzung),
entry(
"Beitragsordnung",
"icon-[ph--file-pdf-duotone]",
documents.Beitragsordnung,
),
entry(
"Mitgliedsantrag",
"icon-[ph--file-pdf-duotone]",
documents.Mitgliedsantrag,
),
]
---
<div class="bg-base-200 border-t border-base-300">
<footer class="footer max-w-7xl mx-auto p-8 grid-cols-1 md:grid-cols-3">
<nav class="w-full">
<header class="footer-title border-b w-full">Social</header>
{
social.map((e) => (
<a
class="link link-hover link-primary flex gap-1 items-center"
href={e.link}
rel="noopener noreferrer"
target="_blank"
>
<span class={e.icon} />
{e.name}
</a>
))
}
</nav>
<nav class="w-full">
<header class="footer-title border-b w-full">Downloads</header>
{
downloads.map((e) => (
<a
class="link link-hover link-primary flex gap-1 items-center"
href={e.link}
target="_blank"
>
<span class={e.icon} />
{e.name}
</a>
))
}
</nav>
<nav class="w-full">
<header class="footer-title border-b w-full">Kontakt</header>
<div class="flex gap-1 items-center">
<span class="icon-[ph--map-pin-duotone]"></span>
Katharinenstr. 1, 72250 Freudenstadt
</div>
<a
class="link link-hover link-primary flex gap-1 items-center"
href="mailto:info@swablab.de"
>
<span class="icon-[ph--envelope-simple-duotone]"></span>
info@swablab.de
</a>
<a
class="link link-hover link-primary flex gap-1 items-center"
href="tel:+4915679232971"
>
<span class="icon-[ph--phone-duotone]"></span>
+49 15679 232971
</a>
</nav>
</footer>
<footer class="footer footer-center p-4 bg-base-200 border-t border-base-300">
<aside>
<p class="flex gap-4">
<a href="/data-privacy" class="text-primary">Datenschutzerklärung</a>
<a href="/imprint" class="text-primary">Impressum</a>
</p>
<p>
<span class="icon-[ph--copyright-duotone]"></span>
<span>{year} swablab e.V.</span>
</p>
</aside>
</footer>
</div>

56
src/layouts/Header.astro Normal file
View file

@ -0,0 +1,56 @@
---
import Presence from "./Presence.astro"
const links = [
{ name: "home", ref: "/" },
{ name: "wir", ref: "/about" },
{ name: "werkstatt", ref: "/lab" },
{ name: "3d druck", ref: "/3d-print" },
{ name: "beitreten", ref: "/join" },
{ name: "nutzung", ref: "/use" },
{ name: "spenden", ref: "/donate" },
]
---
<nav class="fixed top-0 navbar z-30 glass bg-base-100/30">
<div class="navbar-start">
<a class="btn btn-ghost text-xl font-normal" href="/">
<img class="w-8 h-8" src="/logo.svg" alt="swablab logo" />
<span>swablab</span></a
>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal p-0">
{
links.map((link) => (
<li
class:list={{
"text-primary":
link.ref == Astro.url.pathname ||
`${link.ref}/` == Astro.url.pathname,
}}
>
<a href={link.ref}>{link.name}</a>
</li>
))
}
</ul>
</div>
<div class="navbar-end">
<Presence />
<details class="dropdown dropdown-end lg:hidden">
<summary class="btn btn-square btn-ghost">
<span class="icon-[ph--list] w-6 h-6"></span>
</summary>
<ul class="menu dropdown-content shadow bg-base-200 rounded-box w-52">
{
links.map((link) => (
<li>
<a href={link.ref}>{link.name}</a>
</li>
))
}
</ul>
</details>
</div>
</nav>

View file

@ -0,0 +1,19 @@
---
import Base from "./Base.astro"
import Footer from "./Footer.astro"
import Header from "./Header.astro"
const { frontmatter } = Astro.props
---
<Base title={frontmatter.title} description={frontmatter.description}>
<Header />
<div class="h-16"></div>
<main
class="max-w-7xl w-full mx-auto p-8 prose prose-invert prose-a:link prose-a:link-primary"
>
<slot />
</main>
<div class="grow"></div>
<Footer />
</Base>

21
src/layouts/Page.astro Normal file
View file

@ -0,0 +1,21 @@
---
import Base from "./Base.astro"
import Footer from "./Footer.astro"
import Header from "./Header.astro"
export interface Props {
title?: string
overlap?: boolean
}
const { title, overlap = false } = Astro.props
---
<Base title={title}>
<Header />
<div class="text-xl">
{overlap ? null : <div class="h-16" />}
<slot />
</div>
<Footer />
</Base>

View file

@ -0,0 +1,63 @@
<div id="presence" class="hidden">
<div class="md:hidden">
<div class="tooltip tooltip-left">
<button class="btn btn-square font-normal">
<span class="icon-[ph--door-open-duotone] w-6 h-6"></span>
</button>
</div>
</div>
<div class="hidden md:flex">
<button class="btn font-normal">
<span id="presence-text"></span>
<span class="icon-[ph--door-open-duotone] w-6 h-6"></span>
</button>
</div>
</div>
<script>
import { directus, formatDate, formatTime } from "../helper"
async function update() {
const { last, next } = await directus<{ last?: string; next?: string }>(
"items/presence",
)
const fivemin = new Date()
fivemin.setMinutes(fivemin.getMinutes() - 5)
const isOpen = new Date(last ?? 0) > fivemin
let nextDate = new Date(next ?? 0)
if (nextDate < new Date()) {
nextDate = new Date()
nextDate.setDate(nextDate.getDate() + ((3 + 7 - nextDate.getDay()) % 7))
nextDate.setHours(18)
nextDate.setMinutes(0)
nextDate.setSeconds(0)
if (nextDate < new Date()) {
nextDate.setDate(nextDate.getDate() + 7)
}
}
const text = isOpen
? "Gerade geöffnet"
: `Öffnet ${formatDate(nextDate, "short")} um ${formatTime(nextDate)} Uhr`
const presence = document.querySelector("#presence")
presence?.querySelectorAll(".tooltip").forEach((t) => {
t.classList.remove("tooltip-success", "tooltip-error")
t.classList.add(isOpen ? "tooltip-success" : "tooltip-error")
t.setAttribute("data-tip", text)
})
presence?.querySelectorAll(".btn").forEach((t) => {
t.classList.remove("btn-success", "btn-error")
t.classList.add(isOpen ? "btn-success" : "btn-error")
})
presence
?.querySelector("#presence-text")
?.replaceChildren(document.createTextNode(text))
presence?.classList.remove("hidden")
}
update()
setInterval(update, 60000)
</script>

88
src/pages/3d-print.astro Normal file
View file

@ -0,0 +1,88 @@
---
import Contact from "../components/Contact.astro"
import DirectusImg from "../components/DirectusImg.astro"
import Link from "../components/Link.astro"
import List from "../components/List.astro"
import Section from "../components/Section.astro"
import Steps from "../components/Steps.astro"
import Text from "../components/Text.astro"
import { documents } from "../helper"
import Page from "../layouts/Page.astro"
---
<Page title="3D Druck Service">
<Section title="3D Druck Service" jumpId="info">
<p>Egal welche Idee - wir drucken sie für dich!</p>
<DirectusImg
src="745a9061-7d22-48c7-af76-7d44aa3e2bca"
widths={[1000, 500]}
format="webp"
alt="Collage aus 3D-gedruckten Gegenständen"
class="rounded"
/>
<Text>
Mit unserem 3D Druck Service hast du die Möglichkeit deine Ideen und
Prototypen real werden zu lassen.
</Text>
<Text>
Ob im Maßstab oder in Echtgröße, wir drucken die von dir konstruierten
Teile aus - und das für wenig Geld.
</Text>
</Section>
<Section title="Und so läuft das ab" jumpId="howto">
<Steps
items={[
"Teil konstruieren oder herunterladen",
"Schick uns eine Mail mit der STL/OBJ Datei und den unten genannten Informationen",
"Du bekommst ein unverbindliches Angebot mit möglichem Liefertermin genannt",
"Bestätige das Angebot per Mail",
"Schwubs - sind die Teile im Druck und schon fast in deinem Briefkasten",
]}
/>
</Section>
<Section title="Anfrage" jumpId="request">
<Text>
Falls wir dein Interesse geweckt haben, sende uns folgende Informationen
an
<Link
text="info@swablab.de"
href="mailto:info@swablab.de?subject=%5BAnfrage%5D%203D%20Druck%20Service&body=Hallo%2C%0A%0Abitte%20die%20STL%2FOBJ%20Datei%20im%20Anhang%20mit%20folgenden%20Eigenschaften%20drucken%3A%0A%0A-%20Anzahl%3A%202%0A-%20Material%3A%20PLA%20%28PLA%2C%20PETG%2C%20TPU%2C%20PC%29%0A-%20Farbe%3A%20schwarz%20%28schwarz%2C%20wei%C3%9F%2C%20blau%2C%20rot%2C%20gr%C3%BCn%2C%20gelb%2C%20farblos%2C%20zuf%C3%A4llig%29%0A-%20Schichth%C3%B6he%3A%200.2%20%280.1%2C%200.2%2C%200.3%29%0A-%20Orientierung%3A%20flache%20Unterseite%0A-%20Bemerkungen%3A%0A%0AVollständige%20Adresse%3A%0A%0Amit%20freundlichen%20Gr%C3%BC%C3%9Fen"
/>
</Text>
<List
items={[
"STL/OBJ Datei",
"Anzahl",
"Material",
"Farbe",
"Schichthöhe",
"Orientierung",
"Adresse",
]}
/>
</Section>
<Contact>
<Text>
Mit der Nutzung unseres 3D Druck Services erklärst du dich mit <a
class="link link-primary"
href={documents["3DDruckAGB"]}>unseren AGBs</a
> einverstanden.
</Text>
<Text>
Anregungen für 3D Drucke findest du beispielsweise auf
<Link
text="Printables,"
href="https://www.printables.com/@swablabeV_103546"
/>
<Link
text="Cults3D"
href="https://cults3d.com/de/f%C3%BChrungen/beste-STL-dateien"
/>
oder auch
<Link text="Thingiverse" href="https://www.thingiverse.com" />
</Text>
</Contact>
</Page>

20
src/pages/404.astro Normal file
View file

@ -0,0 +1,20 @@
---
import Forest from "../components/Forest.astro"
import Link from "../components/Link.astro"
import Page from "../layouts/Page.astro"
---
<Page title="Seite nicht gefunden" overlap>
<Forest>
<h2 class="card-title justify-center text-3xl">Seite nicht gefunden</h2>
<p>Diese Seite haben wir vermutlich irgendwo im Schwarzwald verloren.</p>
<p>
Wenn die Seite existieren sollte, schick uns eine E-Mail an
<Link text="info@swablab.de" href="mailto:info@swablab.de" />
</p>
<p>
Falls du uns suchen helfen möchtest, kannst du uns gerne
<Link text="beitreten." href="/join" />
</p>
</Forest>
</Page>

99
src/pages/about.astro Normal file
View file

@ -0,0 +1,99 @@
---
import Link from "../components/Link.astro"
import Members from "../components/Members.astro"
import Section from "../components/Section.astro"
import Text from "../components/Text.astro"
import Timeline from "../components/Timeline.astro"
import Page from "../layouts/Page.astro"
---
<Page title="Über uns">
<Section title="Über uns" jumpId="about">
<Text>
Das swablab besteht aus verschiedensten Mitgliedern unterschiedlicher
Alters- und Interessengruppen, die irgenwann genug davon hatten, ihre
Hobbyprojekte alleine im Keller umzusetzen und dafür auch noch jedes
Werkzeug selbst kaufen zu müssen.
</Text>
<Text>
Stattdessen treffen wir uns jetzt mindestens wöchentlich, um in
freundlicher Gesellschaft und mit einer komplett ausgestatteten Werkstatt
entweder unseren eigenen oder gemeinsamen Projekten nachzugehen.
</Text>
<Text>
Folge uns auch gerne auf
<Link text="Mastodon" href="https://mastodon.social/@swablab" /> und
<Link text="Instagram" href="https://www.instagram.com/swablab/" />, dort
posten wir regelmäßig über aktuelle Projekte und Events.
</Text>
</Section>
<Section title="Das sind wir" jumpId="us">
<Members />
</Section>
<Section title="Geschichte" jumpId="history">
<Timeline
items={[
[
"Sommer 2020",
"Erstes Brainstorming für eine mögliche Gründung einer eigenen offenen Werkstatt",
],
[
"Herbst 2020",
`Gründung des "swablab e.V.". Am Anfang noch ohne richtige
Location und nur mit 3D-Druck.`,
],
[
"Winter 2020",
`Durch eine Spende des KLF erhalten wir unsere Grundausstattung an
Maschinen zur Holzverarbeitung.`,
],
[
"Sommer 2021",
`Das erste Mitglied (nach den Gründern) tritt ein, die erste Location im
Jugendhaus in Dornstetten wird bezogen und wir bieten das erste Mal das
Sommerferienprogramm in Lossburg an.`,
],
[
"Herbst 2021",
`Wir eröffnen unseren Elektronikbereich durch eine großzügige Spende der Stadtwerke Freudenstadt (Fördertröpfle).`,
],
[
"Sommer 2022",
`Erneut bieten wir erfolgreich das Sommerferienprogramm in Lossburg an.`,
],
[
"Herbst 2022",
`Wir ziehen um in das Gebäude des Ingenieurbüros Alwin Eppler und erweitern
unsere Holzwerkstatt durch eine Spende der Stadtwerke Freudenstadt (Fördertröpfle).`,
],
["Winter 2022", `Wir feiern den Beitritt des zehnten Mitglieds.`],
[
"Sommer 2023",
`Neben dem Sommerferienprogramm haben wir auch unser erstes eigenes Event,
einen Tag der öffenen Tür. Dort tritt unser 15. Mitglied bei.`,
],
[
"Frühling 2024",
`Beim Dornstetter Ostermontagsmarkt zeigen wir interessierten Personen unsere Werkstatt und bieten Waffeln und Getränke an.`,
],
[
"Sommer 2024",
`Neben unserem Instagram-Account werden jetzt Neuigkeiten und Projekte auch auf Mastodon (@swablab) geposted.`,
],
[
"Herbst 2024",
`Der Andrang beim Sommerferienprogramm war dieses Mal so groß, dass sogar zwei Termine komplett ausgebucht waren!`,
],
[
"Frühling 2025",
`Mit mittlerweile 23 Mitgliedern brauchen wir etwas mehr Platz und ziehen nach monatelangem Renovieren
nach Freudenstadt in die Friedrichstraße 17.`,
],
]}
/>
</Section>
</Page>

207
src/pages/data-privacy.md Normal file
View file

@ -0,0 +1,207 @@
---
layout: ../layouts/Markdown.astro
title: Datenschutzerklärung
---
# Datenschutzerklärung
Eins vorneweg: Wir verzichten auf Cookies.
## 1. Datenschutz auf einen Blick
### Allgemeine Hinweise
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren
personenbezogenen Daten passiert, wenn Sie diese Website besuchen.
Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert
werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie
unserer unter diesem Text aufgeführten Datenschutzerklärung.
### Datenerfassung auf dieser Website
#### Wer ist verantwortlich für die Datenerfassung auf dieser Website?
Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber.
Dessen Kontaktdaten können Sie dem Impressum dieser Website entnehmen.
#### Wie erfassen wir Ihre Daten?
Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen.
Hierbei kann es sich z.B. um Daten handeln, die Sie in ein Kontaktformular
eingeben. Andere Daten werden automatisch oder nach Ihrer Einwilligung beim
Besuch der Website durch unsere ITSysteme erfasst. Das sind vor allem technische
Daten (z.B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die
Erfassung dieser Daten erfolgt automatisch, sobald Sie diese Website betreten.
#### Wofür nutzen wir Ihre Daten?
Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website
zu gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens
verwendet werden.
#### Welche Rechte haben Sie bezüglich Ihrer Daten?
Sie haben jederzeit das Recht, unentgeltlich Auskunft über Herkunft, Empfänger
und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben
außerdem ein Recht, die Berichtigung oder Löschung dieser Daten zu verlangen.
Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben, können Sie diese
Einwilligung jederzeit für die Zukunft widerrufen. Außerdem haben Sie das Recht,
unter bestimmten Umständen die Einschränkung der Verarbeitung Ihrer
personenbezogenen Daten zu verlangen. Des Weiteren steht Ihnen ein
Beschwerderecht bei der zuständigen Aufsichtsbehörde zu. Hierzu sowie zu
weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit unter der im
Impressum angegebenen Adresse an uns wenden.
## 2. Allgemeine Hinweise und Pflichtinformationen
### Datenschutz
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr
ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend
der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.
Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten
erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich
identifiziert werden können. Die vorliegende Datenschutzerklärung erläutert,
welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und
zu welchem Zweck das geschieht.
Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der
Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser
Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.
### Hinweis zur verantwortlichen Stelle
Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:
swablab e.V.\
Katharinenstr. 1\
72250 Freudenstadt\
E-Mail: info@swablab.de\
Telefon: +49 15679 232971
Verantwortliche Stelle ist die natürliche oder juristische Person, die allein
oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von
personenbezogenen Daten (z. B. Namen, E-Mail-Adressen o. Ä.) entscheidet.
### Speicherdauer
Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer
genannt wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck
für die Datenverarbeitung entfällt. Wenn Sie ein berechtigtes Löschersuchen
geltend machen oder eine Einwilligung zur Datenverarbeitung widerrufen, werden
Ihre Daten gelöscht, sofern wir keinen anderen rechtlich zulässigen Gründe für
die Speicherung Ihrer personenbezogenen Daten haben (z.B. steuer- oder
handelsrechtliche Aufbewahrungsfristen); im letztgenannten Fall erfolgt die
Löschung nach Fortfall dieser Gründe.
### Hinweis zur Datenweitergabe in die USA und sonstige Drittstaaten
Auf unserer Website sind unter anderem Tools von Unternehmen mit Sitz in den USA
oder sonstigen datenschutzrechtlich nicht sicheren Drittstaaten eingebunden.
Wenn diese Tools aktiv sind, können Ihre personenbezogene Daten in diese
Drittstaaten übertragen und dort verarbeitet werden. Wir weisen darauf hin, dass
in diesen Ländern kein mit der EU vergleichbares Datenschutzniveau garantiert
werden kann. Beispielsweise sind US-Unternehmen dazu verpflichtet,
personenbezogene Daten an Sicherheitsbehörden herauszugeben, ohne dass Sie als
Betroffener hiergegen gerichtlich vorgehen könnten. Es kann daher nicht
ausgeschlossen werden, dass US-Behörden (z.B. Geheimdienste) Ihre auf US-Servern
befindlichen Daten zu Überwachungszwecken verarbeiten, auswerten und dauerhaft
speichern. Wir haben auf diese Verarbeitungstätigkeiten keinen Einfluss.
### Widerruf Ihrer Einwilligung zur Datenverarbeitung
Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung
möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Die
Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom
Widerruf unberührt.
### Widerspruchsrecht gegen die Datenerhebung in besonderen Fällen sowie gegen Direktwerbung (Art. 21 DSGVO)
Wenn die Datenverarbeitung auf Grundlage von Art. 6 Abs. 1 lit. E oder F DSGVO
erfolgt, haben Sie jederzeit das Recht, aus Gründen, die sich aus Ihrer
besonderen Situation ergeben, gegen die Verarbeitung Ihrer personenbezogenen
Daten Widerspruch einzulegen; dies gilt auch für ein auf diese Bestimmungen
gestütztes Profiling. Die jeweilige Rechtsgrundlage, auf denen eine Verarbeitung
beruht, entnehmen Sie dieser Datenschutzerklärung. Wenn Sie Widerspruch
einlegen, werden wir ihre betroffenen personenbezogenen Daten nicht mehr
verarbeiten, es sei denn, wir können zwingende schutzwürdige Gründe für die
Verarbeitung nachweisen, die Ihre Interessen, Rechte und Freiheiten überwiegen
oder die Verarbeitung dient der Geltendmachung Ausübung oder Verteidigung von
Rechtsansprüchen (Widerspruch nach Art. 21 Abs. 1 DSGVO).
Werden ihre personenbezogenen Daten verarbeitet, um Direktwerbung zu betreiben,
so haben Sie das Recht, jederzeit Widerspruch gegen die Verarbeitung Sie
betreffender personenbezogener Daten zum Zwecke derartiger Werbung einzulegen;
dies gilt auch für das Profiling, soweit es mit solcher Direktwerbung in
Verbindung steht. Wenn Sie widersprechen, werden Ihre personenbezogenen Daten
anschliessend nicht mehr zum Zwecke der Direktwerbung verwendet (Widerspruch
nach Art. 21 Abs. 2 DSGVO).
### Beschwerderecht bei der zuständigen Aufsichtsbehörde
Im Falle von Verstößen gegen die DSGVO steht den Betroffenen ein Beschwerderecht
bei einer Aufsichtsbehörde, insbesondere in dem Mitgliedstaat ihres gewöhnlichen
Aufenthalts, ihres Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes zu.
Das Beschwerderecht besteht unbeschadet anderweitiger verwaltungsrechtlicher
oder gerichtlicher Rechtsbehelfe.
### Recht auf Datenübertragbarkeit
Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in
Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen
Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen.
Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen
verlangen, erfolgt dies nur, soweit es technisch machbar ist.
### SSL- bzw. TLS-Verschlüsselung
Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung
vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an
uns als Seitenbetreiber senden, eine SSL- bzw. TLSVerschlüsselung. Eine
verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers
von „http://“ auf „https://“ wechselt und an dem Schloss-Symbol in Ihrer
Browserzeile.
Wenn die SSL- bzw. TLS-Verschlüsselung aktiviert ist, können die Daten, die Sie
an uns übermitteln, nicht von Dritten mitgelesen werden.
### Auskunft, Löschung und Berichtigung
Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht
auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten,
deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein
Recht auf Berichtigung oder Löschung dieser Daten. Hierzu sowie zu weiteren
Fragen zum Thema personenbezogene Daten können Sie sich jederzeit unter der im
Impressum angegebenen Adresse an uns wenden.
### Recht auf Einschränkung der Verarbeitung
Sie haben das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen
Daten zu verlangen. Hierzu können Sie sich jederzeit unter der im Impressum
angegebenen Adresse an uns wenden. Das Recht auf Einschränkung der Verarbeitung
besteht in folgenden Fällen:
- Wenn Sie die Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten
bestreiten, benötigen wir in der Regel Zeit, um dies zu überprüfen. Für die
Dauer der Prüfung haben Sie das Recht, die Einschränkung der Verarbeitung
Ihrer personenbezogenen Daten zu verlangen.
- Wenn die Verarbeitung Ihrer personenbezogenen Daten unrechtmäßig
geschah/geschieht, können Sie statt der Löschung die Einschränkung der
Datenverarbeitung verlangen.
- Wenn wir Ihre personenbezogenen Daten nicht mehr benötigen, Sie sie jedoch zur
Ausübung, Verteidigung oder Geltendmachung von Rechtsansprüchen benötigen,
haben Sie das Recht, statt der Löschung die Einschränkung der Verarbeitung
Ihrer personenbezogenen Daten zu verlangen.
- Wenn Sie einen Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss
eine Abwägung zwischen Ihren und unseren Interessen vorgenommen werden.
Solange noch nicht feststeht, wessen Interessen überwiegen, haben Sie das
Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu
verlangen.
Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschränkt haben,
dürfen diese Daten von ihrer Speicherung abgesehen nur mit Ihrer
Einwilligung oder zur Geltendmachung, Ausübung oder Verteidigung von
Rechtsansprüchen oder zum Schutz der Rechte einer anderen natürlichen oder
juristischen Person oder aus Gründen eines wichtigen öffentlichen Interesses der
Europäischen Union oder eines Mitgliedstaats verarbeitet werden.

23
src/pages/docs.astro Normal file
View file

@ -0,0 +1,23 @@
---
import Section from "../components/Section.astro"
import { documents } from "../helper"
import Page from "../layouts/Page.astro"
---
<Page title="Offizielle Dokumente">
<Section title="Offizielle Dokumente" jumpId="docs">
<ul class="menu">
{
Object.keys(documents)
.sort()
.map((name) => (
<li>
<a href={documents[name]} class="justify-start">
<p class="font-bold">{name}</p>
</a>
</li>
))
}
</ul>
</Section>
</Page>

48
src/pages/donate.astro Normal file
View file

@ -0,0 +1,48 @@
---
import Link from "../components/Link.astro"
import Section from "../components/Section.astro"
import Text from "../components/Text.astro"
import Page from "../layouts/Page.astro"
---
<Page title="Spenden">
<Section title="Spenden" jumpId="donate">
<Text>
Als gemeinnütziger Verein sind wir für jede Spende sehr dankbar. Jeder
noch so kleine Zuschuss wird zu 100% in die Instandhaltung und die
Erweiterung der Werkstatt investiert. Falls du uns unterstützen möchtest,
kannst du an folgendes Konto deinen Spendenbeitrag überweisen:
</Text>
<div class="stats shadow">
<div class="stat">
<div class="stat-title">IBAN: DE18 6039 1310 0125 6340 05</div>
<div class="stat-title">BIC: GENODES1VBH</div>
<div class="stat-title">Bank: Volksbank in der Region</div>
</div>
</div>
<Text>
Alternativ kannst du uns das Geld auch per
<Link
text="PayPal"
href="https://www.paypal.com/donate/?hosted_button_id=T6VCLCHHSDX98"
/>
schicken. Allerdings würden wir uns über eine Überweisung mehr freuen, da dabei
weniger Gebühren anfallen und somit mehr bei uns ankommt.
</Text>
</Section>
<Section title="Fördermitglied werden" jumpId="supporter">
<Text>
Wenn du uns regelmäßig unterstützen möchtest, kannst du auch monatlich
spenden.
</Text>
<Text>
Hierfür bieten wir eine
<Link text="Fördermitgliedschaft" href="/join" />
an - schon ab 2€ im Monat!
</Text>
</Section>
</Page>

38
src/pages/imprint.md Normal file
View file

@ -0,0 +1,38 @@
---
layout: ../layouts/Markdown.astro
title: Impressum
---
# Impressum
**swablab e.V.**\
Katharinenstr. 1\
72250 Freudenstadt
**Kontakt**\
E-Mail: [info@swablab.de](mailto:info@swablab.de)\
Telefon: [+49 15679 232971](tel:+4915679232971)
**Vertreten durch**\
Fabian Haas\
Manuel Knodel
**Registereintrag**\
Amtsgericht Stuttgart\
VR 724909
Steuernummer: 42099/46775
## Urheberrecht
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten
unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung,
Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes
bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.
Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen
Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber
erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden
Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine
Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden
Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte
umgehend entfernen.

168
src/pages/index.astro Normal file
View file

@ -0,0 +1,168 @@
---
import Card from "../components/Card.astro"
import DirectusImg from "../components/DirectusImg.astro"
import Forest from "../components/Forest.astro"
import Link from "../components/Link.astro"
import Mastodon from "../components/Mastodon.astro"
import Section from "../components/Section.astro"
import Sponsors from "../components/Sponsors.astro"
import Text from "../components/Text.astro"
import Page from "../layouts/Page.astro"
---
<Page overlap>
<Forest>
<p class="text-5xl sm:text-7xl">swablab</p>
<p class="text-sm sm:text-md md:text-lg lg:text-xl">
Die offene Werkstatt im Kreis Freudenstadt
</p>
</Forest>
<a
href="#start"
class="absolute inset-x-0 bottom-16 flex justify-center animate-bounce"
>
<span class="icon-[ph--arrow-circle-down-duotone] h-16 w-16"></span>
</a>
<div class="text-center p-8 space-y-8 bg-secondary">
<div class="text-3xl md:text-5xl">Info für Besucher!</div>
<div class="max-w-4xl mx-auto flex flex-col gap-4">
Wir sind umgezogen! Du findest uns jetzt in der
<Link
text="Friedrichstraße 17 in Freudenstadt!"
href="https://www.openstreetmap.org/node/12119224010"
/>
</div>
</div>
<Section title="Offene Werkstatt?" jumpId="start">
<DirectusImg
src="753d211b-8b28-42cb-8ddd-0777911b3511"
alt="Kernpunkte des swablab"
widths={[900, 500, 200]}
format="webp"
/>
<Text>
Das swablab ist eine von vielen
<Link
text="Offenen Werkstätten"
href="https://www.offene-werkstaetten.org"
/>
in Deutschland.
</Text>
<Text>
In einer offenen Werkstatt stehen Werkzeuge, Materialien und Ressourcen
zur Verfügung, die von den Teilnehmern gemeinsam genutzt werden können.
</Text>
<Text>
Wir bieten dir einen offenen und zugänglichen Raum, um deine Fertigkeiten
zu erweitern, neue Interessen zu entdecken und deine Ideen zu
verwirklichen.
</Text>
</Section>
<Section title="Was interessiert dich?" jumpId="services">
<Card
name="Über uns"
image="19b641b1-3c6e-423c-8b3b-b4d1ac03fa5e"
link="/about"
small
>
Erfahre mehr über unseren Verein, die Mitglieder, und unsere Geschichte.
</Card>
<Card
name="Die Werkstatt"
image="86f806a0-e7d1-4705-9393-190a161c5528"
link="/lab"
small
>
Infos über unsere Ausstattung und wo du uns findest.
</Card>
<Card
name="3D Druck Service"
image="8ea26dab-96b7-4a99-afb2-6855f2bdb5ab"
link="/3d-print"
small
>
Erfahre, wie du ein 3D-Modell demnächst in deinen Händen halten kannst.
</Card>
<Card
name="Mitglied werden"
image="9ffb45f0-b0f8-4943-af9b-511f2b82b2ce"
link="/join"
small
>
Lerne die Vorzüge einer Mitgliedschaft kennen.
</Card>
<Card
name="Werkstattnutzung"
image="5cc8d4aa-70d4-47bb-8d90-33766245e67b"
link="/use"
small
>
Du kannst unsere Ausstattung auch ohne Mitgliedschaft verwenden.
</Card>
<Card
name="Spenden"
image="37111098-2687-4e36-9efd-baf7816fda70"
link="/donate"
small
>
So kannst du unseren gemeinnützigen Verein unterstützen.
</Card>
</Section>
<Section title="Was gibt's neues?" jumpId="news">
<Mastodon />
</Section>
<Section title="Unsere Sponsoren" jumpId="sponsors">
<Sponsors />
</Section>
<div class="fixed bottom-4 left-4 flex">
<a
class="btn btn-square btn-ghost z-10 discord-left"
href="https://swablab.de/discord"
>
<span class="w-6 h-6 icon-[ph--discord-logo-duotone]"></span>
</a>
<a
class="btn text-nowrap truncate p-0 -ml-3 order-last discord-right"
href="https://swablab.de/discord"
>
Tritt unserem Discord bei!
</a>
</div>
<style>
.discord-left {
background-color: #5865f2;
grid-column: 1;
grid-row: 1;
}
@keyframes discord-right {
0% {
width: 0%;
}
25% {
width: 100%;
}
75% {
width: 100%;
}
100% {
width: 0%;
}
}
.discord-right {
border-color: #5865f2;
animation: discord-right 5s;
width: 0%;
grid-column: 1;
grid-row: 1;
}
</style>
</Page>

50
src/pages/join.astro Normal file
View file

@ -0,0 +1,50 @@
---
import Link from "../components/Link.astro"
import Section from "../components/Section.astro"
import Text from "../components/Text.astro"
import { documents } from "../helper"
import Page from "../layouts/Page.astro"
---
<Page title="Mitglied werden">
<Section title="Mitglied werden" jumpId="join">
<Text>
Als Mitglied erhältst du Zugriff auf die Maschinen und Werkzeuge unserer
Werkstatt. Wir treffen wir uns mindestens wöchentlich, um Ideen
auszutauschen, Projekte zu realisieren und uns gut zu unterhalten. Zudem
bekommst du Zugang zu unserem Discord, auf dem wir uns auch außerhalb der
Werkstatt jederzeit gegenseitig helfen.
</Text>
<Text>
Wenn wir dein Interesse geweckt haben und du gerne Mitglied werden willst,
fülle einfach unseren
<Link text="Mitgliedsantrag" href={documents.Mitgliedsantrag} />
aus. Anschließend kannst du ihn einem unserer Vorstände in die Hand drücken
oder ihn an folgende Adresse schicken:
</Text>
<div class="stats shadow">
<div class="stat">
<div class="stat-value">swablab e.V.</div>
<div class="stat-title">Katharinenstr. 1</div>
<div class="stat-title">72250 Freudenstadt</div>
</div>
</div>
</Section>
<Section title="Fördermitglied werden" jumpId="supporter">
<Text>
Falls du kein volles Mitglied werden willst, uns aber trotzdem gerne
monatlich unterstützen möchtest, kannst du schon ab 2€ pro Monat
Fördermitglied werden.
</Text>
<Text>
Hierfür musst du wie oben den
<Link text="Mitgliedsantrag" href={documents.Mitgliedsantrag} />
ausfüllen und den Haken bei "Fördermitglied" oder "selbstgewählter Betrag"
setzen.
</Text>
</Section>
</Page>

101
src/pages/lab.astro Normal file
View file

@ -0,0 +1,101 @@
---
import Card from "../components/Card.astro"
import Contact from "../components/Contact.astro"
import Gallery from "../components/Gallery.astro"
import Map from "../components/Map.astro"
import Section from "../components/Section.astro"
import Text from "../components/Text.astro"
import Page from "../layouts/Page.astro"
---
<Page title="Die Werkstatt">
<Section title="Die Werkstatt" jumpId="lab">
<p>Willkommen in unserer Werkstatt - dem kreativen Herz unseres Vereins!</p>
<Gallery
images={[
"4c4bd68f-e9f3-46e1-b34d-4952db18fec1",
"86f806a0-e7d1-4705-9393-190a161c5528",
"f0843fed-14eb-441b-9761-819c3de82dcb",
"33f3b808-323b-40fb-9b7e-7c538f748364",
"d5c06cc2-729f-4a1f-9b12-9418f214eed4",
]}
/>
</Section>
<Section title="Werkzeugnutzung" jumpId="members">
<Text>
Unsere Werkstatt bietet eine breite Palette von hochwertigem Werkzeug, das
von unseren Mitgliedern genutzt werden kann. Egal, ob du an Holz-,
Elektronik- oder 3D-Druckprojekten arbeitest, wir stellen sicher, dass das
richtige Werkzeug für dich verfügbar ist.
</Text>
<Text>
In unserer Werkstatt herrscht eine offene und freundliche Atmosphäre. Es
ist der perfekte Ort, um Gleichgesinnte zu treffen, Ideen auszutauschen
und gemeinsam an Projekten zu arbeiten. Wir ermutigen die Mitglieder, sich
zu vernetzen und voneinander zu lernen.
</Text>
</Section>
<Section title="Auch für Neueinsteiger" jumpId="learning">
<Text>
Für neue Mitglieder, die sich mit der Werkstatt vertraut machen möchten,
bieten wir Ersteinweisungen an. Dies ist eine großartige Gelegenheit, um
sich mit der Anordnung der Werkzeuge, den Sicherheitsrichtlinien und den
grundlegenden Abläufen vertraut zu machen. Bei Interesse an einer
Ersteinweisung kannst du uns einfach ansprechen.
</Text>
<Text>
Unser Verein legt großen Wert auf Gemeinschaft und Zusammenarbeit. Wenn du
während deines Projekts Fragen hast oder Hilfe benötigst, stehen dir
unsere erfahrenen und freundlichen Mitglieder gerne zur Verfügung. Wir
glauben an den Austausch von Wissen und die Förderung von kreativen Ideen.
Also, zögere nicht, um Unterstützung zu bitten!
</Text>
</Section>
<Section title="Werkstattausstattung" jumpId="tools">
<Card name="Holzverarbeitung" image="5cc8d4aa-70d4-47bb-8d90-33766245e67b">
Von Abricht- und Dickenhobel und der Tischbohrmaschine über Kappsäge und
Tischkreissäge mit Fräsfunktion bis hin zu Handschleifern und einer
Flachdübelfräse, wir haben wirklich alles was man zur Holzverarbeitung
brauchen könnte.
</Card>
<Card name="3D-Drucker" image="23ce6c42-f2b3-45f2-b687-31404c610ca0">
Unsere hochauflösenden 3D-Drucker ermöglichen die präzise Umsetzung von
dreidimensionalen Modellen. Von Prototypen bis zu kreativen Kunstwerken -
hier kannst du deine Ideen in die Realität umsetzen.
</Card>
<Card name="Elektroniklabor" image="11f15c0b-d3fb-43cb-8baf-b5cccf9420a2">
Unsere Elektronikausstattung umfasst Lötstationen, Oszilloskope,
Multimeter und weitere Geräte für die Entwicklung und Reparatur
elektronischer Schaltungen. Hier kannst du in die Welt der Elektronik
eintauchen.
</Card>
<Card name="CNC-Maschine" image="4c4bd68f-e9f3-46e1-b34d-4952db18fec1">
Die CNC-Maschine ermöglicht präzise und automatisierte Fräs- und
Gravurarbeiten auf verschiedenen Materialien. Perfekt für die Umsetzung
von detaillierten Projekten.
</Card>
<Card name="Getränke/Snacks" image="19b641b1-3c6e-423c-8b3b-b4d1ac03fa5e">
In unserem Gemeinschaftsbereich findest du eine Auswahl an Getränken und
Snacks, damit du während deiner Projekte stets erfrischt bleibst.
</Card>
</Section>
<Section title="Hier findest du uns" jumpId="map">
<Map />
</Section>
<Contact>
<Text>
Egal, ob du an 3D-Druck, Elektronik, CNC-Arbeiten oder anderen Projekten
interessiert bist - wir haben die Werkzeuge und Ressourcen, um deine Ideen
Wirklichkeit werden zu lassen. Tauche ein und entdecke die Möglichkeiten!
</Text>
</Contact>
</Page>

207
src/pages/todo-edit.astro Normal file
View file

@ -0,0 +1,207 @@
---
import Base from "../layouts/Base.astro"
const statuses = [
{ id: "not_started", title: "Nicht gestartet" },
{ id: "in_progress", title: "In Arbeit" },
{ id: "hold", title: "Wartend" },
{ id: "done", title: "Abgeschlossen" },
]
const priorities = [
{ id: "low", title: "Niedrig" },
{ id: "normal", title: "Normal" },
{ id: "high", title: "Hoch" },
]
---
<Base title="Todo">
<div x-data="task" x-cloak>
<div class="navbar glass">
<div class="navbar-start">
<a class="btn btn-ghost text-xl">todo</a>
</div>
<div class="navbar-end space-x-2">
<a class="btn btn-success btn-square" x-on:click="submit()">
<span class="icon-[ph--floppy-disk] w-6 h-6"></span>
</a>
<a class="btn btn-error btn-square" href="/todo">
<span class="icon-[ph--x] w-6 h-6"></span>
</a>
</div>
</div>
<div x-show="error" class="p-4 flex flex-col space-y-4">
<div
x-show="!error.includes('permission to access collection')"
class="alert alert-error"
>
<span x-text="error"></span>
</div>
<a
x-show="error.includes('permission to access collection')"
class="btn btn-primary"
href="https://directus.swablab.de/auth/login/zitadel?redirect=https://swablab.de/todo"
>
Klicke hier, um dich anzumelden
</a>
</div>
<div class="drawer">
<input id="drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content">
<div class="p-4 grid md:grid-cols-3 gap-4">
<input x-model="title" class="input input-bordered md:col-span-3" />
<label class="form-control">
<div class="label">Status</div>
<select x-model="status" class="select select-bordered">
{
statuses.map((status) => (
<option value={status.id}>{status.title}</option>
))
}
</select>
</label>
<label class="form-control">
<div class="label">Priorität</div>
<select x-model="priority" class="select select-bordered">
{
priorities.map((prio) => (
<option value={prio.id}>{prio.title}</option>
))
}
</select>
</label>
<label class="form-control">
<div class="label">Fälligkeitdatum</div>
<input
x-model="date_due"
class="input input-bordered"
type="date"
/>
</label>
<div class="form-control md:col-span-3">
<div class="label space-x-2 justify-start">
<span>Verantwortliche</span>
<label for="drawer" class="btn btn-sm btn-primary btn-square">
+
</label>
</div>
<div class="flex space-x-2">
<template x-for="res in responsibles">
<button
class="btn"
x-on:click="responsibles = responsibles.filter(x => x != res)"
x-text="users.find(u => u.id == res)?.first_name ?? res"
>
</button>
</template>
</div>
</div>
<label class="form-control md:col-span-3 h-64">
<div class="label">Beschreibung</div>
<textarea
x-model="description"
class="textarea textarea-bordered md:col-span-3 h-64"
>
</textarea>
</label>
</div>
</div>
<div class="drawer-side">
<label for="drawer" class="drawer-overlay"></label>
<ul class="menu p-4 w-80 min-h-full bg-base-200 text-base-content">
<template x-for="user in users">
<li>
<a
x-on:click="responsibles.indexOf(user.id) == -1 && responsibles.push(user.id)"
x-text="user.first_name"
>
</a>
</li>
</template>
</ul>
</div>
</div>
</div>
</Base>
<script>
import Alpine from "alpinejs"
import { directus, Task } from "../helper"
Alpine.data("task", () => ({
id: globalThis.location.search.replace("?", ""),
title: "",
status: "not_started",
date_due: "",
priority: "normal",
description: "",
responsibles: [],
users: [],
error: "",
init() {
directus<{ id: string; first_name: string }[]>(
`users?fields=id,first_name&filter[provider][_neq]=default`,
).then(
(res) =>
(this.users = res.sort((a, b) =>
a.first_name.localeCompare(b.first_name),
)),
)
if (this.id != "") {
directus<Task>(
`items/tasks_general/${this.id}?fields=*,responsibles.directus_users_id.id`,
)
.then((res) => {
this.title = res.title
this.status = res.status
this.date_due = res.date_due?.substring(0, 10) ?? ""
this.priority = res.priority
this.description = res.description
this.responsibles = res.responsibles.map(
(x) => x.directus_users_id.id,
)
})
.catch((err) => (this.error = err))
}
},
submit() {
const task = {
id: this.id,
title: this.title,
status: this.status,
date_due: this.date_due != "" ? this.date_due : null,
priority: this.priority,
description: this.description,
responsibles: this.responsibles.map(
(r: { directus_users_id: string }) => ({
directus_users_id: r,
}),
),
}
if (this.id != "") {
directus(
`items/tasks_general/${this.id}`,
"PATCH",
JSON.stringify(task),
).then((_) => (document.location.href = "/todo"))
} else {
directus(`items/tasks_general`, "POST", JSON.stringify(task)).then(
(_) => (document.location.href = "/todo"),
)
}
},
}))
Alpine.start()
</script>

133
src/pages/todo.astro Normal file
View file

@ -0,0 +1,133 @@
---
import Base from "../layouts/Base.astro"
const statuses = [
{ id: "not_started", title: "Nicht gestartet" },
{ id: "in_progress", title: "In Arbeit" },
{ id: "hold", title: "Wartend" },
]
---
<Base title="Todo">
<div x-data="kanban" x-cloak>
<div class="navbar glass">
<div class="navbar-start">
<a class="btn btn-ghost text-xl">todo</a>
</div>
<div class="navbar-end">
<a class="btn btn-primary btn-square" href="/todo-edit">
<span class="icon-[ph--plus] w-6 h-6"></span>
</a>
</div>
</div>
<div x-show="error" class="p-4 flex flex-col space-y-4">
<div
x-show="!error.includes('permission to access collection')"
class="alert alert-error"
>
<span x-text="error"></span>
</div>
<a
x-show="error.includes('permission to access collection')"
class="btn btn-primary"
href="https://directus.swablab.de/auth/login/zitadel?redirect=https://swablab.de/todo"
>
Klicke hier, um dich anzumelden
</a>
</div>
<div class="p-4 flex flex-col space-y-2" x-show="!error">
<div class="join">
<input
class="input input-primary w-full join-item"
placeholder="Suchen nach Titel oder Mitglied..."
x-model="search"
/>
<button
class="btn btn-primary join-item"
x-on:click="search = `id:${me}`"
>
Nur meine Aufgaben
</button>
</div>
<ul class="menu grid lg:grid-cols-3 w-full bg-base-200 rounded-box">
{
statuses.map((status) => (
<li x-data={"tasklist('" + status.id + "')"} class="flex-auto">
<a class="pointer-events-none">{status.title}</a>
<ul>
<template x-for="task in sortedTasks()">
<li>
<a x-bind:href="`/todo-edit?${task.id}`">
<span
x-show="task.responsibles.find(x => x.directus_users_id.id == me)"
class="badge badge-xs badge-info"
/>
<span
x-bind:class="{'text-secondary': task.priority == 'high','text-neutral-500': task.priority == 'low'}"
x-text="task.title"
/>
<span
class="badge badge-xs"
x-bind:class="new Date(task.date_due ?? 0) < new Date() ? `badge-error`: `badge-ghost`"
x-show="task.date_due != null"
x-text="formatDate(task.date_due, `short`)"
/>
</a>
</li>
</template>
</ul>
</li>
))
}
</ul>
</div>
</div>
</Base>
<script>
import Alpine from "alpinejs"
import { directus, formatDate, Task } from "../helper"
Alpine.data("kanban", () => ({
me: "",
search: "",
error: "",
init() {
directus<{ id: string }>("users/me?fields=id")
.then((res) => (this.me = res.id))
.catch((err) => (this.error = err))
},
}))
Alpine.data("tasklist", (status: string) => ({
tasks: [],
formatDate: formatDate,
init() {
directus<Task[]>(
`items/tasks_general?filter[status][_eq]=${status}&fields=*,responsibles.directus_users_id.id,responsibles.directus_users_id.first_name`,
)
.then((res) => (this.tasks = res))
.catch((err) => (this.error = err))
},
sortedTasks() {
return (this.tasks as Task[])
.filter(
(a) =>
a.title.toLowerCase().includes(this.search.toLowerCase()) ||
a.responsibles
.map(
(x) =>
`${x.directus_users_id.first_name}|id:${x.directus_users_id.id}`,
)
.join("|")
.toLowerCase()
.includes(this.search.toLowerCase()),
)
.sort((a, b) => a.title.localeCompare(b.title))
.sort((a, b) => (a.date_due ?? "z").localeCompare(b.date_due ?? "z"))
},
}))
Alpine.start()
</script>

85
src/pages/use.astro Normal file
View file

@ -0,0 +1,85 @@
---
import Contact from "../components/Contact.astro"
import Link from "../components/Link.astro"
import List from "../components/List.astro"
import QA from "../components/QA.astro"
import Section from "../components/Section.astro"
import Text from "../components/Text.astro"
import Page from "../layouts/Page.astro"
---
<Page title="Nutzung für Nicht-Mitglieder">
<Section title="Nutzung für Nicht-Mitglieder" jumpId="use">
<Text>
Die Nutzung der Werkstatt steht allen Interessierten offen, auch
Nicht-Mitgliedern. Hier die wichtigsten Fragen und Antworten:
</Text>
<QA question="Was kostet die einmalige Werkstattnutzung?">
Für die Nutzung berechnen wir eine Tagesgebühr von 9€. Diese Gebühr
ermöglicht dir den Zugang zu unseren erstklassigen Einrichtungen und
Werkzeugen für einen Tag. Wenn du einfach mal vorbei schauen möchtest, um
dir die Werkstatt anzuschauen, musst du selbstverständlich keine
Tagesgebühr bezahlen. Melde dich aber bitte trotzdem per Mail bei uns an.
</QA>
<QA
question="Muss ich die Gebühr als Mitglied bezahlen, wenn ich in die Werkstatt möchte?"
>
Als Mitglied ist die Nutzung der Werkstatt für dich kostenlos. Du kannst
einfach zu den Öffnungszeiten vorbeikommen, um an deinen Projekten zu
arbeiten. Nur für Verbrauchsmaterialien können Kosten anfallen.
</QA>
<QA question="Wann habt ihr geöffnet?">
Die Werkstatt ist Mittwochs von 18:00 - 21:30 Uhr und Samstag nach
Absprache geöffnet
</QA>
<QA question="Welche Ausstattung steht mir zur Verfügung?">
Unsere Werkstatt ist mit einer breiten Palette von Werkzeugen und
Maschinen ausgestattet. Eine Teilübersicht findest du
<Link text="hier." href="/lab" />
Teile uns doch gerne vorher mit, was du vorhast, dass wir die benötigten Ressourcen
für dein Projekt bereitstellen können.
</QA>
<QA question="Darf ich ein oder zwei Freunde mitbringen?">
Gemeinsam ist besser - du darfst gerne Unterstützung für dein Projekt
mitbringen. Nur wenn ihr an unterschiedlichen Projekten arbeitet, sollte
jeder eine Gebühr zahlen.
</QA>
</Section>
<Section title="Anfragen" jumpId="howto">
<Text>
Um die Werkstatt zu nutzen, bitten wir dich, eine Anfrage per E-Mail an
<Link text="info@swablab.de" href="mailto:info@swablab.de" /> zu senden. In
deiner E-Mail solltest du uns mitteilen:
</Text>
<List
items={[
"Deinen vollständigen Namen",
"Das gewünschte Datum und die Uhrzeit der Nutzung",
"Eine kurze Beschreibung deines Projekts oder der Tätigkeiten, die du durchführen möchtest",
]}
/>
<Text>
Nach Erhalt deiner Anfrage werden wir uns bemühen, sie so schnell wie
möglich zu bearbeiten. In unserer Antwort erhältst du eine Bestätigung
deiner Anfrage sowie weitere Informationen zur Zahlung der Gebühr und zum
Zugang zur Werkstatt.
</Text>
</Section>
<Section title="Sicherheit und Regeln" jumpId="rules">
<Text>
Deine Sicherheit hat für uns höchste Priorität. Bevor du die Werkstatt
nutzt, erhältst du eine kurze Einweisung in die Sicherheitsrichtlinien und
Regeln. Wir bitten dich, diese sorgfältig zu beachten, um einen sicheren
und angenehmen Aufenthalt in unserer Einrichtung zu gewährleisten.
</Text>
</Section>
<Contact>
<Text>
Wir freuen uns darauf, deine kreativen Ideen in unserer Werkstatt zu
unterstützen! Viel Erfolg bei deinen Projekten!
</Text>
</Contact>
</Page>

36
src/style.css Normal file
View file

@ -0,0 +1,36 @@
input {
color-scheme: dark;
}
@font-face {
font-family: "Ubuntu";
font-style: normal;
font-display: block;
font-weight: 100 800;
src: url(@fontsource-variable/ubuntu-sans/files/ubuntu-sans-latin-wght-normal.woff2)
format("woff2-variations");
unicode-range:
U+0000-00FF,
U+0131,
U+0152-0153,
U+02BB-02BC,
U+02C6,
U+02DA,
U+02DC,
U+0304,
U+0308,
U+0329,
U+2000-206F,
U+2074,
U+20AC,
U+2122,
U+2191,
U+2193,
U+2212,
U+2215,
U+FEFF,
U+FFFD;
}
[x-cloak] {
display: none !important;
}

38
tailwind.config.ts Normal file
View file

@ -0,0 +1,38 @@
module.exports = {
content: ["./src/**/*"],
theme: {
extend: {
colors: {
primary: "#A3FFF1",
secondary: "#FF7F50",
},
fontFamily: {
sans: ["Ubuntu", "sans-serif"],
serif: ["Ubuntu", "sans-serif"],
mono: ["Ubuntu Mono", "monospace"],
},
},
},
daisyui: {
themes: [
{
swablab: {
primary: "#A3FFF1",
secondary: "#FF7F50",
neutral: "#404040",
"base-100": "#171717",
"base-200": "#262626",
"base-300": "#404040",
"--glass-reflex-opacity": "0.001",
},
},
],
logs: false,
},
plugins: [
require("@tailwindcss/typography"),
require("@iconify/tailwind").addDynamicIconSelectors(),
require("daisyui"),
],
}