-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.py
More file actions
244 lines (204 loc) · 9.91 KB
/
Copy pathbuild.py
File metadata and controls
244 lines (204 loc) · 9.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#!/usr/bin/env python3
"""
Generates Liftoff: three coming-soon layouts sharing one stylesheet and script.
Coming-soon is a niche where people choose by layout, not by feature list, so
one layout would be a third of a product. The countdown, notify form, social row
and variant switcher are written once here and shared, so the three pages cannot
drift apart.
"""
import pathlib
ROOT = pathlib.Path(__file__).parent
LAUNCH = "2026-10-01T09:00:00Z"
HEAD = """<!doctype html>
<html lang="en" data-bs-theme="{theme}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title}</title>
<meta name="description" content="{desc}">
<link rel="stylesheet" href="assets/vendor/bootstrap6/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/base.css">
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
"""
TOGGLE = """<button class="btn-icon" type="button" id="themeToggle" aria-label="Toggle color mode">
<svg class="theme-icon-light" width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><circle cx="8" cy="8" r="3.25"/><path d="M8 1v1.5M8 13.5V15M15 8h-1.5M2.5 8H1m11-4-1 1M5 11l-1 1m0-8 1 1m6 6 1 1"/></svg>
<svg class="theme-icon-dark" width="18" height="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M6 .278a.77.77 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278"/></svg>
</button>"""
PAGES = [("index.html", "Full bleed"), ("split.html", "Split"), ("minimal.html", "Minimal")]
def switcher(current):
links = "".join(
f'<a class="{"active" if f == current else ""}" href="{f}">{label}</a>'
for f, label in PAGES)
return f'<nav class="variant-switch" aria-label="Layout variants">{links}</nav>'
def wordmark():
return ('<a class="wordmark" href="index.html">'
'<span class="wordmark-mark" aria-hidden="true">L</span> Liftoff</a>')
def countdown(compact=False):
cls = ' style="--count-compact:1"' if compact else ""
cells = "".join(
f'<div class="count-cell"><span class="count-value" id="cd{k}">--</span>'
f'<span class="count-label">{lab}</span></div>'
for k, lab in (("D", "Days"), ("H", "Hours"), ("M", "Minutes"), ("S", "Seconds")))
return (f'<div class="countdown"{cls} role="timer" aria-live="off" '
f'data-countdown="{LAUNCH}">{cells}</div>')
def notify(id_prefix, start=False):
align = " notify-start" if start else ""
return f"""<form class="notify{align}" data-demo-form data-bs-validate novalidate>
<div class="form-field">
<label class="visually-hidden" for="{id_prefix}Email">Email address</label>
<input type="email" class="form-control" id="{id_prefix}Email" placeholder="you@example.com" required>
<div class="invalid-feedback">A valid email, please.</div>
</div>
<button class="btn-solid theme-primary" type="submit" data-done-text="You’re on the list ✓">Notify me</button>
</form>"""
def social():
icons = {
"Instagram": '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="2.5" y="2.5" width="11" height="11" rx="3.2"/><circle cx="8" cy="8" r="2.6"/><circle cx="11.4" cy="4.6" r=".7" fill="currentColor" stroke="none"/></svg>',
"X": '<svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M12.6 1.5h2.2l-4.8 5.5 5.7 7.5h-4.5l-3.5-4.6-4 4.6H1.5l5.2-5.9L1.2 1.5h4.6l3.2 4.2zm-.8 11.7h1.2L4.9 2.7H3.6z"/></svg>',
"LinkedIn": '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M3.4 5.5h2.2V13H3.4zM4.5 2.2a1.3 1.3 0 1 1 0 2.6 1.3 1.3 0 0 1 0-2.6M7.2 5.5h2.1v1c.3-.6 1.1-1.2 2.2-1.2 2 0 2.5 1.3 2.5 3.1V13h-2.2V8.9c0-1-.2-1.7-1.2-1.7s-1.3.7-1.3 1.6V13H7.2z"/></svg>',
}
links = "".join(f'<a href="#" aria-label="{n}">{svg}</a>' for n, svg in icons.items())
return f'<span class="social">{links}</span>'
def build_bar(pct=72):
return f"""<div class="build-bar">
<div class="d-flex justify-content-between fs-xs mb-1">
<span class="fg-3">Build progress</span><span class="fw-semibold">{pct}%</span>
</div>
<div class="progress" role="progressbar" aria-label="Build progress" aria-valuenow="{pct}" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width:{pct}%"></div>
</div>
</div>"""
# ------------------------------------------------------------------ variants
def page_index():
return HEAD.format(
theme="dark",
title="Liftoff — Free Bootstrap 6 Coming Soon Template",
desc="Liftoff is a free Bootstrap 6 coming-soon template in three layouts — full-bleed countdown, split screen and minimal. Live countdown, notify form and build progress.",
) + f"""
<div class="screen screen-photo">
<img class="backdrop" src="img/backdrop.jpg" alt="" width="1800" height="1200" loading="eager">
<header class="screen-head">
{wordmark()}
<div class="ms-auto d-flex align-items-center gap-2">
{switcher("index.html")}
{TOGGLE}
</div>
</header>
<main class="screen-body">
<div class="text-center" style="max-width:44rem">
<span class="badge badge-subtle theme-primary mb-4">Launching 1 October</span>
<h1 class="headline mb-4">Something worth the wait</h1>
<p class="fs-lg mb-5 fg-2">
We are rebuilding from the ground up. Same team, same prices, considerably
fewer things that annoy you.
</p>
{countdown()}
<div class="mt-5 mb-4">
{notify("fb")}
</div>
<p class="fs-xs fg-3 mb-0">No launch spam. One email when we go live, and that is it.</p>
</div>
</main>
<footer class="screen-foot">
<p class="fs-xs fg-3 mb-0">© <span data-year></span> Liftoff. A free Bootstrap 6 template.</p>
<span class="ms-auto">{social()}</span>
</footer>
</div>
<script type="module" src="assets/js/main.js"></script>
</body>
</html>
"""
def page_split():
return HEAD.format(
theme="light",
title="Split layout — Liftoff | Free Bootstrap 6 Coming Soon Template",
desc="Split-screen coming-soon layout from the Liftoff Bootstrap 6 template — photo panel beside a solid form panel with a live countdown.",
) + f"""
<div class="split">
<div class="split-media">
<img src="img/backdrop.jpg" alt="" width="1200" height="1400" loading="eager">
</div>
<div class="split-panel">
<header class="d-flex flex-wrap align-items-center gap-3 mb-auto">
{wordmark()}
<div class="ms-auto d-flex align-items-center gap-2">
{switcher("split.html")}
{TOGGLE}
</div>
</header>
<main class="split-panel-body py-5">
<span class="badge badge-subtle theme-primary mb-4 align-self-start">Launching 1 October</span>
<h1 class="headline mb-4">Something worth the wait</h1>
<p class="fs-lg fg-2 mb-5" style="max-width:32rem">
We are rebuilding from the ground up. Same team, same prices, considerably
fewer things that annoy you.
</p>
<div class="mb-5" style="max-width:34rem">
<div class="countdown justify-content-start" role="timer" aria-live="off" data-countdown="{LAUNCH}">
<div class="count-cell"><span class="count-value" id="cdD">--</span><span class="count-label">Days</span></div>
<div class="count-cell"><span class="count-value" id="cdH">--</span><span class="count-label">Hours</span></div>
<div class="count-cell"><span class="count-value" id="cdM">--</span><span class="count-label">Minutes</span></div>
<div class="count-cell"><span class="count-value" id="cdS">--</span><span class="count-label">Seconds</span></div>
</div>
</div>
<div style="max-width:28rem">
<div class="mb-3">
{notify("sp", start=True)}
</div>
{build_bar()}
</div>
</main>
<footer class="d-flex flex-wrap align-items-center gap-3 mt-auto pt-4">
<p class="fs-xs fg-3 mb-0">© <span data-year></span> Liftoff.</p>
<span class="ms-auto">{social()}</span>
</footer>
</div>
</div>
<script type="module" src="assets/js/main.js"></script>
</body>
</html>
"""
def page_minimal():
return HEAD.format(
theme="light",
title="Minimal layout — Liftoff | Free Bootstrap 6 Coming Soon Template",
desc="Minimal centred coming-soon layout from the Liftoff Bootstrap 6 template — no photography, live countdown and a notify form.",
) + f"""
<div class="screen">
<header class="screen-head">
{wordmark()}
<div class="ms-auto d-flex align-items-center gap-2">
{switcher("minimal.html")}
{TOGGLE}
</div>
</header>
<main class="screen-body">
<div class="text-center" style="max-width:38rem">
<p class="fs-xs fg-3 mb-4" style="letter-spacing:.22em;text-transform:uppercase">Liftoff · 1 October 2026</p>
<h1 class="headline mb-4">Something worth the wait</h1>
<p class="fs-lg fg-2 mb-5">
No photography, no gradient, nothing to load. Sometimes a holding page
should just hold.
</p>
{countdown()}
<div class="mt-5 mb-4">
{notify("mn")}
</div>
{build_bar()}
</div>
</main>
<footer class="screen-foot">
<p class="fs-xs fg-3 mb-0">© <span data-year></span> Liftoff. A free Bootstrap 6 template.</p>
<span class="ms-auto">{social()}</span>
</footer>
</div>
<script type="module" src="assets/js/main.js"></script>
</body>
</html>
"""
(ROOT / "index.html").write_text(page_index(), encoding="utf-8")
(ROOT / "split.html").write_text(page_split(), encoding="utf-8")
(ROOT / "minimal.html").write_text(page_minimal(), encoding="utf-8")
print("wrote 3 layouts")