Initial commit

This commit is contained in:
2025-05-22 18:08:15 +02:00
commit 364ffa15de
33 changed files with 900 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
name: Build Docker images
on:
workflow_dispatch:
push:
branches: [ "main" ]
tags: [ "*" ]
jobs:
acls:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24.3'
- uses: ko-build/setup-ko@v0.8
with:
version: v0.18.0
env:
KO_DOCKER_REPO: git.prettysunflower.moe/prettysunflower
- env:
auth_token: ${{ secrets.HUB_TOKEN }}
run: |
echo "${auth_token}" | ko login git.prettysunflower.moe --username ${{ vars.HUB_USERNAME }} --password-stdin
ko build -B

View File

@@ -0,0 +1,45 @@
name: Build Docker images for static folder
on:
workflow_dispatch:
push:
branches: [ "main" ]
tags: [ "*" ]
defaults:
run:
working-directory: static
jobs:
acls:
runs-on: ubuntu-latest
steps:
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: git.prettysunflower.moe/prettysunflower/website-static
-
name: Login to Gitea Container Hub
uses: docker/login-action@v3
with:
registry: git.prettysunflower.moe
username: ${{ vars.HUB_USERNAME }}
password: ${{ secrets.HUB_TOKEN }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: "{{defaultContext}}:static"
push: ${{ gitea.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64

26
.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
build/
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/prettysunflower-website.iml" filepath="$PROJECT_DIR$/.idea/prettysunflower-website.iml" />
</modules>
</component>
</project>

9
.idea/prettysunflower-website.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

11
.ko.yaml Normal file
View File

@@ -0,0 +1,11 @@
defaultBaseImage: cgr.dev/chainguard/static
defaultPlatforms:
- linux/arm64
- linux/amd64
- linux/arm/v7
builds:
- id: website
main: .
ldflags:
- -s -w

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module prettysunflower-website
go 1.24

28
keys/init.go Normal file
View File

@@ -0,0 +1,28 @@
package keys
import (
"io"
"net/http"
)
func InitHttpHandlers() {
http.HandleFunc("/ssh/", sshKey)
http.HandleFunc("/age/", ageKey)
http.HandleFunc("/gpg/", gpgKey)
http.HandleFunc("/gpg/koumbit/", gpgKey)
}
func sshKey(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, _ = io.WriteString(w, SSH_KEY)
}
func ageKey(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, _ = io.WriteString(w, AGE_KEY)
}
func gpgKey(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, _ = io.WriteString(w, GPG_KEY)
}

28
keys/keys.go Normal file
View File

@@ -0,0 +1,28 @@
package keys
const SSH_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKK/ydi3HD1cHP40hlYP3EU+h55rAj+nHkLhzcClHStj me@prettysunflower.moe"
const AGE_KEY = "age1r0tjhg6uexyj0p7fp0ftv5h7r7e3ptzkk2797pznfvrvsm576u0s37yyaw"
const GPG_KEY = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: 13EE 61FF 762C 86C7 FC92 71A4 ED9D 604F 0092 91F4
Comment: Remilia Da Costa Faro <remilia@koumbit.org>
Comment: Remilia Da Costa Faro <remilia@remilia.ch>
xjMEZ3w3SBYJKwYBBAHaRw8BAQdAcIpRU4WrTXDfljp/1PSE1fgPDAbOG/Fj/Dqb
hLYx1orNK1JlbWlsaWEgRGEgQ29zdGEgRmFybyA8cmVtaWxpYUBrb3VtYml0Lm9y
Zz7CjwQTFggANwUJBaOagAIbAwQLCQgHBRUICQoLBRYCAwEAFiEEE+5h/3Yshsf8
knGk7Z1gTwCSkfQFAmf+RF0ACgkQ7Z1gTwCSkfT4OQD+KetHdN1L/M7TmFZ+Nbhz
a5XBOmeYT3vpq8aKg0lcG+EA/RVwKvvzgqTzd1zBmgP7gqvFaFXZMlo0VVfsSrCJ
9uANwo8EExYIADcWIQQT7mH/diyGx/yScaTtnWBPAJKR9AUCZ3w3SAUJBaOagAIb
AwQLCQgHBRUICQoLBRYCAwEAAAoJEO2dYE8AkpH0nFgBAPW5fDQxAH8/Pr8ByvAs
CNoCqPIKqvre3U6+JgvsQ/gFAP0arX0+IOjpfFiXCvnYEWMiL2RLi9T45DHrMN1V
n341Bs0qUmVtaWxpYSBEYSBDb3N0YSBGYXJvIDxyZW1pbGlhQHJlbWlsaWEuY2g+
wpwEExYKAEQCGwMFCQWjmoAFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AWIQQT
7mH/diyGx/yScaTtnWBPAJKR9AUCZ/5EXQIZAQAKCRDtnWBPAJKR9N/jAP9C9Lci
uRCwd9WxWbXlQBCdZI8h/0GFlnkOdY4O5nPzLQD/dz2raMl7pp9H7KL5r3ashOYo
wwdGr1H1EEYyDFR9UAnOOARnfDdIEgorBgEEAZdVAQUBAQdAzUl/nTzragxZUHQ3
HmmT0XfGgaNWKXuS1FAOI3KP6jkDAQgHwn4EGBYIACYWIQQT7mH/diyGx/yScaTt
nWBPAJKR9AUCZ3w3SAUJBaOagAIbDAAKCRDtnWBPAJKR9MEaAP9TBEiI63CLyIr1
6qhFwlEsiPOQS/hIYOoHJG1OPUZMfQEA3IvzRpZzFIb/pR4VvF+Zpddm1GD8yh0r
IvOz2WQApgI=
=mMXG
-----END PGP PUBLIC KEY BLOCK-----`

18
main.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"net/http"
"prettysunflower-website/keys"
"prettysunflower-website/pages"
"prettysunflower-website/radio"
"prettysunflower-website/static"
)
func main() {
pages.InitHttpHandlers()
radio.InitHttpHandlers()
static.InitHttpHandlers()
keys.InitHttpHandlers()
_ = http.ListenAndServe(":3334", nil)
}

38
pages/email_autoconfig.go Normal file
View File

@@ -0,0 +1,38 @@
package pages
import (
"html/template"
"net/http"
)
type EmailAutoconfigTemplateData struct {
Domain string
DisplayName string
ShortDisplayName string
ImapServer string
PopServer string
SmtpServer string
}
func emailAutoconfig(w http.ResponseWriter, r *http.Request) {
templateFile := "templates/email_autoconfig.tmpl"
files, err := template.New(templateFile).ParseFS(content, templateFile)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = files.ExecuteTemplate(w, "email_autoconfig.tmpl", EmailAutoconfigTemplateData{
Domain: "prettysunflower.moe",
DisplayName: "prettysunflower's mail server",
ShortDisplayName: "prettysunflower",
ImapServer: "mail.prettysunflower.moe",
PopServer: "mail.prettysunflower.moe",
SmtpServer: "mail.prettysunflower.moe",
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

32
pages/init.go Normal file
View File

@@ -0,0 +1,32 @@
package pages
import (
"embed"
"html/template"
"net/http"
)
//go:embed templates/*
var content embed.FS
func InitHttpHandlers() {
http.HandleFunc("/{$}", homepage)
http.HandleFunc("/.well-known/autoconfig/mail/config-v1.1.xml", emailAutoconfig)
http.HandleFunc("/mail/config-v1.1.xml", emailAutoconfig)
}
func homepage(w http.ResponseWriter, r *http.Request) {
templateFile := "templates/homepage.tmpl"
files, err := template.New(templateFile).ParseFS(content, templateFile)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = files.ExecuteTemplate(w, "homepage.tmpl", nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,28 @@
<clientConfig version="1.1">
<emailProvider id="{{ .Domain }}">
<domain>{{ .Domain }}</domain>
<displayName>{{ .DisplayName }}</displayName>
<displayShortName>{{ .ShortDisplayName }}</displayShortName>
<incomingServer type="imap">
<hostname>{{ .ImapServer }}</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<incomingServer type="pop3">
<hostname>{{ .PopServer }}</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<outgoingServer type="smtp">
<hostname>{{ .SmtpServer }}</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
</emailProvider>
</clientConfig>

View File

@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>prettysunflower</title>
<link rel="stylesheet" type="text/css" href="/static/static/css/style.css">
</head>
<body class="page-index">
<header>
<div>
<img src="https://kakigoori.dev/5819581e-42b9-41a7-bab6-c3f99de30934/auto" alt="Avatar of prettysunflower">
<div>
<h1>prettysunflower</h1>
<p>
Nyallo! We're Remilia, Xeon, and Takeno!
</p>
<p>
We're a system of 3, we're software and website developers at
the <a href="https://koumbit.org">Réseau Koumbit</a>, we're Touhou and Factorio players,
and we're your local trans women/enby/plural person wishing you a good day!
</p>
</div>
</div>
</header>
<main>
<div class="columns">
<div>
<h2>Our pronouns 🏳️‍⚧</h2>
<p>
<strong>Remilia:</strong> <a href="https://pronouns.within.lgbt/they/them/their/theirs/themself">they/them</a> and <a href="https://pronouns.within.lgbt/she/her/her/hers/herself">she/her</a><br>
<strong>Xeon:</strong> <a href="https://pronouns.within.lgbt/they/them/their/theirs/themselves">they/them</a><br>
<strong>Takeno:</strong> <a href="https://pronouns.within.lgbt/she/her/her/hers/herself">she/her</a>
</p>
</div>
<div>
<h2>The ways to contact us</h2>
<p>
<a href="mailto:me@prettysunflower.moe">me@prettysunflower.moe</a><br>
<a href="https://bsky.app/profile/prettysunflower.moe">Bluesky</a><br>
<a href="https://akkoma.prettysunflower.moe">Fediverse</a>
</p>
</div>
<div>
<h2>Our code</h2>
<p>
<a href="https://git.prettysunflower.moe">Gitea</a><br>
<a href="https://github.com/prettysunflower">GitHub</a>
</p>
</div>
</div>
<hr>
<div class="main-projects">
<h2>Our main projects</h2>
<div>
<img src="https://kakigoori.dev/bcf6fdd9-3855-4f4d-9b5a-0791e14e29c7/height/200/auto">
<div>
<h3>Kakigoori</h3>
<p>
Kakigoori is an picture distribution system to publish images on the web.
Upload it once, and Kakigoori will create versions of the image
optimized for the web (AVIF, WebP).
</p>
</div>
</div>
</div>
</main>
</body>
</html>

15
radio/init.go Normal file
View File

@@ -0,0 +1,15 @@
package radio
import (
"embed"
"fmt"
"net/http"
)
//go:embed templates/*
var content embed.FS
func InitHttpHandlers() {
prefix := "/radio/"
http.HandleFunc(fmt.Sprint(prefix, "trains"), trains)
}

130
radio/playlist.go Normal file
View File

@@ -0,0 +1,130 @@
package radio
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
type YoutubeChannel struct {
Id string
Name string
}
type PlaylistItem struct {
Etag string
Id string
PublishedAt time.Time
Channel YoutubeChannel
VideoId string
Title string
Description string
ThumbnailUrl string
Position int
}
type PlaylistResponseSnippet struct {
PublishedAt string `json:"publishedAt"`
VideoOwnerChannelId string `json:"videoOwnerChannelId"`
VideoOwnerChannelTitle string `json:"videoOwnerChannelTitle"`
ResourceId PlaylistResponseResourceId `json:"resourceId"`
Title string `json:"title"`
Description string `json:"description"`
Position int `json:"position"`
}
type PlaylistResponseItem struct {
Etag string `json:"etag"`
Id string `json:"id"`
Snippet PlaylistResponseSnippet `json:"snippet"`
}
type PlaylistResponseResourceId struct {
VideoId string `json:"videoId"`
}
type PlaylistResponse struct {
Items []PlaylistResponseItem `json:"items"`
NextPageToken string `json:"nextPageToken"`
}
func requestYouTubePlaylist(playlistId string, pageToken string) ([]PlaylistItem, string) {
apiUrl, err := url.Parse("https://www.googleapis.com/youtube/v3/playlistItems")
if err != nil {
panic(err)
}
q := apiUrl.Query()
q.Add("key", os.Getenv("GOOGLE_API_KEY"))
q.Add("playlistId", playlistId)
q.Add("part", "id,snippet,contentDetails,status")
q.Add("maxResults", "50")
if pageToken != "" {
q.Add("pageToken", pageToken)
}
apiUrl.RawQuery = q.Encode()
response, err := http.Get(apiUrl.String())
if err != nil {
return nil, ""
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, ""
}
//type playlistResponse PlaylistResponse
var playlistItems PlaylistResponse
err = json.Unmarshal(body, &playlistItems)
if err != nil {
return nil, ""
}
var items []PlaylistItem
for _, item := range playlistItems.Items {
publishedAt, err := time.Parse(time.RFC3339, item.Snippet.PublishedAt)
if err != nil {
panic(err)
}
items = append(items, PlaylistItem{
Etag: item.Etag,
Id: item.Id,
PublishedAt: publishedAt,
VideoId: item.Snippet.ResourceId.VideoId,
Title: item.Snippet.Title,
Description: item.Snippet.Description,
ThumbnailUrl: fmt.Sprint("https://youtubethumbnails.prettysunflower.moe/", item.Snippet.ResourceId.VideoId),
Position: item.Snippet.Position,
Channel: YoutubeChannel{
Id: item.Snippet.VideoOwnerChannelId,
Name: item.Snippet.VideoOwnerChannelTitle,
},
})
}
return items, playlistItems.NextPageToken
}
func getPlaylistOfVideos(playlistId string) []PlaylistItem {
var playlistItems []PlaylistItem
nextPageToken := ""
for {
newItems, newNextPageToken := requestYouTubePlaylist(playlistId, nextPageToken)
playlistItems = append(playlistItems, newItems...)
if newNextPageToken == "" {
return playlistItems
}
nextPageToken = newNextPageToken
}
}

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>prettysunflower - The trains radio</title>
<link rel="stylesheet" type="text/css" href="/static/static/css/style.css">
</head>
<body class="page-radio-trains">
<div class="video-zone">
<video id="video-player" controls></video>
</div>
<h1>The trains radio</h1>
<main>
{{ range .PlaylistItems }}
<div class="playlist-item" data-videoid="{{ .VideoId }}">
<img src="{{ .ThumbnailUrl }}">
<p>
<a href="https://invidious.prettysunflower.moe/watch?v={{ .VideoId }}">
<strong>{{ .Title }}</strong><br>
{{ .Channel.Name }}
</a>
</p>
</div>
{{ end }}
</main>
<script>
const playlistItems = document.getElementsByClassName("playlist-item");
const videoBlock = document.querySelector(".video-zone video");
function loadVideo(videoId) {
videoBlock.src = `https://invidious.prettysunflower.moe/latest_version?local=true&id=${videoId}`;
videoBlock.currentTime = 0;
videoBlock.play();
}
let index = 0;
playlistItems[index].classList.add('active');
loadVideo(playlistItems[index].dataset.videoid);
videoBlock.addEventListener('ended', () => {
playlistItems[index].classList.remove('active');
index = index + 1;
if (index < playlistItems.length) {
playlistItems[index].classList.add('active');
loadVideo(playlistItems[index].dataset.videoid);
}
})
</script>
</body>
</html>

34
radio/trains.go Normal file
View File

@@ -0,0 +1,34 @@
package radio
import (
"html/template"
"math/rand"
"net/http"
)
type TrainsTemplateData struct {
PlaylistItems []PlaylistItem
}
func trains(w http.ResponseWriter, r *http.Request) {
trainsPlaylist := getPlaylistOfVideos("PLZoWUOSrw9RfbTAwQ1pTg3rZD-jZJHd9S")
rand.Shuffle(len(trainsPlaylist), func(i, j int) {
trainsPlaylist[i], trainsPlaylist[j] = trainsPlaylist[j], trainsPlaylist[i]
})
templateFile := "templates/trains.tmpl"
files, err := template.New(templateFile).ParseFS(content, templateFile)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = files.ExecuteTemplate(w, "trains.tmpl", TrainsTemplateData{
PlaylistItems: trainsPlaylist,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

4
static/Caddyfile Normal file
View File

@@ -0,0 +1,4 @@
:8001 {
root * /srv/
file_server
}

4
static/Dockerfile Normal file
View File

@@ -0,0 +1,4 @@
FROM caddy:latest
COPY Caddyfile /etc/caddy/Caddyfile
COPY static/ /srv/

5
static/noStatic.go Normal file
View File

@@ -0,0 +1,5 @@
//go:build !serveStatic
package static
func InitHttpHandlers() {}

16
static/serveStatic.go Normal file
View File

@@ -0,0 +1,16 @@
//go:build serveStatic
package static
import (
"embed"
"net/http"
)
//go:embed static/*
var staticFS embed.FS
func InitHttpHandlers() {
fs := http.FileServerFS(staticFS)
http.Handle("/static/", http.StripPrefix("/static", fs))
}

View File

@@ -0,0 +1,8 @@
@use "fonts";
@use "colors";
body {
font-family: fonts.$font-stack;
background-color: colors.$background-color;
margin: 0;
}

View File

@@ -0,0 +1 @@
$background-color: oklch(0.97 0.0261 90.1);

View File

@@ -0,0 +1,3 @@
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap');
$font-stack: "Open Sans", apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";

View File

@@ -0,0 +1,48 @@
@use "layout";
.page-index {
header {
width: 100%;
height: 75vh;
background-image: url('https://kakigoori.dev/c152e805-b859-4ad0-817c-4d671e5f15ad/auto');
background-size: cover;
background-position: top center;
display: flex;
justify-content: center;
align-items: center;
& > div {
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
width: min(80%, 800px);
margin: 0 auto;
padding: 2rem;
background-color: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(15px);
border-radius: 1rem;
}
img {
height: 200px;
border-radius: 50%;
}
}
hr {
@include layout.light-hr;
}
.columns {
h2 {
text-align: center;
}
}
.main-projects {
& > div {
display: flex;
}
}
}

View File

@@ -0,0 +1,10 @@
.columns {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-evenly;
}
@mixin light-hr($height: .5px, $color: oklch(75% 0 0deg)) {
border: $height solid $color;
}

View File

@@ -0,0 +1,60 @@
@use "colors";
.page-radio-trains {
.video-zone {
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
#video-player {
aspect-ratio: 16 / 9;
max-width: 80%;
max-height: 50vh;
width: 100%;
}
}
h1 {
padding: 0 1rem;
text-align: center;
}
main {
display: grid;
gap: 1rem;
padding: 1rem;
grid-template-columns: repeat(4, 1fr);
& > div {
img {
width: 100%;
}
&.active {
background-color: oklch(from colors.$background-color calc(l - 0.05) c h);
}
p {
margin: .5em;
a {
text-decoration: none;
color: black;
}
}
}
@media screen and (max-width: 992px) {
grid-template-columns: repeat(3, 1fr);
}
@media screen and (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
@media screen and (max-width: 576px) {
grid-template-columns: 1fr;
}
}
}

102
static/static/css/style.css Normal file
View File

@@ -0,0 +1,102 @@
@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap");
.columns {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-evenly;
}
body {
font-family: "Open Sans", apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: oklch(97% 0.0261 90.1deg);
margin: 0;
}
.page-index header {
width: 100%;
height: 75vh;
background-image: url("https://kakigoori.dev/c152e805-b859-4ad0-817c-4d671e5f15ad/auto");
background-size: cover;
background-position: top center;
display: flex;
justify-content: center;
align-items: center;
}
.page-index header > div {
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
width: min(80%, 800px);
margin: 0 auto;
padding: 2rem;
background-color: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(15px);
border-radius: 1rem;
}
.page-index header img {
height: 200px;
border-radius: 50%;
}
.page-index hr {
border: 0.5px solid oklch(75% 0 0deg);
}
.page-index .columns h2 {
text-align: center;
}
.page-index .main-projects > div {
display: flex;
}
.page-radio-trains .video-zone {
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
}
.page-radio-trains .video-zone #video-player {
aspect-ratio: 16/9;
max-width: 80%;
max-height: 50vh;
width: 100%;
}
.page-radio-trains h1 {
padding: 0 1rem;
text-align: center;
}
.page-radio-trains main {
display: grid;
gap: 1rem;
padding: 1rem;
grid-template-columns: repeat(4, 1fr);
}
.page-radio-trains main > div img {
width: 100%;
}
.page-radio-trains main > div.active {
background-color: oklch(from oklch(97% 0.0261 90.1deg) calc(l - 0.05) c h);
}
.page-radio-trains main > div p {
margin: 0.5em;
}
.page-radio-trains main > div p a {
text-decoration: none;
color: black;
}
@media screen and (max-width: 992px) {
.page-radio-trains main {
grid-template-columns: repeat(3, 1fr);
}
}
@media screen and (max-width: 768px) {
.page-radio-trains main {
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (max-width: 576px) {
.page-radio-trains main {
grid-template-columns: 1fr;
}
}
/*# sourceMappingURL=style.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["fonts.scss","layout.scss","body.scss","colors.scss","index.scss","radio.scss"],"names":[],"mappings":"AAAQ;ACAR;EACI;EACA;EACA;EACA;;;ACDJ;EACI,aFFS;EEGT,kBCLe;EDMf;;;AEHA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAIR;EHxBA;;AG6BI;EACI;;AAKJ;EACI;;;ACzCR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAIR;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAGI;EACI;;AAGJ;EACI;;AAGJ;EACI;;AAEA;EACI;EACA;;AAKZ;EAzBJ;IA0BQ;;;AAGJ;EA7BJ;IA8BQ;;;AAGJ;EAjCJ;IAkCQ","file":"style.css"}

View File

@@ -0,0 +1,4 @@
@use "layout";
@use "body";
@use "index";
@use "radio";