5 Commits

Author SHA1 Message Date
admin e2c5f38d55 Merge branch 'maintenance-page'
Adds dynamic maintenance page served by nginx during deploys, toggled
via a flag file by scripts/deploy-remote.sh. Includes brand-styled
maintenance.html with Goodwalk logo and contact fallback, plus an
env-template merger and a11y polish.
2026-05-05 14:10:51 +12:00
admin 8df0e2dfe9 Merge branch 'seo-fixes'
SEO improvements: AggregateOffer on Service schema, AggregateRating
+ Review on home, tighter Puppy Visits meta, removed dead /about-us
duplicate.
2026-05-05 14:10:43 +12:00
admin c2e6282efa Wire maintenance page into deploy script as a dynamic toggle
Replaces the earlier auto-fallback-on-upstream-error approach with an
explicit flag-file toggle controlled by the deploy script. The flag
is touched before stopping the app and removed on successful finish
(or via trap if the deploy aborts), so a failed deploy doesn't strand
the site in maintenance.

- nginx/goodwalk.co.nz.svelte.conf.example: error_page 503 routes to
  /maintenance.html (internal); /m/ serves static maintenance assets;
  the / and /api/submit blocks return 503 when /etc/nginx/conf.d/
  maintenance.flag exists.
- nginx/maintenance.html: brand-styled "Be right back" page — full
  Goodwalk green background, white card with yellow accent, real
  Goodwalk logo, contact details fallback, auto-reload after 60s.
- nginx/logo.png: maintenance-time logo (served from /m/logo.png).
- nginx/nginx.conf: reverted the earlier auto-fallback edits; this
  file is not deployed (the prod conf is goodwalk.co.nz.svelte.conf
  .example).
- scripts/deploy-remote.sh: copies maintenance.html + logo into the
  nginx container, reloads nginx so the new conf is live, touches
  the flag, then runs the rebuild, then clears the flag. Adds a
  trap-based clear_maintenance_flag fallback. Also adds a defensive
  env-file merger that appends new keys from deploy.env.template
  without clobbering live values, with a timestamped .env backup.

Plus a small a11y polish unrelated to maintenance:
- ServicesSection: "Learn more" links now include screen-reader-only
  "about <Service>" context.
- base.css: adds .visually-hidden utility class.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 14:10:16 +12:00
admin c95d2e8c25 SEO: pricing-rich service schema, AggregateRating, tighter meta
- Service pages now include AggregateOffer in JSON-LD (priceCurrency
  NZD, lowPrice/highPrice/offerCount derived from each service's
  pricing.plans). Unlocks price-rich SERP results.
- Homepage LocalBusiness schema now includes AggregateRating and
  per-testimonial Review entries (5 stars, n reviews). Eligible for
  star ratings in SERPs.
- Puppy Visits meta description rewritten — was 241 chars opening
  with "Puppy Visits Introducing Puppy Visits..." Now a tight 144
  chars with Auckland keyword.
- Removed the dead /about-us static-pages entry; the 301 redirect
  in [slug]/+page.server.ts already routes it to /about, so the
  duplicate metadata was unreachable. Pruned matching dead branches
  in [slug]/+page.svelte and RouteSkeleton.svelte for clarity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 14:06:31 +12:00
admin 9d87d08547 Add maintenance page served by nginx during deploys
When the SvelteKit upstream is unreachable (container restart, deploy)
nginx now serves a static, brand-styled "Be right back" page instead
of the default 502/503. Auto-reloads after 60s so visitors don't sit
on it once the app is back.

- nginx/maintenance.html: self-contained, no external assets, inline
  paw SVG, brand colours, contact details fallback
- nginx/nginx.conf: proxy_intercept_errors + error_page 502/503/504
  on both location blocks; 2s proxy_connect_timeout so nginx fails
  over fast instead of holding the connection for 60s

Deploy note: the html file needs to live at /var/www/html/maintenance.html
inside the nginx container (already mounted from /docker/wordpress/goodwalk.co.nz/html).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 08:45:48 +12:00
10 changed files with 395 additions and 30 deletions
+28
View File
@@ -53,6 +53,26 @@ server {
# nginx does not keep stale upstream IPs in memory.
resolver 127.0.0.11 ipv6=off valid=30s;
# Maintenance mode: when /etc/nginx/conf.d/maintenance.flag exists,
# serve the static "be right back" page with a 503 status. The flag is
# toggled by the deploy script (touch / rm) without reloading nginx.
error_page 503 /maintenance.html;
location = /maintenance.html {
root /var/www/html;
internal;
add_header Cache-Control "no-store" always;
}
# Static assets used only by the maintenance page (logo, etc.). Served
# directly from the nginx html mount so they remain reachable while the
# SvelteKit app is down.
location /m/ {
root /var/www/html;
access_log off;
add_header Cache-Control "public, max-age=3600" always;
}
location ~* /\.(git|env|htaccess) {
deny all;
}
@@ -66,6 +86,10 @@ server {
}
location /api/submit {
if (-f /etc/nginx/conf.d/maintenance.flag) {
return 503;
}
set $goodwalk_mail_api goodwalk_svelte_mail_api:8000;
limit_req zone=goodwalk_limit burst=10 nodelay;
proxy_pass http://$goodwalk_mail_api/submit;
@@ -77,6 +101,10 @@ server {
}
location / {
if (-f /etc/nginx/conf.d/maintenance.flag) {
return 503;
}
set $goodwalk_frontend goodwalk_svelte_app:3000;
proxy_pass http://$goodwalk_frontend;
proxy_http_version 1.1;
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

+189
View File
@@ -0,0 +1,189 @@
<!doctype html>
<html lang="en-NZ">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" />
<title>Be right back | Goodwalk</title>
<style>
:root {
--green: #213021;
--green-soft: #2c3f2c;
--yellow: #ffd100;
--ink: #1a1a1a;
--muted: #4c5056;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
color: var(--ink);
background:
radial-gradient(circle at top left, rgba(255, 209, 0, 0.12), transparent 55%),
radial-gradient(circle at bottom right, rgba(255, 209, 0, 0.08), transparent 60%),
var(--green);
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px;
line-height: 1.5;
}
.stage {
width: 100%;
max-width: 560px;
}
.brand-bar {
display: flex;
justify-content: center;
margin-bottom: 28px;
}
.brand-bar img {
display: block;
height: 44px;
width: auto;
max-width: 100%;
filter: brightness(0) invert(1);
}
.card {
position: relative;
background: #fff;
border-radius: 28px;
padding: 44px 40px 38px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
text-align: center;
overflow: hidden;
}
.card::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 6px;
background: var(--yellow);
}
.paw {
display: inline-flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
margin: 4px auto 18px;
border-radius: 50%;
background: rgba(33, 48, 33, 0.08);
color: var(--green);
}
.paw svg {
width: 32px;
height: 32px;
}
.eyebrow {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--green);
margin: 0 0 10px;
}
h1 {
margin: 0 0 14px;
font-size: clamp(28px, 4vw, 38px);
line-height: 1.1;
letter-spacing: -0.02em;
color: var(--ink);
}
p {
margin: 0 0 12px;
color: var(--muted);
font-size: 16px;
}
.meta {
margin-top: 24px;
padding-top: 22px;
border-top: 1px solid rgba(17, 20, 24, 0.08);
font-size: 14px;
color: var(--muted);
}
.meta a {
color: var(--green);
font-weight: 700;
text-decoration: none;
}
.meta a:hover {
text-decoration: underline;
}
.footnote {
margin-top: 22px;
text-align: center;
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
}
@media (max-width: 480px) {
.card {
padding: 36px 24px 28px;
border-radius: 22px;
}
.brand-bar img {
height: 36px;
}
}
</style>
</head>
<body>
<div class="stage">
<div class="brand-bar">
<img src="/m/logo.png" alt="Goodwalk" width="241" height="48" />
</div>
<main class="card" role="main">
<span class="paw" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 10.5c1.1 0 2-1.3 2-3s-.9-3-2-3-2 1.3-2 3 .9 3 2 3Zm9 0c1.1 0 2-1.3 2-3s-.9-3-2-3-2 1.3-2 3 .9 3 2 3ZM4 13.5c1 0 1.8-1.1 1.8-2.5S5 8.5 4 8.5s-1.8 1.1-1.8 2.5S3 13.5 4 13.5Zm16 0c1 0 1.8-1.1 1.8-2.5S21 8.5 20 8.5s-1.8 1.1-1.8 2.5.8 2.5 1.8 2.5ZM12 13.5c-3.3 0-6 2.5-6 5.4 0 1.4 1.1 2.6 2.5 2.6 1 0 1.5-.4 2.2-.8.4-.2.8-.4 1.3-.4s.9.2 1.3.4c.7.4 1.2.8 2.2.8 1.4 0 2.5-1.2 2.5-2.6 0-2.9-2.7-5.4-6-5.4Z"/>
</svg>
</span>
<p class="eyebrow">Goodwalk</p>
<h1>Be right back!</h1>
<p>We're updating the site — should only take a minute. Thanks for your patience.</p>
<div class="meta">
Need us now? Email <a href="mailto:info@goodwalk.co.nz">info@goodwalk.co.nz</a>
or call <a href="tel:+64226421011">(022) 642 1011</a>.
</div>
</main>
<p class="footnote">Auckland Central dog walking · Tiny Gang pack walks · 1:1 walks · Puppy visits</p>
</div>
<script>
// Auto-recheck once a minute so visitors don't sit on this page forever.
setTimeout(function () {
window.location.reload();
}, 60000);
</script>
</body>
</html>
+109 -14
View File
@@ -114,8 +114,19 @@ else
fi
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/goodwalk-deploy.XXXXXX")"
MAINTENANCE_ACTIVE=0
clear_maintenance_flag() {
if (( MAINTENANCE_ACTIVE )) && (( nginx_args_present )); then
echo "[deploy-remote] Clearing maintenance flag"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" \
exec -T nginx rm -f /etc/nginx/conf.d/maintenance.flag || true
MAINTENANCE_ACTIVE=0
fi
}
cleanup() {
clear_maintenance_flag
rm -rf "$STAGING_DIR"
}
@@ -182,6 +193,70 @@ if [[ ! -f "$DEPLOY_PATH/.env" ]]; then
fi
fi
merge_env_file() {
local template="$1"
local live="$2"
[[ -f "$template" ]] || { echo "[deploy-remote] No env template at $template, skipping merge"; return 0; }
[[ -f "$live" ]] || { echo "[deploy-remote] No live .env at $live, skipping merge"; return 0; }
local added diffs backup
added="$(mktemp)"
diffs="$(mktemp)"
backup="${live}.bak.$(date -u +%Y%m%dT%H%M%SZ)"
awk -v live="$live" -v added_log="$added" -v diff_log="$diffs" '
function trim(s) { sub(/^[ \t]+/,"",s); sub(/[ \t]+$/,"",s); return s }
BEGIN {
while ((getline line < live) > 0) {
if (line ~ /^[ \t]*#/ || line ~ /^[ \t]*$/) continue
eq = index(line, "=")
if (eq == 0) continue
k = trim(substr(line, 1, eq-1))
v = substr(line, eq+1)
live_keys[k] = v
live_seen[k] = 1
}
close(live)
}
/^[ \t]*#/ || /^[ \t]*$/ { next }
{
eq = index($0, "=")
if (eq == 0) next
k = trim(substr($0, 1, eq-1))
v = substr($0, eq+1)
if (!(k in live_seen)) {
print k "=" v >> added_log
} else if (live_keys[k] != v) {
print k " (template=" v " | live=" live_keys[k] ")" >> diff_log
}
}
' "$template"
if [[ -s "$added" ]]; then
cp "$live" "$backup"
echo "[deploy-remote] Adding env keys present in template but missing from $live:"
sed 's/^/ + /' "$added"
{
printf '\n# Appended by deploy-remote.sh on %s from deploy.env.template\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
cat "$added"
} >> "$live"
echo "[deploy-remote] Backup of previous .env written to $backup"
else
echo "[deploy-remote] .env is up to date with template (no missing keys)"
fi
if [[ -s "$diffs" ]]; then
echo "[deploy-remote] NOTE: these keys exist in both files but values differ. Live values are PRESERVED:"
sed 's/^/ ! /' "$diffs"
echo "[deploy-remote] If a live value is stale (e.g. an old OWNER_EMAIL), edit $live and re-deploy."
fi
rm -f "$added" "$diffs"
}
merge_env_file "$DEPLOY_PATH/deploy.env.template" "$DEPLOY_PATH/.env"
cd "$DEPLOY_PATH"
echo "[deploy-remote] Validating compose configuration"
@@ -194,6 +269,39 @@ if [[ -n "$SERVICE_NAME" ]]; then
fi
fi
if (( nginx_args_present )); then
[[ -f "$DEPLOY_PATH/$NGINX_SOURCE" ]] || fail "Nginx config missing from deployment payload: $DEPLOY_PATH/$NGINX_SOURCE"
[[ -f "$NGINX_COMPOSE_FILE" ]] || fail "Nginx compose file was not found on the server: $NGINX_COMPOSE_FILE"
MAINTENANCE_HTML_SRC="$DEPLOY_PATH/nginx/maintenance.html"
MAINTENANCE_LOGO_SRC="$DEPLOY_PATH/nginx/logo.png"
[[ -f "$MAINTENANCE_HTML_SRC" ]] || fail "Maintenance page missing from deployment payload: $MAINTENANCE_HTML_SRC"
[[ -f "$MAINTENANCE_LOGO_SRC" ]] || fail "Maintenance logo missing from deployment payload: $MAINTENANCE_LOGO_SRC"
echo "[deploy-remote] Updating shared nginx config (pre-rebuild) so maintenance routing is active"
mkdir -p "$(dirname "$NGINX_TARGET")"
cp "$DEPLOY_PATH/$NGINX_SOURCE" "$NGINX_TARGET"
echo "[deploy-remote] Installing maintenance page assets into nginx container"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" \
exec -T nginx mkdir -p /var/www/html/m
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" \
cp "$MAINTENANCE_HTML_SRC" nginx:/var/www/html/maintenance.html
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" \
cp "$MAINTENANCE_LOGO_SRC" nginx:/var/www/html/m/logo.png
echo "[deploy-remote] Validating nginx configuration"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -t
echo "[deploy-remote] Reloading shared nginx so the new config (incl. maintenance routing) is live"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -s reload
echo "[deploy-remote] Engaging maintenance page (touch maintenance.flag)"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" \
exec -T nginx touch /etc/nginx/conf.d/maintenance.flag
MAINTENANCE_ACTIVE=1
fi
if [[ -n "$SERVICE_NAME" ]]; then
echo "[deploy-remote] Stopping only the Goodwalk service: $SERVICE_NAME"
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop "$SERVICE_NAME" || true
@@ -216,19 +324,6 @@ if [[ -z "$SERVICE_NAME" || "$SERVICE_NAME" == "app" || "$SERVICE_NAME" == "db"
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" exec -T app node scripts/sync-homepage-content.mjs
fi
if (( nginx_args_present )); then
[[ -f "$DEPLOY_PATH/$NGINX_SOURCE" ]] || fail "Nginx config missing from deployment payload: $DEPLOY_PATH/$NGINX_SOURCE"
[[ -f "$NGINX_COMPOSE_FILE" ]] || fail "Nginx compose file was not found on the server: $NGINX_COMPOSE_FILE"
echo "[deploy-remote] Updating shared nginx config to avoid stale container IPs"
mkdir -p "$(dirname "$NGINX_TARGET")"
cp "$DEPLOY_PATH/$NGINX_SOURCE" "$NGINX_TARGET"
echo "[deploy-remote] Validating nginx configuration"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -t
echo "[deploy-remote] Reloading shared nginx"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -s reload
fi
clear_maintenance_flag
echo "[deploy-remote] Remote deployment finished"
+1 -1
View File
@@ -13,7 +13,7 @@
function getVariant(path: string) {
if (path === '/') return 'home';
if (serviceRoutes.has(path)) return 'service';
if (path === '/about' || path === '/about-us') return 'about';
if (path === '/about') return 'about';
if (path === '/our-pricing') return 'pricing';
if (path === '/contact-us') return 'contact';
if (legalRoutes.has(path)) return 'legal';
+3 -1
View File
@@ -25,7 +25,9 @@
{/if}
{#if service.href}
<a href={service.href} class="btn btn-green">Learn more</a>
<a href={service.href} class="btn btn-green">
Learn more<span class="visually-hidden"> about {service.title}</span>
</a>
{/if}
</div>
{/each}
+2 -8
View File
@@ -12,9 +12,9 @@ export const staticPages = {
canonicalPath: '/dog-walking'
},
'puppy-visits': {
title: 'Puppy Visits | Puppy Training',
title: 'Puppy Visits | Auckland In-Home Puppy Care | Goodwalk',
description:
'Puppy Visits Introducing Puppy Visits: Building strong foundations for our pack walks! We love puppies! Our puppy home visits are perfect for young pups not quite ready to join the pack and busy owners with hectic schedules. We lay the groundwork for future pack walks, including fun games, potty breaks, and even feeding if required. Let us help your furry friend thrive while you are away!',
'In-home puppy visits across Auckland Central — toilet breaks, feeding, play and gentle early training for pups not yet ready for pack walks.',
canonicalPath: '/puppy-visits'
},
'our-pricing': {
@@ -29,12 +29,6 @@ export const staticPages = {
'Learn more about Kingsland based dog walking company Goodwalk. We offer our Tiny Gang pack walks throughout the Auckland region. Solo (1:1 walks), homestays and more.',
canonicalPath: '/about'
},
'about-us': {
title: 'About Us | Dog Walkers',
description:
'Learn more about Kingsland based dog walking company Goodwalk. We offer our Tiny Gang pack walks throughout the Auckland region. Solo (1:1 walks), homestays and more.',
canonicalPath: '/about'
},
'contact-us': {
title: 'Contact Us',
description: 'Book a Meet & Greet or send a general enquiry to Goodwalk Auckland dog walking services.',
+12
View File
@@ -34,3 +34,15 @@ input,
textarea {
font: inherit;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
+21 -1
View File
@@ -89,7 +89,27 @@
url: `${siteUrl}${service.href}`
}
}))
}
},
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '5.0',
bestRating: '5',
worstRating: '1',
reviewCount: String(data.content.testimonials.length)
},
review: data.content.testimonials.map((testimonial) => ({
'@type': 'Review',
reviewRating: {
'@type': 'Rating',
ratingValue: '5',
bestRating: '5'
},
author: {
'@type': 'Person',
name: testimonial.reviewer
},
reviewBody: testimonial.quote
}))
},
{
'@context': 'https://schema.org',
+30 -5
View File
@@ -30,6 +30,28 @@
return `${siteUrl}${value.startsWith('/') ? value : `/${value}`}`;
}
function aggregateOfferSchema(plans: { price: string }[]) {
const numericPrices = plans
.map((plan) => Number(plan.price.replace(/[^0-9.]/g, '')))
.filter((value) => Number.isFinite(value) && value > 0);
if (numericPrices.length === 0) {
return null;
}
const lowPrice = Math.min(...numericPrices);
const highPrice = Math.max(...numericPrices);
return {
'@type': 'AggregateOffer',
priceCurrency: 'NZD',
lowPrice: lowPrice.toFixed(2),
highPrice: highPrice.toFixed(2),
offerCount: numericPrices.length,
availability: 'https://schema.org/InStock'
};
}
function breadcrumbSchema(name: string, path: string) {
return {
'@context': 'https://schema.org',
@@ -89,7 +111,8 @@
},
areaServed: 'Auckland Central, New Zealand',
image: absoluteUrl(seoImage),
url: `${siteUrl}${data.page.canonicalPath}`
url: `${siteUrl}${data.page.canonicalPath}`,
offers: aggregateOfferSchema(packWalksContent.pricing.plans)
},
breadcrumbSchema('Pack Walks', data.page.canonicalPath)
];
@@ -111,7 +134,8 @@
},
areaServed: 'Auckland Central, New Zealand',
image: absoluteUrl(seoImage),
url: `${siteUrl}${data.page.canonicalPath}`
url: `${siteUrl}${data.page.canonicalPath}`,
offers: aggregateOfferSchema(dogWalkingContent.pricing.plans)
},
breadcrumbSchema('1:1 Walks', data.page.canonicalPath)
];
@@ -133,7 +157,8 @@
},
areaServed: 'Auckland Central, New Zealand',
image: absoluteUrl(seoImage),
url: `${siteUrl}${data.page.canonicalPath}`
url: `${siteUrl}${data.page.canonicalPath}`,
offers: aggregateOfferSchema(puppyVisitsContent.pricing.plans)
},
breadcrumbSchema('Puppy Visits', data.page.canonicalPath)
];
@@ -148,7 +173,7 @@
},
breadcrumbSchema('Our Pricing', data.page.canonicalPath)
];
} else if (data.slug === 'about' || data.slug === 'about-us') {
} else if (data.slug === 'about') {
seoImage = aboutPageContent.sections[0].imageUrl;
seoImageAlt = aboutPageContent.sections[0].imageAlt;
pageStructuredData = [
@@ -186,7 +211,7 @@
<ServiceLandingPage content={data.content} pageContent={puppyVisitsContent} currentPath={data.page.canonicalPath} />
{:else if data.slug === 'our-pricing'}
<PricingPage content={data.content} pageContent={ourPricingContent} />
{:else if data.slug === 'about' || data.slug === 'about-us'}
{:else if data.slug === 'about'}
<AboutPage content={data.content} pageContent={aboutPageContent} />
{:else if data.slug === 'terms-and-conditions'}
<LegalPage pageContent={termsAndConditionsContent} />