In the previous tutorial, we created the foundation of our modern landing page using Flask-Frozen and Bootstrap 5. We built a responsive navigation bar, an engaging hero section, and an informative About Us section to introduce the pickleball club. In this tutorial, we will continue enhancing the landing page by adding more essential sections that improve the user experience and provide visitors with important information about the club. By the end of this tutorial, the landing page will become more complete and ready for real-world deployment.
This tutorial is part of the Flask Landing Page and Reservation System Series.
📚 View the Complete Flask Landing Page and Reservation System Series
Continues from Part 1 ➡ Next Partpython -m venv venv
venv\Scripts\activate
pip install flask frozen-flaskThen, we need to set up the file and folder structure, as below:<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
(b) JavaScript
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3
/dist/js/bootstrap.bundle.min.js">
</script>
<link rel="stylesheet"
href="{{ url_for('static', filename='style.css') }}">Step 2: Create the facilities, contact me and footer section
The style description of each element is shown below:
html,
body {
overflow-x: hidden;
width: 100%;
background: #3f496a;
}
h2 { color: white;
font-size: 48px;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
text-align: center;
margin: 0;}
p {
text-align: center;
margin-top: 20px;
}
.facilities-subtitle {
color: white;
font-size: 18px;
text-align: center;
text-shadow: 1px 1px 3px rgba(0,0,0,0.3);
}
.card-body {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
color: black;
}
.card-description {
color: #212529;
font-size: 14px;
text-shadow: none;
}
.section-divider {
height: 2px;
background: #dee2e6;
margin: 40px 0;
}
(b) index.html - There is a heading and subheading on the top of this section.
- Every card contains an image, and below an image are a title and a brief description of what the club offers to its members.
- The image is sourced from the static/assets folder.
<hr class="section-divider">
<!---facilities section--->
<section id="facilities" class="section-padding">
<div class="text-center mb-5">
<h2>Our Facilities</h2>
<p class="facilities-subtitle">Everything you need for
an exceptional game.</p>
</div>
<div class="row g-4">
<div class="col-md-3">
<div class="card h-60 shadow-sm">
<img
src="{{ url_for('static',
filename='assets/court.png') }}"
class="card-img-top facility-img"
alt="Professional Courts">
<div class="card-body text-center">
<h5>Professional Courts</h5>
<p class="card-description">
Tournament-quality courts designed for
competitive and recreational play whether
you're a beginner or an experienced player.
</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-60 shadow-sm">
<img
src="{{ url_for('static',
filename='assets/night_lighting.png') }}"
class="card-img-top facility-img"
alt="Night Lighting">
<div class="card-body text-center">
<h5>Night Lighting</h5>
<p class="card-description">
State-of-the-art LED lighting ensures a bright and
enjoyable playing experience,
allowing members to play comfortably day or night.
</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-60 shadow-sm">
<img
src="{{ url_for('static',
filename='assets/changing_rooms.png') }}"
class="card-img-top facility-img"
alt="Changing Rooms">
<div class="card-body text-center">
<h5>Changing Rooms</h5>
<p class="card-description">
Modern changing facilities provide a comfortable space
to prepare for your game and unwind after
an exciting match.
</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-60 shadow-sm">
<img
src="{{ url_for('static',
filename='assets/free_parking.png') }}"
class="card-img-top facility-img"
alt="Free Parking">
<div class="card-body text-center">
<h5>Free Parking</h5>
<p class="card-description">
Convenient complimentary and free parking is available
for all members and guests, offering easy access
to our facilities.
</p>
</div>
</div>
</div>
</div>
</section>
<hr class="section-divider">
(i) Horizontal Divider (<hr>)
Bootstrap/custom class used here is:
<hr>), typically by adjusting its width, colour, thickness, and spacing to visually separate different sections of the webpage.(ii) Facilities Section (<section>)
Classes used here are:
section-padding is a custom CSS class that adds vertical padding above and below the section, creating whitespace and improving readability.
The id="facilities" attribute provides a unique identifier for the section. It allows navigation links such as <a href="#facilities">Facilities</a> to scroll directly to this section.
🎁 Get Your FREE Flask Cheat Sheet
Get more Flask, Python automation, Docker, and HTMX tutorials delivered to your inbox.
✓ Practical coding tutorials
✓ Automation tips for SMEs
✓ New project ideas and templates
Download my FREE Flask Cheat Sheet (PDF)
This is the final section, where the user can always connect to the club at anytime and anywhere. Since the Python code will be frozen, no more backend logic will be applicable. Therefore, I could not use the normal contact form route function; I used Formspree instead.
.hero p,
#about p,
#contact p,
footer p {
color: white;
text-shadow: 1px 1px 3px rgba(0,0,0,0.3);
font-size: 18px;
}
label {
color: white;
font-size: 16px;
margin-bottom: 5px;
}
(b) index.html
<!---call to action section--->
<section id="contact" class="section-padding">
<div class="row">
<div class="col-lg-6">
<h2>Keep In Touch</h2>
<p>
Find us in Jersey City and start your
health journey with us.
</p>
<p>
Email: info@jerseycitypickleball.com
</p>
<p>
Phone: (123) 456-7890
</p>
</div>
<div class="col-lg-6">
<form action="https://formspree.io/f/your_form_id" method="POST">
<div class="mb-3">
<label for="name" class="form-label">
Name
</label>
<input
type="text"
class="form-control"
id="name"
name="Name"
required>
</div>
<div class="mb-3">
<label for="email" class="form-label">
Email
</label>
<input
type="email"
class="form-control"
id="email"
name="Email"
required>
</div>
<div class="mb-3">
<label for="message" class="form-label">
Message
</label>
<textarea
class="form-control"
id="message"
name="Message"
rows="5"
required></textarea>
</div>
<button type="submit"
class="btn btn-primary">
Send Message
</button>
</form>
</div>
</section>
<hr class="section-divider">(i) Contact Section (<section>)
The custom class used here is
"section-padding" is a custom CSS class that adds vertical spacing above and below the section, creating a clean separation from other sections of the webpage.
The id="contact" attribute provides a unique identifier for the section. It allows navigation links, such as <a href="#contact">Contact</a> to scroll directly to this section.
(ii) Bootstrap Grid Row
Bootstrap class used here is:
row creates a horizontal row that contains Bootstrap columns and organises the content into a responsive grid layout.
(iii) Grid Columns
The Bootstrap class used here is
col-lg-6 divides the row into two equal columns on large screens (≥992px). One column displays the contact information, while the other contains the contact form. On smaller screens, the columns automatically stack vertically to improve readability.
(iv) Contact Information
This column contains the club's contact details, including the heading, address information, email address, and telephone number. Standard HTML elements such as <h2> and <p> are used to structure and display the information clearly.
(v) Contact Form
The <form> element collects user information and sends it to the specified destination.
The action attribute specifies the URL where the submitted form data will be sent. In this example, the form uses Formspree, a third-party form handling service.
The method="POST" attribute sends the form data securely within the HTTP request body instead of displaying it in the URL.
(vi) Form Group
The Bootstrap class used here is
mb-3 adds a bottom margin below each form field, providing consistent spacing between the input controls.
Each form group contains a label and its corresponding input element.
(vii) Form Labels
The Bootstrap class used here is
form-label applies Bootstrap's standard styling to form labels, ensuring consistent spacing, font size, and alignment.
The for attribute links the label to its corresponding input field through the matching id value, improving accessibility and allowing users to focus on the input by clicking the label.
(viii) Input Fields
The Bootstrap class used here is
Form-control styles text boxes, email fields, and text areas with Bootstrap's default appearance, including consistent width, padding, borders, and rounded corners.
The required attribute makes each field mandatory and prevents the form from being submitted if the field is left empty.
The type attribute specifies the type of data expected, such as text for names and email for email addresses. The email type also enables built-in browser validation.
(ix) Text Area
The Bootstrap class used here is
form-control applies the same consistent Bootstrap styling used by other input fields.
The rows="5" attribute specifies the initial height of the text area by displaying approximately five lines of text.
(x) Submit Button
Bootstrap classes used here are:
btn applies Bootstrap's standard button styling.
btn-primary styles the button using Bootstrap's primary theme colour, making it stand out as the main call-to-action for submitting the form.
The type="submit" attribute submits the form data to the URL specified in the action attribute when the button is clicked.
(xi) Horizontal Divider (<hr>)
The custom class used here is
section-divider is a custom CSS class that styles the horizontal rule (<hr>), typically by adjusting its width, colour, thickness, and spacing to visually separate different sections of the webpage.
(xii) Overall Layout
This section combines Bootstrap's Grid System (row and col-lg-6) with Bootstrap Form Components (form-control, form-label, btn, and btn-primary) to create a responsive two-column contact section. On large screens, the contact information and contact form are displayed side by side, while on smaller devices they automatically stack vertically to provide a user-friendly and responsive layout.
.footer {
background: #212529;
color: white;
}
#jc {
font-weight: bold;
color: #ffc107; /* Pickleball yellow */
}
(b) index.html
<!--footer section--->
<footer class="footer py-4">
<div class="container">
<div class="row">
<div class="col-md-6 text-center text-md-start">
<p class="mb-0" id="jc">Jersey City Pickleball Club</p>
<p class="mb-0">
Play. Compete. Connect.
</p>
</div>
<div class="col-md-6 text-center text-md-end">
<p class="mb-0">
Email: info@jerseycitypickleball.com
</p>
<p class="mb-0">
© 2026 All Rights Reserved
</p>
</div>
</div>
</div>
</footer>
(i) Footer Section (<footer>)
Bootstrap and custom classes used here are
footer is a custom CSS class used to style the footer, such as setting the background colour, text colour, and overall appearance.
py-4 adds vertical padding (1.5rem) to the top and bottom of the footer, creating adequate spacing around the content.
The <footer> element is a semantic HTML5 element that identifies the footer section of the webpage, typically containing copyright information, contact details, or additional navigation.
(ii) Container
The Bootstrap class used here is
container centres the footer content horizontally and limits its maximum width, ensuring the layout remains well-aligned and responsive across different screen sizes.
(iii) Bootstrap Grid Row
The Bootstrap class used here is
row creates a horizontal row that organises the footer content into responsive columns.
(iv) Footer Columns
The Bootstrap classes used here are:
col-md-6 divides the row into two equal columns on medium-sized screens (≥768px) and larger. On smaller screens, the columns automatically stack vertically.
text-center centres the text on smaller devices, providing a clean mobile layout.
text-md-start aligns the text to the left on medium-sized screens and larger.
text-md-end aligns the text to the right on medium-sized screens and larger.
This combination creates a centred layout on mobile devices while displaying left- and right-aligned content on larger screens.
(v) Footer Text
The Bootstrap class used here is
mb-0 removes the default bottom margin from each paragraph (<p>), allowing the text elements to appear closely grouped and neatly aligned.
The id="jc" attribute uniquely identifies the "Jersey City Pickleball Club" text. It can be used for applying custom CSS styling or for JavaScript interactions if required.
(vi) Overall Layout
This footer combines Bootstrap's Grid System (container, row, and col-md-6) with Responsive Text Alignment Utilities (text-center, text-md-start, and text-md-end) to create a responsive two-column footer. On medium-sized screens and larger, the club information appears on the left while the contact details and copyright information are displayed on the right. On smaller devices, both columns automatically stack vertically with centred text, ensuring a clean and mobile-friendly layout.
from app import app
from flask_frozen import Freezer
# This configuration instructs Frozen-Flask
# to generate relative URLs instead of absolute URLs.
app.config['FREEZER_RELATIVE_URLS'] = True
# Output directory
app.config['FREEZER_DESTINATION'] = 'build'
# Use relative paths for links and assets
app.config['FREEZER_BASE_URL'] = './'
# Preserve static folder structure
app.config['FREEZER_STATIC_IGNORE'] = ['*.scss', '*.less']
freezer = Freezer(app)
if __name__ == '__main__':
freezer.freeze()
# Optional: Copy any additional static files
print("Freezing complete! Check the 'build' folder")from app import app imports the Flask application instance from the app.py file. This allows Frozen-Flask to access all the application's routes and templates.
from flask_frozen import Freezer imports the Freezer class from the Flask-Frozen library. The Freezer class converts a dynamic Flask application into a collection of static HTML files.
Using relative URLs improves portability, allowing the static website to be hosted on platforms such as GitHub Pages or copied to different directories without breaking internal links.
The Freezer object scans all registered Flask routes, renders each page, and generates the corresponding static HTML files.
I have begun the freezing process by:
- Discovering all accessible Flask routes.
- Rendering each template.
- Generating static HTML files.
- Copying the required static assets (such as CSS, JavaScript, and images) into the output directory.
# direct to my directory
cd JCPC
# Freeze the flask app
python freeze.py
My terminal will show "Freezing complete! Check the 'build' folder, and in my files and folders, a new build folder will appear.In this tutorial, we completed the pickleball club landing page by adding the Facilities, Contact Me, and Footer sections. These additions make the website more informative, professional, and user-friendly. We also demonstrated how to integrate a Formspree contact form and perform a live submission test. The submitted messages can be viewed directly from the Formspree portal or processed by your web application. With these sections in place, the landing page is now ready to serve as a complete front-end foundation for future Flask-powered features.
---------------------------------------------------------------------------------------------------------------------------------------------------
.png)









Comments