Markdown blog on Flask with Vite-built assets and WeasyPrint PDF export, rewritten from the Wagtail build that preceded it.
blogdockerflaskmarkdownpythonself-hostedvite
1body {
2 min-height: 100vh;
3 display: flex;
4 flex-direction: column;
5 -webkit-font-smoothing: antialiased;
6 -moz-osx-font-smoothing: grayscale;
7 letter-spacing: 0.01em;
8}
9
10// ── Logo ──
11
12.logo {
13 content: "";
14 display: flex;
15 align-items: center;
16 justify-content: center;
17 background-image: linear-gradient(
18 135deg,
19 rgb(14, 63, 180) 0%,
20 rgb(107, 158, 120) 100%
21 );
22 width: 36px;
23 height: 36px;
24 margin-top: auto;
25 margin-bottom: auto;
26 border-radius: 3px;
27 transition: transform 0.25s ease, box-shadow 0.25s ease;
28 position: relative;
29 text-decoration: none !important;
30
31 &::after {
32 content: ">_";
33 font-family: monospace;
34 font-weight: bold;
35 font-size: 14px;
36 color: rgba(255, 255, 255, 0.9);
37 line-height: 1;
38 text-decoration: none;
39 }
40
41 &[style*="width: 20px"]::after {
42 font-size: 8px;
43 }
44
45 &:hover {
46 transform: rotate(12deg) scale(1.08);
47 box-shadow: 0 0 14px rgba(107, 158, 120, 0.3);
48 }
49}
50
51// ── Navbar ──
52
53.navbar {
54 border-bottom: 1px solid rgba(107, 158, 120, 0.06);
55
56 .nav-link {
57 font-size: 0.82rem;
58 font-weight: 500;
59 letter-spacing: 0.04em;
60 padding-left: 1rem !important;
61 padding-right: 1rem !important;
62 text-transform: lowercase;
63 position: relative;
64
65 &::after {
66 content: '';
67 position: absolute;
68 bottom: 0;
69 left: 1rem;
70 right: 1rem;
71 height: 1px;
72 background: #6b9e78;
73 box-shadow: 0 0 6px rgba(107, 158, 120, 0.3);
74 transform: scaleX(0);
75 transition: transform 250ms ease;
76 }
77
78 &:hover::after,
79 &.active::after {
80 transform: scaleX(1);
81 }
82 }
83}
84
85// ── Breadcrumb bar ──
86
87.breadcrumb-bar {
88 background: rgba(107, 158, 120, 0.02);
89 border-bottom: 1px solid rgba(107, 158, 120, 0.04);
90}
91
92// ── Footer ──
93
94footer {
95 padding-top: 4rem;
96 position: relative;
97
98 .links {
99 padding-top: 4rem;
100 padding-bottom: 2rem;
101 position: relative;
102 z-index: 20;
103 }
104
105 .h5 {
106 font-size: 0.72rem;
107 letter-spacing: 0.1em;
108 text-transform: uppercase;
109 color: rgba(201, 168, 76, 0.55);
110 font-weight: 600;
111 }
112
113 p {
114 color: rgba(221, 215, 205, 0.5);
115 font-size: 0.88rem;
116 line-height: 1.75;
117
118 a {
119 color: rgba(221, 215, 205, 0.75);
120 text-decoration: underline;
121 text-underline-offset: 2px;
122 transition: color 200ms ease;
123
124 &:hover {
125 color: #ddd7cd;
126 }
127 }
128 }
129}
130
131// ── Footer bar (sosumi) ──
132
133.footer-bar {
134 background: #070605;
135 border-top: 1px solid rgba(107, 158, 120, 0.05);
136 padding: 1rem 0;
137 color: rgba(221, 215, 205, 0.3);
138 font-size: 0.78rem;
139 letter-spacing: 0.03em;
140 position: relative;
141 z-index: 20;
142
143 small {
144 color: rgba(221, 215, 205, 0.3);
145 }
146
147 &-link {
148 color: rgba(221, 215, 205, 0.3);
149 transition: color 200ms ease;
150
151 &:hover {
152 color: #ddd7cd;
153 }
154 }
155}
156
157// ── Footer animated plane ──
158
159.footer-plane {
160 position: relative;
161 width: 100%;
162 height: 300px;
163 overflow: hidden;
164 z-index: 10;
165 perspective: 350px;
166
167 &-overlay {
168 width: 100%;
169 height: 100%;
170 position: absolute;
171 z-index: 11;
172 background: radial-gradient(
173 ellipse at 50% 50%,
174 rgba(9, 8, 6, 0) 0,
175 rgb(9, 8, 6) 80%
176 );
177 }
178
179 @keyframes footer-plane-keyframes {
180 0% {
181 transform: rotateX(45deg) translateY(-50%);
182 }
183
184 to {
185 transform: rotateX(45deg) translateY(0);
186 }
187 }
188
189 &-grid {
190 position: relative;
191 width: 100%;
192 height: 200%;
193 background-image: linear-gradient(
194 90deg,
195 rgba(107, 158, 120, 0.18) 1px,
196 transparent 0
197 ),
198 linear-gradient(180deg, rgba(107, 158, 120, 0.18) 1px, transparent 0);
199 background-size: 40px 30px;
200 background-repeat: repeat;
201 transform-origin: 100% 0 0;
202 animation: footer-plane-keyframes 17s linear infinite;
203 }
204}
205
206// ── Section labels — terminal-prompt style with amber ──
207
208.section-label {
209 font-size: 0.72rem;
210 font-weight: 600;
211 letter-spacing: 0.1em;
212 text-transform: uppercase;
213 color: #c9a84c;
214
215 &::before {
216 content: '> ';
217 opacity: 0.5;
218 }
219}
220
221// ── Card hover effects ──
222
223.card {
224 transition: border-color 250ms ease, box-shadow 250ms ease;
225
226 &:hover {
227 border-color: rgba(107, 158, 120, 0.18);
228 box-shadow: 0 0 20px rgba(107, 158, 120, 0.04);
229 }
230}
231
232// ── Search form — terminal style ──
233
234.search-form {
235 .search-icon {
236 position: absolute;
237 left: 0.85rem;
238 top: 50%;
239 transform: translateY(-50%);
240 color: #665f56;
241 pointer-events: none;
242 z-index: 2;
243 }
244
245 .search-input {
246 padding-left: 2.5rem;
247 font-size: 0.85rem;
248 letter-spacing: 0.02em;
249 border: 1px solid rgba(107, 158, 120, 0.15);
250 background: rgba(19, 18, 14, 0.6);
251 color: #ddd7cd;
252 transition: border-color 200ms ease, box-shadow 200ms ease;
253
254 &::placeholder {
255 color: #665f56;
256 font-style: normal;
257 }
258
259 &:focus {
260 border-color: rgba(107, 158, 120, 0.4);
261 box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.1);
262 background: #13120e;
263 }
264 }
265}
266
267// ── Live search results ──
268
269#id_search_results .list-group-item {
270 color: #ddd7cd;
271 border-color: rgba(107, 158, 120, 0.08);
272
273 .fw-bold {
274 color: #ddd7cd;
275 }
276
277 &.active {
278 background: rgba(107, 158, 120, 0.12);
279 border-color: rgba(107, 158, 120, 0.2);
280 color: #ddd7cd;
281 }
282
283 &:hover {
284 background: rgba(107, 158, 120, 0.08);
285 }
286}
287
288// ── Blog index filter pills ──
289
290.filter-label {
291 font-size: 0.68rem;
292 font-weight: 600;
293 letter-spacing: 0.1em;
294 text-transform: uppercase;
295 color: #c9a84c;
296 opacity: 0.7;
297}
298
299.filter-divider {
300 width: 1px;
301 height: 1.2rem;
302 background: rgba(221, 215, 205, 0.1);
303 margin: 0 0.25rem;
304}
305
306.btn-filter {
307 background: rgba(221, 215, 205, 0.04);
308 color: #a09890;
309 border: 1px solid rgba(221, 215, 205, 0.08);
310 font-size: 0.75rem;
311 line-height: 1;
312 padding: 0.4rem 0.75rem;
313 display: inline-flex;
314 align-items: center;
315 transition: all 200ms ease;
316
317 &:hover {
318 background: rgba(107, 158, 120, 0.1);
319 color: #ddd7cd;
320 border-color: rgba(107, 158, 120, 0.2);
321 }
322}
323
324.btn-filter-active {
325 background: rgba(107, 158, 120, 0.15);
326 color: #7db88c;
327 border: 1px solid rgba(107, 158, 120, 0.3);
328 font-size: 0.75rem;
329 line-height: 1;
330 padding: 0.4rem 0.75rem;
331 display: inline-flex;
332 align-items: center;
333}
334
335.btn-filter-clear {
336 background: rgba(196, 112, 85, 0.1);
337 color: #c47055;
338 border: 1px solid rgba(196, 112, 85, 0.2);
339 font-size: 0.75rem;
340 line-height: 1;
341 padding: 0.4rem 0.75rem;
342 display: inline-flex;
343 align-items: center;
344 transition: all 200ms ease;
345
346 &:hover {
347 background: rgba(196, 112, 85, 0.2);
348 color: #d88a70;
349 border-color: rgba(196, 112, 85, 0.35);
350 }
351}
352
353// ── Vertical rule override ──
354
355.vr {
356 background-color: rgba(107, 158, 120, 0.12);
357}