fixed some domain issues
All checks were successful
Deploy SingleChat / deploy (push) Successful in 24s

This commit is contained in:
Torsten Schulz (local)
2026-06-16 12:47:58 +02:00
parent e279215b85
commit 8c9a600645
13 changed files with 61 additions and 42 deletions

View File

@@ -37,12 +37,11 @@ Die Apache-Konfiguration sollte bereits vorhanden sein. Stelle sicher, dass sie
```apache ```apache
<VirtualHost *:443> <VirtualHost *:443>
ServerName ypchat.net ServerName ypchat.net
ServerAlias www.ypchat.net
# SSL-Konfiguration # SSL-Konfiguration
Include /etc/letsencrypt/options-ssl-apache.conf Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/www.ypchat.net/fullchain.pem SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www.ypchat.net/privkey.pem SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem
# Reverse Proxy zu Node.js # Reverse Proxy zu Node.js
ProxyPreserveHost On ProxyPreserveHost On
@@ -55,6 +54,17 @@ Die Apache-Konfiguration sollte bereits vorhanden sein. Stelle sicher, dass sie
RewriteCond %{HTTP:Connection} upgrade [NC] RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:4000/$1" [P,L] RewriteRule ^/?(.*) "ws://localhost:4000/$1" [P,L]
</VirtualHost> </VirtualHost>
<VirtualHost *:443>
ServerName www.ypchat.net
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem
RewriteEngine On
RewriteRule ^ https://ypchat.net%{REQUEST_URI} [R=301,L]
</VirtualHost>
``` ```
Wichtig: Die WebSocket-Rewrite-Regeln sind für Socket.IO erforderlich! Wichtig: Die WebSocket-Rewrite-Regeln sind für Socket.IO erforderlich!

View File

@@ -2,7 +2,7 @@
## 1) Host-/TLS-Konsistenz (Apex und www) ## 1) Host-/TLS-Konsistenz (Apex und www)
- [x] App-Fallback-Redirect in Node aktiv (`ypchat.net` + HTTP -> `https://www.ypchat.net`). - [x] App-Fallback-Redirect in Node aktiv (kanonischer Host: `https://ypchat.net`).
### Zertifikatserstellung (Let's Encrypt / Certbot, Apache) ### Zertifikatserstellung (Let's Encrypt / Certbot, Apache)
@@ -20,10 +20,10 @@
### Apache-Redirects (kanonischer Host + HTTPS) ### Apache-Redirects (kanonischer Host + HTTPS)
Empfohlene Logik: Empfohlene Logik:
- `http://ypchat.net/*` -> `https://www.ypchat.net/*` (301) - `http://ypchat.net/*` -> `https://ypchat.net/*` (301)
- `http://www.ypchat.net/*` -> `https://www.ypchat.net/*` (301) - `http://www.ypchat.net/*` -> `https://ypchat.net/*` (301)
- `https://ypchat.net/*` -> `https://www.ypchat.net/*` (301) - `https://www.ypchat.net/*` -> `https://ypchat.net/*` (301)
- Nur `https://www.ypchat.net/*` liefert `200` - Nur `https://ypchat.net/*` liefert `200`
Beispiel (VirtualHost fuer Port 80): Beispiel (VirtualHost fuer Port 80):
@@ -32,32 +32,32 @@ Beispiel (VirtualHost fuer Port 80):
ServerName ypchat.net ServerName ypchat.net
ServerAlias www.ypchat.net ServerAlias www.ypchat.net
RewriteEngine On RewriteEngine On
RewriteRule ^ https://www.ypchat.net%{REQUEST_URI} [R=301,L] RewriteRule ^ https://ypchat.net%{REQUEST_URI} [R=301,L]
</VirtualHost> </VirtualHost>
``` ```
Beispiel (VirtualHost fuer `https://ypchat.net`): Beispiel (VirtualHost fuer `https://www.ypchat.net`):
```apache ```apache
<VirtualHost *:443> <VirtualHost *:443>
ServerName ypchat.net ServerName www.ypchat.net
SSLEngine on SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem
RewriteEngine On RewriteEngine On
RewriteRule ^ https://www.ypchat.net%{REQUEST_URI} [R=301,L] RewriteRule ^ https://ypchat.net%{REQUEST_URI} [R=301,L]
</VirtualHost> </VirtualHost>
``` ```
Verifikation: Verifikation:
- `curl -I https://ypchat.net/` -> `301 Location: https://www.ypchat.net/` - `curl -I https://ypchat.net/` -> `200`
- `curl -I https://www.ypchat.net/` -> `200` - `curl -I https://www.ypchat.net/` -> `301 Location: https://ypchat.net/`
- Browser ohne TLS-Warnung fuer beide Hosts - Browser ohne TLS-Warnung fuer beide Hosts
## 3) Search Console / Reindexing ## 3) Search Console / Reindexing
- [ ] In Google Search Console `https://www.ypchat.net` als Hauptproperty nutzen. - [ ] In Google Search Console `https://ypchat.net` als Hauptproperty nutzen.
- [ ] Sitemap neu einreichen: `https://www.ypchat.net/sitemap.xml`. - [ ] Sitemap neu einreichen: `https://ypchat.net/sitemap.xml`.
- [ ] Live-Tests ausfuehren fuer: - [ ] Live-Tests ausfuehren fuer:
- [ ] `/` - [ ] `/`
- [ ] `/partners` - [ ] `/partners`

View File

@@ -112,7 +112,7 @@ private val Primary600 = Color(0xFF2F6F46)
private val Primary500 = Color(0xFF3D8654) private val Primary500 = Color(0xFF3D8654)
private val Primary100 = Color(0xFFE7F1EA) private val Primary100 = Color(0xFFE7F1EA)
private val Danger = Color(0xFFA24040) private val Danger = Color(0xFFA24040)
private const val PrivacyPolicyUrl = "https://ypchat.net/datenschutz" private const val PrivacyPolicyUrl = "https://www.ypchat.net/datenschutz"
private data class GenderOption(val value: String, val label: String) private data class GenderOption(val value: String, val label: String)
private data class SmileyItem(val token: String, val hexCode: String, val tooltip: String) private data class SmileyItem(val token: String, val hexCode: String, val tooltip: String)

View File

@@ -105,7 +105,7 @@
<string name="faq_body">Wähle einen Nicknamen, gib deine Profildaten an und starte den Chat. Teile keine sensiblen Daten wie Telefonnummern, Adressen, Passwörter oder Zahlungsinformationen. Du kannst Bilder senden, Benutzer blockieren und Feedback für ernste Vorfälle nutzen.</string> <string name="faq_body">Wähle einen Nicknamen, gib deine Profildaten an und starte den Chat. Teile keine sensiblen Daten wie Telefonnummern, Adressen, Passwörter oder Zahlungsinformationen. Du kannst Bilder senden, Benutzer blockieren und Feedback für ernste Vorfälle nutzen.</string>
<string name="rules_body">Keine Beleidigungen, Hassrede, illegalen Inhalte, Spam oder unerwünschte Belästigung. Sende nur Bilder, die du teilen darfst, und respektiere die Privatsphäre anderer.</string> <string name="rules_body">Keine Beleidigungen, Hassrede, illegalen Inhalte, Spam oder unerwünschte Belästigung. Sende nur Bilder, die du teilen darfst, und respektiere die Privatsphäre anderer.</string>
<string name="safety_body">Nutze einen Nicknamen, der dich nicht identifiziert. Teile keine privaten Kontakt- oder Zahlungsdaten. Sei vorsichtig mit Links von Unbekannten und beende Gespräche, die sich falsch anfühlen. Nutze Blockieren und Feedback bei schweren Vorfällen.</string> <string name="safety_body">Nutze einen Nicknamen, der dich nicht identifiziert. Teile keine privaten Kontakt- oder Zahlungsdaten. Sei vorsichtig mit Links von Unbekannten und beende Gespräche, die sich falsch anfühlen. Nutze Blockieren und Feedback bei schweren Vorfällen.</string>
<string name="privacy_body">SingleChat verarbeitet den von dir gewählten Nickname, Profildaten wie Alter, Geschlecht und Land, Chat-Nachrichten, von dir aktiv gesendete Bilder, Feedback-Nachrichten sowie technisch notwendige Sitzungsdaten. Die Android-App fragt den Kamerazugriff nur an, wenn du in der App aktiv ein Foto aufnehmen möchtest. Die vollständige Datenschutzerklärung für Website und App ist auf ypchat.net veröffentlicht.</string> <string name="privacy_body">SingleChat verarbeitet den von dir gewählten Nickname, Profildaten wie Alter, Geschlecht und Land, Chat-Nachrichten, von dir aktiv gesendete Bilder, Feedback-Nachrichten sowie technisch notwendige Sitzungsdaten. Die Android-App fragt den Kamerazugriff nur an, wenn du in der App aktiv ein Foto aufnehmen möchtest. Die vollständige Datenschutzerklärung für Website und App ist auf www.ypchat.net veröffentlicht.</string>
<string name="privacy_open_policy">Datenschutzerklärung öffnen</string> <string name="privacy_open_policy">Datenschutzerklärung öffnen</string>
<string name="imprint_body">Torsten Schulz, Friedrich-Stampfer-Str. 21, 60437 Frankfurt. Kontakt: tsschulz@tsschulz.de. Für externe Links sind deren Betreiber verantwortlich.</string> <string name="imprint_body">Torsten Schulz, Friedrich-Stampfer-Str. 21, 60437 Frankfurt. Kontakt: tsschulz@tsschulz.de. Für externe Links sind deren Betreiber verantwortlich.</string>
</resources> </resources>

View File

@@ -105,7 +105,7 @@
<string name="faq_body">Choose a nickname, enter your profile details and start chatting. Do not share sensitive data like phone numbers, addresses, passwords or payment information. You can send images, block users and use feedback for serious issues.</string> <string name="faq_body">Choose a nickname, enter your profile details and start chatting. Do not share sensitive data like phone numbers, addresses, passwords or payment information. You can send images, block users and use feedback for serious issues.</string>
<string name="rules_body">No insults, hate speech, illegal content, spam or unwanted harassment. Only send images you are allowed to share and respect the privacy of others.</string> <string name="rules_body">No insults, hate speech, illegal content, spam or unwanted harassment. Only send images you are allowed to share and respect the privacy of others.</string>
<string name="safety_body">Use a nickname that does not identify you. Do not share private contact or payment data. Be careful with links from strangers and end conversations that feel wrong. Use block and feedback for serious incidents.</string> <string name="safety_body">Use a nickname that does not identify you. Do not share private contact or payment data. Be careful with links from strangers and end conversations that feel wrong. Use block and feedback for serious incidents.</string>
<string name="privacy_body">SingleChat processes the nickname you choose, profile details such as age, gender and country, chat messages, images you actively send, feedback messages and technically necessary session data. The Android app requests camera access only if you actively want to take a photo in the app. The full privacy policy for website and app is published on ypchat.net.</string> <string name="privacy_body">SingleChat processes the nickname you choose, profile details such as age, gender and country, chat messages, images you actively send, feedback messages and technically necessary session data. The Android app requests camera access only if you actively want to take a photo in the app. The full privacy policy for website and app is published on www.ypchat.net.</string>
<string name="privacy_open_policy">Open privacy policy</string> <string name="privacy_open_policy">Open privacy policy</string>
<string name="imprint_body">Torsten Schulz, Friedrich-Stampfer-Str. 21, 60437 Frankfurt. Contact: tsschulz@tsschulz.de. External links are the responsibility of their operators.</string> <string name="imprint_body">Torsten Schulz, Friedrich-Stampfer-Str. 21, 60437 Frankfurt. Contact: tsschulz@tsschulz.de. External links are the responsibility of their operators.</string>
</resources> </resources>

View File

@@ -1,12 +1,11 @@
<IfModule mod_ssl.c> <IfModule mod_ssl.c>
<VirtualHost *:443> <VirtualHost *:443>
ServerName ypchat.net ServerName ypchat.net
ServerAlias www.ypchat.net
# SSL-Konfiguration # SSL-Konfiguration
Include /etc/letsencrypt/options-ssl-apache.conf Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/www.ypchat.net/fullchain.pem SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www.ypchat.net/privkey.pem SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem
# DocumentRoot (nur für statische Dateien wie ads.txt) # DocumentRoot (nur für statische Dateien wie ads.txt)
DocumentRoot /opt/ypchat/docroot DocumentRoot /opt/ypchat/docroot
@@ -64,5 +63,15 @@
RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443" RequestHeader set X-Forwarded-Port "443"
</VirtualHost> </VirtualHost>
</IfModule>
<VirtualHost *:443>
ServerName www.ypchat.net
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem
RewriteEngine On
RewriteRule ^ https://ypchat.net%{REQUEST_URI} [R=301,L]
</VirtualHost>
</IfModule>

View File

@@ -1,19 +1,19 @@
<IfModule mod_ssl.c> <IfModule mod_ssl.c>
# 1) Apex-Domain (ypchat.net) liefert NUR Redirect auf www # 1) www-Domain liefert NUR Redirect auf Apex
<VirtualHost *:443> <VirtualHost *:443>
ServerName ypchat.net ServerName www.ypchat.net
Include /etc/letsencrypt/options-ssl-apache.conf Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem SSLCertificateFile /etc/letsencrypt/live/ypchat.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem SSLCertificateKeyFile /etc/letsencrypt/live/ypchat.net/privkey.pem
RewriteEngine On RewriteEngine On
RewriteRule ^ https://www.ypchat.net%{REQUEST_URI} [R=301,L] RewriteRule ^ https://ypchat.net%{REQUEST_URI} [R=301,L]
</VirtualHost> </VirtualHost>
# 2) Canonical Host (www.ypchat.net) liefert die App # 2) Canonical Host (ypchat.net) liefert die App
<VirtualHost *:443> <VirtualHost *:443>
ServerName www.ypchat.net ServerName ypchat.net
# SSL-Konfiguration # SSL-Konfiguration
Include /etc/letsencrypt/options-ssl-apache.conf Include /etc/letsencrypt/options-ssl-apache.conf

View File

@@ -14,8 +14,8 @@
<meta property="og:title" content="SingleChat - Kostenloser Single Chat ohne Anmeldung"> <meta property="og:title" content="SingleChat - Kostenloser Single Chat ohne Anmeldung">
<meta property="og:description" content="Kostenlos chatten, Singles kennenlernen und Bilder sicher austauschen."> <meta property="og:description" content="Kostenlos chatten, Singles kennenlernen und Bilder sicher austauschen.">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="https://ypchat.net/"> <meta property="og:url" content="https://www.ypchat.net/">
<meta property="og:image" content="https://ypchat.net/static/favicon.png"> <meta property="og:image" content="https://www.ypchat.net/static/favicon.png">
<meta property="og:site_name" content="SingleChat"> <meta property="og:site_name" content="SingleChat">
<meta property="og:locale" content="de_DE"> <meta property="og:locale" content="de_DE">
@@ -23,16 +23,16 @@
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="SingleChat - Kostenloser Single Chat ohne Anmeldung"> <meta name="twitter:title" content="SingleChat - Kostenloser Single Chat ohne Anmeldung">
<meta name="twitter:description" content="Kostenlos chatten, Singles kennenlernen und Bilder sicher austauschen."> <meta name="twitter:description" content="Kostenlos chatten, Singles kennenlernen und Bilder sicher austauschen.">
<meta name="twitter:image" content="https://ypchat.net/static/favicon.png"> <meta name="twitter:image" content="https://www.ypchat.net/static/favicon.png">
<!-- Canonical URL --> <!-- Canonical URL -->
<link rel="canonical" href="https://ypchat.net/"> <link rel="canonical" href="https://www.ypchat.net/">
<!-- App Icon / Favicon --> <!-- App Icon / Favicon -->
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="shortcut icon" href="/favicon.ico"> <link rel="shortcut icon" href="/favicon.ico">
<link rel="icon" type="image/png" href="/appicon.png"> <link rel="icon" type="image/png" href="/appicon.png">
<script type="application/ld+json" id="seo-json-ld">{"@context":"https://schema.org","@type":"WebSite","name":"SingleChat","alternateName":"YPChat","url":"https://ypchat.net/","description":"Kostenloser Single Chat ohne lange Registrierung: Profil starten, Singles kennenlernen, privat chatten und Bilder sicher austauschen.","inLanguage":"de-DE"}</script> <script type="application/ld+json" id="seo-json-ld">{"@context":"https://schema.org","@type":"WebSite","name":"SingleChat","alternateName":"YPChat","url":"https://www.ypchat.net/","description":"Kostenloser Single Chat ohne lange Registrierung: Profil starten, Singles kennenlernen, privat chatten und Bilder sicher austauschen.","inLanguage":"de-DE"}</script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -14,7 +14,7 @@ import GuideSafetyView from '../views/GuideSafetyView.vue';
import GuideRedFlagsView from '../views/GuideRedFlagsView.vue'; import GuideRedFlagsView from '../views/GuideRedFlagsView.vue';
import SeoLandingView from '../views/SeoLandingView.vue'; import SeoLandingView from '../views/SeoLandingView.vue';
const SITE_URL = 'https://ypchat.net'; const SITE_URL = 'https://www.ypchat.net';
const DEFAULT_IMAGE = `${SITE_URL}/static/favicon.png`; const DEFAULT_IMAGE = `${SITE_URL}/static/favicon.png`;
const SUPPORTED_LOCALES = ['de', 'en', 'fr', 'es', 'it', 'ja', 'zh', 'th', 'tl']; const SUPPORTED_LOCALES = ['de', 'en', 'fr', 'es', 'it', 'ja', 'zh', 'th', 'tl'];
const LOCALIZED_HOME_META = { const LOCALIZED_HOME_META = {

View File

@@ -15,7 +15,7 @@
<h2>Datenschutzerklärung für Website und App</h2> <h2>Datenschutzerklärung für Website und App</h2>
<p> <p>
Diese Datenschutzerklärung gilt für die Website und die Android-App von SingleChat unter Diese Datenschutzerklärung gilt für die Website und die Android-App von SingleChat unter
der Domain <strong>ypchat.net</strong>. der Domain <strong>www.ypchat.net</strong>.
</p> </p>
<p> <p>
Sie beschreibt die Verarbeitung personenbezogener Daten im Zusammenhang mit der Nutzung der Sie beschreibt die Verarbeitung personenbezogener Daten im Zusammenhang mit der Nutzung der

View File

@@ -13,7 +13,7 @@
<main class="content-page"> <main class="content-page">
<section class="landing-hero"> <section class="landing-hero">
<p class="eyebrow">Single-Chat.net</p> <p class="eyebrow">YPChat</p>
<h2>{{ landing.heading }}</h2> <h2>{{ landing.heading }}</h2>
<p>{{ landing.intro }}</p> <p>{{ landing.intro }}</p>
<div class="action-row"> <div class="action-row">

View File

@@ -20,12 +20,12 @@ const server = createServer(app);
const NODE_ENV = process.env.NODE_ENV || 'development'; const NODE_ENV = process.env.NODE_ENV || 'development';
const PORT = process.env.PORT || (NODE_ENV === 'production' ? 4000 : 3300); const PORT = process.env.PORT || (NODE_ENV === 'production' ? 4000 : 3300);
const IS_PRODUCTION = NODE_ENV === 'production'; const IS_PRODUCTION = NODE_ENV === 'production';
const PRIMARY_HOST = 'ypchat.net'; const PRIMARY_HOST = 'www.ypchat.net';
const LEGACY_HOSTS = new Set(['www.ypchat.net', 'single-chat.net', 'www.single-chat.net']); const LEGACY_HOSTS = new Set(['ypchat.net']);
// CORS-Origins konfigurieren // CORS-Origins konfigurieren
const allowedOrigins = IS_PRODUCTION const allowedOrigins = IS_PRODUCTION
? ['https://ypchat.net', 'https://www.ypchat.net', 'https://single-chat.net', 'https://www.single-chat.net'] ? ['https://www.ypchat.net', 'https://ypchat.net']
: ['http://localhost:5175', 'http://127.0.0.1:5175']; : ['http://localhost:5175', 'http://127.0.0.1:5175'];
// Socket.IO auf dem gleichen HTTP-Server wie Express // Socket.IO auf dem gleichen HTTP-Server wie Express

View File

@@ -2,7 +2,7 @@ import { readFileSync, existsSync } from 'fs';
import { join, resolve } from 'path'; import { join, resolve } from 'path';
import { loadFeedback } from './feedback-store.js'; import { loadFeedback } from './feedback-store.js';
const SITE_URL = 'https://ypchat.net'; const SITE_URL = 'https://www.ypchat.net';
const DEFAULT_IMAGE = `${SITE_URL}/static/favicon.png`; const DEFAULT_IMAGE = `${SITE_URL}/static/favicon.png`;
const SEO_LOCALES = [ const SEO_LOCALES = [
{ code: 'de', label: 'Deutsch' }, { code: 'de', label: 'Deutsch' },
@@ -363,7 +363,7 @@ for (const page of landingPages) {
isPartOf: { isPartOf: {
'@type': 'WebSite', '@type': 'WebSite',
name: 'SingleChat', name: 'SingleChat',
alternateName: 'Single-Chat.net', alternateName: 'YPChat',
url: `${SITE_URL}/` url: `${SITE_URL}/`
}, },
inLanguage: 'de-DE' inLanguage: 'de-DE'
@@ -515,7 +515,7 @@ function buildSeoLandingContent(route) {
.join('\n'); .join('\n');
return `<section lang="de" style="max-width:960px;margin:24px auto;padding:0 16px;"> return `<section lang="de" style="max-width:960px;margin:24px auto;padding:0 16px;">
<p style="font:700 12px/1.2 sans-serif;color:#637067;text-transform:uppercase;margin:0 0 6px;">Single-Chat.net</p> <p style="font:700 12px/1.2 sans-serif;color:#637067;text-transform:uppercase;margin:0 0 6px;">YPChat</p>
<h1 style="font:700 34px/1.15 sans-serif;color:#18201b;margin:0 0 12px;">${escapeHtml(page.heading)}</h1> <h1 style="font:700 34px/1.15 sans-serif;color:#18201b;margin:0 0 12px;">${escapeHtml(page.heading)}</h1>
<p style="font:400 16px/1.6 sans-serif;color:#344038;margin:0 0 18px;max-width:760px;">${escapeHtml(page.intro)}</p> <p style="font:400 16px/1.6 sans-serif;color:#344038;margin:0 0 18px;max-width:760px;">${escapeHtml(page.intro)}</p>
<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;">${sectionMarkup}</div> <div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;">${sectionMarkup}</div>