Bläddra i källkod

Overhaul landing page, add privacy page, SEO assets, and optimized hero images

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
master
Daniel Covington 4 dagar sedan
förälder
incheckning
bd002ae089
14 ändrade filer med 985 tillägg och 195 borttagningar
  1. +7
    -0
      app/controllers/HomeController.asp
  2. +300
    -69
      app/views/Home/landing.asp
  3. +114
    -0
      app/views/Home/privacy.asp
  4. +1
    -0
      public/Default.asp
  5. +517
    -126
      public/css/home.css
  6. Binär
      public/images/hero-postcard-1200.jpg
  7. Binär
      public/images/hero-postcard-1200.webp
  8. Binär
      public/images/hero-postcard-600.jpg
  9. Binär
      public/images/hero-postcard-600.webp
  10. Binär
      public/images/og-image.jpg
  11. +27
    -0
      public/js/nav.js
  12. +4
    -0
      public/robots.txt
  13. +9
    -0
      public/sitemap.xml
  14. +6
    -0
      public/web.config

+ 7
- 0
app/controllers/HomeController.asp Visa fil

@@ -26,6 +26,13 @@ Class HomeController_Class
<%
End Sub

' GET /privacy - the privacy policy page.
Public Sub Privacy()
%>
<!--#include file="../views/Home/privacy.asp" -->
<%
End Sub

' GetAntiCSRFToken returns an empty value the first time it creates a token, so read it again.
Private Function ContactCsrfToken()
ContactCsrfToken = HTMLSecurity().GetAntiCSRFToken("home.contact")


+ 300
- 69
app/views/Home/landing.asp Visa fil

@@ -10,13 +10,110 @@ Response.CodePage = 65001
<title>PostcardMojo | Big Local Reach on One 9×12 Postcard</title>
<meta name="description" content="Put your business on an oversized 9×12 postcard shared by a curated group of local businesses and delivered directly to nearby mailboxes.">
<meta name="theme-color" content="#062f4f">
<link rel="canonical" href="https://postcardmojo.com/">
<meta property="og:type" content="website">
<meta property="og:site_name" content="PostcardMojo">
<meta property="og:title" content="PostcardMojo | Big Local Reach on One 9×12 Postcard">
<meta property="og:description" content="Put your business on an oversized 9×12 postcard shared by a curated group of local businesses and delivered directly to nearby mailboxes.">
<meta property="og:url" content="https://postcardmojo.com/">
<meta property="og:image" content="https://postcardmojo.com/images/og-image.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="PostcardMojo shared 9×12 postcard featuring local business ads">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="PostcardMojo | Big Local Reach on One 9×12 Postcard">
<meta name="twitter:description" content="Put your business on an oversized 9×12 postcard shared by a curated group of local businesses and delivered directly to nearby mailboxes.">
<meta name="twitter:image" content="https://postcardmojo.com/images/og-image.jpg">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://postcardmojo.com/#organization",
"name": "PostcardMojo",
"url": "https://postcardmojo.com/",
"logo": "https://postcardmojo.com/images/logos/png/logo_full_large.png"
},
{
"@type": "WebSite",
"@id": "https://postcardmojo.com/#website",
"url": "https://postcardmojo.com/",
"name": "PostcardMojo",
"publisher": {
"@id": "https://postcardmojo.com/#organization"
}
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is a shared 9×12 postcard?",
"acceptedAnswer": {
"@type": "Answer",
"text": "It is one oversized mail piece featuring a curated collection of local business ads. The format gives recipients multiple useful offers while advertisers share print and mailing costs."
}
},
{
"@type": "Question",
"name": "Will a direct competitor appear beside me?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Category availability is managed by campaign. Ask about category placement and any available exclusivity when reserving your space."
}
},
{
"@type": "Question",
"name": "Do I need a finished ad?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. You can send us your own finished design, or we can create one for you. Either way, start with your logo, offer, contact details, and any brand assets you already have. Creative requirements are confirmed for your campaign."
}
},
{
"@type": "Question",
"name": "Where will the postcard be mailed?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Campaigns are planned around selected local areas. Exact geography, timing, and mailing details are confirmed before you reserve a spot."
}
},
{
"@type": "Question",
"name": "What does a campaign cost?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Pricing depends on the campaign, placement, and mailing plan. Request current details for the area you want to reach."
}
}
]
}
]
}
</script>
<link rel="icon" type="image/jpeg" href="/images/logos/sharp/jpg/icon_64.jpg">
<link rel="preload" as="image" type="image/webp" href="/images/hero-postcard-1200.webp" imagesrcset="/images/hero-postcard-600.webp 600w, /images/hero-postcard-1200.webp 1200w" imagesizes="(max-width: 700px) 92vw, 640px">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/home.css">
</head>
<body id="top">
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<defs>
<symbol id="i-layout" viewBox="0 0 24 24"><path d="M3 3h18v18H3zM3 9h18M9 21V9"/></symbol>
<symbol id="i-pen" viewBox="0 0 24 24"><path d="M16.5 3.5l4 4L8 20l-5 1 1-5zM14 6l4 4"/></symbol>
<symbol id="i-mail" viewBox="0 0 24 24"><path d="M3 5h18v14H3zM3 5l9 8 9-8"/></symbol>
<symbol id="i-home" viewBox="0 0 24 24"><path d="M3 11l9-8 9 8M5 9.5V21h14V9.5M10 21v-6h4v6"/></symbol>
<symbol id="i-heart" viewBox="0 0 24 24"><path d="M12 21s-8-5-8-11a4.5 4.5 0 0 1 8-2.5A4.5 4.5 0 0 1 20 10c0 6-8 11-8 11z"/></symbol>
<symbol id="i-dining" viewBox="0 0 24 24"><path d="M5 3v6a3 3 0 0 0 6 0V3M8 3v18M17 21V3c-2.5 1.5-4 4.5-4 8h4"/></symbol>
<symbol id="i-briefcase" viewBox="0 0 24 24"><path d="M3 7h18v13H3zM9 7V4h6v3M3 13h18"/></symbol>
<symbol id="i-check" viewBox="0 0 24 24"><path d="M4 12.5l5 5L20 6.5"/></symbol>
<symbol id="i-arrow-up-right" viewBox="0 0 24 24"><path d="M7 17L17 7M8 7h9v9"/></symbol>
</defs>
</svg>

<a class="skip-link" href="#main-content">Skip to main content</a>

<header class="site-header">
@@ -24,13 +121,17 @@ Response.CodePage = 65001
<a class="brand" href="/" aria-label="PostcardMojo home">
<img src="/images/logos/sharp/jpg/logo_full_small_b.jpg" width="217" height="73" alt="PostcardMojo">
</a>
<nav class="site-nav" aria-label="Primary navigation">
<nav class="site-nav" id="site-nav" aria-label="Primary navigation">
<a href="#how-it-works">How it works</a>
<a href="#ad-sizes">Ad sizes</a>
<a href="#why-postcards">Why it works</a>
<a href="#good-fit">Who it is for</a>
<a href="#faq">FAQ</a>
</nav>
<a class="button button-small button-primary" href="#contact">Find your spot</a>
<button class="nav-toggle" type="button" aria-expanded="false" aria-controls="site-nav" aria-label="Menu">
<span class="nav-toggle-bars" aria-hidden="true"></span>
</button>
</div>
</header>

@@ -48,66 +149,31 @@ Response.CodePage = 65001
<a class="button button-quiet" href="#how-it-works">See how it works <span aria-hidden="true">→</span></a>
</div>
<ul class="hero-points" aria-label="Campaign highlights">
<li><span aria-hidden="true">✓</span> Oversized 9×12 format</li>
<li><span aria-hidden="true">✓</span> Built for neighborhood reach</li>
<li><span aria-hidden="true">✓</span> Shared production costs</li>
<li><span><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-check"/></svg></span> Oversized 9×12 format</li>
<li><span><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-check"/></svg></span> Built for neighborhood reach</li>
<li><span><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-check"/></svg></span> Shared production costs</li>
</ul>
</div>

<div class="postcard-stage">
<div class="postcard-shadow" aria-hidden="true"></div>
<div class="postcard">
<img class="postcard-image" src="/images/download%20(1).png" width="2048" height="1536" alt="Sample shared postcard featuring advertisements for local home-service businesses">
<picture>
<source type="image/webp" srcset="/images/hero-postcard-600.webp 600w, /images/hero-postcard-1200.webp 1200w" sizes="(max-width: 700px) 92vw, 640px">
<img class="postcard-image" src="/images/hero-postcard-1200.jpg" srcset="/images/hero-postcard-600.jpg 600w, /images/hero-postcard-1200.jpg 1200w" sizes="(max-width: 700px) 92vw, 640px" width="1200" height="900" fetchpriority="high" decoding="async" alt="Sample shared postcard featuring advertisements for local home-service businesses">
</picture>
</div>
<div class="stage-note"><span aria-hidden="true">↗</span> One card. Multiple businesses. A whole neighborhood of opportunity.</div>
<div class="stage-note"><span><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-arrow-up-right"/></svg></span> One card. Multiple businesses. A whole neighborhood of opportunity.</div>
</div>

<aside class="contact-card" id="contact" aria-labelledby="contact-title">
<p class="eyebrow eyebrow-on-dark"><span></span> Contact us</p>
<h2 id="contact-title">Interested in a spot on the next card?</h2>
<% If contactSent Then %>
<p class="contact-success" role="status">Thank you! Your message was sent and we will be in touch soon.</p>
<% Else %>
<form class="contact-form" method="post" action="/contact#contact" novalidate>
<% If UBound(contactErrors) >= 0 Then %>
<div class="contact-errors" role="alert">
<ul>
<% Dim contactErrorIndex : For contactErrorIndex = 0 To UBound(contactErrors) %>
<li><%= Server.HTMLEncode(contactErrors(contactErrorIndex)) %></li>
<% Next %>
</ul>
</div>
<% End If %>
<input type="hidden" name="csrf_token" value="<%= Server.HTMLEncode(ContactCsrfToken()) %>">
<div class="contact-trap" aria-hidden="true"><label>Leave this empty <input type="text" name="zq_trap_field" tabindex="-1" autocomplete="off"></label></div>
<label>Name <span aria-hidden="true">*</span>
<input type="text" name="name" maxlength="100" required autocomplete="name" value="<%= Server.HTMLEncode(contactName & "") %>">
</label>
<label>Email <span aria-hidden="true">*</span>
<input type="email" name="email" maxlength="150" required autocomplete="email" value="<%= Server.HTMLEncode(contactEmail & "") %>">
</label>
<label>Phone
<input type="tel" name="phone" maxlength="40" autocomplete="tel" value="<%= Server.HTMLEncode(contactPhone & "") %>">
</label>
<label>Business
<input type="text" name="business" maxlength="150" autocomplete="organization" value="<%= Server.HTMLEncode(contactBusiness & "") %>">
</label>
<label class="contact-wide">How can we help?
<textarea name="message" rows="3" maxlength="2000"><%= Server.HTMLEncode(contactMessage & "") %></textarea>
</label>
<button class="button button-primary" type="submit">Send my interest</button>
</form>
<% End If %>
</aside>
</div>
</section>

<section class="signal-strip" aria-label="What makes PostcardMojo different">
<div class="shell signal-grid">
<div><strong>9 × 12</strong><span>Oversized presence</span></div>
<div><strong>One card</strong><span>A curated local lineup</span></div>
<div><strong>Shared</strong><span>Print and postage costs</span></div>
<div><strong>Local</strong><span>Campaign-focused reach</span></div>
<div><strong>5,000</strong><span>Mailboxes reached</span></div>
<div><strong>9 × 12 in</strong><span>Oversized postcard</span></div>
<div><strong>2 ad sizes</strong><span>3×4 or 6×4</span></div>
<div><strong>Design help</strong><span>Send yours or we create it</span></div>
</div>
</section>

@@ -121,19 +187,19 @@ Response.CodePage = 65001
<ol class="steps">
<li>
<span class="step-number">01</span>
<div class="step-icon" aria-hidden="true">◎</div>
<div class="step-icon"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-layout"/></svg></div>
<h3>Choose your ad size</h3>
<p>Pick a 3×4 or a 6×8 space on the shared 9×12 postcard.</p>
<p>Pick a 3×4 or a 6×4 space on the shared 9×12 postcard.</p>
</li>
<li>
<span class="step-number">02</span>
<div class="step-icon" aria-hidden="true">✦</div>
<div class="step-icon"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-pen"/></svg></div>
<h3>Send your layout or let us design it</h3>
<p>Already have an ad? Send it over. Need one? We will design it for you.</p>
</li>
<li>
<span class="step-number">03</span>
<div class="step-icon" aria-hidden="true">↗</div>
<div class="step-icon"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-mail"/></svg></div>
<h3>We mail it out</h3>
<p>We print the postcard and deliver it to mailboxes in your area.</p>
</li>
@@ -141,13 +207,59 @@ Response.CodePage = 65001
</div>
</section>

<section class="section section-warm" id="ad-sizes">
<div class="shell">
<div class="section-heading heading-centered">
<p class="eyebrow eyebrow-centered"><span></span> Pick your space</p>
<h2>Two sizes. One clear offer.</h2>
<p>Every ad shares the 9×12 postcard with other local businesses. Choose the size that fits your message.</p>
</div>

<figure class="size-scale">
<div class="size-scale-row" role="img" aria-label="Scale drawing of the 9 by 12 inch postcard beside the 6 by 4 inch and 3 by 4 inch ad sizes">
<div class="scale-item"><span class="scale-shape scale-card"></span><figcaption>9×12 postcard</figcaption></div>
<div class="scale-item"><span class="scale-shape scale-large"></span><figcaption>6×4 ad</figcaption></div>
<div class="scale-item"><span class="scale-shape scale-small"></span><figcaption>3×4 ad</figcaption></div>
</div>
<p class="scale-caption">Shown to scale</p>
</figure>

<div class="size-grid">
<article class="size-card">
<h3>3×4 spot</h3>
<p>A focused space for one strong offer, your logo, and a clear next step.</p>
<ul>
<li>Headline and one offer</li>
<li>Phone number, URL, or QR code</li>
</ul>
</article>
<article class="size-card size-card-featured">
<h3>6×4 spot</h3>
<p>Twice the width of a 3×4, with more room for a photo, a bigger offer, and a message that stands out on the card.</p>
<ul>
<li>Headline, photo, and offer</li>
<li>Phone number, URL, and QR code</li>
</ul>
</article>
</div>

<div class="pricing-note">
<div>
<h3>Pricing and mail dates are set per campaign</h3>
<p>We are building the next campaign now. Tell us your business and service area and we will confirm pricing, availability, and the mail date for your spot.</p>
</div>
<a class="button button-primary" href="#contact">Get pricing for my area</a>
</div>
</div>
</section>

<section class="section section-navy" id="why-postcards">
<div class="shell value-grid">
<div class="value-copy">
<p class="eyebrow eyebrow-on-dark"><span></span> A physical advantage</p>
<h2>Digital ads disappear with a swipe. A great postcard gets held.</h2>
<p>PostcardMojo gives your business a physical moment with a local customer. The oversized format creates room for a useful offer, recognizable branding, and a direct next step.</p>
<a class="text-link" href="#get-started">Put your offer on the next card <span aria-hidden="true">→</span></a>
<a class="text-link" href="#contact">Put your offer on the next card <span aria-hidden="true">→</span></a>
</div>
<div class="benefit-list">
<article>
@@ -181,22 +293,22 @@ Response.CodePage = 65001
</div>
<div class="fit-grid">
<article class="fit-card">
<span class="fit-icon" aria-hidden="true">⌂</span>
<span class="fit-icon"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-home"/></svg></span>
<h3>Home services</h3>
<p>Landscaping, cleaning, roofing, HVAC, plumbing, remodeling, and other trusted pros.</p>
</article>
<article class="fit-card">
<span class="fit-icon" aria-hidden="true">+</span>
<span class="fit-icon"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-heart"/></svg></span>
<h3>Health &amp; wellness</h3>
<p>Dentists, chiropractors, fitness studios, salons, spas, and wellness providers.</p>
</article>
<article class="fit-card">
<span class="fit-icon" aria-hidden="true">♨</span>
<span class="fit-icon"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-dining"/></svg></span>
<h3>Food &amp; experiences</h3>
<p>Restaurants, cafés, entertainment, family activities, and places worth discovering.</p>
</article>
<article class="fit-card">
<span class="fit-icon" aria-hidden="true">◆</span>
<span class="fit-icon"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-briefcase"/></svg></span>
<h3>Professional services</h3>
<p>Real estate, insurance, financial, legal, pet care, automotive, and local experts.</p>
</article>
@@ -218,9 +330,9 @@ Response.CodePage = 65001
<h2>A clear offer turns attention into action.</h2>
<p>Your ad does not need to tell your whole story. It needs to make one strong promise and give a nearby customer one easy next step.</p>
<ul class="check-list">
<li><span>✓</span><div><strong>A direct headline</strong><small>Say what you do and why it matters.</small></div></li>
<li><span>✓</span><div><strong>A compelling reason to respond</strong><small>Feature an offer, service, or timely benefit.</small></div></li>
<li><span>✓</span><div><strong>A simple call to action</strong><small>Use a memorable URL, phone number, or QR code.</small></div></li>
<li><span><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-check"/></svg></span><div><strong>A direct headline</strong><small>Say what you do and why it matters.</small></div></li>
<li><span><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-check"/></svg></span><div><strong>A compelling reason to respond</strong><small>Feature an offer, service, or timely benefit.</small></div></li>
<li><span><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-check"/></svg></span><div><strong>A simple call to action</strong><small>Use a memorable URL, phone number, or QR code.</small></div></li>
</ul>
</div>
</div>
@@ -244,7 +356,7 @@ Response.CodePage = 65001
</details>
<details>
<summary>Do I need a finished ad?<span aria-hidden="true">+</span></summary>
<p>Start with your logo, offer, contact details, and any brand assets you already have. Creative requirements and available assistance can be confirmed for your campaign.</p>
<p>No. You can send us your own finished design, or we can create one for you. Either way, start with your logo, offer, contact details, and any brand assets you already have. Creative requirements are confirmed for your campaign.</p>
</details>
<details>
<summary>Where will the postcard be mailed?<span aria-hidden="true">+</span></summary>
@@ -253,22 +365,56 @@ Response.CodePage = 65001
<details>
<summary>What does a campaign cost?<span aria-hidden="true">+</span></summary>
<p>Pricing depends on the campaign, placement, and mailing plan. Request current details for the area you want to reach.</p>
<a class="button button-small button-primary" href="#contact">Request Details</a>
</details>
</div>
</div>
</section>

<section class="cta-section" id="get-started">
<div class="shell cta-card">
<section class="contact-section" id="contact" aria-labelledby="contact-title">
<div class="shell contact-card">
<div class="cta-rings" aria-hidden="true"></div>
<div>
<div class="contact-intro">
<p class="eyebrow eyebrow-on-dark"><span></span> Your neighborhood is waiting</p>
<h2>Put your business in the next mailbox moment.</h2>
<p>Tell us what you do and where you want to be seen. We will help you explore the right PostcardMojo campaign.</p>
<h2 id="contact-title">Interested in a spot on the next card?</h2>
<p>Tell us what you do and where you want to be seen. We will help you explore the right PostcardMojo campaign and confirm sizes, pricing, and mail dates.</p>
<p class="contact-note">Campaign availability varies by area. We reply within one business day.</p>
</div>
<div class="cta-actions">
<a class="button button-gold" href="#contact">Request campaign details</a>
<span>Campaign availability varies by area.</span>
<div class="contact-panel">
<% If contactSent Then %>
<p class="contact-success" role="status" tabindex="-1">Thank you! Your message was sent and we will be in touch soon.</p>
<% Else %>
<form class="contact-form" method="post" action="/contact#contact" novalidate aria-labelledby="contact-title">
<% If UBound(contactErrors) >= 0 Then %>
<div class="contact-errors" role="alert" tabindex="-1">
<ul>
<% Dim contactErrorIndex : For contactErrorIndex = 0 To UBound(contactErrors) %>
<li><%= Server.HTMLEncode(contactErrors(contactErrorIndex)) %></li>
<% Next %>
</ul>
</div>
<% End If %>
<input type="hidden" name="csrf_token" value="<%= Server.HTMLEncode(ContactCsrfToken()) %>">
<div class="contact-trap" aria-hidden="true"><label>Leave this empty <input type="text" name="zq_trap_field" tabindex="-1" autocomplete="off"></label></div>
<label><span>Name <span aria-hidden="true">*</span></span>
<input type="text" name="name" maxlength="100" required autocomplete="name" value="<%= Server.HTMLEncode(contactName & "") %>">
</label>
<label><span>Email <span aria-hidden="true">*</span></span>
<input type="email" name="email" maxlength="150" required autocomplete="email" value="<%= Server.HTMLEncode(contactEmail & "") %>">
</label>
<label>Phone
<input type="tel" name="phone" maxlength="40" autocomplete="tel" value="<%= Server.HTMLEncode(contactPhone & "") %>">
</label>
<label>Business
<input type="text" name="business" maxlength="150" autocomplete="organization" value="<%= Server.HTMLEncode(contactBusiness & "") %>">
</label>
<label class="contact-wide">How can we help?
<textarea name="message" rows="3" maxlength="2000"><%= Server.HTMLEncode(contactMessage & "") %></textarea>
</label>
<button class="button button-gold" type="submit">Send my interest</button>
<p class="contact-privacy">We use your details only to contact you about our services. We never sell or share them. <a href="/privacy">Privacy policy</a></p>
</form>
<% End If %>
</div>
</div>
</section>
@@ -284,9 +430,94 @@ Response.CodePage = 65001
<a href="#how-it-works">How it works</a>
<a href="#faq">FAQ</a>
<a href="#contact">Contact</a>
<a href="/privacy">Privacy</a>
</nav>
<small>&copy; <%= Year(Date()) %> PostcardMojo. All rights reserved.</small>
</div>
</footer>


<div class="sticky-cta" id="sticky-cta">
<a class="button button-gold" href="#contact">Get pricing for my area</a>
</div>

<script src="/js/nav.js"></script>
<script>
(function () {
var bar = document.getElementById('sticky-cta');
var hero = document.querySelector('.hero');
var contact = document.getElementById('contact');
if (bar && hero && contact && 'IntersectionObserver' in window) {
var heroVisible = true;
var contactVisible = false;
function updateBar() {
bar.classList.toggle('is-visible', !heroVisible && !contactVisible);
}
new IntersectionObserver(function (entries) {
heroVisible = entries[0].isIntersecting;
updateBar();
}).observe(hero);
new IntersectionObserver(function (entries) {
contactVisible = entries[0].isIntersecting;
updateBar();
}).observe(contact);
}
})();
</script>

<script>
(function () {
var form = document.querySelector('.contact-form');
var notice = document.querySelector('.contact-errors, .contact-success');
if (notice) notice.focus();
if (!form) return;

var rules = [
{ name: 'name', message: 'Please enter your name.' },
{ name: 'email', message: 'Please enter a valid email address.', pattern: /^[^\s@<>,;"]+@[^\s@<>,;"]+\.[A-Za-z]{2,}$/ }
];

function clearError(field) {
var old = document.getElementById('error-' + field.name);
if (old) old.remove();
field.removeAttribute('aria-invalid');
field.removeAttribute('aria-describedby');
}

function showError(field, message) {
clearError(field);
var note = document.createElement('p');
note.className = 'field-error';
note.id = 'error-' + field.name;
note.textContent = message;
field.insertAdjacentElement('afterend', note);
field.setAttribute('aria-invalid', 'true');
field.setAttribute('aria-describedby', note.id);
}

form.addEventListener('submit', function (event) {
var firstInvalid = null;
rules.forEach(function (rule) {
var field = form.elements[rule.name];
var value = field.value.trim();
var invalid = value.length === 0 || (rule.pattern && !rule.pattern.test(value));
if (invalid) {
showError(field, rule.message);
if (!firstInvalid) firstInvalid = field;
} else {
clearError(field);
}
});
if (firstInvalid) {
event.preventDefault();
firstInvalid.focus();
}
});

form.addEventListener('input', function (event) {
if (event.target.getAttribute('aria-invalid') === 'true') clearError(event.target);
});
})();
</script>
</body>
</html>

+ 114
- 0
app/views/Home/privacy.asp Visa fil

@@ -0,0 +1,114 @@
<%
Response.Charset = "utf-8"
Response.CodePage = 65001
%>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Privacy Policy | PostcardMojo</title>
<meta name="description" content="How PostcardMojo collects and uses the information you send us. We do not sell or distribute your information.">
<meta name="theme-color" content="#062f4f">
<link rel="canonical" href="https://postcardmojo.com/privacy">
<link rel="icon" type="image/jpeg" href="/images/logos/sharp/jpg/icon_64.jpg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/home.css">
</head>
<body id="top">
<a class="skip-link" href="#main-content">Skip to main content</a>

<header class="site-header">
<div class="shell nav-shell">
<a class="brand" href="/" aria-label="PostcardMojo home">
<img src="/images/logos/sharp/jpg/logo_full_small_b.jpg" width="217" height="73" alt="PostcardMojo">
</a>
<nav class="site-nav" id="site-nav" aria-label="Primary navigation">
<a href="/#how-it-works">How it works</a>
<a href="/#ad-sizes">Ad sizes</a>
<a href="/#why-postcards">Why it works</a>
<a href="/#good-fit">Who it is for</a>
<a href="/#faq">FAQ</a>
</nav>
<a class="button button-small button-primary" href="/#contact">Find your spot</a>
<button class="nav-toggle" type="button" aria-expanded="false" aria-controls="site-nav" aria-label="Menu">
<span class="nav-toggle-bars" aria-hidden="true"></span>
</button>
</div>
</header>

<main id="main-content">
<article class="section legal-page">
<div class="shell legal-shell">
<p class="eyebrow"><span></span> Your privacy</p>
<h1>Privacy Policy</h1>
<p class="legal-updated">Last updated: October 19, 2026</p>

<p class="legal-lead">PostcardMojo respects your privacy. We do not sell or distribute the information you give us. We use it only to contact you about our services.</p>

<h2>Who we are</h2>
<p>This website is operated by PostcardMojo, which offers shared direct-mail postcard advertising for local businesses. You can reach us at <a href="tel:+16162997328">616-299-7328</a> or <a href="mailto:danielcovington@postcardmojo.com">danielcovington@postcardmojo.com</a>.</p>

<h2>Information we collect</h2>
<p>When you fill out our contact form, we collect the details you choose to give us:</p>
<ul>
<li>your name and email address (required)</li>
<li>your phone number, business name, and message (optional)</li>
</ul>
<p>When you submit the form, we also receive the IP address your request came from. This helps us prevent spam and abuse.</p>

<h2>How we use your information</h2>
<p>We use the information you send us only to contact you about PostcardMojo services, such as replying to your inquiry and discussing pricing, availability, ad sizes, and mail dates for a campaign.</p>

<h2>We do not sell or distribute your information</h2>
<p>We do not sell, rent, trade, or distribute your personal information to anyone. We do not use it for advertising by other companies.</p>
<p>Your message is delivered to us by email, so the email service we use handles it only as needed to deliver it to us. We may also disclose information if the law requires it.</p>

<h2>Cookies and third-party services</h2>
<ul>
<li>Our contact form uses a temporary security cookie to protect it from forged submissions. It is not used to track you.</li>
<li>This site loads fonts from Google Fonts. When it does, Google may receive your IP address and browser details. See Google's privacy policy for how it handles that data.</li>
</ul>
<p>We do not use advertising or tracking cookies on this site.</p>

<h2>How long we keep your information</h2>
<p>We keep your inquiry only as long as we need it to respond to you and to manage any campaign you ask about. If you would like us to delete it sooner, just ask.</p>

<h2>Your choices</h2>
<p>You can ask us at any time to tell you what information we have about you, correct it, or delete it. You can also ask us to stop contacting you. Call <a href="tel:+16162997328">616-299-7328</a> or email <a href="mailto:danielcovington@postcardmojo.com">danielcovington@postcardmojo.com</a>.</p>

<h2>Children</h2>
<p>Our site and services are for businesses. We do not knowingly collect information from children under 13.</p>

<h2>Changes to this policy</h2>
<p>If we change how we handle your information, we will update this page and the date at the top.</p>

<h2>Contact us</h2>
<p>Questions about this policy? Call <a href="tel:+16162997328">616-299-7328</a> or email <a href="mailto:danielcovington@postcardmojo.com">danielcovington@postcardmojo.com</a>.</p>

<p class="legal-back"><a class="text-link" href="/">&larr; Back to home</a></p>
</div>
</article>
</main>

<footer class="site-footer">
<div class="shell footer-grid">
<a class="footer-brand" href="/" aria-label="PostcardMojo home">
<img src="/images/logos/sharp/jpg/logo_full_small_a.jpg" width="187" height="63" alt="PostcardMojo">
</a>
<p>Big local visibility, delivered.</p>
<nav aria-label="Footer navigation">
<a href="/#how-it-works">How it works</a>
<a href="/#faq">FAQ</a>
<a href="/#contact">Contact</a>
<a href="/privacy">Privacy</a>
</nav>
<small>&copy; <%= Year(Date()) %> PostcardMojo. All rights reserved.</small>
</div>
</footer>

<script src="/js/nav.js"></script>
</body>
</html>

+ 1
- 0
public/Default.asp Visa fil

@@ -6,6 +6,7 @@
router.AddRoute "GET", "", "HomeController", "Index"
router.AddRoute "GET", "/home", "HomeController", "Index"
router.AddRoute "POST", "/contact", "HomeController", "Contact"
router.AddRoute "GET", "/privacy", "HomeController", "Privacy"

' Dispatch the request (resolves route and executes controller action)
MVC.DispatchRequest Request.ServerVariables("REQUEST_METHOD"), _


+ 517
- 126
public/css/home.css Visa fil

@@ -58,10 +58,16 @@ summary {
}

:focus-visible {
outline: 3px solid var(--gold);
outline: 3px solid var(--navy);
outline-offset: 4px;
}

.section-navy :focus-visible,
.contact-card :focus-visible,
.pricing-note :focus-visible {
outline-color: var(--gold);
}

.skip-link {
position: fixed;
z-index: 1000;
@@ -90,8 +96,7 @@ summary {
z-index: 50;
top: 0;
border-bottom: 1px solid rgba(5, 47, 79, 0.1);
background: rgba(255, 253, 248, 0.94);
backdrop-filter: blur(14px);
background: var(--white);
}

.nav-shell {
@@ -235,7 +240,7 @@ summary {
.hero-grid {
min-height: 710px;
display: grid;
grid-template-columns: minmax(0, 0.93fr) minmax(500px, 1.07fr);
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
align-items: center;
gap: clamp(38px, 6vw, 78px);
padding-block: 74px 80px;
@@ -268,8 +273,7 @@ summary {
.section-heading h2,
.value-copy h2,
.offer-copy h2,
.faq-intro h2,
.cta-card h2 {
.faq-intro h2 {
margin: 0;
color: var(--navy);
font-family: var(--font-display);
@@ -353,15 +357,22 @@ summary {

.postcard {
position: relative;
width: min(100%, 610px);
width: 100%;
max-width: 640px;
aspect-ratio: 12 / 9;
overflow: hidden;
border: 1px solid rgba(5, 47, 79, 0.12);
border-radius: 18px;
border-radius: 0;
background: #fffef9;
box-shadow: 0 34px 80px rgba(5, 47, 79, 0.22), 0 5px 15px rgba(5, 47, 79, 0.12);
}

.postcard picture {
display: block;
width: 100%;
height: 100%;
}

.postcard-image {
width: 100%;
height: 100%;
@@ -538,8 +549,7 @@ summary {
.section-heading h2,
.value-copy h2,
.offer-copy h2,
.faq-intro h2,
.cta-card h2 {
.faq-intro h2 {
font-size: clamp(2.25rem, 4.3vw, 4rem);
}

@@ -607,7 +617,7 @@ summary {
position: absolute;
top: 20px;
right: 22px;
color: #a1acb3;
color: var(--muted);
font-family: var(--font-display);
font-size: 0.76rem;
font-weight: 800;
@@ -677,8 +687,7 @@ summary {

.eyebrow-on-dark { color: #f9d86e; }

.value-copy h2,
.cta-card h2 {
.value-copy h2 {
color: var(--white);
}

@@ -1007,53 +1016,8 @@ summary {
color: var(--muted);
}

.cta-section {
padding: 30px 0 110px;
background: var(--white);
}

.cta-card {
position: relative;
isolation: isolate;
min-height: 365px;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 55px;
overflow: hidden;
padding: clamp(45px, 7vw, 85px);
border-radius: var(--radius-lg);
background: var(--navy);
box-shadow: 0 30px 70px rgba(5, 47, 79, 0.24);
color: var(--white);
}

.cta-card > div:not(.cta-rings) {
position: relative;
z-index: 1;
}

.cta-card h2 {
max-width: 750px;
}

.cta-card p:not(.eyebrow) {
max-width: 650px;
margin: 20px 0 0;
color: #c9d6dd;
font-size: 1.03rem;
}

.cta-actions {
min-width: 245px;
display: grid;
justify-items: center;
gap: 13px;
}

.cta-actions span {
color: #b7c8d1;
font-size: 0.75rem;
.faq-list details .button {
margin: 0 0 25px;
}

.cta-rings {
@@ -1071,7 +1035,7 @@ summary {
.site-footer {
padding-block: 42px;
border-top: 1px solid var(--line);
background: var(--paper);
background: var(--white);
}

.footer-grid {
@@ -1100,7 +1064,7 @@ summary {
grid-column: 1 / -1;
padding-top: 22px;
border-top: 1px solid var(--line);
color: #77838b;
color: var(--muted);
font-size: 0.73rem;
}

@@ -1130,21 +1094,9 @@ summary {
.steps::before {
display: none;
}

.cta-card {
grid-template-columns: 1fr;
}

.cta-actions {
justify-items: start;
}
}

@media (max-width: 840px) {
.site-header {
position: relative;
}

.nav-shell {
min-height: auto;
grid-template-columns: 1fr auto;
@@ -1269,7 +1221,6 @@ summary {

.postcard {
width: 100%;
border-radius: 12px;
}

.postcard-feature {
@@ -1327,8 +1278,7 @@ summary {
.section-heading h2,
.value-copy h2,
.offer-copy h2,
.faq-intro h2,
.cta-card h2 {
.faq-intro h2 {
font-size: clamp(2.05rem, 10vw, 3rem);
}

@@ -1386,22 +1336,6 @@ summary {
margin-right: 0;
}

.cta-section {
padding-bottom: 70px;
}

.cta-card {
min-height: auto;
gap: 35px;
padding: 42px 27px;
border-radius: 24px;
}

.cta-actions,
.cta-actions .button {
width: 100%;
}

.footer-grid {
grid-template-columns: 1fr;
}
@@ -1428,22 +1362,59 @@ summary {
}
}

/* Contact card */
/* Contact section */
.contact-section {
padding: 30px 0 110px;
background: var(--white);
}

.contact-card {
position: relative;
z-index: 2;
padding: clamp(22px, 3vw, 34px);
border-radius: 18px;
isolation: isolate;
display: grid;
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
align-items: center;
gap: clamp(36px, 6vw, 80px);
overflow: hidden;
padding: clamp(36px, 6vw, 80px);
border-radius: var(--radius-lg);
background: var(--navy);
box-shadow: 0 30px 70px rgba(5, 47, 79, 0.24);
color: var(--white);
box-shadow: 0 24px 60px rgba(5, 47, 79, 0.28);
}

.contact-card h2 {
margin: 0 0 18px;
.contact-intro,
.contact-panel {
position: relative;
z-index: 1;
}

.contact-intro h2 {
margin: 0;
color: var(--white);
font-family: var(--font-display);
font-size: clamp(1.5rem, 2.4vw, 2rem);
line-height: 1.15;
font-size: clamp(2rem, 3.6vw, 3.25rem);
font-weight: 800;
letter-spacing: -0.045em;
line-height: 1.06;
}

.contact-intro p:not(.eyebrow) {
margin: 20px 0 0;
color: #c9d6dd;
font-size: 1.03rem;
}

.contact-intro .contact-note {
color: #b7c8d1;
font-size: 0.82rem;
}

.contact-panel {
padding: clamp(22px, 3vw, 34px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 18px;
background: rgba(255, 255, 255, 0.06);
}

.contact-form {
@@ -1496,6 +1467,7 @@ summary {
.contact-success {
margin: 0;
color: var(--gold);
font-size: 1.1rem;
font-weight: 700;
}

@@ -1504,43 +1476,462 @@ summary {
left: -9999px;
}

.hero .hero-grid {
width: min(1640px, calc(100% - 32px));
grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.6fr) minmax(280px, 0.75fr);
grid-template-areas: "copy card contact";
align-items: center;
gap: clamp(20px, 2.5vw, 40px);
.hero-copy .hero-actions + .hero-points {
margin-top: 29px;
}

.hero-grid .hero-copy { grid-area: copy; }
.hero-grid .contact-card { grid-area: contact; }
.hero-grid .postcard-stage { grid-area: card; min-height: 0; }
.hero-grid .postcard { width: 100%; }
@media (max-width: 1080px) {
.contact-card {
grid-template-columns: 1fr;
}
}

.hero-grid h1 { font-size: clamp(2rem, 3vw, 3.1rem); }
.hero-grid .contact-form { grid-template-columns: 1fr; gap: 12px; }
.hero-grid .stage-note { position: static; margin-top: 16px; max-width: none; }
@media (max-width: 600px) {
.contact-section {
padding-bottom: 70px;
}

@media (max-width: 1100px) {
.hero .hero-grid {
grid-template-columns: minmax(0, 1fr) minmax(300px, 0.9fr);
grid-template-areas: "copy contact" "card card";
align-items: start;
.contact-card {
padding: 36px 20px;
border-radius: 24px;
}

.hero-grid .postcard { width: min(100%, 610px); }
.hero-grid .postcard-stage { min-height: 520px; }
.contact-form {
grid-template-columns: 1fr;
}

.contact-form .button {
width: 100%;
}
}

/* Ad sizes */
.size-scale {
margin: 0 0 48px;
text-align: center;
}

.size-scale-row {
--u: clamp(11px, 2.4vw, 20px);
display: flex;
align-items: flex-end;
justify-content: center;
gap: clamp(22px, 5vw, 56px);
}

.scale-item {
display: grid;
justify-items: center;
gap: 10px;
}

.scale-item figcaption {
color: var(--navy);
font-family: var(--font-display);
font-size: 0.82rem;
font-weight: 800;
}

.scale-shape {
display: block;
box-shadow: 0 10px 24px rgba(5, 47, 79, 0.14);
}

.scale-card {
width: calc(var(--u) * 12);
height: calc(var(--u) * 9);
border: 2px solid var(--navy);
background: var(--white);
}

.scale-large {
width: calc(var(--u) * 6);
height: calc(var(--u) * 4);
background: var(--navy);
}

.scale-small {
width: calc(var(--u) * 3);
height: calc(var(--u) * 4);
background: var(--gold);
}

.scale-caption {
margin: 16px 0 0;
color: var(--muted);
font-size: 0.82rem;
}

.size-grid {
max-width: 820px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18px;
margin: 0 auto;
}

.size-card {
padding: 30px;
border: 1px solid rgba(5, 47, 79, 0.1);
background: var(--white);
}

.size-card-featured {
border-color: var(--navy);
box-shadow: 0 20px 50px rgba(5, 47, 79, 0.12);
}

.size-card h3 {
margin: 0;
color: var(--navy);
font-family: var(--font-display);
font-size: 1.4rem;
font-weight: 800;
}

.size-card p {
margin: 10px 0 0;
color: var(--muted);
font-size: 0.95rem;
}

.size-card ul {
margin: 18px 0 0;
padding: 0;
color: var(--ink);
font-size: 0.9rem;
font-weight: 600;
list-style: none;
}

.size-card li {
padding-block: 8px;
border-top: 1px solid var(--line);
}

.pricing-note {
max-width: 820px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 28px;
margin: 18px auto 0;
padding: 28px 30px;
background: var(--navy);
color: var(--white);
}

.pricing-note h3 {
margin: 0;
font-family: var(--font-display);
font-size: 1.15rem;
font-weight: 800;
}

.pricing-note p {
margin: 8px 0 0;
color: #c9d6dd;
font-size: 0.92rem;
}

.pricing-note .button {
flex: 0 0 auto;
background: var(--gold);
color: var(--navy-deep);
}

.pricing-note .button:hover {
background: #ffd44a;
}

@media (max-width: 700px) {
.hero .hero-grid {
.size-grid {
grid-template-columns: 1fr;
grid-template-areas: "copy" "contact" "card";
}

.pricing-note {
flex-direction: column;
align-items: stretch;
text-align: left;
}
}

/* Icons */
.icon {
width: 1em;
height: 1em;
flex: 0 0 auto;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: square;
stroke-linejoin: miter;
}

.step-icon .icon { width: 28px; height: 28px; }
.fit-icon .icon { width: 32px; height: 32px; }
.hero-points .icon { width: 12px; height: 12px; stroke-width: 3; }
.stage-note .icon { width: 16px; height: 16px; }
.check-list .icon { width: 18px; height: 18px; stroke-width: 3; }

/* Form feedback and focus */
.contact-form input:focus-visible,
.contact-form textarea:focus-visible {
outline-offset: 2px;
}

.contact-errors:focus,
.contact-success:focus {
outline: 3px solid var(--gold);
outline-offset: 4px;
}

.contact-form input[aria-invalid="true"],
.contact-form textarea[aria-invalid="true"] {
border: 2px solid #e5533d;
}

.field-error {
margin: 0;
color: #ffd2c9;
font-size: 0.82rem;
font-weight: 600;
}

.contact-form {
align-items: start;
}

.contact-form label {
align-content: start;
}

/* Mobile navigation and sticky call to action */
.nav-toggle,
.sticky-cta {
display: none;
}

[id] {
scroll-margin-top: 90px;
}

@media (max-width: 840px) {
.has-js .nav-shell {
min-height: 72px;
grid-template-columns: 1fr auto auto;
padding-block: 8px;
}

.has-js .nav-toggle {
width: 48px;
height: 48px;
display: inline-grid;
place-items: center;
padding: 0;
border: 1px solid rgba(5, 47, 79, 0.18);
background: var(--white);
cursor: pointer;
}

.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
width: 22px;
height: 2px;
display: block;
background: var(--navy);
content: "";
transition: transform 180ms ease, background-color 180ms ease;
}

.nav-toggle-bars {
position: relative;
}

.nav-toggle-bars::before,
.nav-toggle-bars::after {
position: absolute;
left: 0;
}

.nav-toggle-bars::before { top: -7px; }
.nav-toggle-bars::after { top: 7px; }

.nav-toggle[aria-expanded="true"] .nav-toggle-bars {
background: transparent;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before {
transform: translateY(7px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after {
transform: translateY(-7px) rotate(-45deg);
}

.has-js .site-nav {
position: absolute;
top: 100%;
right: 0;
left: 0;
display: none;
flex-direction: column;
justify-content: flex-start;
gap: 0;
overflow: visible;
padding: 4px 20px 14px;
border-bottom: 1px solid rgba(5, 47, 79, 0.1);
background: var(--white);
box-shadow: 0 18px 30px rgba(5, 47, 79, 0.12);
}

.has-js .site-nav.is-open {
display: flex;
}

.has-js .site-nav a {
padding: 15px 0;
border-bottom: 1px solid var(--line);
font-size: 1rem;
}

.has-js .site-nav a:last-child {
border-bottom: 0;
}

.has-js .site-nav a::after {
display: none;
}
}

@media (max-width: 700px) {
.contact-form {
grid-template-columns: 1fr;
.has-js .nav-shell > .button {
display: none;
}

.has-js .nav-shell {
grid-template-columns: 1fr auto;
}

body {
padding-bottom: 84px;
}

.has-js .sticky-cta {
position: fixed;
z-index: 40;
right: 0;
bottom: 0;
left: 0;
display: block;
padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
border-top: 1px solid rgba(5, 47, 79, 0.12);
background: var(--white);
box-shadow: 0 -10px 30px rgba(5, 47, 79, 0.14);
transform: translateY(110%);
transition: transform 220ms ease;
}

.has-js .sticky-cta.is-visible {
transform: translateY(0);
}

.sticky-cta .button {
width: 100%;
}
}

/* Privacy note and legal pages */
.contact-privacy {
grid-column: 1 / -1;
margin: 0;
color: #c9d6dd;
font-size: 0.82rem;
}

.contact-privacy a {
color: #f9d86e;
}

.legal-page {
background: var(--white);
}

.legal-shell {
max-width: 760px;
}

.legal-shell h1 {
margin: 0;
color: var(--navy);
font-family: var(--font-display);
font-size: clamp(2.2rem, 5vw, 3.4rem);
font-weight: 800;
letter-spacing: -0.045em;
line-height: 1.08;
}

.legal-updated {
margin: 12px 0 0;
color: var(--muted);
font-size: 0.9rem;
}

.legal-lead {
margin: 28px 0 0;
padding: 20px 24px;
border-left: 4px solid var(--gold);
background: var(--cream);
color: var(--navy);
font-size: 1.1rem;
font-weight: 600;
}

.legal-shell h2 {
margin: 40px 0 0;
color: var(--navy);
font-family: var(--font-display);
font-size: 1.35rem;
font-weight: 800;
}

.legal-shell p,
.legal-shell li {
color: #334b5c;
}

.legal-shell p {
margin: 12px 0 0;
}

.legal-shell ul {
margin: 12px 0 0;
padding-left: 22px;
}

.legal-shell li + li {
margin-top: 6px;
}

.legal-shell a:not(.text-link) {
color: var(--blue);
font-weight: 600;
}

.legal-back {
margin-top: 48px;
}

.legal-back .text-link {
color: var(--blue);
}

.legal-shell .legal-lead {
margin-top: 28px;
color: var(--navy);
}

.legal-shell .legal-updated {
color: var(--muted);
}

Binär
public/images/hero-postcard-1200.jpg Visa fil

Before After
Width: 1200  |  Height: 900  |  Size: 275KB

Binär
public/images/hero-postcard-1200.webp Visa fil

Before After

Binär
public/images/hero-postcard-600.jpg Visa fil

Before After
Width: 600  |  Height: 450  |  Size: 90KB

Binär
public/images/hero-postcard-600.webp Visa fil

Before After

Binär
public/images/og-image.jpg Visa fil

Before After
Width: 1200  |  Height: 630  |  Size: 160KB

+ 27
- 0
public/js/nav.js Visa fil

@@ -0,0 +1,27 @@
(function () {
document.documentElement.classList.add('has-js');

var toggle = document.querySelector('.nav-toggle');
var nav = document.getElementById('site-nav');
if (!toggle || !nav) return;

function setMenu(open) {
nav.classList.toggle('is-open', open);
toggle.setAttribute('aria-expanded', String(open));
}

toggle.addEventListener('click', function () {
setMenu(toggle.getAttribute('aria-expanded') !== 'true');
});

nav.addEventListener('click', function (event) {
if (event.target.closest('a')) setMenu(false);
});

document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && nav.classList.contains('is-open')) {
setMenu(false);
toggle.focus();
}
});
})();

+ 4
- 0
public/robots.txt Visa fil

@@ -0,0 +1,4 @@
User-agent: *
Allow: /

Sitemap: https://postcardmojo.com/sitemap.xml

+ 9
- 0
public/sitemap.xml Visa fil

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://postcardmojo.com/</loc>
</url>
<url>
<loc>https://postcardmojo.com/privacy</loc>
</url>
</urlset>

+ 6
- 0
public/web.config Visa fil

@@ -56,6 +56,12 @@
</files>
</defaultDocument>

<!-- Serve WebP images (not registered by default on older IIS) -->
<staticContent>
<remove fileExtension=".webp" />
<mimeMap fileExtension=".webp" mimeType="image/webp" />
</staticContent>

<!-- URL Rewrite: send everything through Default.asp except static assets -->
<rewrite>
<rules>


Laddar…
Avbryt
Spara

Powered by TurnKey Linux.