Compare commits
29 Commits
20feee84a6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 387ef209ab | |||
| 3b27cbd194 | |||
| 61842ebc70 | |||
| 4e2418116f | |||
| c1fd535549 | |||
| 78f5da9cff | |||
| b283816713 | |||
| c77bf3e757 | |||
| 36b2053642 | |||
| c3898170fd | |||
| 4cc1b21c05 | |||
| e41334a7cc | |||
| 47743e9239 | |||
| d271378912 | |||
| 4cc6c4f210 | |||
| fde4adfad5 | |||
| 3e530e0ac5 | |||
| d8153ed619 | |||
| 3df25da009 | |||
| a181993ed5 | |||
| c289541cd5 | |||
| c9d067b1e3 | |||
| 4533f6cc3d | |||
| 4f12ebaa9a | |||
| a7d53ffe21 | |||
| c723e4919d | |||
| f8cbc60a60 | |||
| feec8ed314 | |||
| 2b64a21f16 |
167
.woodpecker.yml
167
.woodpecker.yml
@ -1,4 +1,85 @@
|
|||||||
steps:
|
steps:
|
||||||
|
audit_dependencies:
|
||||||
|
image: node:20
|
||||||
|
commands:
|
||||||
|
- npm install --package-lock-only
|
||||||
|
- npm audit --audit-level=moderate --json > audit-result.json 2>&1 || echo "Audit completed"
|
||||||
|
- npm audit --audit-level=moderate > audit-output.txt 2>&1 || echo "Audit completed"
|
||||||
|
when:
|
||||||
|
- branch: main
|
||||||
|
event: push
|
||||||
|
|
||||||
|
discord_notify_audit:
|
||||||
|
image: alpine:latest
|
||||||
|
environment:
|
||||||
|
DISCORD_WEBHOOK:
|
||||||
|
from_secret: discord_webhook
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache curl jq
|
||||||
|
- |
|
||||||
|
if [ -f audit-result.json ]; then
|
||||||
|
TOTAL=$(jq -r '.metadata.vulnerabilities.total // 0' audit-result.json 2>/dev/null || echo "0")
|
||||||
|
CRITICAL=$(jq -r '.metadata.vulnerabilities.critical // 0' audit-result.json 2>/dev/null || echo "0")
|
||||||
|
HIGH=$(jq -r '.metadata.vulnerabilities.high // 0' audit-result.json 2>/dev/null || echo "0")
|
||||||
|
MODERATE=$(jq -r '.metadata.vulnerabilities.moderate // 0' audit-result.json 2>/dev/null || echo "0")
|
||||||
|
LOW=$(jq -r '.metadata.vulnerabilities.low // 0' audit-result.json 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ] || [ "$MODERATE" -gt 0 ]; then
|
||||||
|
COLOR=16744448
|
||||||
|
STATUS="⚠️ Vulnerabilities Found"
|
||||||
|
else
|
||||||
|
COLOR=3066993
|
||||||
|
STATUS="✅ No Vulnerabilities"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f audit-output.txt ]; then
|
||||||
|
VULNS=$(head -50 audit-output.txt | tail -40 || echo "No details")
|
||||||
|
else
|
||||||
|
VULNS="No audit output available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$VULNS" > /tmp/vulns.txt
|
||||||
|
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg title "🔒 Security Audit - Build #${CI_BUILD_NUMBER}" \
|
||||||
|
--arg status "$STATUS" \
|
||||||
|
--arg total "$TOTAL" \
|
||||||
|
--arg critical "$CRITICAL" \
|
||||||
|
--arg high "$HIGH" \
|
||||||
|
--arg moderate "$MODERATE" \
|
||||||
|
--arg low "$LOW" \
|
||||||
|
--arg commit "${CI_COMMIT_SHA:0:7}" \
|
||||||
|
--rawfile details /tmp/vulns.txt \
|
||||||
|
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
|
||||||
|
--argjson color "$COLOR" \
|
||||||
|
'{
|
||||||
|
embeds: [{
|
||||||
|
title: $title,
|
||||||
|
description: $status,
|
||||||
|
color: $color,
|
||||||
|
fields: [
|
||||||
|
{ name: "Total", value: $total, inline: true },
|
||||||
|
{ name: "Critical", value: $critical, inline: true },
|
||||||
|
{ name: "High", value: $high, inline: true },
|
||||||
|
{ name: "Moderate", value: $moderate, inline: true },
|
||||||
|
{ name: "Low", value: $low, inline: true },
|
||||||
|
{ name: "Commit", value: ("`" + $commit + "`"), inline: true },
|
||||||
|
{ name: "Details", value: ("```\n" + ($details[:800]) + (if ($details | length) > 800 then "\n... (truncated)" else "" end) + "\n```"), inline: false }
|
||||||
|
],
|
||||||
|
timestamp: $timestamp
|
||||||
|
}]
|
||||||
|
}')
|
||||||
|
|
||||||
|
curl -H "Content-Type: application/json" -X POST \
|
||||||
|
-d "$PAYLOAD" "$DISCORD_WEBHOOK"
|
||||||
|
else
|
||||||
|
echo "No audit results found - listing workspace files:"
|
||||||
|
ls -la
|
||||||
|
fi
|
||||||
|
when:
|
||||||
|
- branch: main
|
||||||
|
event: push
|
||||||
|
|
||||||
deploy_frontend:
|
deploy_frontend:
|
||||||
image: node:20
|
image: node:20
|
||||||
environment:
|
environment:
|
||||||
@ -9,5 +90,87 @@ steps:
|
|||||||
- export PATH="$HOME/.fly/bin:$PATH"
|
- export PATH="$HOME/.fly/bin:$PATH"
|
||||||
- flyctl deploy --config fly.toml --app gallus-pub --remote-only
|
- flyctl deploy --config fly.toml --app gallus-pub --remote-only
|
||||||
when:
|
when:
|
||||||
branch: main
|
- branch: main
|
||||||
event: push
|
event: push
|
||||||
|
|
||||||
|
notify_success:
|
||||||
|
image: alpine:latest
|
||||||
|
environment:
|
||||||
|
DISCORD_WEBHOOK:
|
||||||
|
from_secret: discord_webhook
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache curl jq
|
||||||
|
- |
|
||||||
|
# Schreibe Commit-Message in Datei (sicher gegen Shell-Sonderzeichen)
|
||||||
|
printf '%s\n' "$CI_COMMIT_MESSAGE" > /tmp/commit_msg.txt
|
||||||
|
|
||||||
|
PAYLOAD=$(cat /tmp/commit_msg.txt | jq -Rs \
|
||||||
|
--arg title "✅ Build #${CI_BUILD_NUMBER} - Success" \
|
||||||
|
--arg repo "${CI_REPO}" \
|
||||||
|
--arg branch "${CI_COMMIT_BRANCH}" \
|
||||||
|
--arg commit "${CI_COMMIT_SHA:0:7}" \
|
||||||
|
--arg author "${CI_COMMIT_AUTHOR}" \
|
||||||
|
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
|
||||||
|
'. as $message | {
|
||||||
|
embeds: [{
|
||||||
|
title: $title,
|
||||||
|
description: "Build und Deployment erfolgreich abgeschlossen!",
|
||||||
|
color: 3066993,
|
||||||
|
fields: [
|
||||||
|
{ name: "Repository", value: $repo, inline: true },
|
||||||
|
{ name: "Branch", value: $branch, inline: true },
|
||||||
|
{ name: "Commit", value: ("`" + $commit + "`"), inline: true },
|
||||||
|
{ name: "Author", value: $author, inline: true },
|
||||||
|
{ name: "Commit Message", value: $message, inline: false }
|
||||||
|
],
|
||||||
|
timestamp: $timestamp
|
||||||
|
}]
|
||||||
|
}')
|
||||||
|
|
||||||
|
curl -H "Content-Type: application/json" -X POST \
|
||||||
|
-d "$PAYLOAD" "$DISCORD_WEBHOOK"
|
||||||
|
when:
|
||||||
|
- branch: main
|
||||||
|
event: push
|
||||||
|
status: success
|
||||||
|
|
||||||
|
notify_failure:
|
||||||
|
image: alpine:latest
|
||||||
|
environment:
|
||||||
|
DISCORD_WEBHOOK:
|
||||||
|
from_secret: discord_webhook
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache curl jq
|
||||||
|
- |
|
||||||
|
# Schreibe Commit-Message in Datei (sicher gegen Shell-Sonderzeichen)
|
||||||
|
printf '%s\n' "$CI_COMMIT_MESSAGE" > /tmp/commit_msg.txt
|
||||||
|
|
||||||
|
PAYLOAD=$(cat /tmp/commit_msg.txt | jq -Rs \
|
||||||
|
--arg title "❌ Build #${CI_BUILD_NUMBER} - Failure" \
|
||||||
|
--arg repo "${CI_REPO}" \
|
||||||
|
--arg branch "${CI_COMMIT_BRANCH}" \
|
||||||
|
--arg commit "${CI_COMMIT_SHA:0:7}" \
|
||||||
|
--arg author "${CI_COMMIT_AUTHOR}" \
|
||||||
|
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
|
||||||
|
'. as $message | {
|
||||||
|
embeds: [{
|
||||||
|
title: $title,
|
||||||
|
description: "Build oder Deployment ist fehlgeschlagen!",
|
||||||
|
color: 15158332,
|
||||||
|
fields: [
|
||||||
|
{ name: "Repository", value: $repo, inline: true },
|
||||||
|
{ name: "Branch", value: $branch, inline: true },
|
||||||
|
{ name: "Commit", value: ("`" + $commit + "`"), inline: true },
|
||||||
|
{ name: "Author", value: $author, inline: true },
|
||||||
|
{ name: "Commit Message", value: $message, inline: false }
|
||||||
|
],
|
||||||
|
timestamp: $timestamp
|
||||||
|
}]
|
||||||
|
}')
|
||||||
|
|
||||||
|
curl -H "Content-Type: application/json" -X POST \
|
||||||
|
-d "$PAYLOAD" "$DISCORD_WEBHOOK"
|
||||||
|
when:
|
||||||
|
- branch: main
|
||||||
|
event: push
|
||||||
|
status: failure
|
||||||
@ -14,7 +14,7 @@ primary_region = "ams"
|
|||||||
GIT_WORKSPACE_DIR = "/app/data/workspace"
|
GIT_WORKSPACE_DIR = "/app/data/workspace"
|
||||||
# Cross-site frontend and OAuth
|
# Cross-site frontend and OAuth
|
||||||
FRONTEND_URL = "https://gallus-pub.ch"
|
FRONTEND_URL = "https://gallus-pub.ch"
|
||||||
CORS_ORIGIN = "https://gallus-pub.ch"
|
CORS_ORIGIN = "https://gallus-pub.ch,https://www.gallus-pub.ch"
|
||||||
GITEA_REDIRECT_URI = "https://cms.gallus-pub.ch/api/auth/callback"
|
GITEA_REDIRECT_URI = "https://cms.gallus-pub.ch/api/auth/callback"
|
||||||
|
|
||||||
[http_service]
|
[http_service]
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
// Users table - stores Gitea user info for audit and access control
|
|
||||||
export const users = sqliteTable('users', {
|
export const users = sqliteTable('users', {
|
||||||
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
giteaId: text('gitea_id').notNull().unique(),
|
giteaId: text('gitea_id').notNull().unique(),
|
||||||
|
|||||||
@ -40,8 +40,24 @@ const fastify = Fastify({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Register plugins
|
// Register plugins
|
||||||
|
// Support multiple origins for CORS
|
||||||
|
const allowedOrigins = env.CORS_ORIGIN.split(',').map(o => o.trim());
|
||||||
fastify.register(cors, {
|
fastify.register(cors, {
|
||||||
origin: env.CORS_ORIGIN,
|
origin: (origin, cb) => {
|
||||||
|
// Allow requests with no origin (like mobile apps or curl)
|
||||||
|
if (!origin) {
|
||||||
|
return cb(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if origin is in allowed list
|
||||||
|
const isAllowed = allowedOrigins.includes(origin);
|
||||||
|
|
||||||
|
if (isAllowed) {
|
||||||
|
return cb(null, true);
|
||||||
|
} else {
|
||||||
|
return cb(null, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
credentials: true,
|
credentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { eq } from 'drizzle-orm';
|
|||||||
import { GiteaService } from '../services/gitea.service.js';
|
import { GiteaService } from '../services/gitea.service.js';
|
||||||
import { env } from '../config/env.js';
|
import { env } from '../config/env.js';
|
||||||
|
|
||||||
// Use explicit JSON schema for Fastify route validation to avoid provider issues
|
|
||||||
const callbackQueryJsonSchema = {
|
const callbackQueryJsonSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['code', 'state'],
|
required: ['code', 'state'],
|
||||||
|
|||||||
152
backend/src/routes/banners.ts
Normal file
152
backend/src/routes/banners.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
|
import { db } from '../config/database.js';
|
||||||
|
import { banners } from '../db/schema.js';
|
||||||
|
import { eq, and, lte, gte, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
const bannerBodyJsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: ['text', 'startDate', 'endDate'],
|
||||||
|
properties: {
|
||||||
|
text: { type: 'string' },
|
||||||
|
startDate: { type: 'string' },
|
||||||
|
endDate: { type: 'string' },
|
||||||
|
isActive: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const bannersRoute: FastifyPluginAsync = async (fastify) => {
|
||||||
|
|
||||||
|
// Get active banner (public endpoint)
|
||||||
|
fastify.get('/banners/active', async (request, reply) => {
|
||||||
|
// Use local date to avoid timezone issues
|
||||||
|
const now = new Date();
|
||||||
|
const today = new Date(now.getTime() - (now.getTimezoneOffset() * 60000))
|
||||||
|
.toISOString()
|
||||||
|
.split('T')[0]; // YYYY-MM-DD
|
||||||
|
|
||||||
|
const [activeBanner] = await db
|
||||||
|
.select()
|
||||||
|
.from(banners)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(banners.isActive, true),
|
||||||
|
lte(banners.startDate, today),
|
||||||
|
gte(banners.endDate, today)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(desc(banners.createdAt))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!activeBanner) {
|
||||||
|
return { banner: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
banner: {
|
||||||
|
id: activeBanner.id,
|
||||||
|
text: activeBanner.text,
|
||||||
|
startDate: activeBanner.startDate,
|
||||||
|
endDate: activeBanner.endDate,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all banners (admin only)
|
||||||
|
fastify.get('/banners', {
|
||||||
|
preHandler: [fastify.authenticate],
|
||||||
|
}, async (request, reply) => {
|
||||||
|
const allBanners = await db.select().from(banners);
|
||||||
|
|
||||||
|
return {
|
||||||
|
banners: allBanners.map((b: any) => ({
|
||||||
|
id: b.id,
|
||||||
|
text: b.text,
|
||||||
|
startDate: b.startDate,
|
||||||
|
endDate: b.endDate,
|
||||||
|
isActive: b.isActive,
|
||||||
|
createdAt: b.createdAt,
|
||||||
|
updatedAt: b.updatedAt,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create banner (admin only)
|
||||||
|
fastify.post('/banners', {
|
||||||
|
schema: {
|
||||||
|
body: bannerBodyJsonSchema,
|
||||||
|
},
|
||||||
|
preHandler: [fastify.authenticate],
|
||||||
|
}, async (request, reply) => {
|
||||||
|
const { text, startDate, endDate, isActive = true } = request.body as any;
|
||||||
|
|
||||||
|
const [newBanner] = await db
|
||||||
|
.insert(banners)
|
||||||
|
.values({
|
||||||
|
text,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
isActive,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return {
|
||||||
|
banner: {
|
||||||
|
id: newBanner.id,
|
||||||
|
text: newBanner.text,
|
||||||
|
startDate: newBanner.startDate,
|
||||||
|
endDate: newBanner.endDate,
|
||||||
|
isActive: newBanner.isActive,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update banner (admin only)
|
||||||
|
fastify.put('/banners/:id', {
|
||||||
|
schema: {
|
||||||
|
body: bannerBodyJsonSchema,
|
||||||
|
},
|
||||||
|
preHandler: [fastify.authenticate],
|
||||||
|
}, async (request, reply) => {
|
||||||
|
const { id } = request.params as { id: string };
|
||||||
|
const { text, startDate, endDate, isActive } = request.body as any;
|
||||||
|
|
||||||
|
const [updated] = await db
|
||||||
|
.update(banners)
|
||||||
|
.set({
|
||||||
|
text,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
isActive,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(banners.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!updated) {
|
||||||
|
return reply.code(404).send({ error: 'Banner not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
banner: {
|
||||||
|
id: updated.id,
|
||||||
|
text: updated.text,
|
||||||
|
startDate: updated.startDate,
|
||||||
|
endDate: updated.endDate,
|
||||||
|
isActive: updated.isActive,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete banner (admin only)
|
||||||
|
fastify.delete('/banners/:id', {
|
||||||
|
preHandler: [fastify.authenticate],
|
||||||
|
}, async (request, reply) => {
|
||||||
|
const { id } = request.params as { id: string };
|
||||||
|
|
||||||
|
await db.delete(banners).where(eq(banners.id, id));
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default bannersRoute;
|
||||||
@ -43,6 +43,7 @@ export class FileGeneratorService {
|
|||||||
|
|
||||||
return `---
|
return `---
|
||||||
import Layout from "../components/Layout.astro";
|
import Layout from "../components/Layout.astro";
|
||||||
|
import Banner from "../components/Banner.astro";
|
||||||
import Hero from "../components/Hero.astro";
|
import Hero from "../components/Hero.astro";
|
||||||
import Welcome from "../components/Welcome.astro";
|
import Welcome from "../components/Welcome.astro";
|
||||||
import EventsGrid from "../components/EventsGrid.astro";
|
import EventsGrid from "../components/EventsGrid.astro";
|
||||||
@ -62,6 +63,7 @@ ${imagesCode}
|
|||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
\t<Hero id="hero" />
|
\t<Hero id="hero" />
|
||||||
|
\t<Banner />
|
||||||
\t<Welcome id="welcome" />
|
\t<Welcome id="welcome" />
|
||||||
\t<EventsGrid id="events" events={events} />
|
\t<EventsGrid id="events" events={events} />
|
||||||
\t<ImageCarousel id="gallery" images={images} />
|
\t<ImageCarousel id="gallery" images={images} />
|
||||||
|
|||||||
1092
package-lock.json
generated
1092
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
BIN
public/images/events/mjbg64v5-ii50hf.jpeg
Normal file
BIN
public/images/events/mjbg64v5-ii50hf.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
public/images/events/mjbgwbzv-n60vrw.jpeg
Normal file
BIN
public/images/events/mjbgwbzv-n60vrw.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
public/images/events/mjbgxwyk-ygcymt.jpeg
Normal file
BIN
public/images/events/mjbgxwyk-ygcymt.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
BIN
public/images/events/mk6wdnz2-rpxzvl.jpeg
Normal file
BIN
public/images/events/mk6wdnz2-rpxzvl.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
40
src/components/Banner.astro
Normal file
40
src/components/Banner.astro
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
// src/components/Banner.astro
|
||||||
|
import "../styles/components/Banner.css"
|
||||||
|
---
|
||||||
|
|
||||||
|
<div id="banner-container"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const API_BASE = 'https://cms.gallus-pub.ch';
|
||||||
|
|
||||||
|
async function loadBanner() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/banners/active`);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.banner) {
|
||||||
|
const container = document.getElementById('banner-container');
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="banner-wrapper">
|
||||||
|
<div class="banner container">
|
||||||
|
<p>${data.banner.text}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch banner:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load banner when DOM is ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', loadBanner);
|
||||||
|
} else {
|
||||||
|
loadBanner();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,8 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../components/Layout.astro";
|
import Layout from "../components/Layout.astro";
|
||||||
|
import Banner from "../components/Banner.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
|
<Banner />
|
||||||
|
|
||||||
<h1>Gallery</h1>
|
<h1>Gallery</h1>
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../components/Layout.astro";
|
import Layout from "../components/Layout.astro";
|
||||||
|
import Banner from "../components/Banner.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
|
<Banner />
|
||||||
|
|
||||||
<h1>Openings</h1>
|
<h1>Openings</h1>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../components/Layout.astro";
|
import Layout from "../components/Layout.astro";
|
||||||
import Hero from "../components/Hero.astro";
|
|
||||||
import Banner from "../components/Banner.astro";
|
import Banner from "../components/Banner.astro";
|
||||||
|
import Hero from "../components/Hero.astro";
|
||||||
import Welcome from "../components/Welcome.astro";
|
import Welcome from "../components/Welcome.astro";
|
||||||
import EventsGrid from "../components/EventsGrid.astro";
|
import EventsGrid from "../components/EventsGrid.astro";
|
||||||
import Drinks from "../components/Drinks.astro";
|
import Drinks from "../components/Drinks.astro";
|
||||||
@ -28,28 +28,13 @@ Plätze sind begrenzt! Jetzt reservieren unter 🍀WA 077 232 27 70
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: "/images/events/mj67ssjo-sp3i0e.jpeg",
|
image: "/images/events/mjbgxwyk-ygcymt.jpeg",
|
||||||
title: "Adventskalender",
|
title: "Schlager Flyer",
|
||||||
date: "2025-12-03",
|
date: "2026-01-15",
|
||||||
description: `
|
description: `
|
||||||
Jeden Tag neue Überraschungen!
|
Schalger- HüttenzauberKARAOKE geht in die 2.Runde!
|
||||||
Check unsere Social Media Stories oder komm am besten gleich vorbei!
|
Eintritt ist frei!
|
||||||
`,
|
Plätze reservieren unter WA 077 232 27 70
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/events/miyxej2c-7l8end.jpeg",
|
|
||||||
title: "Ferien Flyer",
|
|
||||||
date: "2026-01-02",
|
|
||||||
description: `
|
|
||||||
Wir sind ab 02.01.2026 wieder wie gewohnt für euch da! 🍀Für Anfragen WA 077 232 27 70 Antwort innerhalb 48h
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/events/miyxfgm1-jg8gqi.jpeg",
|
|
||||||
title: "New Year Flyer",
|
|
||||||
date: "2026-01-02",
|
|
||||||
description: `
|
|
||||||
-
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -59,6 +44,16 @@ Check unsere Social Media Stories oder komm am besten gleich vorbei!
|
|||||||
description: `
|
description: `
|
||||||
Celtic Folk Night im Gallus Pub!✨🌿20:30Uhr Eintritt ist Frei/Hutkollekte. Reservation via WA 077 232 27 70
|
Celtic Folk Night im Gallus Pub!✨🌿20:30Uhr Eintritt ist Frei/Hutkollekte. Reservation via WA 077 232 27 70
|
||||||
`,
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/events/mk6wdnz2-rpxzvl.jpeg",
|
||||||
|
title: "Pg Petricca - LIVE",
|
||||||
|
date: "2026-03-20",
|
||||||
|
description: `
|
||||||
|
LIVE Musik mit Pg Petricca! - Folk & Blues.
|
||||||
|
Eintritt ist Frei / Hutkollekte
|
||||||
|
Reservation unter 🍀WA 077 232 27 70
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
26
src/styles/components/Banner.css
Normal file
26
src/styles/components/Banner.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.banner-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--color-orange1);
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
max-width: var(--container-max-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--padding-horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner p {
|
||||||
|
color: #000;
|
||||||
|
font-size: var(--font-size-small-medium);
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.banner p {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user