Inital Commit
11
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
**target/
|
||||||
|
**logs/
|
||||||
|
**.minions/
|
||||||
|
.envrc
|
||||||
|
.env
|
||||||
|
.direnv
|
||||||
|
**.jsonl
|
||||||
|
**.ignore.**
|
||||||
|
snapshot*
|
||||||
|
cert.pem
|
||||||
|
key.pem
|
||||||
61
flake.lock
generated
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776548001,
|
||||||
|
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
55
flake.nix
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
description = "Rust Development Flake";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config = {
|
||||||
|
allowUnfree = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
gcc
|
||||||
|
gnumake
|
||||||
|
python3
|
||||||
|
sqlite
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
rustc
|
||||||
|
cargo
|
||||||
|
rust-analyzer
|
||||||
|
rustfmt
|
||||||
|
clippy
|
||||||
|
|
||||||
|
pkgs.cudaPackages.cuda_nvcc
|
||||||
|
pkgs.cudaPackages.cudatoolkit
|
||||||
|
|
||||||
|
# --- LIBRARIES ---
|
||||||
|
openssl
|
||||||
|
libiconv
|
||||||
|
glib
|
||||||
|
libva
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export CUDA_PATH=${pkgs.cudaPackages.cudatoolkit}
|
||||||
|
export LD_LIBRARY_PATH=${pkgs.cudaPackages.cuda_nvcc}/lib:${pkgs.cudaPackages.cudatoolkit}/lib:/run/opengl-driver/lib:$LD_LIBRARY_PATH
|
||||||
|
|
||||||
|
|
||||||
|
echo "Entered nix shell"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
1120
server/Cargo.lock
generated
Normal file
17
server/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
axum = { version = "0.8.9", features = ["tokio", "http2"] }
|
||||||
|
axum-server = { version = "0.8.0", features = ["tls-rustls"] }
|
||||||
|
tower = "0.5.3"
|
||||||
|
tower-http = { version = "0.6.8", features = ["fs", "cors", "trace"] }
|
||||||
|
rustls = "0.23"
|
||||||
|
rustls-pemfile = "2"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
154
server/src/main.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
extract::{Path, State},
|
||||||
|
http::{header, StatusCode, Uri},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use axum_server::tls_rustls::RustlsConfig;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::fs;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
website_root: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let website_root = PathBuf::from("../website");
|
||||||
|
let state = AppState {
|
||||||
|
website_root: website_root.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(serve_index))
|
||||||
|
.route("/post/{file}", get(serve_post_page))
|
||||||
|
.route("/api/backdrops.json", get(serve_backdrops))
|
||||||
|
.route("/api/team.json", get(serve_team))
|
||||||
|
.route("/api/posts.json", get(serve_posts))
|
||||||
|
.route("/api/post/{file}", get(serve_post_md))
|
||||||
|
.nest_service("/assets", ServeDir::new(website_root.join("assets")))
|
||||||
|
.nest_service("/posts", ServeDir::new(website_root.join("posts")))
|
||||||
|
.fallback(get(serve_static))
|
||||||
|
.with_state(state.clone());
|
||||||
|
|
||||||
|
// HTTP on port 80
|
||||||
|
let http_app = app.clone();
|
||||||
|
let http_handle = tokio::spawn(async move {
|
||||||
|
let addr = SocketAddr::from(([0, 0, 0, 0], 80));
|
||||||
|
info!("HTTP server listening on {}", addr);
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
axum::serve(listener, http_app).await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// HTTPS on port 443
|
||||||
|
let certs_dir = website_root.join("certs");
|
||||||
|
let cert_path = certs_dir.join("cert.pem");
|
||||||
|
let key_path = certs_dir.join("key.pem");
|
||||||
|
|
||||||
|
if cert_path.exists() && key_path.exists() {
|
||||||
|
let tls_config = RustlsConfig::from_pem_file(cert_path, key_path)
|
||||||
|
.await
|
||||||
|
.expect("Failed to load TLS certificates");
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([0, 0, 0, 0], 443));
|
||||||
|
info!("HTTPS server listening on {}", addr);
|
||||||
|
|
||||||
|
axum_server::bind_rustls(addr, tls_config)
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
warn!("TLS certificates not found, running HTTP only on port 8080");
|
||||||
|
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
http_handle.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_index(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
serve_file(state.website_root.join("index.html")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_post_page(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(_file): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
serve_file(state.website_root.join("post.html")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_backdrops(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
serve_file(state.website_root.join("api/backdrops.json")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_team(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
serve_file(state.website_root.join("api/team.json")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_posts(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
serve_file(state.website_root.join("api/posts.json")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_post_md(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(file): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let path = state.website_root.join("posts").join(&file);
|
||||||
|
if path.exists() {
|
||||||
|
let content = fs::read_to_string(path).await.unwrap_or_default();
|
||||||
|
Response::builder()
|
||||||
|
.header(header::CONTENT_TYPE, "text/markdown; charset=utf-8")
|
||||||
|
.body(Body::from(content))
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
StatusCode::NOT_FOUND.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_static(uri: Uri, State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
let path = uri.path().trim_start_matches('/');
|
||||||
|
let file_path = state.website_root.join(path);
|
||||||
|
|
||||||
|
if file_path.exists() && file_path.is_file() {
|
||||||
|
serve_file(file_path).await
|
||||||
|
} else {
|
||||||
|
StatusCode::NOT_FOUND.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_file(path: PathBuf) -> Response {
|
||||||
|
if !path.exists() {
|
||||||
|
return StatusCode::NOT_FOUND.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = match fs::read(&path).await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mime = match path.extension().and_then(|e| e.to_str()) {
|
||||||
|
Some("html") => "text/html",
|
||||||
|
Some("css") => "text/css",
|
||||||
|
Some("js") => "application/javascript",
|
||||||
|
Some("json") => "application/json",
|
||||||
|
Some("png") => "image/png",
|
||||||
|
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||||
|
Some("mp4") => "video/mp4",
|
||||||
|
Some("md") => "text/markdown; charset=utf-8",
|
||||||
|
_ => "application/octet-stream",
|
||||||
|
};
|
||||||
|
|
||||||
|
Response::builder()
|
||||||
|
.header(header::CONTENT_TYPE, mime)
|
||||||
|
.body(Body::from(content))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
32
website/api/backdrops.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "horse_fog",
|
||||||
|
"destructive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "horse_rainbow",
|
||||||
|
"destructive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "campsite",
|
||||||
|
"destructive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "village_sunset",
|
||||||
|
"destructive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "outpost",
|
||||||
|
"destructive": true,
|
||||||
|
"time": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tower_small",
|
||||||
|
"destructive": true,
|
||||||
|
"time": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tower_large",
|
||||||
|
"destructive": false
|
||||||
|
}
|
||||||
|
]
|
||||||
11
website/api/posts.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"date": "2026-04-01",
|
||||||
|
|
||||||
|
"head_img": "village_sunset.png",
|
||||||
|
"file": "CoolPost123.md",
|
||||||
|
|
||||||
|
"title": "This is a cool post",
|
||||||
|
"description": "Look at this text"
|
||||||
|
}
|
||||||
|
]
|
||||||
50
website/api/team.json
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"icon": "morice.png",
|
||||||
|
"name": "MoriceMC",
|
||||||
|
"priority": 0,
|
||||||
|
"role": "Owner & Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "ninivx.png",
|
||||||
|
"name": "NiniVX",
|
||||||
|
"priority": 1,
|
||||||
|
"role": "Administrator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Burrata",
|
||||||
|
"priority": 1,
|
||||||
|
"role": "Administrator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pitrex",
|
||||||
|
"priority": 2,
|
||||||
|
"role": "Moderator & Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Litus",
|
||||||
|
"priority": 2,
|
||||||
|
"role": "Moderator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ahnyokatzin",
|
||||||
|
"priority": 2,
|
||||||
|
"role": "Moderator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "T1X",
|
||||||
|
"priority": 2,
|
||||||
|
"role": "Moderator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Harrpy",
|
||||||
|
"priority": 2,
|
||||||
|
"role": "Moderator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "alexemmet.png",
|
||||||
|
"name": "AlexEmmet",
|
||||||
|
"priority": 3,
|
||||||
|
"role": "Web-Developer & Critic (Professional Hater)"
|
||||||
|
}
|
||||||
|
]
|
||||||
249
website/app.js
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
// Smooth scroll for nav links
|
||||||
|
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
||||||
|
anchor.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const target = document.querySelector(this.getAttribute("href"));
|
||||||
|
if (target) {
|
||||||
|
target.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Parallax ───
|
||||||
|
function initParallax() {
|
||||||
|
const sections = document.querySelectorAll(".bg-image-section:not(#hero)");
|
||||||
|
const heroLayer = document.getElementById("hero-slideshow");
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
sections.forEach((section) => {
|
||||||
|
const rect = section.getBoundingClientRect();
|
||||||
|
const yPos = -(rect.top * 0.4);
|
||||||
|
section.style.backgroundPositionY = `${yPos}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (heroLayer) {
|
||||||
|
const hero = document.getElementById("hero");
|
||||||
|
if (hero) {
|
||||||
|
const rect = hero.getBoundingClientRect();
|
||||||
|
const yPos = -(rect.top * 0.4);
|
||||||
|
heroLayer.style.transform = `translateY(${yPos}px) scale(1.1)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", update, { passive: true });
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Hero Slideshow with Destructive Theme ───
|
||||||
|
let currentBackdrop = null;
|
||||||
|
const NEAR_TOP_THRESHOLD = 600;
|
||||||
|
|
||||||
|
function checkDestructiveTheme() {
|
||||||
|
if (!currentBackdrop) return;
|
||||||
|
const nearTop = window.scrollY < NEAR_TOP_THRESHOLD;
|
||||||
|
if (currentBackdrop.destructive && nearTop) {
|
||||||
|
document.body.classList.add("destructive-mode");
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove("destructive-mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initHeroSlideshow(allBackdrops, usedNames) {
|
||||||
|
const hero = document.getElementById("hero");
|
||||||
|
const layer = document.getElementById("hero-slideshow");
|
||||||
|
if (!hero || !layer) return;
|
||||||
|
|
||||||
|
const slides = allBackdrops.filter((b) => !usedNames.includes(b.name));
|
||||||
|
|
||||||
|
if (slides.length === 0) {
|
||||||
|
console.warn("No unused backdrops for hero slideshow");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = 0;
|
||||||
|
|
||||||
|
function showSlide(index) {
|
||||||
|
const backdrop = slides[index];
|
||||||
|
currentBackdrop = backdrop;
|
||||||
|
|
||||||
|
layer.style.opacity = "0";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
layer.style.backgroundImage = `url('/assets/${backdrop.name}.png')`;
|
||||||
|
layer.style.opacity = "1";
|
||||||
|
checkDestructiveTheme();
|
||||||
|
}, 600);
|
||||||
|
|
||||||
|
const delayMs = (backdrop.time || 6) * 1000;
|
||||||
|
idx = (index + 1) % slides.length;
|
||||||
|
setTimeout(() => showSlide(idx), delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBackdrop = slides[0];
|
||||||
|
layer.style.backgroundImage = `url('/assets/${slides[0].name}.png')`;
|
||||||
|
layer.style.opacity = "1";
|
||||||
|
checkDestructiveTheme();
|
||||||
|
|
||||||
|
idx = 1;
|
||||||
|
const initialDelay = (slides[0].time || 6) * 1000;
|
||||||
|
setTimeout(() => showSlide(idx), initialDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Load Backdrops ───
|
||||||
|
async function loadBackdrops() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/backdrops.json");
|
||||||
|
const backdrops = await response.json();
|
||||||
|
|
||||||
|
const safeBackdrops = backdrops.filter((b) => !b.destructive);
|
||||||
|
const sections = document.querySelectorAll(".bg-image-section:not(#hero)");
|
||||||
|
const used = [];
|
||||||
|
|
||||||
|
sections.forEach((section) => {
|
||||||
|
let pick;
|
||||||
|
let safety = 0;
|
||||||
|
do {
|
||||||
|
pick = safeBackdrops[Math.floor(Math.random() * safeBackdrops.length)];
|
||||||
|
safety++;
|
||||||
|
} while (
|
||||||
|
used.includes(pick.name) &&
|
||||||
|
used.length < safeBackdrops.length &&
|
||||||
|
safety < 50
|
||||||
|
);
|
||||||
|
used.push(pick.name);
|
||||||
|
section.style.backgroundImage = `url('/assets/${pick.name}.png')`;
|
||||||
|
});
|
||||||
|
|
||||||
|
initParallax();
|
||||||
|
initHeroSlideshow(backdrops, used);
|
||||||
|
|
||||||
|
window.addEventListener("scroll", checkDestructiveTheme, { passive: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load backdrops:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Load Team ───
|
||||||
|
async function loadTeam() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/team.json");
|
||||||
|
const team = await response.json();
|
||||||
|
team.sort((a, b) => a.priority - b.priority);
|
||||||
|
|
||||||
|
const container = document.getElementById("team-grid");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = team
|
||||||
|
.map(
|
||||||
|
(member) => `
|
||||||
|
<div class="team-card">
|
||||||
|
${
|
||||||
|
member.icon
|
||||||
|
? `<img src="/assets/${member.icon}" alt="${member.name}" onerror="this.style.display='none'">`
|
||||||
|
: '<div style="width:100px;height:100px;border-radius:50%;background:#2a2e33;margin:0 auto 1.2rem;border:3px solid #2a2e33;"></div>'
|
||||||
|
}
|
||||||
|
<div class="name">${member.name}</div>
|
||||||
|
<div class="role">${member.role}</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load team:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Load Posts ───
|
||||||
|
async function loadPosts() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/posts.json");
|
||||||
|
const posts = await response.json();
|
||||||
|
|
||||||
|
const container = document.getElementById("posts-grid");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = posts
|
||||||
|
.map(
|
||||||
|
(post) => `
|
||||||
|
<a href="/post/${post.file}" class="post-card">
|
||||||
|
<div class="post-date">${post.date}</div>
|
||||||
|
<div class="post-title">${post.title}</div>
|
||||||
|
<div class="post-desc">${post.description}</div>
|
||||||
|
</a>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load posts:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Video Play/Pause ───
|
||||||
|
function initVideo() {
|
||||||
|
const playBtn = document.getElementById("play-btn");
|
||||||
|
const video = document.getElementById("trailer-video");
|
||||||
|
const container = document.querySelector(".video-container");
|
||||||
|
|
||||||
|
if (!playBtn || !video || !container) return;
|
||||||
|
|
||||||
|
playBtn.addEventListener("click", () => {
|
||||||
|
if (video.paused) {
|
||||||
|
video.play();
|
||||||
|
container.classList.add("playing");
|
||||||
|
} else {
|
||||||
|
video.pause();
|
||||||
|
container.classList.remove("playing");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
video.addEventListener("ended", () => {
|
||||||
|
container.classList.remove("playing");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scroll Effects ───
|
||||||
|
function initScrollEffects() {
|
||||||
|
const header = document.querySelector("header");
|
||||||
|
const footer = document.querySelector("footer");
|
||||||
|
const scrollThreshold = 200;
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
const docHeight = document.documentElement.scrollHeight;
|
||||||
|
const winHeight = window.innerHeight;
|
||||||
|
const nearBottom = scrollY + winHeight >= docHeight - 80;
|
||||||
|
const shortPage = docHeight <= winHeight + 100;
|
||||||
|
|
||||||
|
if (scrollY > 10) {
|
||||||
|
header.classList.add("scrolled");
|
||||||
|
} else {
|
||||||
|
header.classList.remove("scrolled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scrollY < scrollThreshold && !shortPage) {
|
||||||
|
footer.classList.remove("visible");
|
||||||
|
} else {
|
||||||
|
footer.classList.add("visible");
|
||||||
|
if (nearBottom || shortPage) {
|
||||||
|
footer.classList.remove("glass");
|
||||||
|
footer.classList.add("solid");
|
||||||
|
} else {
|
||||||
|
footer.classList.add("glass");
|
||||||
|
footer.classList.remove("solid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", update, { passive: true });
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Initialize ───
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadBackdrops();
|
||||||
|
loadTeam();
|
||||||
|
loadPosts();
|
||||||
|
initVideo();
|
||||||
|
initScrollEffects();
|
||||||
|
});
|
||||||
BIN
website/assets/alexemmet.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
website/assets/anarchyphase.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
website/assets/banner.png
Normal file
|
After Width: | Height: | Size: 365 KiB |
BIN
website/assets/campsite.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
website/assets/horse_fog.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
website/assets/horse_rainbow.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
website/assets/icon.png
Normal file
|
After Width: | Height: | Size: 365 KiB |
BIN
website/assets/morice.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
website/assets/ninivx.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
website/assets/outpost.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
website/assets/tower_large.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
website/assets/tower_small.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
website/assets/trailer.mp4
Normal file
BIN
website/assets/village_sunset.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
156
website/index.html
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Anarchy Phase</title>
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<a href="/" class="logo">Anarchy Phase</a>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li><a href="#concept">Concept</a></li>
|
||||||
|
<li><a href="#socials">Social</a></li>
|
||||||
|
<li><a href="#team">Team</a></li>
|
||||||
|
<li><a href="#posts">Posts</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section id="hero" class="bg-image-section">
|
||||||
|
<div class="slideshow-layer" id="hero-slideshow"></div>
|
||||||
|
<div class="destructive-tint" id="hero-tint"></div>
|
||||||
|
<div class="hero-content">
|
||||||
|
<h2>Join us on:</h2>
|
||||||
|
<p>Anarchyphase.net</p>
|
||||||
|
<p>Bedrock & Java</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="concept">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<h2>Concept</h2>
|
||||||
|
<div class="video-container">
|
||||||
|
<video
|
||||||
|
id="trailer-video"
|
||||||
|
src="/assets/trailer.mp4"
|
||||||
|
preload="metadata"
|
||||||
|
></video>
|
||||||
|
<div class="play-btn" id="play-btn"></div>
|
||||||
|
</div>
|
||||||
|
<div class="concept-text">
|
||||||
|
<h3>THE CORE IDEA</h3>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
With just a few text commands, players can
|
||||||
|
easily claim chunks and invite others. These
|
||||||
|
claims will offer complete protection, except
|
||||||
|
for
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The Anarchy Phase. Monday to Thursday from
|
||||||
|
20-21:00CET and Friday to Sunday from
|
||||||
|
19-21:00CET claims can be raided. Even during
|
||||||
|
this time, bases can't be easily deleted, as
|
||||||
|
TNT, fire, and mining are nerfed inside claims.
|
||||||
|
Destroying bases requires effort, just like
|
||||||
|
building them.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Even when a base is pillaged, there is no
|
||||||
|
incentive to wipe it off the map. TNT and fire
|
||||||
|
are nerfed all over the overworld, so ruins may
|
||||||
|
be repaired or become historic.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>IMMERSION</h3>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
There are no GUIs, official currency, or complex
|
||||||
|
systems. Only a few text commands for claim
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Instead of global chat, there is now local text
|
||||||
|
chat and SimpleVoiceChat support. Death messages
|
||||||
|
are also only local.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The world is limited, and elytras are disabled
|
||||||
|
to encourage spontaneous meetings between
|
||||||
|
players and building of infrastructure.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
PvP is kept simple, fun, and impactful by
|
||||||
|
turning off totems, maces, and crystals.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="socials" class="bg-image-section">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<h2>Socials</h2>
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/@MoriceMC"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="social-btn"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="social-icon"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Youtube
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://discord.com/invite/Bq4GhxDwU5"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="social-btn"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="social-icon"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="team">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<h2>Team</h2>
|
||||||
|
<div id="team-grid" class="team-grid"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="posts" class="bg-image-section">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<h2>Posts</h2>
|
||||||
|
<div id="posts-grid" class="posts-grid"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Imprint ....</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
115
website/md-render.js
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Basic Markdown renderer
|
||||||
|
function renderMarkdown(md) {
|
||||||
|
let html = md
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/^### (.*$)/gim, "<h3>$1</h3>")
|
||||||
|
.replace(/^## (.*$)/gim, "<h2>$1</h2>")
|
||||||
|
.replace(/^# (.*$)/gim, "<h1>$1</h1>")
|
||||||
|
.replace(/\*\*\*(.*?)\*\*\*/g, "<strong><em>$1</em></strong>")
|
||||||
|
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
||||||
|
.replace(/\*(.*?)\*/g, "<em>$1</em>")
|
||||||
|
.replace(/___(.*?)___/g, "<strong><em>$1</em></strong>")
|
||||||
|
.replace(/__(.*?)__/g, "<strong>$1</strong>")
|
||||||
|
.replace(/_(.*?)_/g, "<em>$1</em>")
|
||||||
|
.replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>")
|
||||||
|
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
||||||
|
.replace(/^> (.*$)/gim, "<blockquote>$1</blockquote>")
|
||||||
|
.replace(/^\s*-\s+(.*$)/gim, "<li>$1</li>")
|
||||||
|
.replace(/^\s*\d+\.\s+(.*$)/gim, "<li>$1</li>")
|
||||||
|
.replace(
|
||||||
|
/\[([^\]]+)\]\(([^)]+)\)/g,
|
||||||
|
'<a href="$2" target="_blank" rel="noopener">$1</a>',
|
||||||
|
)
|
||||||
|
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">')
|
||||||
|
.replace(/^---$/gim, "<hr>")
|
||||||
|
.replace(/\n/g, "<br>");
|
||||||
|
|
||||||
|
html = html.replace(/(<li>.*?<\/li>(?:<br><li>.*?<\/li>)*)/gs, (match) => {
|
||||||
|
const clean = match.replace(/<br>/g, "");
|
||||||
|
return `<ul>${clean}</ul>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallax for post hero
|
||||||
|
function initPostParallax() {
|
||||||
|
const hero = document.getElementById("post-hero");
|
||||||
|
if (!hero) return;
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const rect = hero.getBoundingClientRect();
|
||||||
|
const yPos = -(rect.top * 0.4);
|
||||||
|
hero.style.backgroundPositionY = `${yPos}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", update, { passive: true });
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load post hero image from posts.json head_img, fallback to random backdrop
|
||||||
|
async function loadPostHero(filename) {
|
||||||
|
try {
|
||||||
|
// Try to find the post metadata
|
||||||
|
const response = await fetch("/api/posts.json");
|
||||||
|
const posts = await response.json();
|
||||||
|
const post = posts.find((p) => p.file === filename);
|
||||||
|
|
||||||
|
const hero = document.getElementById("post-hero");
|
||||||
|
if (!hero) return;
|
||||||
|
|
||||||
|
if (post && post.head_img) {
|
||||||
|
hero.style.backgroundImage = `url('/assets/${post.head_img}')`;
|
||||||
|
} else {
|
||||||
|
// Fallback to random backdrop
|
||||||
|
const bdResponse = await fetch("/api/backdrops.json");
|
||||||
|
const backdrops = await response.json();
|
||||||
|
const random = backdrops[Math.floor(Math.random() * backdrops.length)];
|
||||||
|
hero.style.backgroundImage = `url('/assets/${random.name}.png')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
initPostParallax();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load post hero:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load post content
|
||||||
|
async function loadPostContent() {
|
||||||
|
const pathParts = window.location.pathname.split("/");
|
||||||
|
const filename = pathParts[pathParts.length - 1];
|
||||||
|
if (!filename) return;
|
||||||
|
|
||||||
|
loadPostHero(filename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/post/${filename}`);
|
||||||
|
const md = await response.text();
|
||||||
|
|
||||||
|
let contentMd = md;
|
||||||
|
const titleMatch = md.match(/^#\s+(.+)$/m);
|
||||||
|
if (titleMatch) {
|
||||||
|
document.title = `${titleMatch[1]} - Anarchy Phase`;
|
||||||
|
const titleEl = document.getElementById("post-title");
|
||||||
|
if (titleEl) titleEl.textContent = titleMatch[1];
|
||||||
|
contentMd = md.replace(/^#\s+.+$/m, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.getElementById("post-body");
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = renderMarkdown(contentMd);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load post:", err);
|
||||||
|
const container = document.getElementById("post-body");
|
||||||
|
if (container)
|
||||||
|
container.innerHTML =
|
||||||
|
'<p style="color:#d48a8a;">Failed to load post content.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadPostContent();
|
||||||
|
});
|
||||||
41
website/post.html
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Post - Anarchy Phase</title>
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<a href="/" class="logo">Anarchy Phase</a>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li><a href="/#concept">Concept</a></li>
|
||||||
|
<li><a href="/#socials">Social</a></li>
|
||||||
|
<li><a href="/#team">Team</a></li>
|
||||||
|
<li><a href="/#posts">Posts</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section id="post-hero" class="bg-image-section">
|
||||||
|
<h1 id="post-title">Loading...</h1>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="post-content">
|
||||||
|
<div id="post-body" class="post-content">
|
||||||
|
<p>Loading post content...</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Imprint ....</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="/app.js"></script>
|
||||||
|
<script src="/md-render.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
website/posts/CoolPost123.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# This is a cool post!
|
||||||
|
|
||||||
|
Look at this text..
|
||||||
|
|
||||||
|
# somt
|
||||||
|
|
||||||
|
a
|
||||||
777
website/style.css
Normal file
|
|
@ -0,0 +1,777 @@
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Theme Variables ─── */
|
||||||
|
:root {
|
||||||
|
--bg-body: #0a0c0e;
|
||||||
|
--bg-header: #14171a;
|
||||||
|
--bg-footer: #14171a;
|
||||||
|
--bg-solid: linear-gradient(180deg, #181a1d 0%, #14171a 100%);
|
||||||
|
--bg-card: linear-gradient(145deg, #1c1e21, #181a1d);
|
||||||
|
--bg-content: linear-gradient(145deg, #1a1d20, #14171a);
|
||||||
|
--bg-postcard: rgba(20, 23, 26, 0.92);
|
||||||
|
--border-base: #252a2e;
|
||||||
|
--border-hover: #3a424a;
|
||||||
|
--text-muted: #8a9eaa;
|
||||||
|
--text-dim: #6e7a80;
|
||||||
|
--accent: #5a7d8c;
|
||||||
|
--accent-light: #7ab8d4;
|
||||||
|
--accent-border: #4a5560;
|
||||||
|
--tint-color: rgba(90, 125, 140, 0.15);
|
||||||
|
--transition-theme:
|
||||||
|
background-color 0.8s ease, border-color 0.8s ease, color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode {
|
||||||
|
--bg-body: #0e0a0a;
|
||||||
|
--bg-header: #1a1414;
|
||||||
|
--bg-footer: #1a1414;
|
||||||
|
--bg-solid: linear-gradient(180deg, #1d1818 0%, #1a1414 100%);
|
||||||
|
--bg-card: linear-gradient(145deg, #211c1c, #1d1818);
|
||||||
|
--bg-content: linear-gradient(145deg, #201a1a, #1a1414);
|
||||||
|
--bg-postcard: rgba(26, 20, 20, 0.92);
|
||||||
|
--border-base: #2e2525;
|
||||||
|
--border-hover: #4a3a3a;
|
||||||
|
--text-muted: #9a8a8a;
|
||||||
|
--text-dim: #806e6e;
|
||||||
|
--accent: #8c5a5a;
|
||||||
|
--accent-light: #d47a7a;
|
||||||
|
--accent-border: #604a4a;
|
||||||
|
--tint-color: rgba(160, 60, 60, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: var(--bg-body);
|
||||||
|
color: #d0d0d0;
|
||||||
|
line-height: 1.6;
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: background-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Header ─── */
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--bg-header);
|
||||||
|
border-bottom: 1px solid #1f2428;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 0 2rem;
|
||||||
|
transition:
|
||||||
|
background 0.4s ease,
|
||||||
|
backdrop-filter 0.4s ease,
|
||||||
|
border-color 0.4s ease,
|
||||||
|
box-shadow 0.4s ease,
|
||||||
|
background-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.scrolled {
|
||||||
|
background: rgba(20, 23, 26, 0.65);
|
||||||
|
backdrop-filter: blur(20px) saturate(1.4);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(1.4);
|
||||||
|
border-bottom-color: rgba(255, 255, 255, 0.06);
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode header.scrolled {
|
||||||
|
background: rgba(26, 20, 20, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 60px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
color: #aaa;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Layout ─── */
|
||||||
|
main {
|
||||||
|
padding-top: 60px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Backdrop Image Sections ─── */
|
||||||
|
.bg-image-section {
|
||||||
|
padding: 10rem 2rem;
|
||||||
|
min-height: 600px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-image-section::before,
|
||||||
|
.bg-image-section::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-image-section::before {
|
||||||
|
top: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
var(--bg-header) 0%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-image-section::after {
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(to top, var(--bg-header) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hero::before {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideshow-layer {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
transition: opacity 1.2s ease-in-out;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.destructive-tint {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse at center,
|
||||||
|
var(--tint-color),
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 1.2s ease;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode .destructive-tint {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Solid Sections ─── */
|
||||||
|
section:not(.bg-image-section) {
|
||||||
|
background: var(--bg-solid);
|
||||||
|
position: relative;
|
||||||
|
padding: 6rem 2rem;
|
||||||
|
transition: background 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Hero ─── */
|
||||||
|
#hero {
|
||||||
|
min-height: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hero h2 {
|
||||||
|
font-size: 3.2rem;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
text-shadow: 0 3px 15px rgba(0, 0, 0, 0.9);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hero p {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9);
|
||||||
|
color: #e0e0e0;
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Concept ─── */
|
||||||
|
#concept h2 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto 3rem;
|
||||||
|
background: #0a0c0e;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
transition: border-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
transform: translate(-50%, -50%) scale(1.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn::after {
|
||||||
|
content: "";
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 30px solid #fff;
|
||||||
|
border-top: 18px solid transparent;
|
||||||
|
border-bottom: 18px solid transparent;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.playing .play-btn {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.playing:hover .play-btn {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.playing .play-btn::after {
|
||||||
|
border: none;
|
||||||
|
width: 30px;
|
||||||
|
height: 36px;
|
||||||
|
margin-left: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
#fff 35%,
|
||||||
|
transparent 35%,
|
||||||
|
transparent 65%,
|
||||||
|
#fff 65%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.concept-text {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.9;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concept-text h3 {
|
||||||
|
color: var(--accent-light);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-left: 4px solid var(--accent);
|
||||||
|
padding-left: 1rem;
|
||||||
|
transition: var(--transition-theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
.concept-text ol {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concept-text li {
|
||||||
|
margin-bottom: 0.9rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Socials ─── */
|
||||||
|
#socials h2 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-shadow: 0 3px 15px rgba(0, 0, 0, 0.9);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
width: 260px;
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
padding: 1.2rem 2rem;
|
||||||
|
background: rgba(10, 12, 14, 0.75);
|
||||||
|
border: 1px solid #2f333a;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-btn:hover {
|
||||||
|
background: rgba(20, 23, 26, 0.9);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
border-color: var(--accent-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Team ─── */
|
||||||
|
#team h2 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2.5rem 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
transition:
|
||||||
|
transform 0.3s,
|
||||||
|
border-color 0.8s ease,
|
||||||
|
background 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
border-color: var(--border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
background: #2a2e33;
|
||||||
|
border: 3px solid #2a2e33;
|
||||||
|
transition: border-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card:hover img {
|
||||||
|
border-color: var(--accent-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card .name {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card .role {
|
||||||
|
color: var(--text-muted);
|
||||||
|
transition: color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Posts ─── */
|
||||||
|
#posts h2 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-shadow: 0 3px 15px rgba(0, 0, 0, 0.9);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.posts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card {
|
||||||
|
background: var(--bg-postcard);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2.2rem;
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
border-color: var(--border-hover);
|
||||||
|
background: rgba(24, 27, 30, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode .post-card:hover {
|
||||||
|
background: rgba(30, 24, 24, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card .post-date {
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card .post-title {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card .post-desc {
|
||||||
|
color: #aaa;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Post Page ─── */
|
||||||
|
#post-hero {
|
||||||
|
min-height: 450px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#post-hero h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
text-shadow: 0 3px 20px rgba(0, 0, 0, 0.95);
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#post-content {
|
||||||
|
padding: 5rem 2rem;
|
||||||
|
min-height: calc(100vh - 450px - 60px - 50px);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Markdown Viewer Theme ─── */
|
||||||
|
.post-content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--bg-content);
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 3.5rem;
|
||||||
|
line-height: 1.9;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #ccc;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
transition:
|
||||||
|
background 0.8s ease,
|
||||||
|
border-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content h1 {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 2.4rem;
|
||||||
|
border-bottom: 3px solid var(--accent);
|
||||||
|
padding-bottom: 0.8rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
margin-top: 0;
|
||||||
|
transition: var(--transition-theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content h2 {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-top: 3rem;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
border-left: 5px solid var(--accent);
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
transition: var(--transition-theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content h3 {
|
||||||
|
color: var(--accent-light);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
transition: var(--transition-theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content p {
|
||||||
|
margin-bottom: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content a {
|
||||||
|
color: #66aaff;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dotted #66aaff;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content a:hover {
|
||||||
|
color: #99ccff;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content code {
|
||||||
|
background: #0f1214;
|
||||||
|
color: #d48a8a;
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: "Fira Code", "Courier New", monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
border: 1px solid #1f2428;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content pre {
|
||||||
|
background: #0f1214;
|
||||||
|
border: 1px solid #1f2428;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.8rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content pre code {
|
||||||
|
background: transparent;
|
||||||
|
color: #ddd;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content blockquote {
|
||||||
|
border-left: 4px solid var(--accent);
|
||||||
|
background: #1a1d20;
|
||||||
|
padding: 1.5rem 1.8rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: #bbb;
|
||||||
|
transition: var(--transition-theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode .post-content blockquote {
|
||||||
|
background: #201a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content ul,
|
||||||
|
.post-content ol {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content li {
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
margin: 2rem 0;
|
||||||
|
display: block;
|
||||||
|
transition: border-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 2px solid var(--border-base);
|
||||||
|
margin: 3rem 0;
|
||||||
|
transition: border-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content th,
|
||||||
|
.post-content td {
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
padding: 0.8rem;
|
||||||
|
text-align: left;
|
||||||
|
transition: border-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content th {
|
||||||
|
background: #1c1e21;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode .post-content th {
|
||||||
|
background: #211c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content tr:nth-child(even) {
|
||||||
|
background: #181a1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode .post-content tr:nth-child(even) {
|
||||||
|
background: #1d1818;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Footer ─── */
|
||||||
|
footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--bg-footer);
|
||||||
|
border-top: 1px solid #1f2428;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
color: #5a6b72;
|
||||||
|
z-index: 1000;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition:
|
||||||
|
transform 0.4s ease,
|
||||||
|
background 0.4s ease,
|
||||||
|
backdrop-filter 0.4s ease,
|
||||||
|
border-color 0.4s ease,
|
||||||
|
box-shadow 0.4s ease,
|
||||||
|
background-color 0.8s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer.visible {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer.glass {
|
||||||
|
background: rgba(20, 23, 26, 0.65);
|
||||||
|
backdrop-filter: blur(20px) saturate(1.4);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(1.4);
|
||||||
|
border-top-color: rgba(255, 255, 255, 0.06);
|
||||||
|
box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.destructive-mode footer.glass {
|
||||||
|
background: rgba(26, 20, 20, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer.solid {
|
||||||
|
background: var(--bg-footer);
|
||||||
|
backdrop-filter: none;
|
||||||
|
border-top-color: #1f2428;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #7a8e9a;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Scrollbar ─── */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #0a0c0e;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #2a2e33;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #3a424a;
|
||||||
|
}
|
||||||