15 Commits

Author SHA1 Message Date
4e367d73a0 order projects
Some checks failed
test / cargo test (push) Failing after 57s
2024-09-27 13:34:07 +02:00
85c98fac56 showcase page
Some checks failed
test / cargo test (push) Failing after 1m12s
2024-09-27 11:52:25 +02:00
f62673d6a7 yutube and instagram
Some checks failed
test / cargo test (push) Failing after 59s
2024-09-26 14:59:18 +02:00
221d5cef23 twitch and tiktok hover effects
Some checks failed
test / cargo test (push) Failing after 1m5s
2024-09-26 14:12:55 +02:00
dc1a01c352 yes yes yes
Some checks failed
test / cargo test (push) Failing after 1m6s
2024-09-26 12:19:45 +02:00
20d1314925 lol hide instagram between lg-xl
Some checks failed
test / cargo test (push) Failing after 1m12s
2024-09-25 16:21:35 +02:00
1eb0f55264 holy shit the responsiveness
Some checks failed
test / cargo test (push) Failing after 1m24s
2024-09-25 12:55:05 +02:00
fc26d77bfc few style 2024-09-25 11:05:19 +02:00
2949429fa4 calendly link
Some checks failed
test / cargo test (push) Failing after 58s
2024-09-20 16:31:00 +02:00
52bd4e5590 Contact page favicon
Some checks failed
test / cargo test (push) Failing after 1m3s
2024-09-20 12:35:30 +02:00
377aee315e mooooar moooar pretty fixes
Some checks failed
test / cargo test (push) Failing after 1m4s
2024-09-20 11:51:29 +02:00
f3c4df4458 strong bold medium and heading tag handling 2024-09-20 10:44:59 +02:00
6650366e60 better theme colors
Some checks failed
test / cargo test (push) Failing after 1m3s
2024-09-19 12:06:35 +02:00
0937de96dd coloooors 2024-09-18 18:36:54 +02:00
fb63a2011b fuckit 2024-09-18 16:30:16 +02:00
74 changed files with 1346 additions and 580 deletions

View File

@@ -1,8 +1,9 @@
name: release name: release
on: # on:
push: # push:
branches: # branches:
main # main
on: workflow_dispatch
env: env:
PORT: 3081 PORT: 3081

View File

@@ -17,7 +17,9 @@ tags:
Creating my own website with blog was something I had in my mind from start of my professional career after I left school. Creating my own website with blog was something I had in my mind from start of my professional career after I left school.
I had a lot of new experience with development which I wanted to elaborate on and save into a small library so I can take a look back on my thoughts how they evolve over time. I had a lot of new experience with development which I wanted to elaborate on and save into a small library so I can take a look back on my thoughts how they evolve over time.
This was like 6 years ago. I had a successful first attempt at doing so. I created a WordPress with the simplest theme I've found and wrote some articles. I've published it under a domain of one of the first startups I've been part of. I still have a backup of the _Wordpress_ database somewhere so I can export those articles here when I will feel like doing so. The blog haven't lived for long as the domain once expired and I was not satisfied with it enough to deploy it somewhere else. This was like 6 years ago. I had a successful first attempt at doing so.
I created a WordPress with the simplest theme I've found and wrote some articles. I've published it under a domain of one of the first startups I've been part of. I still have a backup of the _Wordpress_ database somewhere so I can export those articles here when I will feel like doing so. The blog haven't lived for long as the domain once expired and I was not satisfied with it enough to deploy it somewhere else.
For all those years I was trying to create it in my spare time (of which wasn't that much apparently). There were several attempts. One with _Angular_ when it was "the cool kid on the block". Another one with _cycle.js_ which was not that far from being done. I regret it now as it would be really satisfying to finish that one. I had created neat <abbr title="Server side rendering">SSR</abbr> layer which was not really difficult to accomplish with _cycle.js_ as it is reactive and it only required skipping first client render of _virtual-dom_ after page load. I'm still in love with _cycle.js_ but after _sapper_ was released I've found out of its ability to create a nice **static site** I wanted to try it out. I think that the approach of **compiling the source code** as classic client applications have been doing for many years makes a lot of sense on the internet as well. This is the one thing I'd really like to be able to accomplish with reactive frameworks like _cycle.js_. For all those years I was trying to create it in my spare time (of which wasn't that much apparently). There were several attempts. One with _Angular_ when it was "the cool kid on the block". Another one with _cycle.js_ which was not that far from being done. I regret it now as it would be really satisfying to finish that one. I had created neat <abbr title="Server side rendering">SSR</abbr> layer which was not really difficult to accomplish with _cycle.js_ as it is reactive and it only required skipping first client render of _virtual-dom_ after page load. I'm still in love with _cycle.js_ but after _sapper_ was released I've found out of its ability to create a nice **static site** I wanted to try it out. I think that the approach of **compiling the source code** as classic client applications have been doing for many years makes a lot of sense on the internet as well. This is the one thing I'd really like to be able to accomplish with reactive frameworks like _cycle.js_.
@@ -115,7 +117,7 @@ For example this is the model of this blog post:
Neat part of the _CMS_ are those widgets. In editor they will be presented by appropriate component as well as in the editor preview. Neat part of the _CMS_ are those widgets. In editor they will be presented by appropriate component as well as in the editor preview.
I am very satisfied with it and I recommend it. I am very satisfied with it and I recommend it.
## What's next ## What's next {#whats-next}
I've decided not to wait for perfect product and I want to release this blog as soon as possible. I will have same approach as with other products. Make a <abbr title="Minimum Viable Product">**MVP**</abbr> and then release features as they are done. I've decided not to wait for perfect product and I want to release this blog as soon as possible. I will have same approach as with other products. Make a <abbr title="Minimum Viable Product">**MVP**</abbr> and then release features as they are done.
I've put some features into _Github Projects Board_. I will very likely make a redesign with experimental layout changes. I'd like to experiment with colors and make the blog look distinguishable and personal while maintaining accessibility. I've put some features into _Github Projects Board_. I will very likely make a redesign with experimental layout changes. I'd like to experiment with colors and make the blog look distinguishable and personal while maintaining accessibility.

View File

@@ -38,7 +38,9 @@ I've set a new **sub goal** for buying a streaming PC. I'll buy it when the stre
Last week I've been showing off new headphones that I've ordered and the *YouTube* algorithm has been suggesting great videos on the topic since. I will give another shoutout. This time it is to [Joshua's Valour *YouTube* channel](https://www.youtube.com/channel/UCx9bOYEjkevIDYONBAstK-A) Last week I've been showing off new headphones that I've ordered and the *YouTube* algorithm has been suggesting great videos on the topic since. I will give another shoutout. This time it is to [Joshua's Valour *YouTube* channel](https://www.youtube.com/channel/UCx9bOYEjkevIDYONBAstK-A)
<iframe width="560" height="315" src="https://www.youtube.com/embed/hoLMdrD5pic" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <div class="video-embed">
<iframe class="embed" width="100%" height="100%" src="https://www.youtube.com/embed/hoLMdrD5pic" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
My next pick will be a [podcast *Lifespan* hosted by Dr. David Sinclair](https://open.spotify.com/show/3PkkSdQE8DfeiKvSk1Mg1J) where he talks about what we can do and how should we treat our bodies to live a longer and healthier life. My next pick will be a [podcast *Lifespan* hosted by Dr. David Sinclair](https://open.spotify.com/show/3PkkSdQE8DfeiKvSk1Mg1J) where he talks about what we can do and how should we treat our bodies to live a longer and healthier life.

View File

@@ -28,7 +28,7 @@ This week I've attended a [Rusty game jam #2](https://itch.io/jam/rusty-jam-2).
![Egg fetcher game preview](/images/uploads/screenshot-from-2022-06-26-22-37-16.png "Egg fetcher game preview") ![Egg fetcher game preview](/images/uploads/screenshot-from-2022-06-26-22-37-16.png "Egg fetcher game preview")
[You can check the rusult built with WASM here.](/showcase/egg-fetcher/) [You can check the result built with WASM here.](/showcase/egg-fetcher/)
## What's up with the weeklys ## What's up with the weeklys

View File

@@ -1,11 +1,12 @@
--- ---
title: CK Vive title: CK Vive
displayed: true displayed: true
description: Websitefor *CK Vive* travel agency with a **custom CMS system** for
managing travel destinations.
cover_image: /images/uploads/ck_vive_logo.svg cover_image: /images/uploads/ck_vive_logo.svg
link: https://ckvive.sk/
classification: website classification: website
tags: tags:
- PHP - PHP
featured: false featured: false
--- ---
Websitefor *CK Vive* travel agency with a **custom CMS system** for
managing travel destinations.

View File

@@ -1,8 +1,6 @@
--- ---
title: Košice Peace Marathon title: Košice Peace Marathon
displayed: true displayed: true
description: "*Košice Peace Marathon* is the oldest marathon in Europe and the
third-oldest in the world."
link: https://www.kosicemarathon.com/ link: https://www.kosicemarathon.com/
cover_image: /images/uploads/screenshot-from-2024-08-06-18-22-52.png cover_image: /images/uploads/screenshot-from-2024-08-06-18-22-52.png
classification: website classification: website
@@ -11,3 +9,5 @@ tags:
- MySQL - MySQL
featured: false featured: false
--- ---
*Košice Peace Marathon* is the oldest marathon in Europe and the
third-oldest in the world.

View File

@@ -1,8 +1,6 @@
--- ---
title: Docker title: Docker
displayed: false displayed: false
description: An introduction to Docker containerization technology and how it
differs from virtualization.
cover_image: /images/uploads/docker-use-cases.png cover_image: /images/uploads/docker-use-cases.png
classification: presentation classification: presentation
tags: tags:
@@ -10,3 +8,5 @@ tags:
- Docker - Docker
featured: false featured: false
--- ---
An introduction to Docker containerization technology and how it
differs from virtualization.

View File

@@ -0,0 +1,13 @@
---
title: Unstoppable growth of front-end frameworks
displayed: true
link: https://michalvankodev.github.io/unstoppable-growth-of-frontend-frameworks/
classification: presentation
tags:
- Presentation
- NodeJS
featured: false
---
A simple summary of the web front-end evolution. Describes how and
why new tools in the NodeJS ecosystem improve & why there is still something
to explore.

View File

@@ -0,0 +1,10 @@
---
title: Skosy
displayed: false
classification: webapp
tags:
- Webapp
featured: false
---
*Skosy* is a web application whose purpose is to **automate the
writing of integration tests** for websites.

View File

@@ -0,0 +1,13 @@
---
title: beinSports
displayed: true
link: https://www.beinsports.com/en-us
cover_image: /images/uploads/bein_logo.af017869.webp
classification: website
tags:
- Freemarker
featured: false
---
*beIN Sports* is a global network of sports channels jointly owned
and operated by *Qatari Sports Investments*, an affiliate of *Al Jazeera Media
Networks*

View File

@@ -0,0 +1,10 @@
---
title: Livesport.tv
displayed: false
classification: website
tags:
- Freemarker
featured: false
---
*Livesport.tv* is a network of premium online sports channels,
featuring all the top sports competitions from around the world.

View File

@@ -0,0 +1,14 @@
---
title: Spreading the Web
displayed: true
link: https://michalvankodev.github.io/spreading-the-web
cover_image: /images/uploads/screenshot-from-2024-08-06-18-48-02.png
classification: presentation
tags:
- Presentation
- NodeJS
featured: false
---
A presentation about the rising number of use cases for utilizing
web technologies outside of the web platform such as native mobile
applications and robotics. 2015

View File

@@ -0,0 +1,15 @@
---
title: FX Sales
displayed: true
link: https://www.caplin.com/business/fx-sales
cover_image: /images/uploads/fx_sales_screen2x.png
classification: webapp
tags:
- Webapp
- React
- Knockout
featured: false
---
*Caplin FX Sales* allows sales people to **trade on behalf of
their clients**. This needs to be an efficient workflow providing all the
relevant information to the sales user

View File

@@ -1,11 +1,11 @@
--- ---
title: SHIP (Structured heard input process) title: SHIP (Structured heard input process)
displayed: true displayed: true
description: "*SHIP* is a web application for **editors** who actively **track
trades offers and bids** on the commodity market."
classification: webapp classification: webapp
tags: tags:
- Webapp - Webapp
- Angular - Angular
featured: false featured: false
--- ---
*SHIP* is a web application for **editors** who actively **track
trades offers and bids** on the commodity market.

View File

@@ -0,0 +1,15 @@
---
title: responzIO
displayed: true
link: https://www.croptech.com/
cover_image: /images/uploads/responzio.png
classification: embedded
tags:
- Webapp
- Embedded
- NodeJS
featured: false
---
***responzIO*** is a smart, easy-to-use monitoring and automation
system. The ultimate tool for various applications such as hydroponics,
aquariums, and gardens.

View File

@@ -0,0 +1,14 @@
---
title: Manualogic
displayed: false
classification: webapp
tags:
- Webapp
- Angular
- RxJS
featured: false
---
*Manualogic* is a **single-page application** for product manual
creators. It contains **custom web editor** and management system of
**translatable pages, books** and **products.** Its main goal is to enable
customers to get manuals of their products in digital form.

View File

@@ -0,0 +1,13 @@
---
title: Signal Hub Manager
displayed: true
classification: webapp
tags:
- Webapp
- React
featured: false
---
*Signal Hub* is an end-to-end **Big Data analytics platform** for
large enterprises. It accelerates the process of extracting insights and
intelligence from large volumes of data, including data of different types and
in different formats.

View File

@@ -1,9 +1,6 @@
--- ---
title: Panoramic title: Panoramic
displayed: true displayed: true
description: "*Panoramic* was a company focused on building a web application
for data scientists to be able to create and share models and graphs in
between each other."
classification: webapp classification: webapp
tags: tags:
- Webapp - Webapp
@@ -14,3 +11,6 @@ tags:
- Data analytics - Data analytics
featured: false featured: false
--- ---
*Panoramic* was a company focused on building a web application
for data scientists to be able to create and share models and graphs in
between each other.

View File

@@ -1,11 +1,6 @@
--- ---
title: The Expert title: The Expert
displayed: true displayed: true
description: _The Expert_ is a digital platform that connects clients to
interior designers around the world. For experts, it allows **managing** their
**portfolio and profile** and **schedule** in which they are open for
**consultations**. Clients are able to view their profiles and book
consultations.
link: https://www.theexpert.com/ link: https://www.theexpert.com/
cover_image: /images/uploads/the-expert-logo.svg cover_image: /images/uploads/the-expert-logo.svg
classification: webapp classification: webapp
@@ -17,3 +12,8 @@ tags:
- GraphQL - GraphQL
featured: true featured: true
--- ---
_The Expert_ is a digital platform that connects clients to
interior designers around the world. For experts, it allows **managing** their
**portfolio and profile** and **schedule** in which they are open for
**consultations**. Clients are able to view their profiles and book
consultations.

View File

@@ -1,8 +1,7 @@
--- ---
title: WebAssembly presentation title: WebAssembly presentation
displayed: true displayed: true
description: A presentation about what WebAssembly is about and how it might link: https://michalvankodev.github.io/presentation-webassembly/
affect the future of the world.
cover_image: /images/uploads/screenshot-from-2024-08-06-18-52-41.png cover_image: /images/uploads/screenshot-from-2024-08-06-18-52-41.png
classification: presentation classification: presentation
tags: tags:
@@ -10,3 +9,5 @@ tags:
- WebAssembly - WebAssembly
featured: false featured: false
--- ---
A presentation about what WebAssembly is about and how it might
affect the future of the world.

View File

@@ -0,0 +1,14 @@
---
title: The Grand Escape
displayed: true
link: https://michalvankodev.itch.io/the-grand-escape
cover_image: /images/uploads/logo.png
classification: videogame
tags:
- Rust
- Bevy
featured: true
---
A videogame where you need to steer your boat to avoid obstacles
and enemy bullets. The difficulty will be increased after a certain time and
new enemies will be spawned to make your escape harder.

View File

@@ -1,8 +1,6 @@
--- ---
title: Renaissance of hypermedia systems title: Renaissance of hypermedia systems
displayed: true displayed: true
description: A presentation about hypermedia systems, HTMX, HyperView, and the
HATEOAS principles. 2024
link: https://michalvankodev.github.io/presentation-renaissance-of-hypermedia-systems/#/intro link: https://michalvankodev.github.io/presentation-renaissance-of-hypermedia-systems/#/intro
cover_image: /images/uploads/screenshot-from-2024-08-06-19-01-03.png cover_image: /images/uploads/screenshot-from-2024-08-06-19-01-03.png
classification: presentation classification: presentation
@@ -12,3 +10,5 @@ tags:
- HTMX - HTMX
featured: true featured: true
--- ---
A presentation about hypermedia systems, HTMX, HyperView, and the
HATEOAS principles. 2024

View File

@@ -1,12 +0,0 @@
---
title: beinSports
displayed: true
description: "*beIN Sports* is a global network of sports channels jointly owned
and operated by *Qatari Sports Investments*, an affiliate of *Al Jazeera Media
Networks*"
cover_image: /images/uploads/bein_logo.af017869.webp
classification: website
tags:
- Freemarker
featured: false
---

View File

@@ -1,9 +1,9 @@
--- ---
title: dev project test title: dev project test
displayed: true displayed: false
description: Testing project
classification: webapp classification: webapp
tags: tags:
- Webapp - Webapp
featured: false featured: false
--- ---
Testing project

View File

@@ -1,14 +0,0 @@
---
title: FX Sales
displayed: true
description: "*Caplin FX Sales* allows sales people to **trade on behalf of
their clients**. This needs to be an efficient workflow providing all the
relevant information to the sales user"
cover_image: /images/uploads/fx_sales_screen2x.png
classification: webapp
tags:
- Webapp
- React
- Knockout
featured: false
---

View File

@@ -1,8 +1,7 @@
--- ---
title: HeyLady! title: HeyLady!
displayed: true displayed: true
description: A thriving online community supporting women to learn and practise link: https://www.heylady.io/
speaking English.
cover_image: /images/uploads/heyladylogo.svg cover_image: /images/uploads/heyladylogo.svg
classification: webapp classification: webapp
tags: tags:
@@ -14,3 +13,5 @@ tags:
- PostgreSQL - PostgreSQL
featured: false featured: false
--- ---
A thriving online community supporting women to learn and practise
speaking English.

View File

@@ -1,10 +0,0 @@
---
title: Livesport.tv
displayed: false
description: "*Livesport.tv* is a network of premium online sports channels,
featuring all the top sports competitions from around the world."
classification: website
tags:
- Freemarker
featured: false
---

View File

@@ -1,14 +0,0 @@
---
title: Manualogic
displayed: false
description: "*Manualogic* is a **single-page application** for product manual
creators. It contains **custom web editor** and management system of
**translatable pages, books** and **products.** Its main goal is to enable
customers to get manuals of their products in digital form."
classification: webapp
tags:
- Webapp
- Angular
- RxJS
featured: false
---

View File

@@ -1,14 +0,0 @@
---
title: responzIO
displayed: true
description: "***responzIO*** is a smart, easy-to-use monitoring and automation
system. The ultimate tool for various applications such as hydroponics,
aquariums, and gardens."
cover_image: /images/uploads/responzio.png
classification: embedded
tags:
- Webapp
- Embedded
- NodeJS
featured: false
---

View File

@@ -1,13 +0,0 @@
---
title: Signal Hub Manager
displayed: true
description: "*Signal Hub* is an end-to-end **Big Data analytics platform** for
large enterprises. It accelerates the process of extracting insights and
intelligence from large volumes of data, including data of different types and
in different formats."
classification: webapp
tags:
- Webapp
- React
featured: false
---

View File

@@ -1,10 +0,0 @@
---
title: Skosy
displayed: false
description: "*Skosy* is a web application whose purpose is to **automate the
writing of integration tests** for websites."
classification: webapp
tags:
- Webapp
featured: false
---

View File

@@ -1,13 +0,0 @@
---
title: Spreading the Web
displayed: true
description: A presentation about the rising number of use cases for utilizing
web technologies outside of the web platform such as native mobile
applications and robotics. 2015
cover_image: /images/uploads/screenshot-from-2024-08-06-18-48-02.png
classification: presentation
tags:
- Presentation
- NodeJS
featured: false
---

View File

@@ -1,14 +0,0 @@
---
title: The Grand Escape
displayed: true
description: A videogame where you need to steer your boat to avoid obstacles
and enemy bullets. The difficulty will be increased after a certain time and
new enemies will be spawned to make your escape harder.
link: https://michalvankodev.itch.io/the-grand-escape
cover_image: /images/uploads/logo.png
classification: videogame
tags:
- Rust
- Bevy
featured: true
---

View File

@@ -1,12 +0,0 @@
---
title: Unstoppable growth of front-end frameworks
displayed: true
description: A simple summary of the web front-end evolution. Describes how and
why new tools in the NodeJS ecosystem improve & why there is still something
to explore.
classification: presentation
tags:
- Presentation
- NodeJS
featured: false
---

View File

@@ -24,6 +24,7 @@ image = "0.25.2"
anyhow = "1.0.86" anyhow = "1.0.86"
rayon = "1.10.0" rayon = "1.10.0"
syntect = "5.2.0" syntect = "5.2.0"
indoc = "2.0.5"
[build] [build]
rustflags = ["-Z", "threads=8"] rustflags = ["-Z", "threads=8"]
@@ -34,7 +35,7 @@ rustflags = ["-Z", "threads=8"]
# ] # ]
[profile.dev] [profile.dev]
debug = false debug = true
opt-level = 0 opt-level = 0
# codegen-units = 16 # codegen-units = 16
# lto = "thin" # lto = "thin"

View File

@@ -46,7 +46,7 @@ wait_for_port:
# Kill the application running on port # Kill the application running on port
kill: kill:
kill $(lsof -t -i:{{port}}) kill $(pidof axum_server)
# Clean the dist folder # Clean the dist folder
clean: clean:

View File

@@ -35,6 +35,7 @@ async fn main() {
.nest_service("/fonts", ServeDir::new("../static/fonts")) .nest_service("/fonts", ServeDir::new("../static/fonts"))
.nest_service("/generated_images", ServeDir::new("generated_images")) .nest_service("/generated_images", ServeDir::new("generated_images"))
.nest_service("/svg", ServeDir::new("../static/svg")) .nest_service("/svg", ServeDir::new("../static/svg"))
// TODO manifest logos have bad link, #directory-swap
.nest_service( .nest_service(
"/config.yml", "/config.yml",
ServeDir::new("../static/resources/config.yml"), ServeDir::new("../static/resources/config.yml"),
@@ -53,12 +54,11 @@ async fn main() {
// TODO Socials // TODO Socials
// - fotos // - fotos
// TODO ul li article styles // background gradient color
// TODO header height difference // TODO Change DNS system
// TODO Colors // THINK deploy to alula? rather then katelyn? can be change whenever
// TODO print css and other 404 css linked in base.html
// TODO go live pipeline
// TODO after release // TODO after release
// - contact // OG tags
// Remove old web completely
// Restructure repository
// - projects page // - projects page
// - linktree page

View File

@@ -64,7 +64,7 @@ pub async fn render_blog_post_list(
}; };
Ok(PostListTemplate { Ok(PostListTemplate {
title: "Posts".to_owned(), title: "Blog posts".to_owned(),
posts, posts,
tag, tag,
header_props, header_props,

View File

@@ -3,3 +3,4 @@ pub mod blog_post_list;
pub mod blog_post_page; pub mod blog_post_page;
pub mod contact; pub mod contact;
pub mod index; pub mod index;
pub mod project_list;

View File

@@ -0,0 +1,30 @@
use askama::Template;
use axum::http::StatusCode;
use crate::{
components::site_header::HeaderProps,
post_utils::{post_listing::get_post_list, post_parser::ParseResult},
projects::project_model::ProjectMetadata,
};
#[derive(Template)]
#[template(path = "project_list.html")]
pub struct ProjectListTemplate {
pub title: String,
pub project_list: Vec<ParseResult<ProjectMetadata>>,
pub header_props: HeaderProps,
}
pub async fn render_projects_list() -> Result<ProjectListTemplate, StatusCode> {
let mut project_list = get_post_list::<ProjectMetadata>("../_projects").await?;
project_list.sort_by_key(|post| post.slug.to_string());
project_list.retain(|project| project.metadata.displayed);
project_list.reverse();
Ok(ProjectListTemplate {
title: "Showcase".to_owned(),
header_props: HeaderProps::default(),
project_list,
})
}

View File

@@ -5,6 +5,7 @@ use std::{
use anyhow::Context; use anyhow::Context;
use image::{image_dimensions, ImageReader}; use image::{image_dimensions, ImageReader};
use indoc::formatdoc;
use super::{ use super::{
export_format::ExportFormat, image_generator::generate_images, export_format::ExportFormat, image_generator::generate_images,
@@ -18,16 +19,34 @@ pub fn generate_picture_markup(
width: u32, width: u32,
height: u32, height: u32,
alt_text: &str, alt_text: &str,
class_name: Option<&str>,
generate_image: bool, generate_image: bool,
) -> Result<String, anyhow::Error> { ) -> Result<String, anyhow::Error> {
let exported_formats = get_export_formats(orig_img_path); let exported_formats = get_export_formats(orig_img_path);
let class_attr = if let Some(class) = class_name {
format!(r#"class="{class}""#)
} else {
"".to_string()
};
if exported_formats.is_empty() {
return Ok(formatdoc!(
r#"<img
src="{orig_img_path}"
width="{width}"
height="{height}"
{class_attr}
alt="{alt_text}"
>"#
));
}
let path_to_generated = get_generated_file_name(orig_img_path); let path_to_generated = get_generated_file_name(orig_img_path);
// TODO This should get removed when we move the project structure #move // TODO This should get removed when we move the project structure #directory-swap
let dev_only_img_path = let disk_img_path =
Path::new("../static/").join(orig_img_path.strip_prefix("/").unwrap_or(orig_img_path)); Path::new("../static/").join(orig_img_path.strip_prefix("/").unwrap_or(orig_img_path));
let orig_img_dimensions = image_dimensions(&dev_only_img_path)?; let orig_img_dimensions = image_dimensions(&disk_img_path)?;
let resolutions = get_resolutions(orig_img_dimensions, width, height); let resolutions = get_resolutions(orig_img_dimensions, width, height);
let path_to_generated_arc = Arc::new(path_to_generated); let path_to_generated_arc = Arc::new(path_to_generated);
@@ -39,8 +58,8 @@ pub fn generate_picture_markup(
if generate_image { if generate_image {
rayon::spawn(move || { rayon::spawn(move || {
let orig_img = ImageReader::open(&dev_only_img_path) let orig_img = ImageReader::open(&disk_img_path)
.with_context(|| format!("Failed to read instrs from {:?}", &dev_only_img_path)) .with_context(|| format!("Failed to read instrs from {:?}", &disk_img_path))
.unwrap() .unwrap()
.decode() .decode()
.unwrap(); .unwrap();
@@ -66,7 +85,7 @@ pub fn generate_picture_markup(
.map(|format| { .map(|format| {
let srcset = generate_srcset(&path_to_generated, format, &resolutions); let srcset = generate_srcset(&path_to_generated, format, &resolutions);
let format_type = format.get_type(); let format_type = format.get_type();
format!( formatdoc!(
r#"<source r#"<source
srcset="{srcset}" srcset="{srcset}"
type="{format_type}" type="{format_type}"
@@ -81,16 +100,17 @@ pub fn generate_picture_markup(
resolutions.first().expect("Should this error ever happen?"), resolutions.first().expect("Should this error ever happen?"),
exported_formats.last().expect("Can this one ever happen?"), exported_formats.last().expect("Can this one ever happen?"),
); );
let image_tag = format!( let image_tag = formatdoc!(
r#"<img r#"<img
src="{image_path}" src="{image_path}"
width="{width}" width="{width}"
height="{height}" height="{height}"
alt="{alt_text}" alt="{alt_text}"
{class_attr}
>"# >"#
); );
let result = format!( let result = formatdoc!(
r#"<picture> r#"<picture>
{source_tags} {source_tags}
{image_tag} {image_tag}
@@ -272,10 +292,12 @@ fn test_generate_srcset() {
#[test] #[test]
fn test_generate_picture_markup() { fn test_generate_picture_markup() {
use indoc::indoc;
let width = 300; let width = 300;
let height = 200; let height = 200;
let orig_img_path = "/images/uploads/2020-03-23_20-24-06_393.jpg"; let orig_img_path = "/images/uploads/2020-03-23_20-24-06_393.jpg";
let result = r#"<picture> let result = indoc! {
r#"<picture>
<source <source
srcset="/generated_images/images/uploads/2020-03-23_20-24-06_393_300x200.avif 1x, /generated_images/images/uploads/2020-03-23_20-24-06_393_450x300.avif 1.5x, /generated_images/images/uploads/2020-03-23_20-24-06_393_600x400.avif 2x, /generated_images/images/uploads/2020-03-23_20-24-06_393_900x600.avif 3x, /generated_images/images/uploads/2020-03-23_20-24-06_393_1200x800.avif 4x" srcset="/generated_images/images/uploads/2020-03-23_20-24-06_393_300x200.avif 1x, /generated_images/images/uploads/2020-03-23_20-24-06_393_450x300.avif 1.5x, /generated_images/images/uploads/2020-03-23_20-24-06_393_600x400.avif 2x, /generated_images/images/uploads/2020-03-23_20-24-06_393_900x600.avif 3x, /generated_images/images/uploads/2020-03-23_20-24-06_393_1200x800.avif 4x"
type="image/avif" type="image/avif"
@@ -290,9 +312,17 @@ fn test_generate_picture_markup() {
height="200" height="200"
alt="Testing image alt" alt="Testing image alt"
> >
</picture>"#; </picture>"#,
};
assert_eq!( assert_eq!(
generate_picture_markup(orig_img_path, width, height, "Testing image alt", false) generate_picture_markup(
orig_img_path,
width,
height,
"Testing image alt",
None,
false
)
.expect("picture markup has to be generated"), .expect("picture markup has to be generated"),
result result
); );

View File

@@ -1,14 +1,16 @@
use core::panic;
use std::path::Path; use std::path::Path;
use axum::http::StatusCode; use axum::http::StatusCode;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use gray_matter::{engine::YAML, Matter}; use gray_matter::{engine::YAML, Matter};
use image::image_dimensions; use image::image_dimensions;
use indoc::formatdoc;
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
use serde::{de::DeserializeOwned, Deserialize, Deserializer}; use serde::{de::DeserializeOwned, Deserialize, Deserializer};
use syntect::{highlighting::ThemeSet, html::highlighted_html_for_string, parsing::SyntaxSet}; use syntect::{highlighting::ThemeSet, html::highlighted_html_for_string, parsing::SyntaxSet};
use tokio::fs; use tokio::fs;
use tracing::debug; use tracing::{debug, error, info};
use crate::picture_generator::{ use crate::picture_generator::{
picture_markup_generator::generate_picture_markup, resolutions::get_max_resolution, picture_markup_generator::generate_picture_markup, resolutions::get_max_resolution,
@@ -71,6 +73,7 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>(
enum TextKind { enum TextKind {
Text, Text,
Heading(Option<String>),
Code(String), Code(String),
} }
@@ -87,6 +90,7 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String {
let syntax_set = SyntaxSet::load_defaults_newlines(); let syntax_set = SyntaxSet::load_defaults_newlines();
let theme_set = ThemeSet::load_defaults(); let theme_set = ThemeSet::load_defaults();
let theme = theme_set.themes.get("InspiredGitHub").unwrap(); let theme = theme_set.themes.get("InspiredGitHub").unwrap();
let mut heading_ended: Option<bool> = None;
let parser = Parser::new_ext(markdown, options).map(|event| match event { let parser = Parser::new_ext(markdown, options).map(|event| match event {
/* /*
@@ -102,7 +106,7 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String {
}) => { }) => {
if !dest_url.starts_with("/") { if !dest_url.starts_with("/") {
return Event::Html( return Event::Html(
format!( formatdoc!(
r#"<img r#"<img
alt="{title}" alt="{title}"
src="{dest_url}" src="{dest_url}"
@@ -123,9 +127,15 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String {
); );
// Place image into the content with scaled reso to a boundary // Place image into the content with scaled reso to a boundary
let picture_markup = let picture_markup = generate_picture_markup(
generate_picture_markup(&dest_url, max_width, max_height, &title, generate_images) &dest_url,
.unwrap_or(format!( max_width,
max_height,
&title,
None,
generate_images,
)
.unwrap_or(formatdoc!(
r#" r#"
<img <img
alt="{alt}" alt="{alt}"
@@ -139,7 +149,7 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String {
link_type, dest_url, title, id link_type, dest_url, title, id
); );
Event::Html( Event::Html(
format!( formatdoc!(
r#"<figure> r#"<figure>
{picture_markup} {picture_markup}
<figcaption> <figcaption>
@@ -168,14 +178,55 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String {
.unwrap(); .unwrap();
Event::Html(highlighted.into()) Event::Html(highlighted.into())
} }
TextKind::Heading(provided_id) => {
let heading_id = provided_id.clone().unwrap_or({
text.to_lowercase()
.replace(|c: char| !c.is_alphanumeric(), "-")
});
debug!("heading_id: {}", heading_id.clone());
match heading_ended {
None => {
error!("Heading should have set state");
panic!("Heading should have set state");
}
Some(true) => Event::Html(text),
Some(false) => {
heading_ended = Some(true);
Event::Html(
formatdoc!(
r##"id="{heading_id}">
{text}"##
)
.into(),
)
}
}
}
_ => Event::Text(text), _ => Event::Text(text),
}, },
Event::Start(Tag::Heading {
level,
id,
classes: _,
attrs: _,
}) => {
let id_str = id.map(|id| id.to_string());
debug!("heading_start: {:?}, level: {}", &id_str, level);
text_kind = TextKind::Heading(id_str);
heading_ended = Some(false);
Event::Html(format!("<{level} ").into())
}
Event::Start(_) => event, Event::Start(_) => event,
Event::End(TagEnd::Image) => Event::Html("</figcaption></figure>".into()), Event::End(TagEnd::Image) => Event::Html("</figcaption></figure>".into()),
Event::End(TagEnd::CodeBlock) => { Event::End(TagEnd::CodeBlock) => {
text_kind = TextKind::Text; text_kind = TextKind::Text;
Event::End(TagEnd::CodeBlock) Event::End(TagEnd::CodeBlock)
} }
Event::End(TagEnd::Heading(heading_level)) => {
text_kind = TextKind::Text;
heading_ended = None;
Event::End(TagEnd::Heading(heading_level))
}
_ => event, _ => event,
}); });

View File

@@ -1,9 +1,6 @@
use axum::http::StatusCode; use axum::http::StatusCode;
use crate::post_utils::{ use crate::post_utils::{post_listing::get_post_list, post_parser::ParseResult};
post_listing::get_post_list,
post_parser::{parse_html, ParseResult},
};
use super::project_model::ProjectMetadata; use super::project_model::ProjectMetadata;
@@ -13,10 +10,6 @@ pub async fn get_featured_projects() -> Result<Vec<ParseResult<ProjectMetadata>>
let featured_projects = project_list let featured_projects = project_list
.into_iter() .into_iter()
.filter(|post| post.metadata.featured) .filter(|post| post.metadata.featured)
.map(|mut post| {
post.metadata.description = parse_html(&post.metadata.description, false);
post
})
.collect(); .collect();
Ok(featured_projects) Ok(featured_projects)

View File

@@ -3,7 +3,6 @@ use serde::Deserialize;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct ProjectMetadata { pub struct ProjectMetadata {
pub title: String, pub title: String,
pub description: String,
pub classification: String, pub classification: String,
pub displayed: bool, pub displayed: bool,
pub cover_image: Option<String>, pub cover_image: Option<String>,
@@ -18,6 +17,7 @@ pub fn translate_classification(classification: &str) -> &str {
"website" => "Web site", "website" => "Web site",
"presentation" => "Presentation", "presentation" => "Presentation",
"videogame" => "Video game", "videogame" => "Video game",
"embedded" => "Embedded system",
any => any, any => any,
} }
} }

View File

@@ -3,6 +3,7 @@ use crate::{
pages::{ pages::{
admin::render_admin, blog_post_list::render_blog_post_list, admin::render_admin, blog_post_list::render_blog_post_list,
blog_post_page::render_blog_post, contact::render_contact, index::render_index, blog_post_page::render_blog_post, contact::render_contact, index::render_index,
project_list::render_projects_list,
}, },
}; };
use axum::{extract::MatchedPath, http::Request, routing::get, Router}; use axum::{extract::MatchedPath, http::Request, routing::get, Router};
@@ -16,6 +17,7 @@ pub fn get_router() -> Router {
.route("/blog/tags/:tag", get(render_blog_post_list)) .route("/blog/tags/:tag", get(render_blog_post_list))
.route("/blog/:post_id", get(render_blog_post)) .route("/blog/:post_id", get(render_blog_post))
.route("/contact", get(render_contact)) .route("/contact", get(render_contact))
.route("/showcase", get(render_projects_list))
.route("/admin", get(render_admin)) .route("/admin", get(render_admin))
.route("/feed.xml", get(render_rss_feed)) .route("/feed.xml", get(render_rss_feed))
.layer( .layer(

View File

@@ -69,11 +69,11 @@
} }
a { a {
@apply text-pink-600 underline underline-offset-2; @apply text-pink-800 underline underline-offset-2 hover:transition hover:text-blue-500;
}
&:hover { strong {
@apply transition text-blue-400; @apply font-medium;
}
} }
.article-body { .article-body {
@@ -83,15 +83,23 @@ a {
h2 { h2 {
@apply px-4 text-xl font-semibold text-blue-900 mb-3 mt-4 max-w-read mx-auto md:text-2xl md:mb-6 md:mt-8 lg:mb-8 lg:mt-12 lg:text-4xl; @apply px-4 text-xl font-semibold text-blue-900 mb-3 mt-4 max-w-read mx-auto md:text-2xl md:mb-6 md:mt-8 lg:mb-8 lg:mt-12 lg:text-4xl;
} }
h3 {
@apply px-4 text-lg font-semibold text-blue-900 mb-3 mt-4 max-w-read mx-auto md:text-xl md:mb-6 md:mt-8 lg:mb-8 lg:mt-12 lg:text-3xl;
}
h4 {
@apply px-4 text-lg font-medium text-blue-900 mb-2 mt-3 max-w-read mx-auto md:text-lg md:mb-6 md:mt-8 lg:mb-8 lg:mt-12 lg:text-3xl;
}
p { p {
@apply px-4 my-2 text-slate-950 text-justify mx-auto max-w-read md:text-lg md:my-8 lg:text-readxl; @apply px-4 my-2 text-slate-950 text-justify mx-auto max-w-read md:text-lg md:my-8 lg:text-readxl;
} }
strong {
@apply font-medium;
}
pre { pre {
@apply p-4 my-1 overflow-auto text-sm mx-auto max-w-read; @apply p-4 my-1 overflow-auto text-sm mx-auto max-w-read;
} }
figure { figure {
@apply p-4; @apply p-4;
@@ -104,7 +112,7 @@ a {
} }
table { table {
@apply text-sm mx-auto my-4 max-w-image table-auto border-collapse border-spacing-12 border border-gray-200 rounded md:text-base lg:text-xl lg:my-8; @apply text-sm mx-auto my-4 max-w-image table-auto border-collapse border-spacing-12 border border-slate-200 rounded md:text-base lg:text-xl lg:my-8;
} }
thead { thead {
@@ -125,7 +133,7 @@ a {
} }
blockquote { blockquote {
@apply mx-6 py-1 px-2 bg-pink-50 lg:mx-auto max-w-note border-l-4 border-pink-500; @apply mx-6 py-1 px-2 bg-pink-50 lg:mx-auto max-w-note border-l-4 border-pink-600;
p { p {
@apply my-2 md:my-4 text-slate-600 max-w-note; @apply my-2 md:my-4 text-slate-600 max-w-note;
@@ -133,14 +141,150 @@ a {
} }
:not(pre) code { :not(pre) code {
@apply text-pink-950 rounded border border-blue-300 px-1 py-0.5 bg-blue-100 text-sm md:text-base lg:text-xl; @apply text-pink-900 rounded border border-blue-300 px-1 py-0.5 bg-blue-100 text-sm md:text-base lg:text-xl;
} }
pre code pre { pre code pre {
@apply mx-2 rounded lg:mx-auto lg:text-lg shadow-sm lg:max-w-note; @apply mx-2 rounded lg:mx-auto lg:text-lg shadow-sm lg:max-w-note;
} }
ul,
ol {
@apply pl-10 pr-6 my-2 text-slate-950 mx-auto max-w-read md:text-lg md:my-8 lg:text-readxl lg:pl-14;
& p {
@apply px-2;
}
}
ul {
@apply list-disc;
}
ol {
@apply list-decimal;
}
iframe {
@apply rounded shadow-md mx-auto lg:max-w-image;
}
}
article a {
@apply visited:text-purple-700;
} }
.video-embed { .video-embed {
@apply m-4 mx-auto max-w-image aspect-video; @apply m-4 mx-auto max-w-image aspect-video;
} }
.social-card-twitch:hover {
transform: translate3d(0.6rem, -0.6rem, 0px);
box-shadow: -3px 3px 0px 3px #6441a5;
transition-delay: 75ms;
}
.social-card-youtube:hover {
@apply rounded-none;
transform: scale(1.02);
transition-delay: 100ms;
}
.social-card-instagram:hover {
filter: brightness(84%);
transition-delay: 100ms;
}
.social-card-tiktok {
position: relative;
&:hover {
animation: tiktok-glitch 1.5s infinite;
animation-delay: 200ms;
}
}
@keyframes tiktok-glitch {
0% {
box-shadow:
0px 0px 0 rgba(0, 255, 255, 0),
0px 0px 0 rgba(255, 0, 255, 0);
transform: translate(0, 0);
}
10% {
box-shadow:
-3px -3px 0 rgba(0, 255, 255, 0.8),
3px 3px 0 rgba(255, 0, 255, 0.8);
transform: translate(-1px, -1px);
}
15% {
box-shadow:
2px -2px 0 rgba(0, 255, 255, 0.6),
-2px 2px 0 rgba(255, 0, 255, 0.6);
transform: translate(2px, -2px);
}
20% {
box-shadow:
-1px 1px 0 rgba(0, 255, 255, 0.4),
1px -1px 0 rgba(255, 0, 255, 0.4);
transform: translate(1px, 1px);
}
25% {
box-shadow:
-4px 4px 0 rgba(0, 255, 255, 1),
4px -4px 0 rgba(255, 0, 255, 1);
transform: translate(-2px, 2px);
}
30% {
box-shadow:
3px -3px 0 rgba(0, 255, 255, 0.5),
-3px 3px 0 rgba(255, 0, 255, 0.5);
transform: translate(3px, -3px);
}
40% {
box-shadow:
-2px 2px 0 rgba(0, 255, 255, 0.9),
2px -2px 0 rgba(255, 0, 255, 0.9);
transform: translate(-1px, 1px);
}
50% {
box-shadow:
-1px -2px 0 rgba(0, 255, 255, 0.7),
2px -1px 0 rgba(255, 0, 255, 0.7);
transform: translate(1px, -1px);
}
60% {
box-shadow:
2px -2px 0 rgba(0, 255, 255, 0.3),
-2px 2px 0 rgba(255, 0, 255, 0.3);
transform: translate(2px, -2px);
}
75% {
box-shadow:
-3px 3px 0 rgba(0, 255, 255, 1),
3px -3px 0 rgba(255, 0, 255, 1);
transform: translate(-3px, 3px);
}
85% {
box-shadow:
-2px -2px 0 rgba(0, 255, 255, 0.2),
2px 2px 0 rgba(255, 0, 255, 0.2);
transform: translate(-2px, -2px);
}
100% {
box-shadow:
0px 0px 0 rgba(0, 255, 255, 0),
0px 0px 0 rgba(255, 0, 255, 0);
transform: translate(0, 0);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,73 @@ module.exports = {
}, },
], ],
}, },
colors: {
// blue: {
// 50: "#ecf6fe",
// 100: "#d9edfc",
// 200: "#b3dbf9",
// 300: "#8ecaf6",
// 400: "#68b8f3",
// 500: "#42a6f0",
// 600: "#3585c0",
// 700: "#286490",
// 800: "#1F4E71",
// 900: "#173A54",
// 950: "#0F2637",
// },
blue: {
50: "#f1f7fe",
100: "#e1effd",
200: "#bddefa",
300: "#82c3f7",
400: "#42a6f0",
500: "#1789e0",
600: "#0a6cbf",
700: "#0a569a",
800: "#0c4980",
900: "#103e6a",
950: "#0b2746",
},
// pink: {
// 50: "#FFFBFE",
// 100: "#FFE4F9",
// 200: "#FECEF4",
// 300: "#FEB8EF",
// 400: "#fea6eb",
// 500: "#D38AC3",
// 600: "#B476A7",
// 700: "#96628B",
// 800: "#774E6E",
// 900: "#593A52",
// 950: "#3A2636",
// },
pink: {
50: "#fff4fd",
100: "#ffe7fb",
200: "#ffcff7",
300: "#fea6eb",
400: "#fc76dd",
500: "#f342ca",
600: "#d722a9",
700: "#b31889",
800: "#92166e",
900: "#771859",
950: "#500238",
},
purple: {
50: "#F8F5FC",
100: "#D5C2ED",
200: "#B28EDE",
300: "#8F5BCF",
400: "#6D30B9",
500: "#5F2AA2",
600: "#52248A",
700: "#441E73",
800: "#36185C",
900: "#281244",
950: "#1A0C2D",
},
},
}, },
}, },
plugins: [], plugins: [],

View File

@@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<title>{% block title %} {{title}} @michalvankodev {% endblock %}</title> <title>{% block title %} {{title}} {% endblock %} @michalvankodev</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="theme-color" content="#333333" /> <meta name="theme-color" content="#333333" />
@@ -13,19 +13,16 @@
rel="alternate" rel="alternate"
type="application/rss+xml" type="application/rss+xml"
title="RSS feed for latest posts" title="RSS feed for latest posts"
href="https://michalvanko.dev/feed.xml" href="/feed.xml"
/> />
<!-- Tailwind output file --> <!-- Tailwind output file -->
<link rel="stylesheet" href="/styles/output.css" /> <link rel="stylesheet" href="/styles/output.css" />
<link rel="stylesheet" href="/print.css" media="print" />
<link rel="stylesheet" href="/fonts.css" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="/prism.css" />
<link rel="icon" type="image/svg+xml" href="/m-logo.svg" /> <link rel="icon" type="image/svg+xml" href="/images/m-logo.svg" />
<link rel="icon" type="image/png" href="/m-logo-192.png" /> <link rel="icon" type="image/png" href="/images/m-logo-192.png" />
</head> </head>
<body class="bg-blue-50"> <body class="bg-blue-50">
{% include "site_header.html" %} {% include "site_header.html" %}

View File

@@ -5,10 +5,10 @@
{% block content %} {% block content %}
<article class="mb-6"> <article class="mb-6">
<header class="px-4 max-w-read mx-auto"> <header class="px-4 max-w-read mx-auto">
<h1 class="text-3xl md:text-4xl lg:text-6xl lg:mt-20 text-blue-900 mb-3 font-semibold">{{title}}</h1> <h1 class="text-3xl md:text-4xl lg:text-6xl lg:mt-20 text-blue-900 mb-3 font-bold">{{title}}</h1>
<aside class="flex justify-between flex-row"> <aside class="flex justify-between flex-row">
{% include "post_tag_list.html" %} {% include "post_tag_list.html" %}
<section class="created-at m-1 text-right text-sm text-gray-600 md:text-lg"> <section class="created-at m-1 text-right text-sm text-slate-600 md:text-lg">
<span>Published on</span> <span>Published on</span>
<time datetime="{date}"> {{date|pretty_date}} </time> <time datetime="{date}"> {{date|pretty_date}} </time>
</section> </section>
@@ -21,6 +21,7 @@
</article> </article>
<!-- TODO: Next recommendations for reading --> <!-- TODO: Next recommendations for reading -->
<!-- TODO: Bact to all posts -->
{# footer #} {# footer #}
{% endblock %} {% endblock %}

View File

@@ -9,7 +9,7 @@
{% else %} {% else %}
<h1 class="m-5 text-4xl text-blue-950 font-extrabold md:text-6xl"> <h1 class="m-5 text-4xl text-blue-950 font-extrabold md:text-6xl">
{% if let Some(t) = tag %} {% if let Some(t) = tag %}
<em>{{t}}</em> #{{t}}
{% else %} {% else %}
Blog posts Blog posts
{% endif %} {% endif %}
@@ -25,13 +25,13 @@
</ul> </ul>
</section> </section>
<hr class="border-blue-950 m-5 md:my-8"> <hr class="border-slate-300 m-5 md:my-8">
<ul class="mx-5"> <ul class="mx-5">
{% for post in posts %} {% for post in posts %}
<li> <li>
{% include "components/blog_post_preview.html" %} {% include "components/blog_post_preview.html" %}
<hr class="border-blue-950 my-5 md:my-8"> <hr class="border-slate-300 my-5 md:my-8">
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -42,15 +42,18 @@
</section> <!-- /#socials --> </section> <!-- /#socials -->
<section id="showcase" class="hidden lg:block"> <section id="showcase" class="hidden lg:block">
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Showcase</h2> <h2 class="text-blue-950 font-bold text-2xl m-5 md:text-4xl"><a href="/showcase" class="text-blue-950 no-underline">Showcase</a></h2>
<ul class=""> <ul class="mx-6">
{% for project in featured_projects %} {% for project in featured_projects %}
<li class="my-2"> <li class="my-4">
{% include "components/project_preview_card.html" %} {% include "components/project_preview_card.html" %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<section class="text-center my-3 md:text-lg">
<a href="/showcase">check out more projects</a>
</section>
</section> <!-- /#showcase --> </section> <!-- /#showcase -->
</section> <!-- /#blog-container --> </section> <!-- /#blog-container -->
{% endblock %} {% endblock %}

View File

@@ -1,3 +1,3 @@
<div class="rounded-2xl w-[180px] h-[240px] bg-blue-100 border-4 border-blue-500 flex justify-center items-center"> <div class="w-[180px] h-[240px] bg-blue-100 flex justify-center items-center">
<span class="text-blue-500 text-8xl -translate-y-1.5">{{post.metadata.title|fmt("{:.1}")|lower}}</span> <span class="text-blue-500 text-8xl -translate-y-1.5">{{post.metadata.title|fmt("{:.1}")|lower}}</span>
</div> </div>

View File

@@ -1,8 +1,8 @@
<article class="grid grid-cols-[max-content_1fr] grid-rows-[max-content_1fr_max-content] grid-flow-col gap-4 md:gap-x-8"> <article class="sm:grid sm:grid-cols-[max-content_1fr] sm:grid-rows-[max-content_1fr_max-content] sm:grid-flow-col sm:gap-4 md:gap-x-8 break-inside-avoid clear-both sm:clear-none">
<aside class="row-span-3 self-center"> <aside class="row-span-3 self-center float-start sm:float-none mr-3 mb-3 sm:ml-0 sm:mb-0">
{% match post.metadata.thumbnail %} {% match post.metadata.thumbnail %}
{% when Some with (orig_path) %} {% when Some with (orig_path) %}
{{ crate::picture_generator::picture_markup_generator::generate_picture_markup(orig_path, 180, 240, "Article thumbnail", true).unwrap_or("thumbnail not found".to_string())|safe }} {{ crate::picture_generator::picture_markup_generator::generate_picture_markup(orig_path, 180, 240, "Article thumbnail", None, true).unwrap_or("thumbnail not found".to_string())|safe }}
{% when None %} {% when None %}
<div> <div>
{% include "components/blog_post_default_thumbnail.html" %} {% include "components/blog_post_default_thumbnail.html" %}
@@ -11,11 +11,11 @@
</aside> </aside>
<header> <header>
<h3 class="text-lg font-bold mb-1 md:text-3xl"> <h3 class="text-lg font-bold mb-1 md:text-3xl">
<a rel="prefetch" href="/blog/{{post.slug}}" class="text-blue-950 no-underline">{{post.metadata.title}}</a> <a rel="prefetch" href="/blog/{{post.slug}}" class="text-blue-950 visited:text-purple-700 no-underline">{{post.metadata.title}}</a>
</h3> </h3>
</header> </header>
<section class="text-base leading-5 text-gray-800 md:text-xl text-justify">{{post.body|description_filter|safe}}</section> <section class="text-base leading-5 text-slate-800 md:text-xl text-justify">{{post.body|description_filter|safe}}</section>
<footer class="text-sm md:text-base lg:text-lg"> <footer class="text-sm md:text-base lg:text-lg mt-3 sm:mt-0 clear-both sm:clear-none">
<ul class="inline-block"> <ul class="inline-block">
{% for tag in post.metadata.tags %} {% for tag in post.metadata.tags %}
<li class="inline-block"> <li class="inline-block">

View File

@@ -1,4 +1,4 @@
<article class="border rounded-md bg-white m-4 p-4"> <section class="border rounded-md bg-white p-4 break-inside-avoid">
<header class="px-4 mb-3"> <header class="px-4 mb-3">
<h2 class="text-xl font-semibold text-blue-900 md:text-2xl"> <h2 class="text-xl font-semibold text-blue-900 md:text-2xl">
{% match project.metadata.link %} {% match project.metadata.link %}
@@ -11,23 +11,24 @@
{% endmatch %} {% endmatch %}
</h2> </h2>
<section class="description text-slate-800 my-2 md:text-xl text-justify"> <section class="description text-slate-800 my-2 md:text-xl text-justify">
{{project.metadata.description|safe}} {{project.body|safe}}
</section> </section>
</header> </header>
<!-- <hr class="border-blue-950 my-5"> --> <!-- <hr class="border-blue-950 my-5"> -->
{% match project.metadata.cover_image %} {% match project.metadata.cover_image %}
{% when Some with (source) %} {% when Some with (source) %}
<figure class="mx-4 my-2"> {% let picture = crate::picture_generator::picture_markup_generator::generate_picture_markup(source, 420, 236, "Project cover", Some("max-h-[236px]"), true).unwrap_or("cover not found".to_string()) %}
<figure class="mx-4 my-2 flex justify-center">
{% match project.metadata.link %} {% match project.metadata.link %}
{% when Some with (href) %} {% when Some with (href) %}
<a href="{{href}}"> <a href="{{href}}">
<img src="{{source}}" class="object-contain w-full aspect-video"/> {{picture|safe}}
</a> </a>
{% when None %} {% when None %}
<img src="{{source}}" /> {{picture|safe}}
{% endmatch %} {% endmatch %}
<!-- TODO <figure> --> <!-- TODO <figure> generate_image -->
</figure> </figure>
{% when None %} {% when None %}
{% endmatch %} {% endmatch %}
@@ -45,4 +46,4 @@
{% endfor %} {% endfor %}
</ul> </ul>
</footer> </footer>
</article> </section>

View File

@@ -1,15 +1,14 @@
{% macro social_card_start(svg, heading) %} {% macro social_card_start(svg, url, heading, img, class) %}
<section class="border rounded-md bg-pink-200 m-4 p-4"> <a href="{{url}}" class="block no-underline border rounded-md bg-pink-200 m-4 p-4 max-w-[392px] {{class}}">
<header class="flex text-center justify-center items-center gap-2 mb-2"> <header class="flex text-center justify-center items-center gap-2 mb-2">
<svg aria-hidden="true" class="h-7 w-7 fill-blue-950"> <svg aria-hidden="true" class="h-7 w-7 fill-blue-950">
<use xlink:href="/svg/icons-sprite.svg#{{svg}}" /> <use xlink:href="/svg/icons-sprite.svg#{{svg}}" />
</svg> </svg>
<h3 class="text-lg font-medium mb-1">{{heading|safe}}</h3> <h3 class="text-lg font-medium mb-1 text-blue-950 visited:text-blue-950">{{heading|safe}}</h3>
</header> </header>
{% let alt_text = format!("{svg} thumbnail") %}
{% endmacro %} {{ crate::picture_generator::picture_markup_generator::generate_picture_markup(img, 360, 128, alt_text, Some("h-auto mx-auto rounded-sm"), true).unwrap_or("thumbnail not found".to_string())|safe }}
</a>
{% macro social_card_end() %}
</section>
{% endmacro %} {% endmacro %}

View File

@@ -1,6 +1,6 @@
{% macro talent_card(svg, heading, description) %} {% macro talent_card(svg, heading, description) %}
<section class="flex border rounded bg-white m-4 p-3"> <section class="flex border rounded bg-white m-4 p-3 max-w-[32rem]">
<aside class="flex justify-center items-center pr-3"> <aside class="flex justify-center items-center pr-3">
<svg aria-hidden="true" class="fill-blue-950 h-12 w-12 md:h-16 md:w-16"> <svg aria-hidden="true" class="fill-blue-950 h-12 w-12 md:h-16 md:w-16">
<use xlink:href="/svg/icons-sprite.svg#{{svg}}" /> <use xlink:href="/svg/icons-sprite.svg#{{svg}}" />
@@ -10,7 +10,7 @@
<header> <header>
<h3 class="text-lg font-medium mb-1 md:text-2xl">{{heading}}</h3> <h3 class="text-lg font-medium mb-1 md:text-2xl">{{heading}}</h3>
</header> </header>
<p class="text-sm leading-5 text-gray-800 md:text-lg">{{description|safe}}</p> <p class="text-sm leading-5 text-slate-800 md:text-lg">{{description|safe}}</p>
</section> </section>
</section> </section>

View File

@@ -1,25 +1,27 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1 class="mx-6 mt-3 text-4xl text-blue-950 font-extrabold"> <section id="contact-page">
<h1 class="mx-6 mt-3 text-4xl text-blue-950 font-extrabold lg:mx-auto max-w-read">
Contact Contact
</h1> </h1>
<ul class="mx-6"> <ul class="mx-6">
{% for link in links %} {% for link in links %}
<li class="my-6"> <li class="my-2 sm:my-4 lg:my-6 max-w-[32rem] mx-auto">
<a <a
class="flex border-2 place-content-center items-center rounded-full border-blue-500 py-5 hover:bg-pink-200 transition-colors" class="flex border-2 place-content-center items-center rounded-full text-blue-900 border-blue-500 py-2 sm:py-4 hover:bg-pink-200 fill-blue-900 hover:fill-blue-400 transition-colors no-underline"
href="{{link.href}}" href="{{link.href}}"
title="{{link.title}}" title="{{link.title}}"
> >
<svg aria-hidden="true" class="h-6 w-6 fill-blue-950 mx-2"> <svg aria-hidden="true" class="h-6 w-6 mx-2 self-start">
<use xlink:href="/svg/icons-sprite.svg#{{link.svg}}" /> <use xlink:href="/svg/icons-sprite.svg#{{link.svg}}" />
</svg> </svg>
<span class="text-lg font-semibold">{{link.label}}</span> <span class="text-lg font-semibold">{{link.label}}</span>
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</section>
{% endblock %} {% endblock %}

View File

@@ -7,25 +7,24 @@
{% block content %} {% block content %}
<section class="index-container lg:grid lg:grid-cols-2 xl:grid-cols-[1fr_2fr] lg:gap-y-8 lg:gap-x-32 max-w-maxindex mx-auto"> <section class="index-container lg:grid lg:grid-cols-2 xl:grid-cols-[1fr_2fr] lg:gap-y-8 lg:gap-x-32 max-w-maxindex mx-auto">
<section id="about-me"> <section id="about-me">
<header class="index-header hidden"> <!-- <header class="index-header hidden"> -->
<figure class="profile-pic"> <!-- <figure class="profile-pic"> -->
<picture> <!-- <picture> -->
<img <!-- <img -->
alt="Portrait" <!-- alt="Portrait" -->
{# TODO generate `srcset` for optimal image #} <!-- {# TODO Take a new photo #} -->
{# TODO Take a new photo #} <!-- src="/images/profile-portugal-landscape.jpg" -->
src="/images/profile-portugal-landscape.jpg" <!-- /> -->
/> <!-- </picture> -->
</picture> <!-- </figure> -->
</figure>
<p class="motto"> <!-- <p class="motto"> -->
<cite>“Let your ambition carry you.”</cite> <!-- <cite>“Let your ambition carry you.”</cite> -->
<span class="cite-owner">- La Flame</span> <!-- <span class="cite-owner">- La Flame</span> -->
</p> <!-- </p> -->
</header> <!-- </header> -->
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">About me</h2> <h2 class="text-blue-950 font-bold text-2xl m-5 md:text-4xl">About me</h2>
<p class="mx-5 md:text-xl text-justify"> <p class="mx-5 md:text-xl text-justify">
Welcome to my personal website. My name is Welcome to my personal website. My name is
@@ -34,15 +33,15 @@
<em> <a href="https://en.wikipedia.org/wiki/Programmer">programmer</a> </em> <em> <a href="https://en.wikipedia.org/wiki/Programmer">programmer</a> </em>
. I am developing software for more than half of my life and <strong>I love it!</strong> Sometimes I stream working on my side projects and building a <a href="https://discord.gg/2cGg7kwZEh">community of like minded people</a>. Here you can find blogs of my thoughts and journeys, as well as links to my socials where you can see other content.</p> . I am developing software for more than half of my life and <strong>I love it!</strong> Sometimes I stream working on my side projects and building a <a href="https://discord.gg/2cGg7kwZEh">community of like minded people</a>. Here you can find blogs of my thoughts and journeys, as well as links to my socials where you can see other content.</p>
<section id="talent-cards"> <section id="talent-cards" class="flex flex-col items-center">
{% call tc::talent_card("code", "Web development", "Extensive expertise in creating performant, live web applications and websites") %} {% call tc::talent_card("code", "Web development", "Extensive expertise in creating performant, live web applications and websites") %}
{% call tc::talent_card("gamepad", "Game development", "Extensive expertise in creating performant, live web applications and websites") %} {% call tc::talent_card("gamepad", "Game development", "Extensive expertise in creating performant, live web applications and websites") %}
{% call tc::talent_card("person-chalkboard", "Mentoring & Consulting", "I offer consulting sessions to assist you in developing <strong>higher-quality software</strong> and share insights from crafting robust, professional web applications. <a href=\"TODO callendly\">Schedule a session with me</a> and elevate your projects together.") %} {% call tc::talent_card("person-chalkboard", "Mentoring & Consulting", "I offer consulting sessions to assist you in developing <strong>higher-quality software</strong> and share insights from crafting robust, professional web applications. <a href=\"https://calendly.com/michalvankosk/30min\">Schedule a session with me</a> and elevate your projects together.") %}
</section> </section>
</section> </section>
<section id="blog" class="lg:col-span-2 lg:row-start-2 xl:col-auto xl:row-start-auto xl:row-span-2"> <section id="blog" class="lg:col-span-2 lg:row-start-2 xl:col-auto xl:row-start-auto xl:row-span-2">
<h2 class="text-blue-950 font-semibold text-2xl md:text-4xl m-5"><a href="/blog" class="text-blue-950 no-underline">Blog</a></h2> <h2 class="text-blue-950 font-bold text-2xl md:text-4xl m-5"><a href="/blog" class="text-blue-950 no-underline">Blog</a></h2>
<section id="blog-tags"> <section id="blog-tags">
<ul class="mx-5"> <ul class="mx-5">
{% for tag in blog_tags %} {% for tag in blog_tags %}
@@ -52,23 +51,26 @@
{% endfor %} {% endfor %}
</ul> </ul>
</section> </section>
<hr class="border-blue-950 m-5"> <hr class="border-slate-300 m-5">
<ul class="mx-5"> <ul class="mx-5">
{% for post in featured_blog_posts %} {% for post in featured_blog_posts %}
<li> <li>
{% include "components/blog_post_preview.html" %} {% include "components/blog_post_preview.html" %}
<hr class="border-blue-950 my-5 md:my-8"> <hr class="border-slate-300 my-5 md:my-8">
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<section class="text-center my-3 md:text-lg">
<a href="/blog">see all blog posts</a>
</section>
</section> </section>
<section id="socials"> <section id="socials">
{% include "sections/social.html" %} {% include "sections/social.html" %}
</section> </section>
<hr class="border-blue-950 m-5 lg:hidden"> <hr class="border-slate-300 m-5 lg:hidden">
<section id="showcase" class="col-span-2"> <section id="showcase" class="col-span-2">
{% include "sections/showcase.html" %} {% include "sections/showcase.html" %}

View File

@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block content %}
<section id="project-list-container" class="max-w-maxindex mx-auto">
<section id="project-list">
{% if project_list.len() == 0 %}
<p class="no-posts">You've found void in the space.</p>
{% else %}
<h1 class="m-5 text-4xl text-blue-950 font-extrabold md:text-6xl">
Showcase
</h1>
<ul class="m-6 grid grid-flow-row gap-6 md:grid-cols-2 md:grid-rows-[masonry] md:justify-stretch md:items-stretch xl:grid-cols-3">
{% for project in project_list %}
<li>
{% include "components/project_preview_card.html" %}
</li>
{% endfor %}
</ul>
{% endif %}
</section> <!-- /#project-list -->
</section> <!-- /#project-list-container -->
{% endblock %}

View File

@@ -1,9 +1,13 @@
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Showcase</h2> <h2 class="text-blue-950 font-bold text-2xl m-5 md:text-4xl"><a href="/showcase" class="text-blue-950 no-underline">Showcase</a></h2>
<ul class="mx-5 md:grid md:grid-cols-2 md:grid-rows-[masonry] md:justify-stretch md:items-stretch xl:grid-cols-3"> <ul class="mx-6 grid grid-flow-row gap-6 md:grid-cols-2 md:grid-rows-[masonry] md:justify-stretch md:items-stretch xl:grid-cols-3">
{% for project in featured_projects %} {% for project in featured_projects %}
<li class="my-2"> <li>
{% include "components/project_preview_card.html" %} {% include "components/project_preview_card.html" %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<section class="text-center my-3 md:text-lg">
<a href="/showcase">check out more projects</a>
</section>

View File

@@ -1,52 +1,14 @@
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Socials</h2> <h2 class="text-blue-950 font-bold text-2xl m-5 md:text-4xl">Socials</h2>
{% call sc::social_card_start("twitch", "I stream (almost) regularly on <em>twitch.tv</em>") %}
<!-- <script src= "https://player.twitch.tv/js/embed/v1.js"></script> -->
<!-- <div id="twitch-player" class="h-64 aspect-video rounded overflow-hidden"></div> -->
<!-- <script type="text/javascript"> -->
<!-- var options = { -->
<!-- width: "100%", -->
<!-- height: "100%", -->
<!-- channel: "michalvankodev", -->
<!-- parent: ["localhost"] -->
<!-- }; -->
<!-- var player = new Twitch.Player("twitch-player", options); -->
<!-- player.setVolume(0.5); -->
</script>
{% call sc::social_card_end() %}
{% call sc::social_card_start("tiktok", "Highlights can be found on <em>TikTok</em>") %} <section class="grid grid-flow-row justify-center">
<!-- STYLES needed to overwrite tiktok embed css --> {% call sc::social_card_start("twitch", "https://twitch.tv/michalvankodev", "I stream (almost) regularly on <em>twitch.tv</em>", "images/social/twitch_wo.png", "social-card-twitch") %}
<!-- <blockquote -->
<!-- class="h-64 aspect-video overflow-hidden p-0 m-0 tiktok-embed bg-pink-200" -->
<!-- cite="https://www.tiktok.com/@michalvankodev" -->
<!-- data-unique-id="michalvankodev" -->
<!-- data-embed-from="embed_page" -->
<!-- data-embed-type="creator" -->
<!-- style="max-width:780px; min-width:288px; margin: 0; padding: 0; border-radius: 8px" -->
<!-- > -->
<!-- <section> -->
<!-- <a target="_blank" href="https://www.tiktok.com/@michalvankodev?refer=creator_embed">@michalvankodev</a> -->
<!-- </section> -->
<!-- </blockquote> -->
<!-- <script async src="https://www.tiktok.com/embed.js"></script> -->
{% call sc::social_card_end() %}
{% call sc::social_card_start("youtube", "Vlogs and highlights can be found on <em>YouTube</em>") %} {% call sc::social_card_start("tiktok", "https://www.tiktok.com/@michalvankodev","Highlights can be found on <em>TikTok</em>", "images/social/tiktok_wo.png", "social-card-tiktok") %}
<!-- TODO create our own youtube widget which will populate this window on build -->
<!-- <iframe -->
<!-- class="h-64 aspect-video" -->
<!-- id="ytplayer" -->
<!-- type="text/html" -->
<!-- width="100%" -->
<!-- height="100%" -->
<!-- src="https://www.youtube.com/embed/?listType=playlist&list=PLjUl8tFKyR8rCsckLn93PAwQg6tf0cyBl&enablejsapi=1&color=white" -->
<!-- frameborder="0" -->
<!-- allowfullscreen -->
<!-- ></iframe> -->
{% call sc::social_card_end() %}
{% call sc::social_card_start("youtube", "https://www.youtube.com/@michalvankodev", "Videos and vlogs posted on <em>YouTube</em>", "images/social/youtube_wo.png", "social-card-youtube") %}
{% call sc::social_card_start("instagram", "Photos and stories shared on <em>Instagram</em>") %} <div class="lg:hidden xl:block">
<!-- <blockquote class="instagram-media aspect-video h-64" data-instgrm-permalink="https://www.instagram.com/michalvankodev/" data-instgrm-version="12" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; height:256px; max-height:100%;"></blockquote><script async src="https://www.instagram.com/embed.js"></script> --> {% call sc::social_card_start("instagram", "https://www.instagram.com/michalvankodev/", "Photos and stories shared on <em>Instagram</em>", "images/social/instagram_plain.png", "social-card-instagram") %}
{% call sc::social_card_end() %} </div>
</section>

View File

@@ -1,4 +1,5 @@
<footer> <footer class="my-4">
<hr class="mb-4 border-slate-300 mx-5">
<p <p
class="text-center" class="text-center"
xmlns:cc="http://creativecommons.org/ns#" xmlns:cc="http://creativecommons.org/ns#"
@@ -14,7 +15,7 @@
<a <a
rel="cc:attributionURL dct:creator" rel="cc:attributionURL dct:creator"
property="cc:attributionName" property="cc:attributionName"
href="https://michalvanko.dev/" href="/contact"
>Michal Vanko</a >Michal Vanko</a
> >
is licensed under is licensed under
@@ -26,16 +27,24 @@
>CC BY-NC-ND 4.0<img >CC BY-NC-ND 4.0<img
src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1"
alt="cc" alt="cc"
class="inline-block h-6 mx-0.5" /><img class="inline-block h-6 mx-0.5"
height="24"
width="24" /><img
src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1"
alt="by" alt="by"
class="inline-block h-6 mx-0.5" /><img class="inline-block h-6 mx-0.5"
height="24"
width="24" /><img
src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1"
alt="nc" alt="nc"
class="inline-block h-6 mx-0.5" /><img class="inline-block h-6 mx-0.5"
height="24"
width="24" /><img
src="https://mirrors.creativecommons.org/presskit/icons/nd.svg?ref=chooser-v1" src="https://mirrors.creativecommons.org/presskit/icons/nd.svg?ref=chooser-v1"
alt="nd" alt="nd"
class="inline-block h-6 mx-0.5" class="inline-block h-6 mx-0.5"
height="24"
width="24"
/></a> /></a>
<!-- TODO Display link to feed with icon --> <!-- TODO Display link to feed with icon -->
<a href="/feed.xml" class="hidden">RSS feed</a> <a href="/feed.xml" class="hidden">RSS feed</a>

View File

@@ -1,9 +1,9 @@
<header class="min-h-full bg-blue-50 mb-5"> <header class="min-h-full bg-blue-50 mb-5">
<nav class="flex"> <nav class="flex">
{% match header_props.back_link %} {% match header_props.back_link %}
{% when Some with (link) %} {% when Some with (link) %}
<a <a
class="p-3 text-lg font-medium drop-shadow-md" class="px-3 py-2 text-lg font-medium print:hidden"
href="{{link.href}}" href="{{link.href}}"
> >
{{link.label}} {{link.label}}
@@ -15,6 +15,6 @@
@michalvankodev @michalvankodev
</a> </a>
</aside> </aside>
</nav> </nav>
<hr class="border-blue-950 mx-5"> <hr class="border-slate-300 mx-5">
</header> </header>

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -96,7 +96,7 @@ collections:
widget: boolean, widget: boolean,
default: true, default: true,
} }
- { label: Description, name: description, widget: markdown } - { label: Description, name: body, widget: markdown }
- { - {
label: Cover image, label: Cover image,
name: cover_image, name: cover_image,
@@ -141,7 +141,7 @@ collections:
widget: boolean, widget: boolean,
default: true, default: true,
} }
- { label: Description, name: description, widget: markdown } - { label: Description, name: body, widget: markdown }
- { - {
label: Link, label: Link,
name: link, name: link,