summaryrefslogtreecommitdiff
diff refs
from: back
to: back
| flip
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build-and-deploy.yml37
-rw-r--r--.gitignore4
-rw-r--r--deploy/inventory.yml28
-rw-r--r--deploy/playbook.yml29
-rw-r--r--internal/b2/client.go4
-rw-r--r--main.go42
-rw-r--r--views/index.html60
-rw-r--r--views/layouts/blog-main.html147
-rw-r--r--views/layouts/blog-page.html8
9 files changed, 84 insertions, 275 deletions
diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index ac5fa1f..57cfc96 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -8,8 +8,6 @@ jobs:
if: github.repository == 'SayaAndy/saya-today-web'
runs-on: ubuntu-latest
environment: Production
- permissions:
- packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -20,17 +18,17 @@ jobs:
output: static/output.css
params: "--minify"
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3
+ uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
- uses: docker/login-action@v3
+ uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build and push
- uses: docker/build-push-action@v6
+ uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
@@ -47,31 +45,22 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
-
- - name: Set up SSH connection for pl.saya.today
- run: |
- mkdir -p ~/.ssh
- (cat <<EOF
- ${{ secrets.SVC_GITHUB_PK }}
- EOF
- ) > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- sed -i 's/\r$//' ~/.ssh/id_ed25519
- ssh-keyscan -H pl.saya.today >> ~/.ssh/known_hosts
-
- - name: Test SSH connection
+ - name: Set up SSH private key
run: |
- ssh -o ConnectTimeout=10 -i ~/.ssh/id_ed25519 svc_github@pl.saya.today "echo 'SSH connection successful'"
-
+ echo "${{ secrets.SVC_GITHUB_PK }}" > deploy/private_key.pem
+ chmod 600 deploy/private_key.pem
- name: Install Ansible
shell: bash
run: |
sudo apt update
sudo apt install -y ansible
-
- name: Run Ansible playbook
env:
ANSIBLE_HOST_KEY_CHECKING: False
working-directory: ./deploy
- run: |
- ansible-playbook -i inventory.yml -l stage playbook.yml --private-key ~/.ssh/id_ed25519 -u svc_github -e saya_today_web_b2_key_id=${{ secrets.B2_KEY_ID }} -e saya_today_web_b2_application_key=${{ secrets.B2_APPLICATION_KEY }} -e saya_today_web_tag=${{ github.ref_name }}
+ run: >
+ ansible-playbook -i inventory.yml -l stage playbook.yml
+ --private-key private_key.pem
+ -e saya_today_web.b2.key_id=${{ secrets.B2_KEY_ID }}
+ -e saya_today_web.b2.application_key=${{ secrets.B2_APPLICATION_KEY }}
+ -e saya_today_web.tag=${{ github.ref_name }}
diff --git a/.gitignore b/.gitignore
index b5ae410..5e9bfe0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,9 +36,7 @@ config*.json
/main
/sayana-demo
-/static/output.css
+/static/input.css
/.vscode
.envrc
-
-/.ansible
diff --git a/deploy/inventory.yml b/deploy/inventory.yml
index 3c1220d..d3c325f 100644
--- a/deploy/inventory.yml
+++ b/deploy/inventory.yml
@@ -1,15 +1,15 @@
-all:
- children:
- prod:
- hosts:
- pl.saya.today:
- saya_today_web_name: sayana-web
- saya_today_web_hostname: sayana-web
- saya_today_web_ipv4_address: 172.16.0.18
+prod:
+ hosts:
+ pl.saya.today:
+ saya_today_web:
+ name: sayana-web
+ hostname: sayana-web
+ ipv4_address: 172.16.0.18
- stage:
- hosts:
- pl.saya.today:
- saya_today_web_name: sayana-demo
- saya_today_web_hostname: sayana-demo
- saya_today_web_ipv4_address: 172.16.0.17
+stage:
+ hosts:
+ pl.saya.today:
+ saya_today_web:
+ name: sayana-demo
+ hostname: sayana-demo
+ ipv4_address: 172.16.0.17
diff --git a/deploy/playbook.yml b/deploy/playbook.yml
index 773bb61..571ed2b 100644
--- a/deploy/playbook.yml
+++ b/deploy/playbook.yml
@@ -1,21 +1,20 @@
---
- name: Deploy saya-today-web Docker Container
- hosts: all
remote_user: svc_github
tasks:
- - name: Deploy saya-today-web Docker Container
- community.docker.docker_container:
- name: "{{ saya_today_web_name }}"
- hostname: "{{ saya_today_web_hostname }}"
- image: "ghcr.io/sayaandy/saya-today-web:{{ saya_today_web_tag }}"
- env:
- B2_KEY_ID: "{{ saya_today_web_b2_key_id }}"
- B2_APPLICATION_KEY: "{{ saya_today_web_b2_application_key }}"
- network_mode: caddy
- networks:
- - name: caddy
- ipv4_address: "{{ saya_today_web_ipv4_address }}"
- restart_policy: unless-stopped
- no_log: true
+ - name: Deploy saya-today-web Docker Container
+ community.docker.docker_container:
+ name: "{{ saya_today_web.name }}"
+ hostname: "{{ saya_today_web.hostname }}"
+ image: "ghcr.io/sayaandy/saya-today-web:{{ saya_today_web.tag }}"
+ env:
+ B2_KEY_ID: "{{ saya_today_web.b2.key_id }}"
+ B2_APPLICATION_KEY: "{{ saya_today_web.b2.application_key }}"
+ network_mode: caddy
+ networks:
+ - name: caddy
+ ipv4_address: "{{ saya_today_web.ipv4_address }}"
+ restart_policy: unless-stopped
+ no_log: true
...
diff --git a/internal/b2/client.go b/internal/b2/client.go
index 1a97e5f..f2b6cad 100644
--- a/internal/b2/client.go
+++ b/internal/b2/client.go
@@ -36,10 +36,10 @@ type BlogPage struct {
Metadata *frontmatter.Metadata
}
-func (c *B2Client) Scan(prefix string) ([]*BlogPage, error) {
+func (c *B2Client) Scan() ([]*BlogPage, error) {
filePaths := []*BlogPage{}
- iter := c.bucket.List(context.Background(), b2.ListPrefix(c.prefix+prefix))
+ iter := c.bucket.List(context.Background(), b2.ListPrefix(c.prefix))
for iter.Next() {
obj := iter.Object()
diff --git a/main.go b/main.go
index 303fadd..447329b 100644
--- a/main.go
+++ b/main.go
@@ -8,7 +8,6 @@ import (
"log"
"log/slog"
"os"
- "slices"
"strconv"
"strings"
@@ -18,7 +17,6 @@ import (
"github.com/SayaAndy/saya-today-web/internal/lightgallery"
"github.com/SayaAndy/saya-today-web/internal/tailwind"
"github.com/gofiber/fiber/v2"
- "github.com/gofiber/fiber/v2/middleware/redirect"
"github.com/gofiber/template/html/v2"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser"
@@ -38,9 +36,8 @@ var (
gmhtml.WithXHTML(),
),
)
- b2Client *b2.B2Client
- configPath = flag.String("c", "config.yaml", "Path to the configuration file (in YAML format)")
- availableLanguages = make([]string, 0)
+ b2Client *b2.B2Client
+ configPath = flag.String("c", "config.yaml", "Path to the configuration file (in YAML format)")
)
func main() {
@@ -63,37 +60,14 @@ func main() {
os.Exit(1)
}
- for _, lang := range cfg.BlogPages.AvailableLanguages {
- availableLanguages = append(availableLanguages, lang.Name)
- }
-
engine := html.New("./views", ".html")
app := fiber.New(fiber.Config{
Views: engine,
})
- app.Use(redirect.New(redirect.Config{
- Rules: map[string]string{
- "/blog": "/",
- },
- StatusCode: 301,
- }))
-
app.Get("/", func(c *fiber.Ctx) error {
- return c.Status(fiber.StatusOK).Render("index", fiber.Map{
- "AvailableLanguages": cfg.BlogPages.AvailableLanguages,
- })
- })
-
- app.Get("/blog/:lang", func(c *fiber.Ctx) error {
- lang := c.Params("lang")
- if !slices.Contains(availableLanguages, lang) {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
- }
-
- pages, err := b2Client.Scan(lang + "/")
+ pages, err := b2Client.Scan()
status := fiber.StatusOK
if err != nil {
status = fiber.StatusPartialContent
@@ -114,20 +88,14 @@ func main() {
})
}
- return c.Status(status).Render("layouts/blog-main", fiber.Map{
+ return c.Status(status).Render("index", fiber.Map{
"PublishedYear": "2025",
"BlogPages": pageMeta,
})
})
app.Get("/blog/:lang/:title", func(c *fiber.Ctx) error {
- lang := c.Params("lang")
- if !slices.Contains(availableLanguages, lang) {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
- }
-
- metadata, parsedMarkdownDesktop, parsedMarkdownMobile, err := readBlogPost(lang + "/" + c.Params("title"))
+ metadata, parsedMarkdownDesktop, parsedMarkdownMobile, err := readBlogPost(c.Params("lang") + "/" + c.Params("title"))
if err != nil {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("failed to find '%s' post", c.Params("title")))
diff --git a/views/index.html b/views/index.html
index 1c57bd1..731def6 100644
--- a/views/index.html
+++ b/views/index.html
@@ -12,45 +12,33 @@
<style>
.menu-outer-body {
border-radius: 10%;
- width: 40vh;
- height: 30vh;
+ width: 60vw;
+ height: 40vh;
}
.menu-inner-body {
width: 85%;
}
- .flex-adaptive {
- flex-direction: row;
- }
-
- .language-icon {
- max-width: calc(80% / {{ .AvailableLanguages | len }});
- max-height: 80%;
- }
-
- @media (max-aspect-ratio: 0.8/1) {
+ @media (max-aspect-ratio: 1.2/1) {
.menu-outer-body {
border-radius: 5%;
}
- .menu-outer-body {
- width: min(40vh, 100vw);
- height: 40vh;
- border-radius: 0;
+ .menu-inner-body {
+ width: 92.5%;
}
+ }
- .language-icon {
- max-width: 40%;
- max-height: calc(80% / {{ .AvailableLanguages | len }});
+ @media (max-aspect-ratio: 0.8/1) {
+ .menu-outer-body {
+ width: 100vw;
+ height: 80vh;
+ border-radius: 0;
}
.menu-inner-body {
- width: 92.5%;
- }
-
- .flex-adaptive {
- flex-direction: column;
+ width: 100%;
}
}
</style>
@@ -103,13 +91,27 @@
</script>
<div class="menu-outer-body p-4 z-1 bg-background-dark flex flex-col mx-auto my-auto opacity-70">
- <p class="font-patua mx-auto text-[2.5vmax] mt-[0.8vmin] mb-[0.8vmin]">saya.today</p>
- <div class="menu-inner-body grow bg-background-light flex flex-adaptive mx-auto opacity-100">
- {{- range .AvailableLanguages }}
- <img onclick="location.href='/blog/{{ .Name }}';" class="object-scale-down cursor-pointer flex-1 rounded-[20%] language-icon mx-auto my-auto relative" src="{{ .Flag }}" alt="{{ .Alt }}">
+ <p class="font-patua mx-auto text-[2.5vmax] mt-[0.8vmin] mb-[1.6vmin]">saya.today</p>
+ <div class="menu-inner-body grow bg-background-light flex flex-col mx-auto mb-[1.6vmin] overflow-y-auto opacity-100">
+ {{- range .BlogPages }}
+ <div class="m-2 flex flex-row justify-center justify-items-center gap-4">
+ <img onclick="location.href='/blog/{{ .Link }}';" style="flex-grow: 0;" class="flex-1 cursor-pointer object-cover rounded-4xl select-none w-[4vmax] h-[4vmax]" src="https://f003.backblazeb2.com/file/sayana-photos/thumbnails/{{ .Thumbnail }}.webp">
+ <div class="flex-5 justify-self-center flex-col border-r-1 border-dashed border-opacity-50 border-main-medium">
+ <a href="/blog/{{ .Link }}" class="block font-extrabold">{{ .Title }}</a>
+ <a href="/blog/{{ .Link }}" class="block italic text-[1vmax] text-secondary">{{ .ActionDate }}</a>
+ <p>{{ .ShortDescription }}</p>
+ </div>
+ <div class="flex-3 justify-self-center flex flex-col text-right">
+ <p class="grow text-[1vmax]">
+ Тэги: {{ .Tags }}
+ </p>
+ <p class="italic text-secondary text-[1vmax]">
+ {{ .PublishedTime }}
+ </p>
+ </div>
+ </div>
{{- end }}
</div>
- <p class="font-spectral mx-auto text-[1.5vmax] text-secondary mt-[0.8vmin] mb-[0.8vmin]">Choose your language</p>
</div>
<script>
diff --git a/views/layouts/blog-main.html b/views/layouts/blog-main.html
deleted file mode 100644
index 1b8e149..0000000
--- a/views/layouts/blog-main.html
+++ /dev/null
@@ -1,147 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>SAYA TODAY // Заглавная</title>
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,200;1,300;1,400;1,500;1,600;1,700;1,800&family=Patua+One&display=swap" rel="stylesheet">
- <link href="/output.css" rel="stylesheet">
- <style>
- .menu-outer-body {
- border-radius: 10%;
- width: 60vw;
- height: 40vh;
- }
-
- .menu-inner-body {
- width: 85%;
- }
-
- @media (max-aspect-ratio: 1.2/1) {
- .menu-outer-body {
- border-radius: 5%;
- }
-
- .menu-inner-body {
- width: 92.5%;
- }
- }
-
- @media (max-aspect-ratio: 0.8/1) {
- .menu-outer-body {
- width: 100vw;
- height: 80vh;
- border-radius: 0;
- }
-
- .menu-inner-body {
- width: 100%;
- }
- }
- </style>
-</head>
-
-<body data-theme="night" class="font-spectral text-main-dark text-[1.5vmax] overflow-hidden bg-interlocked-hexagons h-screen flex flex-col">
-
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
- <div class="absolute star z-0">*</div>
-
- <script>
- function switchTheme(theme) {
- if (theme == "" || typeof theme == "undefined") {
- const currentTheme = document.body.getAttribute('data-theme');
- switch (currentTheme) {
- case "olive": theme = "lettuce"; break;
- case "lettuce": theme = "night"; break;
- case "night": theme = "olive"; break;
- }
- }
- // localStorage.setItem('theme', theme);
- document.body.setAttribute('data-theme', theme);
- }
-
- const savedTheme = 'night';
- switchTheme(savedTheme);
- </script>
-
- <div class="menu-outer-body p-4 z-1 bg-background-dark flex flex-col mx-auto my-auto opacity-70">
- <p class="font-patua mx-auto text-[2.5vmax] mt-[0.8vmin] mb-[1.6vmin]">saya.today</p>
- <div class="menu-inner-body grow bg-background-light flex flex-col mx-auto mb-[1.6vmin] overflow-y-auto opacity-100">
- {{- range .BlogPages }}
- <div class="m-2 flex flex-row justify-center justify-items-center gap-4">
- <img onclick="location.href='/blog/{{ .Link }}';" style="flex-grow: 0;" class="flex-1 cursor-pointer object-cover rounded-4xl select-none w-[4vmax] h-[4vmax]" src="https://f003.backblazeb2.com/file/sayana-photos/thumbnails/{{ .Thumbnail }}.webp">
- <div class="flex-5 justify-self-center flex-col border-r-1 border-dashed border-opacity-50 border-main-medium">
- <a href="/blog/{{ .Link }}" class="block font-extrabold">{{ .Title }}</a>
- <a href="/blog/{{ .Link }}" class="block italic text-[1vmax] text-secondary">{{ .ActionDate }}</a>
- <p>{{ .ShortDescription }}</p>
- </div>
- <div class="flex-3 justify-self-center flex flex-col text-right">
- <p class="grow text-[1vmax]">
- {{ .Tags }}
- </p>
- <p class="italic text-secondary text-[1vmax]">
- {{ .PublishedTime }}
- </p>
- </div>
- </div>
- {{- end }}
- </div>
- </div>
-
- <script>
- function generateStars() {
- var clWidth = document.documentElement.clientWidth;
- var clHeight = document.documentElement.clientHeight;
-
- stars = document.getElementsByClassName("star");
- for (let starIndex = 0; starIndex < stars.length; starIndex++) {
- const star = stars[starIndex];
- const x = Math.floor(Math.random() * clWidth);
- const y = Math.floor(Math.random() * clHeight * 0.8);
- const rot = Math.random() * 90;
- const animDuration = (Math.random() * 10) + 3;
- const sz = Math.floor(Math.random() * 8) + 12;
- star.style.transform = `translateX(${x}px) translateY(${y}px) rotate(${rot}deg)`;
- star.style.animationDuration = `${animDuration}s`;
- star.style.fontSize = `${sz}px`;
- }
- }
-
- document.addEventListener('DOMContentLoaded', generateStars);
-
- let generateStarsTimer;
- window.addEventListener("resize", () => {
- clearTimeout(generateStarsTimer);
- generateStarsTimer = setTimeout(() => {
- generateStars();
- }, 1000);
- });
- </script>
-</body>
-</html>
diff --git a/views/layouts/blog-page.html b/views/layouts/blog-page.html
index 46fe9f4..08a6f93 100644
--- a/views/layouts/blog-page.html
+++ b/views/layouts/blog-page.html
@@ -49,7 +49,7 @@
saya.today
</div>
<div class="flex flex-col gap-0 relative z-20">
- <i onclick="location.href+='/../';" class="fas fa-home nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
+ <i onclick="location.href='/';" class="fas fa-home nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
<i class="fas fa-search nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
<i class="fas fa-images nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
<i onclick="switchTheme('')" class="fas fa-swatchbook nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
@@ -61,7 +61,7 @@
<div class="content-square flex flex-col bg-background-dark relative mt-[1.6vmin] mx-[1.6vmin]">
<div class="flex flex-row mb-[0.4vmin]">
- <i onclick="location.href+='/../';" class="fas fa-circle-left hover:bg-main-dark hover:bg-opacity-10 cursor-pointer transition-colors duration-300 rounded-md text-[2vmax] mt-auto mb-auto px-[0.8vmin]"></i>
+ <i onclick="location.href='/';" class="fas fa-circle-left hover:bg-main-dark hover:bg-opacity-10 cursor-pointer transition-colors duration-300 rounded-md text-[2vmax] mt-auto mb-auto px-[0.8vmin]"></i>
<p class="text-[2vmax] font-spectral italic font-extrabold select-none mt-auto pl-[0.8vmin] pr-[1.2vmin]">//</p>
<p class="text-[2vmax] font-spectral italic text-left mt-auto">{{ .Title }}</p>
<p class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p>
@@ -94,7 +94,7 @@
<div class="mobile-topbar-custom fixed bg-squares-and-triangles bg-repeat flex-row items-center shadow-2xl w-[90vw] z-20 mobile">
<div class="flex flex-row gap-0 relative z-20">
- <i onclick="location.href+='/../';" class="fas fa-home nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
+ <i onclick="location.href='/';" class="fas fa-home nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
<i class="fas fa-search nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
<i class="fas fa-images nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
<i onclick="switchTheme('')" class="fas fa-swatchbook nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i>
@@ -109,7 +109,7 @@
<div class="content-square flex flex-col bg-background-dark relative mt-[1.6vmin] mx-[1.6vmin]">
<div class="flex flex-row mb-[0.4vmin]">
- <i onclick="location.href+='/../';" class="fas fa-circle-left hover:bg-main-dark hover:bg-opacity-10 cursor-pointer transition-colors duration-300 rounded-md text-[2vmax] mt-auto mb-auto px-[0.8vmin]"></i>
+ <i onclick="location.href='/';" class="fas fa-circle-left hover:bg-main-dark hover:bg-opacity-10 cursor-pointer transition-colors duration-300 rounded-md text-[2vmax] mt-auto mb-auto px-[0.8vmin]"></i>
<p class="text-[2vmax] font-spectral italic font-extrabold select-none mt-auto pl-[0.8vmin] pr-[1.2vmin]">//</p>
<p class="text-[2vmax] font-spectral italic text-left mt-auto">{{ .Title }}</p>
<p class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p>