[Add] Team section & teams post
This commit is contained in:
parent
723e3762ec
commit
c2d03efc57
15 changed files with 1063 additions and 53 deletions
369
file_graph_generator.py
Normal file
369
file_graph_generator.py
Normal file
|
|
@ -0,0 +1,369 @@
|
||||||
|
"""
|
||||||
|
File Graph Generator
|
||||||
|
|
||||||
|
Creates a tree-like visualization of files with their content,
|
||||||
|
respecting .gitignore patterns with proper tree rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import fnmatch
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterator, List, Optional
|
||||||
|
|
||||||
|
# ───────────────────────────── Logging Setup ──────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(level: int = logging.INFO) -> None:
|
||||||
|
"""Configure structured logging to stderr (stdout is reserved for output)."""
|
||||||
|
logging.basicConfig(
|
||||||
|
stream=sys.stderr,
|
||||||
|
level=level,
|
||||||
|
format="%(asctime)s | %(levelname)-8s | %(message)s",
|
||||||
|
datefmt="%H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger("file_graph")
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────── Gitignore Engine ─────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GitignoreRule:
|
||||||
|
"""A single parsed gitignore rule."""
|
||||||
|
|
||||||
|
pattern: str
|
||||||
|
is_negation: bool = False
|
||||||
|
is_dir_only: bool = False
|
||||||
|
is_anchored: bool = False
|
||||||
|
is_relative: bool = False # contains / (not at start)
|
||||||
|
|
||||||
|
|
||||||
|
class GitignoreParser:
|
||||||
|
"""Parse a .gitignore file into ordered rules."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse(path: Path) -> List[GitignoreRule]:
|
||||||
|
rules = []
|
||||||
|
if not path.exists():
|
||||||
|
return rules
|
||||||
|
try:
|
||||||
|
with path.open("r", encoding="utf-8", errors="ignore") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.rstrip("\n\r")
|
||||||
|
# Skip empty lines and comments
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_negation = line.startswith("!")
|
||||||
|
if is_negation:
|
||||||
|
line = line[1:]
|
||||||
|
|
||||||
|
# Handle escaped ! and #
|
||||||
|
if line.startswith("\\!") or line.startswith("\\#"):
|
||||||
|
line = line[1:]
|
||||||
|
|
||||||
|
line = line.rstrip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_dir_only = line.endswith("/")
|
||||||
|
if is_dir_only:
|
||||||
|
line = line[:-1]
|
||||||
|
|
||||||
|
is_anchored = line.startswith("/")
|
||||||
|
if is_anchored:
|
||||||
|
line = line[1:]
|
||||||
|
|
||||||
|
is_relative = "/" in line
|
||||||
|
|
||||||
|
rules.append(
|
||||||
|
GitignoreRule(
|
||||||
|
pattern=line,
|
||||||
|
is_negation=is_negation,
|
||||||
|
is_dir_only=is_dir_only,
|
||||||
|
is_anchored=is_anchored,
|
||||||
|
is_relative=is_relative,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except OSError as e:
|
||||||
|
logger.warning(f"Could not read {path}: {e}")
|
||||||
|
return rules
|
||||||
|
|
||||||
|
|
||||||
|
class GitignoreChecker:
|
||||||
|
"""Check if paths should be ignored, respecting per-directory .gitignore files."""
|
||||||
|
|
||||||
|
def __init__(self, root: Path):
|
||||||
|
self.root = root.resolve()
|
||||||
|
self._cache: dict[Path, List[GitignoreRule]] = {}
|
||||||
|
self._skip_dirs = {
|
||||||
|
".git",
|
||||||
|
"__pycache__",
|
||||||
|
".pytest_cache",
|
||||||
|
".mypy_cache",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
"venv",
|
||||||
|
}
|
||||||
|
self._skip_files = {
|
||||||
|
"file_graph_generator.py",
|
||||||
|
"file_graph_generator_v2.py",
|
||||||
|
"file_graph_generator_v3.py",
|
||||||
|
"file_tree.txt",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _load(self, directory: Path) -> List[GitignoreRule]:
|
||||||
|
"""Load rules for a directory (cached)."""
|
||||||
|
directory = directory.resolve()
|
||||||
|
if directory not in self._cache:
|
||||||
|
gitignore_path = directory / ".gitignore"
|
||||||
|
if gitignore_path.exists():
|
||||||
|
logger.debug(f"Parsing gitignore: {gitignore_path}")
|
||||||
|
self._cache[directory] = GitignoreParser.parse(gitignore_path)
|
||||||
|
return self._cache[directory]
|
||||||
|
|
||||||
|
def _match_rule(
|
||||||
|
self, rule: GitignoreRule, name: str, rel_path: str, is_dir: bool
|
||||||
|
) -> bool:
|
||||||
|
"""Check if a single rule matches."""
|
||||||
|
if rule.is_dir_only and not is_dir:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Anchored pattern: match only at .gitignore level
|
||||||
|
if rule.is_anchored:
|
||||||
|
return fnmatch.fnmatch(rel_path, rule.pattern) or fnmatch.fnmatch(
|
||||||
|
rel_path, rule.pattern + "/"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Relative path pattern (contains /): match against rel_path
|
||||||
|
if rule.is_relative:
|
||||||
|
return fnmatch.fnmatch(rel_path, rule.pattern) or fnmatch.fnmatch(
|
||||||
|
rel_path, rule.pattern + "/"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Basename pattern: match name at any level
|
||||||
|
return fnmatch.fnmatch(name, rule.pattern)
|
||||||
|
|
||||||
|
def is_ignored(self, path: Path, is_dir: bool) -> bool:
|
||||||
|
"""Check if path should be ignored, walking up to root."""
|
||||||
|
name = path.name
|
||||||
|
|
||||||
|
# Fast-path: always skip common VCS/build dirs and script/output files
|
||||||
|
if is_dir and name in self._skip_dirs:
|
||||||
|
logger.debug(f"Skipping common dir: {name}")
|
||||||
|
return True
|
||||||
|
if not is_dir and name in self._skip_files:
|
||||||
|
logger.debug(f"Skipping script/output file: {name}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Walk from current directory up to root
|
||||||
|
current = path.parent.resolve()
|
||||||
|
while True:
|
||||||
|
rules = self._load(current)
|
||||||
|
if rules:
|
||||||
|
rel_path = path.relative_to(current).as_posix()
|
||||||
|
ignored = False
|
||||||
|
|
||||||
|
for rule in rules:
|
||||||
|
if self._match_rule(rule, name, rel_path, is_dir):
|
||||||
|
if rule.is_negation:
|
||||||
|
ignored = False
|
||||||
|
else:
|
||||||
|
ignored = True
|
||||||
|
|
||||||
|
if ignored:
|
||||||
|
logger.debug(f"Ignored by gitignore: {path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if current == self.root:
|
||||||
|
break
|
||||||
|
current = current.parent
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────── File Reading ────────────────────────────────
|
||||||
|
|
||||||
|
MAX_CONTENT_BYTES = 50 * 100 # ~50 lines avg
|
||||||
|
|
||||||
|
|
||||||
|
def is_binary(path: Path) -> bool:
|
||||||
|
"""Fast binary detection using first 8KB chunk."""
|
||||||
|
try:
|
||||||
|
with path.open("rb") as f:
|
||||||
|
chunk = f.read(8192)
|
||||||
|
if not chunk:
|
||||||
|
return False
|
||||||
|
if b"\x00" in chunk:
|
||||||
|
return True
|
||||||
|
if chunk[:2] in (b"\xff\xfe", b"\xfe\xff", b"\x1f\x8b"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except OSError:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def read_content(path: Path, max_lines: int = 50) -> Iterator[str]:
|
||||||
|
"""Yield content lines with minimal memory footprint."""
|
||||||
|
if is_binary(path):
|
||||||
|
yield "[binary file — content skipped]"
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with path.open("r", encoding="utf-8", errors="replace") as f:
|
||||||
|
for i, line in enumerate(f):
|
||||||
|
if i >= max_lines:
|
||||||
|
remaining = sum(1 for _ in f)
|
||||||
|
yield f"... ({remaining} more lines)"
|
||||||
|
return
|
||||||
|
yield line.rstrip("\n")
|
||||||
|
except OSError as e:
|
||||||
|
yield f"[Error reading file: {e}]"
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────── Tree Generator ───────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def get_visible_children(path: Path, checker: GitignoreChecker) -> List[os.DirEntry]:
|
||||||
|
"""Get sorted, filtered children of a directory."""
|
||||||
|
try:
|
||||||
|
entries = list(os.scandir(path))
|
||||||
|
except PermissionError:
|
||||||
|
logger.warning(f"Permission denied: {path}")
|
||||||
|
return []
|
||||||
|
except OSError as e:
|
||||||
|
logger.error(f"Error scanning {path}: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
visible = []
|
||||||
|
for entry in entries:
|
||||||
|
# Skip .gitignore files entirely
|
||||||
|
if entry.name == ".gitignore":
|
||||||
|
continue
|
||||||
|
|
||||||
|
child_path = Path(entry.path)
|
||||||
|
is_dir = entry.is_dir()
|
||||||
|
|
||||||
|
if not checker.is_ignored(child_path, is_dir):
|
||||||
|
visible.append(entry)
|
||||||
|
|
||||||
|
# Sort: dirs first, then files, both alphabetically (case-insensitive)
|
||||||
|
visible.sort(key=lambda e: (not e.is_dir(), e.name.lower()))
|
||||||
|
return visible
|
||||||
|
|
||||||
|
|
||||||
|
def build_tree_lines(
|
||||||
|
path: Path,
|
||||||
|
checker: GitignoreChecker,
|
||||||
|
prefix: str = "",
|
||||||
|
max_lines: int = 50,
|
||||||
|
max_depth: Optional[int] = None,
|
||||||
|
current_depth: int = 0,
|
||||||
|
) -> Iterator[str]:
|
||||||
|
"""Recursively yield tree lines. Correctly handles is_last filtering."""
|
||||||
|
|
||||||
|
if path.is_dir():
|
||||||
|
if max_depth is not None and current_depth >= max_depth:
|
||||||
|
return
|
||||||
|
|
||||||
|
children = get_visible_children(path, checker)
|
||||||
|
count = len(children)
|
||||||
|
|
||||||
|
for i, entry in enumerate(children):
|
||||||
|
is_last = i == count - 1
|
||||||
|
connector = "└── " if is_last else "├── "
|
||||||
|
name = entry.name
|
||||||
|
|
||||||
|
if entry.is_dir():
|
||||||
|
yield prefix + connector + name + "/"
|
||||||
|
extension = " " if is_last else "│ "
|
||||||
|
yield from build_tree_lines(
|
||||||
|
Path(entry.path),
|
||||||
|
checker,
|
||||||
|
prefix + extension,
|
||||||
|
max_lines,
|
||||||
|
max_depth,
|
||||||
|
current_depth + 1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
yield prefix + connector + name
|
||||||
|
content_prefix = prefix + (" " if is_last else "│ ") + " "
|
||||||
|
for content_line in read_content(Path(entry.path), max_lines):
|
||||||
|
yield content_prefix + content_line
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────── Main ─────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="File tree generator with gitignore support",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Examples:
|
||||||
|
%(prog)s # Scan current directory
|
||||||
|
%(prog)s ./src -o tree.txt # Scan ./src, write to tree.txt
|
||||||
|
%(prog)s --max-depth 3 # Limit depth to 3 levels
|
||||||
|
%(prog)s --max-lines 20 # Show only 20 lines per file
|
||||||
|
%(prog)s -v # Verbose logging
|
||||||
|
%(prog)s --no-gitignore # Ignore all .gitignore files
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"directory", nargs="?", default=".", help="Root directory to scan"
|
||||||
|
)
|
||||||
|
parser.add_argument("-o", "--output", default="file_tree.txt", help="Output file")
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-depth", type=int, default=None, help="Maximum directory depth"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-lines", type=int, default=50, help="Max lines to show per file"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-gitignore", action="store_true", help="Disable .gitignore processing"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose", action="store_true", help="Enable debug logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
setup_logging(logging.DEBUG if args.verbose else logging.INFO)
|
||||||
|
|
||||||
|
root = Path(args.directory).resolve()
|
||||||
|
if not root.is_dir():
|
||||||
|
logger.error(f"Not a directory: {root}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
checker = GitignoreChecker(root)
|
||||||
|
if args.no_gitignore:
|
||||||
|
# Disable by making all paths return empty rules
|
||||||
|
checker._cache = {d: [] for d in checker._cache}
|
||||||
|
# Also override _load to always return empty
|
||||||
|
checker._load = lambda d: []
|
||||||
|
logger.info("Gitignore processing disabled")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(args.output, "w", encoding="utf-8") as f:
|
||||||
|
# Print root
|
||||||
|
f.write(root.name + "/\n")
|
||||||
|
|
||||||
|
# Print tree
|
||||||
|
for line in build_tree_lines(
|
||||||
|
root, checker, max_lines=args.max_lines, max_depth=args.max_depth
|
||||||
|
):
|
||||||
|
f.write(line + "\n")
|
||||||
|
|
||||||
|
logger.info(f"Output written to: {args.output}")
|
||||||
|
except OSError as e:
|
||||||
|
logger.error(f"Failed to write output: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
173
file_tree.txt
Normal file
173
file_tree.txt
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
Anarchyphase-Website/
|
||||||
|
├── server/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ └── main.rs
|
||||||
|
│ │ use axum::{
|
||||||
|
│ │ body::Body,
|
||||||
|
│ │ extract::{Path, State},
|
||||||
|
│ │ http::{header, StatusCode, Uri},
|
||||||
|
│ │ response::{IntoResponse, Response},
|
||||||
|
│ │ ... (194 more lines)
|
||||||
|
│ ├── Cargo.lock
|
||||||
|
│ │ # This file is automatically @generated by Cargo.
|
||||||
|
│ │ # It is not intended for manual editing.
|
||||||
|
│ │ version = 4
|
||||||
|
│ │
|
||||||
|
│ │ [[package]]
|
||||||
|
│ │ ... (1114 more lines)
|
||||||
|
│ └── Cargo.toml
|
||||||
|
│ [package]
|
||||||
|
│ name = "server"
|
||||||
|
│ version = "0.1.0"
|
||||||
|
│ edition = "2021"
|
||||||
|
│
|
||||||
|
│ ... (11 more lines)
|
||||||
|
├── website/
|
||||||
|
│ ├── api/
|
||||||
|
│ │ ├── backdrops.json
|
||||||
|
│ │ │ [
|
||||||
|
│ │ │ {
|
||||||
|
│ │ │ "name": "horse_fog",
|
||||||
|
│ │ │ "destructive": false
|
||||||
|
│ │ │ },
|
||||||
|
│ │ │ ... (26 more lines)
|
||||||
|
│ │ ├── posts.json
|
||||||
|
│ │ │ [
|
||||||
|
│ │ │ {
|
||||||
|
│ │ │ "date": "2026-04-21, by AlexEmmet",
|
||||||
|
│ │ │
|
||||||
|
│ │ │ "head_img": "village_sunset.png",
|
||||||
|
│ │ │ ... (32 more lines)
|
||||||
|
│ │ ├── staff.json
|
||||||
|
│ │ │ [
|
||||||
|
│ │ │ {
|
||||||
|
│ │ │ "icon": "morice.png",
|
||||||
|
│ │ │ "name": "MoriceMC",
|
||||||
|
│ │ │ "priority": 0,
|
||||||
|
│ │ │ ... (48 more lines)
|
||||||
|
│ │ └── teams.json
|
||||||
|
│ │ [
|
||||||
|
│ │ {
|
||||||
|
│ │ "title": "Anarchyphase Public Infrastructure",
|
||||||
|
│ │ "acronym": "API",
|
||||||
|
│ │ "leader": "NiniVX",
|
||||||
|
│ │ ... (5 more lines)
|
||||||
|
│ ├── assets/
|
||||||
|
│ │ ├── ahnyokatzin.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── alexemmet.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── anarchyphase.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── api.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── banner.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── burrata.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── campsite.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── harrpy.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── harrpyrose.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── horse_fog.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── horse_rainbow.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── icon.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── morice.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── ninivx.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── outpost.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── pitrex.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── tower_large.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── tower_small.png
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ ├── trailer.mp4
|
||||||
|
│ │ │ [binary file — content skipped]
|
||||||
|
│ │ └── village_sunset.png
|
||||||
|
│ │ [binary file — content skipped]
|
||||||
|
│ ├── directs/
|
||||||
|
│ │ └── api
|
||||||
|
│ │ https://dsc.gg/api
|
||||||
|
│ ├── posts/
|
||||||
|
│ │ ├── AlexsOpinion.md
|
||||||
|
│ │ │ # AnarchyPhase Balance Changes
|
||||||
|
│ │ │ An advice & opinion
|
||||||
|
│ │ │
|
||||||
|
│ │ │ ## Proposed by AlexEmmet
|
||||||
|
│ │ │ ### Edited by xLagging & reviewed mewjo_
|
||||||
|
│ │ │ ... (201 more lines)
|
||||||
|
│ │ ├── Home.md
|
||||||
|
│ │ │ # The /Home command
|
||||||
|
│ │ │
|
||||||
|
│ │ │ Hello Yall, Alex here,
|
||||||
|
│ │ │
|
||||||
|
│ │ │ I made this website so a little selfinsert won't hurt.
|
||||||
|
│ │ │ ... (6 more lines)
|
||||||
|
│ │ ├── JoinAnarchyphase.md
|
||||||
|
│ │ │ # Join Anarchyphase
|
||||||
|
│ │ │
|
||||||
|
│ │ │ Join the Anarchyphase community today!
|
||||||
|
│ │ └── ThisWebsite.md
|
||||||
|
│ │ # This website
|
||||||
|
│ │
|
||||||
|
│ │ Hello Yall, Alex here,
|
||||||
|
│ │
|
||||||
|
│ │ I made this website so a little selfinsert won't hurt.
|
||||||
|
│ │ ... (7 more lines)
|
||||||
|
│ ├── app.js
|
||||||
|
│ │ // 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"));
|
||||||
|
│ │ ... (304 more lines)
|
||||||
|
│ ├── index.html
|
||||||
|
│ │ <!doctype html>
|
||||||
|
│ │ <html lang="en">
|
||||||
|
│ │ <head>
|
||||||
|
│ │ <meta charset="UTF-8" />
|
||||||
|
│ │ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
│ │ ... (163 more lines)
|
||||||
|
│ ├── md-render.js
|
||||||
|
│ │ // Basic Markdown renderer
|
||||||
|
│ │ function renderMarkdown(md) {
|
||||||
|
│ │ let html = md
|
||||||
|
│ │ .replace(/&/g, "&")
|
||||||
|
│ │ .replace(/</g, "<")
|
||||||
|
│ │ ... (109 more lines)
|
||||||
|
│ ├── post.html
|
||||||
|
│ │ <!doctype html>
|
||||||
|
│ │ <html lang="en">
|
||||||
|
│ │ <head>
|
||||||
|
│ │ <meta charset="UTF-8" />
|
||||||
|
│ │ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
│ │ ... (39 more lines)
|
||||||
|
│ └── style.css
|
||||||
|
│ * {
|
||||||
|
│ margin: 0;
|
||||||
|
│ padding: 0;
|
||||||
|
│ box-sizing: border-box;
|
||||||
|
│ }
|
||||||
|
│ ... (987 more lines)
|
||||||
|
├── flake.lock
|
||||||
|
│ {
|
||||||
|
│ "nodes": {
|
||||||
|
│ "flake-utils": {
|
||||||
|
│ "inputs": {
|
||||||
|
│ "systems": "systems"
|
||||||
|
│ ... (55 more lines)
|
||||||
|
└── flake.nix
|
||||||
|
{
|
||||||
|
description = "Rust Development Flake";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
... (49 more lines)
|
||||||
|
|
@ -18,6 +18,46 @@ struct AppState {
|
||||||
website_root: PathBuf,
|
website_root: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn git_pull(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
let output = tokio::process::Command::new("git")
|
||||||
|
.arg("pull")
|
||||||
|
.current_dir(&state.website_root)
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(o) => {
|
||||||
|
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||||
|
let stderr = String::from_utf8_lossy(&o.stderr);
|
||||||
|
let status = if o.status.success() { "ok" } else { "error" };
|
||||||
|
|
||||||
|
info!("git pull stdout: {}", stdout.trim());
|
||||||
|
if !stderr.is_empty() {
|
||||||
|
warn!("git pull stderr: {}", stderr.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
if o.status.success() {
|
||||||
|
StatusCode::OK
|
||||||
|
} else {
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
},
|
||||||
|
format!(
|
||||||
|
"status: {}\n\nstdout:\n{}\nstderr:\n{}",
|
||||||
|
status, stdout, stderr
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("failed to spawn git pull: {}", e);
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("failed to run git pull: {}", e),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
@ -31,9 +71,12 @@ async fn main() {
|
||||||
.route("/", get(serve_index))
|
.route("/", get(serve_index))
|
||||||
.route("/post/{file}", get(serve_post_page))
|
.route("/post/{file}", get(serve_post_page))
|
||||||
.route("/api/backdrops.json", get(serve_backdrops))
|
.route("/api/backdrops.json", get(serve_backdrops))
|
||||||
.route("/api/team.json", get(serve_team))
|
.route("/api/staff.json", get(serve_staff))
|
||||||
|
.route("/api/teams.json", get(serve_teams))
|
||||||
.route("/api/posts.json", get(serve_posts))
|
.route("/api/posts.json", get(serve_posts))
|
||||||
.route("/api/post/{file}", get(serve_post_md))
|
.route("/api/post/{file}", get(serve_post_md))
|
||||||
|
.route("/api/update", get(git_pull))
|
||||||
|
.route("/directs/{name}", get(serve_direct))
|
||||||
.nest_service("/assets", ServeDir::new(website_root.join("assets")))
|
.nest_service("/assets", ServeDir::new(website_root.join("assets")))
|
||||||
.nest_service("/posts", ServeDir::new(website_root.join("posts")))
|
.nest_service("/posts", ServeDir::new(website_root.join("posts")))
|
||||||
.fallback(get(serve_static))
|
.fallback(get(serve_static))
|
||||||
|
|
@ -90,8 +133,12 @@ async fn serve_backdrops(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
serve_file(state.website_root.join("api/backdrops.json")).await
|
serve_file(state.website_root.join("api/backdrops.json")).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_team(State(state): State<AppState>) -> impl IntoResponse {
|
async fn serve_staff(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
serve_file(state.website_root.join("api/team.json")).await
|
serve_file(state.website_root.join("api/staff.json")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_teams(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
serve_file(state.website_root.join("api/teams.json")).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_posts(State(state): State<AppState>) -> impl IntoResponse {
|
async fn serve_posts(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
|
|
@ -152,3 +199,40 @@ async fn serve_file(path: PathBuf) -> Response {
|
||||||
.body(Body::from(content))
|
.body(Body::from(content))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use axum::http::HeaderValue;
|
||||||
|
|
||||||
|
async fn serve_direct(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(name): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
if name.contains('/') || name.contains('\\') || name.starts_with('.') {
|
||||||
|
return StatusCode::BAD_REQUEST.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = state.website_root.join("directs").join(&name);
|
||||||
|
|
||||||
|
if !path.exists() || !path.is_file() {
|
||||||
|
return StatusCode::NOT_FOUND.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = match fs::read_to_string(&path).await {
|
||||||
|
Ok(c) => c.trim().to_string(),
|
||||||
|
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if content.is_empty() {
|
||||||
|
return StatusCode::NO_CONTENT.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !content.starts_with("http://") && !content.starts_with("https://") {
|
||||||
|
warn!("invalid redirect target in {:?}: {}", path, content);
|
||||||
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::FOUND) // 302 redirect
|
||||||
|
.header(header::LOCATION, HeaderValue::from_str(&content).unwrap())
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"date": "2026-04-21",
|
"date": "2026-04-22, by AlexEmmet",
|
||||||
|
|
||||||
|
"head_img": "outpost.png",
|
||||||
|
"file": "Teams.md",
|
||||||
|
|
||||||
|
"title": "Teams",
|
||||||
|
"description": "Get to know the teams!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-04-21, by AlexEmmet",
|
||||||
|
|
||||||
"head_img": "village_sunset.png",
|
"head_img": "village_sunset.png",
|
||||||
"file": "JoinAnarchyphase.md",
|
"file": "JoinAnarchyphase.md",
|
||||||
|
|
@ -9,7 +18,7 @@
|
||||||
"description": "Join the Anarchyphase community today!"
|
"description": "Join the Anarchyphase community today!"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-04-21",
|
"date": "2026-04-21, by AlexEmmet",
|
||||||
|
|
||||||
"head_img": "horse_rainbow.png",
|
"head_img": "horse_rainbow.png",
|
||||||
"file": "Home.md",
|
"file": "Home.md",
|
||||||
|
|
@ -18,7 +27,7 @@
|
||||||
"description": "Learn about the /Home command."
|
"description": "Learn about the /Home command."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-04-21",
|
"date": "2026-04-21, by AlexEmmet",
|
||||||
|
|
||||||
"head_img": "horse_rainbow.png",
|
"head_img": "horse_rainbow.png",
|
||||||
"file": "ThisWebsite.md",
|
"file": "ThisWebsite.md",
|
||||||
|
|
@ -27,7 +36,7 @@
|
||||||
"description": "Learn about this website."
|
"description": "Learn about this website."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-04-21",
|
"date": "2026-04-21, by AlexEmmet",
|
||||||
|
|
||||||
"head_img": "horse_rainbow.png",
|
"head_img": "horse_rainbow.png",
|
||||||
"file": "AlexsOpinion.md",
|
"file": "AlexsOpinion.md",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"icon": "ninivx.png",
|
"icon": "ninivx.png",
|
||||||
"name": "NiniVX",
|
"name": "NiniVX",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
"role": "Administrator"
|
"role": "Community Manager"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon": "burrata.png",
|
"icon": "burrata.png",
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"role": "Administrator"
|
"role": "Administrator"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"icon": "pitrex.png",
|
||||||
"name": "Pitrex",
|
"name": "Pitrex",
|
||||||
"priority": 2,
|
"priority": 2,
|
||||||
"role": "Moderator & Developer"
|
"role": "Moderator & Developer"
|
||||||
44
website/api/teams.json
Normal file
44
website/api/teams.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Anarchyphase Public Infrastructure",
|
||||||
|
"acronym": "API",
|
||||||
|
"leader": "NiniVX",
|
||||||
|
"vips": "mewjo_",
|
||||||
|
"description": "Building infrastructure across the server.",
|
||||||
|
"image": "api.png",
|
||||||
|
"discord": "directs/api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Commune",
|
||||||
|
"acronym": "",
|
||||||
|
"leader": "StormGamer0512",
|
||||||
|
"vips": "",
|
||||||
|
"description": "Spreading Communism around the world",
|
||||||
|
"image": "campsite.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Orbit",
|
||||||
|
"acronym": "",
|
||||||
|
"leader": "Yippee",
|
||||||
|
"vips": "Skely, xLeggings, Luxi",
|
||||||
|
"description": "Me when I Orb it",
|
||||||
|
"image": "village_sunset.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Chunky Town",
|
||||||
|
"acronym": "CT",
|
||||||
|
"leader": "MoriceMC",
|
||||||
|
"vips": "Yeah he's the owner",
|
||||||
|
"description": "We are number one",
|
||||||
|
"image": "icon.png",
|
||||||
|
"discord": "directs/ct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ardubia",
|
||||||
|
"acronym": "",
|
||||||
|
"leader": "Liwitikiwi",
|
||||||
|
"vips": "",
|
||||||
|
"description": "Build borders to keep out foreign aliens",
|
||||||
|
"image": "outpost.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -124,24 +124,24 @@ async function loadBackdrops() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Load Team ───
|
// ─── Load Staff (in Socials section) ───
|
||||||
async function loadTeam() {
|
async function loadStaff() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/team.json");
|
const response = await fetch("/api/staff.json");
|
||||||
const team = await response.json();
|
const staff = await response.json();
|
||||||
team.sort((a, b) => a.priority - b.priority);
|
staff.sort((a, b) => a.priority - b.priority);
|
||||||
|
|
||||||
const container = document.getElementById("team-grid");
|
const container = document.getElementById("staff-grid");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
container.innerHTML = team
|
container.innerHTML = staff
|
||||||
.map(
|
.map(
|
||||||
(member) => `
|
(member) => `
|
||||||
<div class="team-card">
|
<div class="team-card staff-card">
|
||||||
${
|
${
|
||||||
member.icon
|
member.icon
|
||||||
? `<img src="/assets/${member.icon}" alt="${member.name}" onerror="this.style.display='none'">`
|
? `<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="staff-placeholder"></div>'
|
||||||
}
|
}
|
||||||
<div class="name">${member.name}</div>
|
<div class="name">${member.name}</div>
|
||||||
<div class="role">${member.role}</div>
|
<div class="role">${member.role}</div>
|
||||||
|
|
@ -150,10 +150,70 @@ async function loadTeam() {
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load team:", err);
|
console.error("Failed to load staff:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ─── Load Teams ───
|
||||||
|
async function loadTeams() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/teams.json");
|
||||||
|
const teams = await response.json();
|
||||||
|
|
||||||
|
const container = document.getElementById("teams-grid");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = teams
|
||||||
|
.map((team) => {
|
||||||
|
const acronym = team.acronym || "";
|
||||||
|
const vips = team.vips || "";
|
||||||
|
const hasDiscord = team.discord && team.discord.trim() !== "";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="team-card-large">
|
||||||
|
<div class="team-card-image" style="background-image: url('/assets/${team.image}')">
|
||||||
|
<div class="team-card-overlay"></div>
|
||||||
|
<div class="team-card-header">
|
||||||
|
<div class="team-name">${team.title}</div>
|
||||||
|
${acronym ? `<div class="team-acronym">${acronym}</div>` : ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="team-card-body">
|
||||||
|
<div class="fade-line"></div>
|
||||||
|
<div class="team-meta-row">
|
||||||
|
<span class="meta-label">${team.leader}</span>
|
||||||
|
</div>
|
||||||
|
${
|
||||||
|
vips
|
||||||
|
? `
|
||||||
|
<div class="team-meta-row">
|
||||||
|
<span class="meta-value vips">${vips}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
<div class="fade-line"></div>
|
||||||
|
<p class="team-description">${team.description}</p>
|
||||||
|
${
|
||||||
|
hasDiscord
|
||||||
|
? `
|
||||||
|
<a href="${team.discord}" target="_blank" rel="noopener" class="team-discord-btn">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" class="discord-mini">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load teams:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
// ─── Load Posts ───
|
// ─── Load Posts ───
|
||||||
async function loadPosts() {
|
async function loadPosts() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -242,7 +302,8 @@ function initScrollEffects() {
|
||||||
// ─── Initialize ───
|
// ─── Initialize ───
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
loadBackdrops();
|
loadBackdrops();
|
||||||
loadTeam();
|
loadStaff();
|
||||||
|
loadTeams();
|
||||||
loadPosts();
|
loadPosts();
|
||||||
initVideo();
|
initVideo();
|
||||||
initScrollEffects();
|
initScrollEffects();
|
||||||
|
|
|
||||||
BIN
website/assets/api.png
Normal file
BIN
website/assets/api.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.8 KiB |
BIN
website/assets/pitrex.png
Normal file
BIN
website/assets/pitrex.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
1
website/directs/api
Normal file
1
website/directs/api
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
https://discord.gg/3NJ6ZjWTJR
|
||||||
1
website/directs/ct
Normal file
1
website/directs/ct
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
https://discord.gg/dKuPGE32bT
|
||||||
|
|
@ -95,47 +95,55 @@
|
||||||
<section id="socials" class="bg-image-section">
|
<section id="socials" class="bg-image-section">
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<h2>Socials</h2>
|
<h2>Socials</h2>
|
||||||
<a
|
|
||||||
href="https://www.youtube.com/@MoriceMC"
|
<div class="social-links">
|
||||||
target="_blank"
|
<a
|
||||||
rel="noopener"
|
href="https://www.youtube.com/@MoriceMC"
|
||||||
class="social-btn"
|
target="_blank"
|
||||||
>
|
rel="noopener"
|
||||||
<svg
|
class="social-btn"
|
||||||
class="social-icon"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
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"
|
class="social-icon"
|
||||||
/>
|
viewBox="0 0 24 24"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
Youtube
|
>
|
||||||
</a>
|
<path
|
||||||
<a
|
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"
|
||||||
href="https://discord.com/invite/Bq4GhxDwU5"
|
/>
|
||||||
target="_blank"
|
</svg>
|
||||||
rel="noopener"
|
Youtube
|
||||||
class="social-btn"
|
</a>
|
||||||
>
|
<a
|
||||||
<svg
|
href="https://discord.com/invite/Bq4GhxDwU5"
|
||||||
class="social-icon"
|
target="_blank"
|
||||||
viewBox="0 0 24 24"
|
rel="noopener"
|
||||||
fill="currentColor"
|
class="social-btn"
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
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"
|
class="social-icon"
|
||||||
/>
|
viewBox="0 0 24 24"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
Discord
|
>
|
||||||
</a>
|
<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>
|
||||||
|
|
||||||
|
<div class="staff-section">
|
||||||
|
<h2>Staff</h2>
|
||||||
|
<div id="staff-grid" class="team-grid"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="team">
|
<section id="team">
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<h2>Team</h2>
|
<h2>Teams</h2>
|
||||||
<div id="team-grid" class="team-grid"></div>
|
<div id="teams-grid" class="teams-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
44
website/posts/Teams.md
Normal file
44
website/posts/Teams.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Teams
|
||||||
|
|
||||||
|
Hello Yall, Alex here,
|
||||||
|
|
||||||
|
Today I added a teams section & filled it with the most important teams I found, at the moment all descriptions are jokes but I hope the leaders will contact me for actual details & icons.
|
||||||
|
|
||||||
|
Now all I know about the teams:
|
||||||
|
|
||||||
|
## Anarchyphase Public Infrastructure
|
||||||
|
I work with API's a lot, though barely in Minecraft, I took part in designing the doors & walls of the new API base currently under construction.
|
||||||
|
|
||||||
|
Their leader is NiniVX,
|
||||||
|
and i know mewjo_ personally, quite the nice guy to be aroung.
|
||||||
|
They are Building public infrastructure across the server (who could guess).
|
||||||
|
|
||||||
|
|
||||||
|
## Commune
|
||||||
|
Led by StormGamer0512
|
||||||
|
|
||||||
|
They are spreading Communism around the world.
|
||||||
|
Not really it's just the first thing that comes to mind.
|
||||||
|
I think they fight a lot.
|
||||||
|
|
||||||
|
## Orbit
|
||||||
|
Led by Yippee,
|
||||||
|
with Skely, xLeggings, Luxi,
|
||||||
|
|
||||||
|
They are extremley good at PVP.
|
||||||
|
|
||||||
|
## Chunky Town
|
||||||
|
Led by the owner of Anarchyphase MoriceMC
|
||||||
|
|
||||||
|
they are building a base inside a chunk.
|
||||||
|
|
||||||
|
|
||||||
|
## Ardubia
|
||||||
|
Led byLiwitikiwi
|
||||||
|
|
||||||
|
They are builing a safe region for builders & non pvpers.
|
||||||
|
I neither know where nor how.
|
||||||
|
|
||||||
|
|
||||||
|
With some fulfilling love
|
||||||
|
Alex
|
||||||
|
|
@ -775,3 +775,218 @@ footer a:hover {
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #3a424a;
|
background: #3a424a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Socials Section Layout ─── */
|
||||||
|
#socials .content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Social links come first */
|
||||||
|
.social-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Staff section below socials */
|
||||||
|
.staff-section {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Staff title */
|
||||||
|
.staff-section h2 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-shadow: 0 3px 15px rgba(0, 0, 0, 0.9);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-card {
|
||||||
|
padding: 1.8rem 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-placeholder {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #2a2e33;
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
border: 3px solid #2a2e33;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Team Cards ─── */
|
||||||
|
#team {
|
||||||
|
background: var(--bg-solid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#team h2 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teams-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card-large {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--border-base);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s;
|
||||||
|
min-height: 420px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card-large:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
border-color: var(--border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* background image */
|
||||||
|
.team-card-image {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom half */
|
||||||
|
.team-card-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 0%,
|
||||||
|
transparent 35%,
|
||||||
|
rgba(10, 12, 14, 0.85) 65%,
|
||||||
|
rgba(10, 12, 14, 0.95) 100%
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(0px);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header text centered */
|
||||||
|
.team-card-header {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem 1.5rem 1rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-name {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-acronym {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--accent-light);
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-card-body {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 0 1.5rem 1.5rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fade divider lines */
|
||||||
|
.fade-line {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 0%,
|
||||||
|
var(--border-hover) 30%,
|
||||||
|
var(--border-hover) 70%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
margin: 0.8rem 0;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-meta-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-value.vips {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-description {
|
||||||
|
color: #bbb;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0.5rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Discord button centered at bottom */
|
||||||
|
.team-discord-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
width: calc(100% - 1rem);
|
||||||
|
margin: 0.5rem auto 0;
|
||||||
|
padding: 0.7rem 1rem;
|
||||||
|
background: rgba(88, 101, 242, 0.2);
|
||||||
|
border: 1px solid rgba(88, 101, 242, 0.35);
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #aab2ff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-discord-btn:hover {
|
||||||
|
background: rgba(88, 101, 242, 0.35);
|
||||||
|
border-color: rgba(88, 101, 242, 0.55);
|
||||||
|
color: #c5cbff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.discord-mini {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Responsive ─── */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.teams-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue