commit 0746cc429605987883179c11042ce3c0ea4f8aec Author: zwitschi Date: Tue Sep 16 09:25:23 2025 +0200 initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..21998d7 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Proxmox +PROXMOX_API_URL=https://proxmox.example/api2/json +PROXMOX_USER=root@pam +PROXMOX_PASSWORD=secret + +# Check_MK +CHECK_MK_API_URL=https://checkmk.example +CHECK_MK_USER=automation +CHECK_MK_PASSWORD=secret + +VERIFY_TLS=false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4aa4de9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# environment variables +.env +# virtual environment +.venv/ +# python excludes +__pycache__/ +__pypackages__/ +*.py[cod] +*$py.class +*.pyo +*.pyd +# distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ + +# cache +cache/ + +# github +.github/instructions/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d969f96 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.testing.pytestArgs": ["tests"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bc35af6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +FROM python:3.11-slim + +# Set a working directory +WORKDIR /app + +# Prevent Python from writing .pyc files and enable unbuffered stdout/stderr +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + APP_PORT=8081 \ + GUNICORN_CMD_ARGS="--bind=0.0.0.0:${APP_PORT} --workers=4 --threads=2" + +# Install system deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + gcc \ + libffi-dev \ + && rm -rf /var/lib/apt/lists/* + +# Create a non-root user +RUN useradd --create-home --shell /bin/bash appuser + +# Copy requirements and install +COPY requirements.txt /app/ +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application +COPY . /app +RUN chown -R appuser:appuser /app + +USER appuser + +# expose the default APP_PORT (can be overridden at runtime) +EXPOSE 8081 + +# Default command: run the app with gunicorn +CMD ["gunicorn", "--chdir", "./", "app:app"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b58bb0 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# LAN Web Overview + +This project is a web application that uses Flask to provide an interactive interface to view proxmox servers, virtual machines, and other devices monitored by check_mk. +It is designed to run on a local area network (LAN) and provides a StarTrek-themed interface for easy navigation and monitoring. + +## Features + +- View proxmox servers and their virtual machines +- View devices monitored by check_mk and their services +- Interactive and user-friendly interface +- StarTrek-themed design for a unique user experience +- Responsive layout for various screen sizes + +## Requirements + +- Python 3.x +- Flask +- Requests + +## Installation (Development Setup) + +1. Clone the repository: + + ```bash + git clone + ``` + +2. Navigate to the project directory: + + ```bash + cd lan-web + ``` + +3. Install the required dependencies: + + ```bash + pip install -r requirements.txt + ``` + +4. Set up environment variables for configuration (e.g., PROXMOX_API_URL, CHECK_MK_API_URL, etc.). +5. Run the Flask application: + + ```bash + python app.py + ``` + +6. Open your web browser and navigate to `http://localhost:5000`. + +## Installation (Docker Setup) + +1. Ensure you have Docker installed on your machine. +2. Clone the repository: + + ```bash + git clone + ``` + +3. Navigate to the project directory: + + ```bash + cd lan-web + ``` + +4. Build the Docker image: + + ```bash + docker build -t lan-web . + ``` + +5. Run the Docker container (the application listens on APP_PORT, default 8081): + + ```bash + # default image runs gunicorn bound to port 8081 + docker run -d -p 8081:8081 --env-file .env -e APP_PORT=8081 lan-web + ``` + +6. Alternatively, use docker-compose (recommended for development). You can set APP_PORT in your `.env` file or use the default 8081: + + ```bash + # with default APP_PORT + APP_PORT=8081 docker compose up --build -d + ``` + +7. Open your web browser and navigate to `http://localhost:8081` (or the port you configured via APP_PORT). + +## Configuration + +The application requires configuration through environment variables. Copy the sample `.env.example` file in the project root to a new file named `.env` and fill in the required values. + +## Usage + +1. Start the application using either the development setup or Docker setup. +2. Navigate to the web interface in your browser. +3. Use the navigation menu to explore proxmox servers, virtual machines, and check_mk monitored devices. +4. Click on individual items to view detailed information and status. +5. Use the search functionality to quickly find specific servers or devices. diff --git a/app.py b/app.py new file mode 100644 index 0000000..f284859 --- /dev/null +++ b/app.py @@ -0,0 +1,133 @@ +import random +import requests +import json +import os +from flask import Flask, render_template, abort +from utils.proxmox_client import ProxmoxClient +from utils.check_mk_client import CheckMKClient +from config import settings + +app = Flask(__name__) + + +def make_client(client_cls, base_url: str): + """Generic factory to construct API clients with TLS and auth from settings.""" + return client_cls( + base_url, + user=settings.CHECK_MK_USER if client_cls is CheckMKClient else settings.PROXMOX_USER, + password=settings.CHECK_MK_PASSWORD if client_cls is CheckMKClient else settings.PROXMOX_PASSWORD, + api_token=( + settings.CHECK_MK_API_TOKEN if client_cls is CheckMKClient else settings.PROXMOX_API_TOKEN) or None, + verify=settings.VERIFY_TLS, + ca_bundle=settings.CA_BUNDLE or None, + ) + + +proxmox = make_client(ProxmoxClient, settings.PROXMOX_API_URL) +checkmk = make_client(CheckMKClient, settings.CHECK_MK_API_URL) + + +@app.route('/') +def index(): + # gather cluster hosts and VMs + try: + cluster = proxmox.get_cluster() + except Exception as e: + return render_template('error.html', error=str(e)), 500 + + # enrich hosts with check_mk status + hosts = [] + for node in cluster.get('nodes', []): + host = { + 'name': node.get('name'), + 'status': node.get('status'), + # convert to GB + 'memory': round((node.get('memory') or node.get('maxmem') or 0)/1024/1024/1024, 2), + 'cpu': node.get('cpu') or node.get('maxcpu') or 0, + 'vm_count': len(node.get('qemu', [])) if node.get('qemu') else 0, + 'lxc_count': len(node.get('lxc', [])) if node.get('lxc') else 0, + } + try: + host['check_mk'] = checkmk.get_host_status(host['name']) + except Exception: + host['check_mk'] = None + hosts.append(host) + + return render_template('index.html', hosts=hosts) + + +@app.route('/host/') +def host_detail(hostname): + # get services for host from check_mk + try: + services = checkmk.get_host_services(hostname) + except Exception as e: + return render_template('error.html', error=str(e)), 500 + + return render_template('host_detail.html', hostname=hostname, services=services) + + +@app.route('/service/') +def service(url: str): + print(f"Received URL parameter: {url}") + # urldecode service_url + service_url = url.encode('utf-8').decode('unicode_escape') + print(f"Decoded service URL: {service_url}") + client = make_client(CheckMKClient, settings.CHECK_MK_API_URL) + # fetch service detail from check_mk + try: + request = client.get_service_detail(service_url) + ret = request + except Exception as e: + return render_template('error.html', error=str(e)), 500 + + return render_template('service_detail.html', service=ret) + + +@app.route('/numbers') +def numbers(): + # generates numbers for the data cascade design + n = 216 # total numbers to generate + # n = 24 * 12 + # n = 9*7 + min_len = 1 + max_len = 7 + letter_list = ['A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'K', 'L', 'S', 'T', 'U', 'X', 'Y', 'Z'] + number_list = [] + for i in range(1, n + 1): + prefix_length = random.randint(0, 2) + prefix = '' + for k in range(0, prefix_length): + prefix += random.choice(letter_list) + if prefix_length > 0 and random.choice([True, False]): + prefix += '-' + else: + prefix = '' + + max_num = 10**(max_len - len(prefix)) - 1 + if max_num < min_len: + max_num = min_len + + number = random.randint(1, max_num) + number_list.append(f"{prefix}{number}") + return json.dumps(number_list) + + +@app.route('/favicon.ico') +def favicon(): + svg = """ + + + + """ + return svg, 200, {'Content-Type': 'image/svg+xml'} + + +if __name__ == '__main__': + # configurable port via APP_PORT env var; default to 8081 + try: + APP_PORT = int(os.getenv('APP_PORT', '8081')) + except Exception: + APP_PORT = 8081 + app.run(host='0.0.0.0', port=APP_PORT, debug=True) diff --git a/config.py b/config.py new file mode 100644 index 0000000..f563422 --- /dev/null +++ b/config.py @@ -0,0 +1,27 @@ +import os +from dataclasses import dataclass +from dotenv import load_dotenv + +load_dotenv() + + +@dataclass +class Settings: + PROXMOX_API_URL: str = os.environ.get( + 'PROXMOX_API_URL', 'https://proxmox.example/api2/json') + PROXMOX_USER: str = os.environ.get('PROXMOX_USER', '') + PROXMOX_PASSWORD: str = os.environ.get('PROXMOX_PASSWORD', '') + PROXMOX_API_TOKEN: str = os.environ.get('PROXMOX_API_TOKEN', '') + + CHECK_MK_API_URL: str = os.environ.get( + 'CHECK_MK_API_URL', 'https://checkmk.example') + CHECK_MK_USER: str = os.environ.get('CHECK_MK_USER', '') + CHECK_MK_PASSWORD: str = os.environ.get('CHECK_MK_PASSWORD', '') + CHECK_MK_API_TOKEN: str = os.environ.get('CHECK_MK_API_TOKEN', '') + # TLS verification controls + VERIFY_TLS: bool = os.environ.get( + 'VERIFY_TLS', 'true').lower() in ('1', 'true', 'yes') + CA_BUNDLE: str = os.environ.get('CA_BUNDLE', '') + + +settings = Settings() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e953bce --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: "3.8" + +services: + lan-web: + build: . + image: lan-web:latest + restart: unless-stopped + ports: + - "${APP_PORT:-8081}:${APP_PORT:-8081}" + env_file: + - .env + volumes: + - ./:/app:ro + healthcheck: + test: + ["CMD-SHELL", "curl -f http://localhost:${APP_PORT:-8081}/ || exit 1"] + interval: 30s + timeout: 5s + retries: 3 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..29b4282 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Flask>=2.0 +requests>=2.25 +python-dotenv>=0.19 +pytest>=7.0 +gunicorn>=20.1 \ No newline at end of file diff --git a/static/theme/LCARS/assets/Antonio-Bold.woff b/static/theme/LCARS/assets/Antonio-Bold.woff new file mode 100644 index 0000000..6f24591 Binary files /dev/null and b/static/theme/LCARS/assets/Antonio-Bold.woff differ diff --git a/static/theme/LCARS/assets/Antonio-Bold.woff2 b/static/theme/LCARS/assets/Antonio-Bold.woff2 new file mode 100644 index 0000000..e772d58 Binary files /dev/null and b/static/theme/LCARS/assets/Antonio-Bold.woff2 differ diff --git a/static/theme/LCARS/assets/Antonio-Regular.woff b/static/theme/LCARS/assets/Antonio-Regular.woff new file mode 100644 index 0000000..19027a5 Binary files /dev/null and b/static/theme/LCARS/assets/Antonio-Regular.woff differ diff --git a/static/theme/LCARS/assets/Antonio-Regular.woff2 b/static/theme/LCARS/assets/Antonio-Regular.woff2 new file mode 100644 index 0000000..2c89826 Binary files /dev/null and b/static/theme/LCARS/assets/Antonio-Regular.woff2 differ diff --git a/static/theme/LCARS/assets/beep1.mp3 b/static/theme/LCARS/assets/beep1.mp3 new file mode 100644 index 0000000..9b8c564 Binary files /dev/null and b/static/theme/LCARS/assets/beep1.mp3 differ diff --git a/static/theme/LCARS/assets/beep2.mp3 b/static/theme/LCARS/assets/beep2.mp3 new file mode 100644 index 0000000..c7e07f6 Binary files /dev/null and b/static/theme/LCARS/assets/beep2.mp3 differ diff --git a/static/theme/LCARS/assets/beep3.mp3 b/static/theme/LCARS/assets/beep3.mp3 new file mode 100644 index 0000000..26c59dc Binary files /dev/null and b/static/theme/LCARS/assets/beep3.mp3 differ diff --git a/static/theme/LCARS/assets/beep4.mp3 b/static/theme/LCARS/assets/beep4.mp3 new file mode 100644 index 0000000..8b39d77 Binary files /dev/null and b/static/theme/LCARS/assets/beep4.mp3 differ diff --git a/static/theme/LCARS/assets/classic.css b/static/theme/LCARS/assets/classic.css new file mode 100644 index 0000000..5875c89 --- /dev/null +++ b/static/theme/LCARS/assets/classic.css @@ -0,0 +1,3011 @@ +@charset "utf-8"; + +/* + + CSS Document + LCARS Classic Theme + Version 24.2 + By Jim Robertus www.thelcars.com + Modified: 2025 Jul 27 + +*/ + +:root { + font-size: 1.375rem; + color-scheme: dark; + --lfw: 240px; + --african-violet: #c9f; + --almond: #ffaa90; + --almond-creme: #fba; + --blue: #56f; + --bluey: #89f; + --butterscotch: #f96; + --gold: #fa0; + --golden-orange: #f90; + --gray: #668; + --green: #993; + --ice: #9cf; + --lilac: #c5f; + --lima-bean: #cc6; + --magenta: #c59; + --mars: #f20; + --moonlit-violet: #96f; + --orange: #f80; + --peach: #f86; + --red: #c44; + --sky: #aaf; + --space-white: #f5f6fa; + --sunflower: #fc9; + --tomato: #f55; + --violet-creme: #dbf; + --left-frame-top-color: var(--bluey); + --left-frame-color: var(--red); /* .panel-9 inherits this color */ + --left-frame-padding: .75rem; + --corner-color-top: var(--bluey); + --corner-color-bottom: var(--red); + --panel-1-color: var(--african-violet); + --panel-4-color: var(--red); + --panel-5-color: var(--orange); + --panel-6-color: var(--butterscotch); + --panel-7-color: var(--bluey); + --panel-8-color: var(--butterscotch); + --panel-10-color: var(--orange); + --panel-top-button-color: var(--african-violet); + --bar-height: 28px; + --bar-1-6-width: 40%; + --bar-2-7-width: 4%; + --bar-3-8-width: 17%; + --bar-5-10-width: 4%; + --bar-1-color: var(--bluey); + --bar-2-color: var(--orange); + --bar-3-color: var(--african-violet); + --bar-4-color: var(--african-violet); + --bar-5-color: var(--red); + --bar-6-color: var(--red); + --bar-7-color: var(--butterscotch); + --bar-8-color: var(--red); + --bar-9-color: var(--african-violet); + --bar-10-color: var(--butterscotch); + +/* Ultra layout elements */ + + --section-2-color: var(--almond-creme); + --pill-1-color: var(--red); + --pill-2-color: var(--butterscotch); + --pill-3-color: var(--bluey); + --pill-4-color: var(--almond-creme); + --pill-5-color: var(--orange); + --pill-6-color: var(--moonlit-violet); + --pill-a1-color: var(--moonlit-violet); + --pill-a2-color: var(--moonlit-violet); + --pill-a3-color: black; + --pill-a4-color: var(--african-violet); + --pill-a5-color: var(--orange); + --pill-a6-color: var(--almond-creme); + --panel-11-color: var(--red); + --panel-12-color: var(--bluey); + --panel-13-color: var(--almond-creme); + --panel-14-color: var(--almond-creme); + --panel-15-color: var(--almond-creme); + + /* End Ultra layout elements */ + + --radius-top: 0 0 0 160px; + --radius-bottom: 160px 0 0 0; + --radius-content-top: 0 0 0 60px; + --radius-content-bottom: 60px 0 0 0; + --panel-border: .25rem solid black; + --bar-border: .25rem solid black; + --bar-cut-width: 34%; + --bar-cut-out-width: 34%; + --divider-height: .5rem; +/* + NOTE: --font-color also sets the following: + 1. horizontal line
color + 2. lcars-list default bullet color + 3. blockquote border color + 4. images with the *border* class border color +*/ + --font-color: var(--african-violet); + --sub-fonts: .875rem; + --dc-font-size: .875rem; + --dc-row-height: calc(var(--dc-font-size) + .125rem); + --banner-color: var(--orange); + --meta-data-color: var(--african-violet); + --data-cascade-color: var(--orange); + --light-color: white; + --h1-color: var(--african-violet); + --h2-color: var(--african-violet); + --h3-color: var(--african-violet); + --h4-color: var(--african-violet); + --link-color: #c16fff; + --code-color: var(--orange); + --nav-width: 240px; + --nav-1-color: var(--bluey); + --nav-2-color: var(--butterscotch); + --nav-3-color: var(--orange); + --nav-4-color: var(--red); + --button-color: var(--african-violet); + --button-color-sidebar: var(--red); + --lcars-bar-color: var(--african-violet); + --lcars-bar-start-color: var(--moonlit-violet); + --lcars-bar-end-color: var(--moonlit-violet); + --lcars-bar-text-color: var(--golden-orange); + +/* Image Frame */ + + --image-border-color: var(--almond-creme); + --primary-color: var(--orange); + --secondary-color: var(--moonlit-violet); + --accent-color: var(--almond-creme); + --title-color: var(--almond-creme); + --spacers: .65rem; + --frame-height: 40px; +} + +@media (max-width: 1500px) { + :root { + --lfw: 200px; + --radius-top: 0 0 0 130px; + --radius-bottom: 130px 0 0 0; + --divider-height: .4rem; + --nav-width: 210px; + --dc-font-size: .75rem; + --bar-height: 24px; + } +} + +@media (max-width: 1300px) { + :root { + font-size: 1.2rem; + --sub-fonts: .9rem; + --lfw: 180px; + --nav-width: 180px; + --radius-top: 0 0 0 100px; + --radius-bottom: 100px 0 0 0; + --radius-content-top: 0 0 0 40px; + --radius-content-bottom: 40px 0 0 0; + --bar-height: 20px; + } +} + +@media (max-width: 950px) { + :root { + --lfw: 150px; + } +} + +@media (max-width: 750px) { + :root { + --panel-border: .25rem solid black; + --bar-border: .25rem solid black; + --lfw: 120px; + --radius-top: 0 0 0 80px; + --radius-bottom: 80px 0 0 0; + --radius-content-top: 0 0 0 34px; + --radius-content-bottom: 34px 0 0 0; + --nav-width: 174px; + --bar-height: 16px; + --spacers: .5rem; + --frame-height: 25px; + } +} + +@media (max-width: 525px) { + :root { + --lfw: 62px; + --left-frame-padding: .5rem; + --radius-top: 0 0 0 40px; + --radius-bottom: 40px 0 0 0; + --bar-height: 10px; + --divider-height: .3rem; + } +} + +@media (max-width: 450px) { + :root { + --nav-width: 48%; + } +} + +*, *:after, *:before { + box-sizing: border-box; +} + +* { + margin: 0; + padding: 0; + font: inherit; +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +input, textarea, button, select { + font: inherit; +} + +@font-face { + font-family: 'Antonio'; + font-weight: 400; + src: url('Antonio-Regular.woff2') format('woff2'), + url('Antonio-Regular.woff') format('woff'); +} + +@font-face { + font-family: 'Antonio'; + font-weight: 700; + src: url('Antonio-Bold.woff2') format('woff2'), + url('Antonio-Bold.woff') format('woff') +} + +html { + scroll-behavior: smooth; +} + +body { + display: flex; + padding-top: 10px; + padding-left: 5px; + background-color: black; + font-family: 'Antonio', 'Arial Narrow', 'Avenir Next Condensed', sans-serif; + font-weight: 400; + line-height: 1.5; + color: var(--font-color); +} + +a { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: .2rem; + color: var(--link-color); +} + +a:hover { + filter: brightness(115%); + animation: none; +} + +a:active { + filter: brightness(80%); + outline: none; +} + +button { + border: none; + outline: none; + color: black; + transition: width 1s; +} + +button:hover { + cursor: pointer; + animation: none; + filter: brightness(115%); + color: black; +} + +button:active { + filter: brightness(85%); +} + +/* Ultra Layout elements */ + +.wrap-everything { + display: flex; + width: 100%; + column-gap: 10px; +} + +.wrap-standard { + width: 100%; +} + +#column-1 { + width: 350px; + padding: 10px 10px 10px 20px; + transition: 800ms; +} + +#column-2 { + width: var(--lfw); + background-color: var(--section-2-color); + text-align: right; + font-weight: bold; + line-height: 1.2; + color: black; + transition: 800ms; + z-index: 2; +} + +#column-2 a { + color: black; + text-decoration: none; +} + +#column-3 { + flex: 1; + margin-inline: auto; +} + +.wrap { + display: flex; + margin-inline: auto; + padding-left: 5px; + padding-right: 15px; + overflow: hidden; +} + +@media (max-width: 1680px) { + #column-1 { + margin-left: -370px; + } + + #column-2 { + margin-left: -230px; + } + + .wrap-everything { + column-gap: 5px; + } +} + +@media (max-width: 1500px) { + #column-1, + #column-2 { + display: none; + } +} + +.lcars-frame { + display: flex; + min-height: 280px; + position: relative; + --frame-color: var(--african-violet); +} + +.frame-col-1 { + width: 20px; + height: 280px; + background: var(--frame-color); + border-radius: 16px 0 0 16px; + position: relative; +} + +.frame-col-1:before { + content: ''; + display: block; + width: 20px; + height: 200px; + border-top: 5px solid black; + border-bottom: 5px solid black; + background-color: var(--frame-color); + position: absolute; + top: 40px; + left: 0; +} + +.frame-col-1-cell-a { + width: 14px; + height: 65px; + background-color: var(--tomato); + border-left: 4px solid black; + border-bottom: 4px solid black; + position: absolute; + top: 45px; + right: 0; + z-index: 2; +} + +.frame-col-1-cell-b { + width: 14px; + height: 70px; + background-color: var(--bluey); + border-left: 4px solid black; + position: absolute; + top: 110px; + right: 0; + z-index: 2; +} + +.frame-col-1-cell-c { + width: 14px; + height: 65px; + background-color: var(--orange); + border-top: 4px solid black; + border-left: 4px solid black; + position: absolute; + bottom: 45px; + right: 0; + z-index: 2; +} + +.frame-col-1-blocks:before { + content: ''; + display: block; + width: 10px; + height: 3px; + background-color: black; + position: absolute; + top: 54px; + left: 0; +} + +.frame-col-2 { + width:20px; + height: 280px; + background-color: var(--frame-color); + position: relative; +} + +.frame-col-2:before { + content: ''; + display: block; + width: 20px; + height: 240px; + background-color: black; + border-radius: 10px 0 0 10px; + position: absolute; + top: 20px; + left: 0; +} + +.frame-col-3 { + display: flex; + width: 240px; + height: 280px; + align-items: center; + justify-content: center; +} + +.frame-col-4 { + width:20px; + height: 280px; + background-color: var(--frame-color); + position: relative; +} + +.frame-col-4:before { + content: ''; + display: block; + width: 20px; + height: 240px; + background-color: black; + border-radius: 0 10px 10px 0; + position: absolute; + top: 20px; + left: 0; +} + +.display-horizontal { + rotate: 90deg; +} + +.frame-col-5 { + width:20px; + height: 280px; + background-color: var(--frame-color); + border-radius: 0 16px 16px 0; + padding-top: 40px; + position: relative; +} + +.frame-col-5:before { + content: ''; + display: block; + width: 20px; + height: 200px; + border-top: 5px solid black; + border-bottom: 5px solid black; + background-color: var(--frame-color); +} + +.frame-col-5-cell-a { + width: 14px; + height: 65px; + background-color: var(--lilac); + border-bottom: 4px solid black; + border-right: 4px solid black; + position: absolute; + top: 45px; + left: 0; + z-index: 2; +} + +.frame-col-5-cell-b { + width: 14px; + height: 70px; + background-color: var(--violet-creme); + border-right: 4px solid black; + position: absolute; + top: 110px; + left: 0; + z-index: 2; +} + +.frame-col-5-cell-c { + width: 14px; + height: 65px; + background-color: var(--moonlit-violet); + border-top: 4px solid black; + border-right: 4px solid black; + position: absolute; + bottom: 45px; + left: 0; + z-index: 2; +} + +.line { + height: 20px; + width: 12px; + background: linear-gradient(#600, var(--mars), #600); +} + +.line:nth-child(1) { + animation: animateLine6 1s 0.2s infinite; +} + +.line:nth-child(2) { + animation: animateLine5 1s 0.3s infinite; +} + +.line:nth-child(3) { + animation: animateLine3 1s 0.4s infinite; +} + +.line:nth-child(4) { + animation: animateLine3 1s 0.5s infinite; +} + +.line:nth-child(5) { + animation: animateLine2 1s 0.6s infinite; +} + +.line:nth-child(6) { + animation: animateLine2 1s 0.7s infinite; +} + +.line:nth-child(7) { + animation: animateLine2 1s 0.8s infinite; +} + +/* 8 & 9 are middle lines*/ + +.line:nth-child(8) { + animation: animateLine4 1s 0.9s infinite; +} + +.line:nth-child(9) { + animation: animateLine4 1s 1s infinite; +} + +.line:nth-child(10) { + animation: animateLine2 1s 0.8s infinite; +} + +.line:nth-child(11) { + animation: animateLine2 1s 0.7s infinite; +} + +.line:nth-child(12) { + animation: animateLine2 1s 0.6s infinite; +} + +.line:nth-child(13) { + animation: animateLine3 1s 0.5s infinite; +} + +.line:nth-child(14) { + animation: animateLine3 1s 0.4s infinite; +} + +.line:nth-child(15) { + animation: animateLine5 1s 0.3s infinite; +} + +.line:nth-child(16) { + animation: animateLine6 1s 0.2s infinite; +} + +@keyframes animateLine2 { + 0% { + height: 180px; + } + 50% { + height: 90px; + } + 100% { + height: 180px; + } +} + +@keyframes animateLine3 { + 0% { + height: 120px; + } + 50% { + height: 60px; + } + 100% { + height: 120px; + } +} + +@keyframes animateLine4 { + 0% { + height: 230px; + } + 50% { + height: 115px; + } + 100% { + height: 230px; + } +} + +@keyframes animateLine5 { + 0% { + height: 60px; + } + 50% { + height: 30px; + } + 100% { + height: 60px; + } +} + +@keyframes animateLine6 { + 0% { + height: 30px; + } + 50% { + height: 15px; + } + 100% { + height: 30px; + } +} + +.pillbox, +.pillbox-2 { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 8px; + margin: 1.25rem auto; + text-align: right; + font-size: var(--sub-fonts); +} + +.pill, +.pill-2 { + display: flex; + width: 100%; + height: 56px; + justify-content: flex-end; + align-items: flex-end; + text-decoration: none; + color: black; + font-weight: bold; + padding-right: .75rem; + padding-bottom: .35rem; +} + +.pillbox a { + display: flex; + width: 100%; + height: 56px; + justify-content: flex-end; + align-items: flex-end; + text-decoration: none; + color: black; + font-weight: bold; + padding-right: .75rem; + padding-bottom: .35rem; +} + +.pillbox-2 a { + display: flex; + width: 100%; + height: 56px; + justify-content: flex-end; + align-items: flex-end; + text-decoration: none; + color: black; + font-weight: bold; + padding-right: .75rem; + padding-bottom: .35rem; +} + +.pill:hover, +.pill-2:hover { + filter: brightness(115%); +} + +.pill:active, +.pill-2:active { + filter: brightness(80%); +} + +.pill:nth-child(1), +.pillbox a:nth-child(1) { + border-radius: 100vmax 0 0 100vmax; + background-color: var(--pill-1-color); +} + +.pill:nth-child(2), +.pillbox a:nth-child(2) { + background-color: var(--pill-2-color); +} + +.pill:nth-child(3), +.pillbox a:nth-child(3) { + border-radius: 100vmax 0 0 100vmax; + background-color: var(--pill-3-color); +} + +.pill:nth-child(4), +.pillbox a:nth-child(4) { + background-color: var(--pill-4-color); +} + +.pill:nth-child(5), +.pillbox a:nth-child(5) { + background-color: var(--pill-5-color); + border-radius: 100vmax 0 0 100vmax; +} + +.pill:nth-child(6), +.pillbox a:nth-child(6) { + background-color: var(--pill-6-color); +} + +.pill-2:nth-child(1), +.pillbox-2 a:nth-child(1) { + border-radius: 100vmax 0 0 100vmax; + background-color: var(--pill-a1-color); +} + +.pill-2:nth-child(2), +.pillbox-2 a:nth-child(2) { + background-color: var(--pill-a2-color); + border-radius: 0 100vmax 100vmax 0; + padding-right: 1rem; +} + +.pill-2:nth-child(3), +.pillbox-2 a:nth-child(3) { + background-color: var(--pill-a3-color); +} + +.pill-2:nth-child(4), +.pillbox-2 a:nth-child(4) { + background-color: var(--pill-a4-color); + border-radius: 0 100vmax 100vmax 0; + padding-right: 1rem; +} + +.pill-2:nth-child(5), +.pillbox-2 a:nth-child(5) { + background-color: var(--pill-a5-color); + border-radius: 100vmax 0 0 100vmax; +} + +.pill-2:nth-child(6), +.pillbox-2 a:nth-child(6) { + background-color: var(--pill-a6-color); + border-radius: 0 100vmax 100vmax 0; + padding-right: 1rem; +} + +.lcars-list-2 ul { + list-style: none; +} + +.lcars-list-2 { + margin: 0 auto 50px auto; + padding-left: 5px; +} + +.lcars-list-2 li { + position: relative; + padding-bottom: 5px; + padding-left: 38px; + font-size: var(--sub-fonts); + color: var(--orange); +} + +.lcars-list-2 li::before { + content: ''; + display: block; + width: 24px; + height: 14px; + border-radius: 50%; + background-color: var(--orange); + position: absolute; + top: 8px; + left: 0; +} + +.panel-11 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 27vh; + max-height: 275px; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-11-color); + border-bottom: var(--panel-border); +} + + +.panel-12 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 34vh; + max-height: 350px; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-12-color); + border-bottom: var(--panel-border); +} + +.panel-13 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 20vh; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-13-color); + border-bottom: var(--panel-border); +} + +.panel-14 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 20vh; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-14-color); + border-bottom: var(--panel-border); +} + +.panel-15 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 20vh; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-15-color); + border-bottom: var(--panel-border); +} + +.section-2-buttons a { + display: block; + text-decoration: none; + text-align: right; + border-bottom: var(--panel-border); + padding: 1.5rem .75rem .75rem 2px; + background-color: var(--section-2-color); + text-transform: uppercase; + color: black; +} + +.section-2-buttons a:nth-child(2) { + background-color: var(--butterscotch); +} + +.section-2-buttons a:nth-child(3) { + background-color: var(--african-violet); +} + +/* End Ultra layout elements */ + +.scroll-top { + display: none; +} + +.left-frame-top, +.left-frame { + width: var(--lfw); + text-align: right; + font-size: clamp(.875rem, 2vw, 1rem); + line-height: 1.2; + font-weight: bold; + color: black; + transition: width 1s; +} + +.left-frame-top { + background-color: var(--left-frame-top-color); + border-radius: var(--radius-top); +} + +.left-frame-top a, +.left-frame a { + text-decoration: none; + color: black; +} + +.left-frame { + display: flex; + flex-direction: column; + justify-content: space-between; + padding-top: 100px; + background-color: var(--left-frame-color); + border-radius: var(--radius-bottom); +} + +.right-frame-top { + flex: 1; + align-content: flex-end; + position: relative; +} + +.right-frame-top:before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to top right, var(--corner-color-top) 50%, black 50%); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +.right-frame-top:after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-top); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +@media (max-width: 650px) { + .right-frame-top:before { + bottom: 16px; + } + + .right-frame-top:after { + bottom: 16px; + } +} + +@media (max-width: 525px) { + .right-frame-top:before { + bottom: 10px; + } + + .right-frame-top:after { + bottom: 10px; + } +} + +.banner { + padding-bottom: 1rem; + padding-left: 5px; + text-align: right; + text-transform: uppercase; + line-height: 1.1; + font-size: clamp(1.25rem, 0.75rem + 4vw, 4rem); + color: var(--banner-color); +} + +.banner a { + color: var(--banner-color); + text-decoration: none; +} + +.data-cascade-button-group { + display: flex; + justify-content: flex-end; + align-items: flex-start; + flex-wrap: wrap; +} + +.header-content { + flex: 1; + min-height: 180px; + padding-right: .5rem; + padding-left: clamp(20px, 3vw, 50px); +} + +.header-content > *:first-child { + margin-top: 0; +} + +.header-content > *:last-child { + margin-bottom: 0; +} + +/* Data Cascade 2025 */ + +.data-cascade-wrapper { + flex: 1; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + max-width: 100%; + height: calc(var(--dc-row-height) * 9); /* 204px */ + overflow: hidden; + padding-right: .5rem; + padding-left: clamp(20px, 3vw, 50px); + column-gap: .5rem; +} + +.data-column { + display: grid; + grid-template-columns: 1fr; + margin-top: 1px; + text-align: right; + font-size: var(--dc-font-size); /* .938 */ + line-height: 1; + color: black; +} + +.dc-row-1, +.dc-row-2, +.dc-row-3, +.dc-row-4, +.dc-row-5, +.dc-row-6, +.dc-row-7 { + text-box: trim-both cap alphabetic; + height: var(--dc-row-height); +} + +@media (max-width: 780px) { + .data-cascade-wrapper { + display: none; + } +} + +@keyframes data-group-1 { + + 0%, + 3.99% { + color: black; + } + + 4%, + 45.99% { + color: var(--data-cascade-color); + } + + 46%, + 49.99% { + color: var(--light-color); + } + + 50%, + 63.99% { + color: var(--data-cascade-color); + } + + 64%, + 67.99% { + color: var(--light-color); + } + + 68%, + 100% { + color: var(--data-cascade-color); + } +} + +@keyframes data-group-1a { + + 0%, + 4.99% { + color: black; + } + + 5%, + 45.99% { + color: var(--data-cascade-color); + } + + 46%, + 49.99% { + color: var(--light-color); + } + + 50%, + 63.99% { + color: var(--data-cascade-color); + } + + 64%, + 67.99% { + color: var(--light-color); + } + + 68%, + 100% { + color: var(--data-cascade-color); + } +} + +@keyframes data-group-2 { + + 0%, + 12.99% { + color: black; + } + + 13%, + 49.99% { + color: var(--data-cascade-color); + } + + 50%, + 53.99% { + color: var(--light-color); + } + + 54%, + 67.99% { + color: var(--data-cascade-color); + } + + 68%, + 71.99% { + color: var(--light-color); + } + + 72%, + 100% { + color: var(--data-cascade-color); + } +} + +@keyframes data-group-2b { + + 0%, + 14.99% { + color: black; + } + + 15%, + 49.99% { + color: var(--data-cascade-color); + } + + 50%, + 53.99% { + color: var(--light-color); + } + + 54%, + 67.99% { + color: var(--data-cascade-color); + } + + 68%, + 71.99% { + color: var(--light-color); + } + + 72%, + 81.99% { + color: var(--data-cascade-color); + } + + 82%, + 100% { + color: var(--light-color); + } +} + +@keyframes data-group-3 { + + 0%, + 26.99% { + color: black; + } + + 27%, + 40.99% { + color: var(--light-color); + } + + 41%, + 53.99% { + color: var(--data-cascade-color); + } + + 54%, + 57.99% { + color: var(--light-color); + } + + 58%, + 71.99% { + color: var(--data-cascade-color); + } + + 72%, + 75.99% { + color: var(--light-color); + } + + 76%, + 100% { + color: var(--data-cascade-color); + } +} + +.dc-row-1 { + animation: data-group-1 6000ms ease 200ms infinite; +} + +.dc-row-2 { + animation: data-group-1a 6000ms ease 200ms infinite; +} + +.dc-row-3 { + animation: data-group-2 6000ms ease 200ms infinite; +} + +.dc-row-4 { + animation: data-group-2b 6000ms ease 200ms infinite; +} + +.dc-row-5 { + animation: data-group-3 6000ms ease 200ms infinite; +} + +.dc-row-6 { + animation: data-group-3 6000ms ease 200ms infinite; +} + +.dc-row-7 { + animation: data-group-3 6000ms ease 200ms infinite; +} + +/* Static data cascade */ + +.data-cascade-wrapper#frozen .dc-row-1 { + animation: none; + color: var(--orange); +} + +.data-cascade-wrapper#frozen .dc-row-2 { + animation: none; + color: var(--orange); +} + +.data-cascade-wrapper#frozen .dc-row-3 { + animation: none; + color: var(--orange); +} + +.data-cascade-wrapper#frozen .dc-row-4 { + animation: none; + color: var(--orange); +} + +.data-cascade-wrapper#frozen .dc-row-5 { + animation: none; + color: var(--light-color); +} + +.data-cascade-wrapper#frozen .dc-row-6 { + animation: none; + color: var(--light-color); +} + +.data-cascade-wrapper#frozen .dc-row-7 { + animation: none; + color: var(--light-color); +} + +/* Navigation & buttons */ + +.lcars-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + margin-block: 1rem; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1 a { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + height: clamp(90px, 11vw, 160px); + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + transition: width 1s; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1-button { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + min-height: clamp(90px, 11vw, 160px); + overflow: hidden; + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +nav { + display: flex; + flex-wrap: wrap; + align-self: center; + width: calc(var(--nav-width) + var(--nav-width) + 1rem); + justify-content: flex-end; + column-gap: .5rem; + row-gap: .65rem; +} + +@media (max-width: 1500px) { + nav { + column-gap: .375rem; + row-gap: .5rem; + } +} + +@media (max-width: 1440px) { + .data-cascade-button-group:has(.header-content) { + row-gap: 1rem; + } + + .data-cascade-button-group:has(.header-content) > nav { + width: 100%; + } +} + +nav a, +nav button, +.buttons button { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + width: var(--nav-width); + height: calc(var(--nav-width) / 2.8); + padding-inline: 1.5rem; + padding-bottom: .7rem; + border-radius: 100vmax; + background-color: var(--button-color); + text-align: right; + line-height: 1.175; + text-decoration: none; + text-transform: uppercase; + font-weight: bold; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +nav a:nth-child(1), +nav button:nth-child(1) { + background-color: var(--nav-1-color); +} + +nav a:nth-child(2), +nav button:nth-child(2) { + background-color: var(--nav-2-color); +} + +nav a:nth-child(3), +nav button:nth-child(3) { + background-color: var(--nav-3-color); +} + +nav a:nth-child(4), +nav button:nth-child(4) { + background-color: var(--nav-4-color); +} + +@media (max-width: 1300px) { + nav { + padding-left: .5rem; + gap: .5rem; + } + + nav button { + padding-bottom: .5rem; + font-size: .875rem; + padding-inline: 1.25rem; + } +} + +@media (max-width: 780px) { + nav { + flex: 1; + } + + .data-cascade-button-group:has(.header-content) > *:nth-child(2) { + flex: none; + } +} + +@media (max-width: 450px) { + nav a, + nav button { + height: 63px; + } +} + +.buttons { + display: flex; + flex-wrap: wrap; + gap: .5rem; + margin-block: 2rem; +} + +.justify-space-between { + justify-content: space-between; +} + +.justify-center { + justify-content: center; +} + +.justify-flex-end { + justify-content: flex-end; +} + +.justify-space-around { + justify-content: space-around; +} + +.justify-space-evenly { + justify-content: space-evenly; +} + +.buttons a, +.buttons button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.sidebar-nav button, +.sidebar-nav a, +.sidebar-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + min-height: 3.75rem; + width: var(--lfw); + background-color: var(--button-color-sidebar); + border-radius: 0; + border-bottom: var(--panel-border); + padding: var(--left-frame-padding); + text-decoration: none; + text-align: right; + word-break: break-all; + text-transform: uppercase; + color: black; +} + +@media (max-width: 1300px) { + .lcars-button, + .buttons a, + .buttons button { + width: 200px; + height: 74px; + } +} + +.panel-button { + display: flex; + justify-content: flex-end; + width: var(--lfw); + border-radius: 0; + padding: var(--left-frame-padding); + border-bottom: var(--panel-border); +} + +.pan-0 /* use this if you're not mimicking an established panel */ { + height: 18vh; + max-height: 240px; + background-color: var(--button-color-sidebar); +} + +.pan-4 { + height: 22vh; + max-height: 300px; + background-color: var(--panel-4-color) !important; +} + +.pan-5 { + height: 4.25rem; + background-color: var(--panel-5-color) !important; + align-items: center; +} + +.pan-6 { + height: 29vh; + max-height: 360px; + background-color: var(--panel-6-color) !important; +} + +.pan-7 { + height: 27vh; + max-height: 350px; + background-color: var(--panel-7-color) !important; +} + +.pan-8 { + height: 15vh; + background-color: var(--panel-8-color) !important; +} + +.pan-10 { + height: 30vh; + background-color: var(--panel-10-color) !important; +} + +.text-bottom { + align-items: flex-end; +} + +#topBtn { + display: none; + position: fixed; + bottom: 0; + z-index: 99; + border-radius: 0; + border-top: var(--panel-border); + border-right: none; + border-bottom: var(--panel-border); + border-left: none; + outline: none; + width: var(--lfw); + padding: var(--left-frame-padding); + padding-bottom: 10vh; + background-color: var(--panel-top-button-color); + text-align: right; + line-height: 1; + font-weight: bold; + text-transform: uppercase; + color: black; + cursor: pointer; +} + +#topBtn:hover { + filter: brightness(115%); +} + +#topBtn:active { + filter: brightness(80%); +} + +@media (max-width: 525px) { + .sidebar-button, + .sidebar-nav a, + .sidebar-nav button, + .panel-button { + font-size: .75rem; + } + + #topBtn { + padding-bottom: 6vh; + } +} + +/* --- Horizontal bar panels & sidebar panels --- */ + +.bar-panel { + display: flex; + height: var(--bar-height); +} + +.first-bar-panel { + margin-top: clamp(15px, 2vw, 30px); +} + +.bar-1, +.bar-2, +.bar-3, +.bar-4, +.bar-5, +.bar-6, +.bar-7, +.bar-9, +.bar-10 { + height: var(--bar-height); +} + +.bar-1, +.bar-2, +.bar-3, +.bar-4, +.bar-6, +.bar-7, +.bar-8, +.bar-9 { + border-right: var(--bar-border); +} + +.bar-1 { + width: var(--bar-1-6-width); + background-color: var(--bar-1-color); +} + +.bar-2 { + width: var(--bar-2-7-width); + background-color: var(--bar-2-color); +} + +.bar-3 { + width: var(--bar-3-8-width); + background-color: var(--bar-3-color); +} + +.bar-4 { + flex: 1; + background-color: var(--bar-4-color); +} + +.bar-5 { + width: var(--bar-5-10-width); + background-color: var(--bar-5-color); +} + +.bar-6 { + width: var(--bar-1-6-width); + background-color: var(--bar-6-color); +} + +.bar-7 { + width: var(--bar-2-7-width); + background-color: var(--bar-7-color); +} + +.bar-8 { + width: var(--bar-3-8-width); + height: 50%; + background-color: var(--bar-8-color); +} + +.bar-9 { + flex: 1; + background-color: var(--bar-9-color); +} + +.bar-10 { + width: var(--bar-5-10-width); + background-color: var(--bar-10-color); +} + +#gap { + margin-top: var(--divider-height); +} + +.panel-3, +.panel-4, +.panel-5, +.panel-6, +.panel-7, +.panel-8 { + border-bottom: var(--panel-border); +} + +.panel-2, +.panel-3, +.panel-4, +.panel-6, +.panel-7, +.panel-8, +.panel-10 { + padding: var(--left-frame-padding); +} + +.panel-4 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 22vh; + max-height: 300px; + background-color: var(--panel-4-color); +} + +.panel-5 { + display: flex; + justify-content: flex-end; + align-items: center; + height: 4.25rem; + padding: var(--left-frame-padding); + background-color: var(--panel-5-color); +} + +.panel-6 { + height: 29vh; + max-height: 360px; + background-color: var(--panel-6-color); +} + +.panel-7 { + height: 27vh; + max-height: 350px; + background-color: var(--panel-7-color); +} + +.panel-8 { + height: 15vh; + background-color: var(--panel-8-color); +} + +/* Note: panel-9 height is fluid and governed by the amount of content on the page. Background color is inherited from :root --left-frame-color */ + +.panel-9 { + min-height: 27vh; + padding: var(--left-frame-padding); +} + +.panel-10 { + height: 30vh; + border-top: var(--panel-border); + background-color: var(--panel-10-color); +} + +@media (max-width: 525px) { + .panel-4, + .panel-6, + .panel-7 { + height: 18vh; + } +} + +.right-frame { + flex: 1; + position: relative; +} + +.right-frame::before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to bottom right, var(--corner-color-bottom) 50%, black 50%); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +.right-frame::after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-bottom); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +main { + padding-top: 1.5rem; + padding-bottom: .5rem; + padding-left: clamp(20px, 3vw, 50px); +} + +main > *:first-child, +article > *:first-child { + margin-top: 0; +} + +main:has(.floor-heading) > *:nth-child(2) { + margin-top: 0; +} + +@media (max-width: 1400px) { + main { + padding-top: 1rem; + } +} + +.flexbox { + display: flex; + gap: 2vw; + flex-wrap: wrap; +} + +.col { + flex: 1 1 360px; +} + +.col > *:first-child { + margin-top: 0; +} + +h1, h2, h3, h4 { + margin-block: 1.75rem; + font-weight: normal; + line-height: 1.2; + text-transform: uppercase; + text-box: trim-both cap alphabetic; +} + +h1 { + font-size: clamp(1.5rem, 1.25rem + 3.5vw, 4rem); + text-align: right; + color: var(--h1-color); +} + +h2 { + font-size: clamp(1.4rem, 1.1rem + 2.25vw, 2.3rem); + color: var(--h2-color); +} + +h3 { + font-size: clamp(1.15rem, 1.05rem + 1.25vw, 1.875rem); + color: var(--h3-color); +} + +h4 { + font-size: clamp(1.025rem, 1rem + 1.125vw, 1.575rem); + color: var(--h4-color); +} + +.floor-heading { + display: flex; + justify-content: flex-end; + width: 100%; + position: fixed; + left: 50%; + transform: translate(-50%, 0); + bottom: 10px; + z-index:4; +} + +.floor-heading > * { + margin-block: 0; + width: fit-content; + padding-inline: .5rem; + padding-bottom: .5rem; + background-color: black; +} + +p { + margin-block: 1.75rem; + text-box: trim-both cap alphabetic; +} + +.caption { + margin-top: -1rem; + margin-bottom: 2.75rem; + text-align: center; + font-size: var(--sub-fonts); +} + +.pics-right .caption, +.pics-left .caption { + margin-top: 1rem; +} + +.indent { + padding-left: 1.5rem; +} + +.postmeta { + margin-block: 1.25rem; + text-align: right; + font-size: clamp(1.2rem, 0.88rem + 1.28vw, 1.6rem); + line-height: 1.2; + text-transform: uppercase; +} + +article h1 { + margin-bottom: 0; +} + +hr { + margin-block: 1.5rem; + height: 6px; + border: none; + background-color: var(--font-color); + border-radius: 3px; +} + +blockquote { + margin-block: 1.75rem; + margin-left: 1.5rem; + padding-block: .25rem; + padding-left: 1.5rem; + position: relative; + text-box: trim-both cap alphabetic; +} + +blockquote::before { + content: ''; + display: block; + width: 10px; + height: 100%; + background-color: var(--font-color); + border-radius: 5px; + position: absolute; + left: 0; + top: 0; +} + +blockquote > *:first-child { + margin-top: 0; +} + +blockquote > * { + margin-bottom: 0; +} + +iframe { + display: block; + width: 100%; + border: none; +} + +.flush { + margin-top: -1rem; +} + +.nomar { + margin-block: 0 !important; +} + +.go-center { + text-align: center !important; +} + +.go-right { + text-align: right !important; +} + +.go-left { + text-align: left !important; +} + +.go-big { + font-size: clamp(1.2rem, 1rem + 1vw, 1.275rem); +} + +.uppercase { + text-transform: uppercase; +} + +.strike { + text-decoration: line-through; + text-decoration-thickness: .15rem; +} + +.now { + white-space: nowrap; +} + +.big-sky { + margin-top: 5rem; +} + +.smoke-glass { + opacity: .35; +} + +strong { + font-weight: bold; +} + +code { + font-family: monospace; + font-size: .9rem; + color: var(--code-color); +} + +.code { + width: 100%; + min-height: 5rem; + padding-block: .5rem; + padding-inline: 1rem; + background-color: black; + border-color: var(--code-color); + tab-size: 4; + font-family: monospace; + font-size: .85rem; + color: var(--code-color); +} + +::selection { + background-color: var(--orange); + color: black; +} + +@keyframes blink { + 0% {opacity: 0} + 49%{opacity: 0} + 50% {opacity: 1} +} + +.blink-slow { + animation: blink 3500ms infinite; +} + +.blink { + animation: blink 2s infinite; +} + +.blink-fast { + animation: blink 1s infinite; +} + +@keyframes pulse { + 0% {filter: brightness(1.0)} + 50% {filter: brightness(.25)} +} + +.pulse { + animation: pulse 2s infinite; +} + +.pulse-rate-high { + animation: pulse 1s infinite; +} + +.pulse-rate-low { + animation: pulse 3s infinite; +} + +/* Accordion Dropdown */ + +.accordion-wrapper { + display: block; + margin: 1.75rem auto; + width: 100%; +} + +.limit-width { + max-width: 1240px; +} + +.accordion { + display: flex; + align-items: center; + min-height: 3rem; + width: 100%; + padding-right: 2.75rem; + padding-left: 1rem; + border-radius: 100vmax; + background-color: var(--african-violet); + border-left: solid 3rem var(--moonlit-violet); + text-align: left; + font-size: 1.25rem; + color: black; + cursor: pointer; + transition: 0.4s ease; + position: relative; +} + +.active, .accordion:hover { + background-color: var(--violet-creme); + border-left-color: var(--orange); + filter: none; +} + +.accordion:before { + content: ''; + display: block; + width: .5rem; + height: 4rem; + background-color: black; + position: absolute; + top: 0; + left: 0; +} + +.accordion:after { + display: block; + content: '\276F'; + position: absolute; + right: 1.5rem; + top: 21%; + transform: rotate(90deg); + transition: 0.4s; + font-weight: bold; + color: black; +} + +.active:after { + content: "\276F"; + transform:rotate(-90deg); + transition: 0.4s ease; +} + +.accordionContent { + padding-block: .5rem; + padding-inline: 3.5rem; + max-height: 0; + overflow: hidden; + transition: max-height 0.25s ease-out; +} + +.accordionContent ul { + margin-left: 0; +} + +@media (max-width: 525px) { + .accordion { + min-height: 2.5rem; + font-size: 1rem; + border-left-width: 2.5rem; + } +} + +/* Images */ + +.pics-right { + float: right; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; + clear: both; +} + +.pics-left { + float: left; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; +} + +@media (max-width: 1060px) { + .pics-right, + .pics-left { + float: none; + margin-inline: auto; + } + + .pics-right img, + .pics-left img { + margin-inline: auto; + } +} + +.pics { + margin-block: 2rem; + margin-inline: auto; +} + +.border { + border: 2px solid var(--font-color); +} + +/* + +Gallery + +NOTE: Gallery is experimental and still under development. + +*/ + +.gallery { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: .75rem; + list-style: none; + text-align: center; + font-size: .875rem; +} + +.gallery img { + align-self: flex-start; + border: 4px solid black; +} + +.gallery li div { + padding-block: .6rem; + text-box: trim-both cap alphabetic; +} + +.thumbs img { + width: 340px; +} + +.gallery a { + align-self: flex-start; +} + +.gallery a img:hover { + border-color: var(--font-color); +} + +.lcars-list { + margin-block: 1.15rem; + margin-left: 2rem; + list-style: none; +} + +.lcars-list li { + position: relative; + padding-block: .45rem; + padding-left: 2.25rem; + text-box: trim-both cap alphabetic; +} + +.lcars-list li::before { + content: ''; + display: block; + width: 34px; + height: 18px; + border-radius: 50%; + background-color: var(--font-color); + position: absolute; + top: .45rem; + left: 0; +} + +@media (max-width: 650px) { + .lcars-list { + margin-left: .5rem; + } + + .lcars-list li::before { + transform: scale(90%); + } +} + +.lcars-bar { + margin-block: 1.75rem; + height: clamp(15px, 2vh, 25px); + background: transparent; + border-right: clamp(15px, 2vh, 25px) solid var(--lcars-bar-end-color); + border-left: clamp(15px, 2vh, 25px) solid var(--lcars-bar-start-color); + border-radius: 100vmax; + position: relative; +} + +.lcars-bar::after { + content: ''; + display: block; + height: clamp(15px, 2vh, 25px); + width: 100%; + background-color: var(--lcars-bar-color); + border-right: .8vh solid black; + border-left: .8vh solid black; + position: absolute; + top: 0; + left: 0; +} + +.lcars-text-bar { + display: flex; + position: relative; + height: clamp(16px, 4vh, 41px); + margin-block: 2.75rem; + overflow: visible; + border-radius: 100vmax; + background-color: var(--lcars-bar-color); + border-right: clamp(16px, 4vh, 43px) solid var(--lcars-bar-end-color); + border-left: clamp(16px, 4vh, 43px) solid var(--lcars-bar-start-color); +} + +.the-end { + justify-content: flex-end; +} + +.lcars-text-bar h2, +.lcars-text-bar h3, +.lcars-text-bar h4, +.lcars-text-bar span { + margin-block: 0; + background-color: black; + height: clamp(20px, 5.5vh, 60px); /* Setting height is a Firefox fix */ + overflow: visible; + border-top: 1px solid black; + padding-inline: 1vh; + font-size: clamp(17px, 4.5vh, 46px); + line-height: 1; + text-transform: uppercase; + color: var(--lcars-bar-text-color); + z-index: 1; + text-box: trim-both cap alphabetic; +} + +.lcars-text-bar::before { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + left: 0; + overflow: hidden; +} + +.lcars-text-bar::after { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + right: 0; + overflow: hidden; +} + +/* Image Frame */ + +.image-frame { + display: block; + margin: 2.75rem auto; + width: fit-content; + background: linear-gradient(var(--primary-color) 56%, var(--secondary-color) 44%); + border-radius: 50px 25px 0 50px; + position: relative; +} + +.image-frame::before { + content: ''; + display: block; + width: 40px; + height: 40px; + background-color: black; + position: absolute; + right: 0; + top: 0; +} + +.image-frame::after { + content: ''; + display: block; + border-top: var(--spacers) solid black; + border-bottom: var(--spacers) solid black; + width: 45px; + height: 80px; + background-color: var(--secondary-color); + position: absolute; + left: 0; + top: 56%; +} + +.imgf-title { + display: flex; + justify-content: flex-end; + height: 40px; + border-right: 40px solid var(--secondary-color); + border-radius: 25px 100vh 100vh 0; + position: relative; + z-index: 1; + text-align: right; +} + +.h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + border-right: var(--spacers) solid black; +} + +.imgf-title h4 { + margin-block: 0; + width: fit-content; + background-color: black; + padding-left: var(--spacers); + font-size: 2.05rem; + color: var(--title-color); + line-height: 40px; + text-transform: uppercase; +} + +.imgf-image-body { + margin-left: 45px; + background-color: black; + width: fit-content; + padding: 1rem; + border-radius: 28px 0 0 28px; + +} + +.image-holder { + width: fit-content; + padding: 1rem; + border: 2px solid var(--image-border-color); + border-radius: 20px; +} + +.imgf-base { + display: grid; + grid-template-columns: 20% 13% 35px 15% 1fr; + margin-left: 80px; + border-left: var(--spacers) solid black; + +} + +.imgf-block-1 { + height: var(--frame-height); + background-color: var(--accent-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-2 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-3 { + height: var(--frame-height); + background-color: black; + border-right: 10px solid var(--secondary-color); + border-left: 10px solid var(--accent-color); +} + +.imgf-block-4 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-left: var(--spacers) solid black; +} + +.imgf-block-5 { + height: var(--frame-height); + background-color: black; +} + +@media (max-width: 750px) { + .image-frame { + border-radius: 40px 25px 0 40px; + } + + .image-frame::after { + width: 25px; + height: 60px; + top: 50%; + } + + .imgf-title { + height: 25px; + border-right: 24px solid var(--secondary-color); + } + + .h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + } + + .imgf-title h4 { + font-size: 1.45rem; + } + + .imgf-image-body { + margin-left: 25px; + padding: .75rem; + border-radius: 20px 0 0 20px; + } + + .image-holder { + padding: .75rem; + border-radius: 10px; + } +} + +/* color custom classes */ + +.font-african-violet { + color: var(--african-violet) !important; +} + +.button-african-violet, +.background-african-violet, +.bullet-african-violet { + background-color: var(--african-violet) !important; +} + +.font-almond { + color: var(--almond) !important; +} + +.button-almond, +.background-almond, +.bullet-almond::before { + background-color: var(--almond) !important; +} + +.font-almond-creme { + color: var(--almond-creme) !important; +} + +.button-almond-creme, +.background-almond-creme, +.bullet-almond-creme::before { + background-color: var(--almond-creme) !important; +} + +.font-blue { + color: var(--blue) !important; +} + +.button-blue, +.background-blue, +.bullet-blue::before { + background-color: var(--blue) !important; +} + +.font-bluey { + color: var(--bluey) !important; +} + +.button-bluey, +.background-bluey, +.bullet-bluey::before { + background-color: var(--bluey) !important; +} + +.font-butterscotch { + color: var(--butterscotch) !important; +} + +.button-butterscotch, +.background-butterscotch, +.bullet-butterscotch::before { + background-color: var(--butterscotch) !important; +} + +.font-gold { + color: var(--gold) !important; +} + +.button-gold, +.background-gold, +.bullet-gold::before { + background-color: var(--gold) !important; +} + +.font-golden-orange { + color: var(--golden-orange) !important; +} + +.button-golden-orange, +.background-golden-orange, +.bullet-golden-orange::before { + background-color: var(--golden-orange) !important; +} + +.font-gray { + color: var(--gray) !important; +} + +.button-gray, +.background-gray, +.bullet-gray::before { + background-color: var(--gray) !important; +} + +.font-green { + color: var(--green) !important; +} + +.button-green, +.background-green, +.bullet-green::before { + background-color: var(--green) !important; +} + +.font-ice { + color: var(--ice) !important; +} + +.button-ice, +.background-ice, +.bullet-ice::before { + background-color: var(--ice) !important; +} + +.font-lilac { + color: var(--lilac) !important; +} + +.button-lilac, +.background-lilac, +.bullet-lilac::before { + background-color: var(--lilac) !important; +} + +.font-lima-bean { + color: var(--lima-bean) !important; +} + +.button-lima-bean, +.background-lima-bean, +.bullet-lima-bean::before { + background-color: var(--lima-bean) !important; +} + +.font-magenta { + color: var(--magenta) !important; +} + +.button-magenta, +.background-magenta, +.bullet-magenta::before { + background-color: var(--magenta) !important; +} + +.font-mars { + color: var(--mars) !important; +} + +.button-mars, +.background-mars, +.bullet-mars::before { + background-color: var(--mars) !important; +} + +.font-moonlit-violet { + color: var(--moonlit-violet) !important; +} + +.button-moonlit-violet, +.background-moonlit-violet, +.bullet-moonlit-violet::before { + background-color: var(--moonlit-violet) !important; +} + +.font-orange { + color: var(--orange) !important; +} + +.button-orange, +.background-orange, +.bullet-orange::before { + background-color: var(--orange) !important; +} + +.font-peach { + color: var(--peach) !important; +} + +.button-peach, +.background-peach, +.bullet-peach::before { + background-color: var(--peach) !important; +} + +.font-red { + color: var(--red) !important; +} + +.button-red, +.background-red, +.bullet-red::before { + background-color: var(--red) !important; +} + +.font-sky { + color: var(--sky) !important; +} + +.button-sky, +.background-sky, +.bullet-sky::before { + background-color: var(--sky) !important; +} + +.font-space-white { + color: var(--space-white) !important; +} + +.button-space-white, +.background-space-white, +.bullet-space-white::before { + background-color: var(--space-white) !important; +} + +.font-sunflower { + color: var(--sunflower) !important; +} + +.button-sunflower, +.background-sunflower, +.bullet-sunflower::before { + background-color: var(--sunflower) !important; +} + +.font-tomato { + color: var(--tomato) !important; +} + +.button-tomato, +.background-tomato, +.bullet-tomato::before { + background-color: var(--tomato) !important; +} + +.font-violet-creme { + color: var(--violet-creme) !important; +} + +.button-violet-creme, +.background-violet-creme, +.bullet-violet-creme::before { + background-color: var(--violet-creme) !important; +} + +/* Footer */ + +footer { + margin-top: 2.5vw; + padding-bottom: 3rem; + padding-left: clamp(20px, 3vw, 50px); + font-size: .825rem; + --footer-bar-height: 1rem; +} + +footer:has(.footer-frame) { + font-size: inherit; +} + +.footer-frame { + display: grid; + grid-template-columns: 1fr var(--lfw); + align-items: center; + background: linear-gradient(var(--red) 50%, var(--bluey) 50%); + border-radius: 0 2rem 2rem 0; + padding-block: var(--footer-bar-height); + position: relative; +} + +.footer-frame::before { + content: ''; + display: block; + width: 20%; + height: calc(var(--footer-bar-height) / 2); + /*border-radius: 100vmax 0 0 100vmax;*/ + position: absolute; + left: 0; + top: calc(var(--footer-bar-height) + .5rem); + background-color: var(--red); + z-index: 1; +} + +.footer-frame::after { + content: ''; + display: block; + width: 20%; + height: calc(var(--footer-bar-height) / 2); + /*border-radius: 100vmax 0 0 100vmax;*/ + position: absolute; + left: 0; + bottom: var(--footer-bar-height); + background-color: var(--bluey); +} + +.footer-frame-content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: black; + min-height: 250px; + border-radius: 0 1.5rem 1.5rem 0; + padding-block: 2.5rem; + padding-inline: 2rem; + position: relative; + text-align: center; +} + +.footer-frame-content::before { + content: ''; + display: block; + width: .25rem; + height: var(--footer-bar-height); + background-color: black; + position: absolute; + top: calc(var(--footer-bar-height) - var(--footer-bar-height) * 2); + left: 62%; +} + +.footer-frame-content::after { + content: ''; + display: block; + width: .25rem; + height: var(--footer-bar-height); + background-color: black; + position: absolute; + bottom: calc(var(--footer-bar-height) - var(--footer-bar-height) * 2); + left: 62%; +} + +.footer-frame-content p { + margin-block: .8rem; +} + +@media (max-width: 880px) { + .footer-frame-content .buttons { + justify-content: center; + } +} + +@media (max-width: 600px) { + footer { + --footer-bar-height: .75rem; + } + + .footer-frame-content { + padding-inline: .5rem; + } +} + +.footer-frame-content *:first-child { + margin-top: 0; +} + +.footer-frame-content *:last-child { + margin-bottom: 0; +} + +.footer-frame-panel { + height: 50%; + min-height: 100px; + background-color: var(--butterscotch); + border-block: var(--bar-border); + padding: var(--left-frame-padding); + font-size: clamp(.875rem, 2vw, 1rem); + font-weight: bold; + color: black; +} + +.headtrim { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + top: 0; + left: 0; + z-index: 999; +} + +.baseboard { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + bottom: 0; + left: 0; + z-index: 999; +} + +/* Grouped @media */ + +@media (max-width: 525px) { + body { + padding: .25rem; + } + + .baseboard, + .headtrim { + display: none; + } + + .wrap { + padding-left: 0; + padding-right: .25rem; + } + + .left-frame { + padding-top: 25px; + } + + .floor-heading { + bottom: 0; + } + + .hop { + display: none; + } + + blockquote { + margin-left: .75rem; + } +} + +@-moz-document url-prefix() { + + main { + padding-top: clamp(1rem, 4vw, 30px); + } + + h1, h2, h3, h4, p { + margin-block: 1.15rem; + } + + .imgf-title h4 { + margin-top: -4px; + margin-bottom: 0; + } + + .postmeta { + margin-top: .75rem; + } + + .lcars-text-bar h2, + .lcars-text-bar h3, + .lcars-text-bar h4, + .lcars-text-bar span { + position: absolute; + top: -.7vh; + } + + .lcars-list li::before { + top: .85rem; + } + + .meta-data { + height: 1.3rem; + line-height: 1rem; + } +} \ No newline at end of file diff --git a/static/theme/LCARS/assets/lcars.js b/static/theme/LCARS/assets/lcars.js new file mode 100644 index 0000000..3c067eb --- /dev/null +++ b/static/theme/LCARS/assets/lcars.js @@ -0,0 +1,50 @@ +document.addEventListener("touchstart", function() {},false); +let mybutton = document.getElementById("topBtn"); +window.onscroll = function() {scrollFunction()}; +function scrollFunction() { + if (document.body.scrollTop > 200 || document.documentElement.scrollTop > 200) { + mybutton.style.display = "block"; + } else { + mybutton.style.display = "none"; + } +} +function topFunction() { + document.body.scrollTop = 0; + document.documentElement.scrollTop = 0; +} +function playSoundAndRedirect(audioId, url) { + var audio = document.getElementById(audioId); + audio.play(); + + audio.onended = function() { + window.location.href = url; + }; +} +function goToAnchor(anchorId) { + window.location.hash = anchorId; +} +// Accordion drop-down +var acc = document.getElementsByClassName("accordion"); +var i; + +for (i = 0; i < acc.length; i++) { + acc[i].addEventListener("click", function() { + this.classList.toggle("active"); + var accordionContent = this.nextElementSibling; + if (accordionContent.style.maxHeight){ + accordionContent.style.maxHeight = null; + } else { + accordionContent.style.maxHeight = accordionContent.scrollHeight + "px"; + } + }); +} +// LCARS keystroke sound (not to be used with hyperlinks) + const LCARSkeystroke = document.getElementById('LCARSkeystroke'); + const allPlaySoundButtons = document.querySelectorAll('.playSoundButton'); + allPlaySoundButtons.forEach(button => { + button.addEventListener('click', function() { + LCARSkeystroke.pause(); + LCARSkeystroke.currentTime = 0; // Reset to the beginning of the sound + LCARSkeystroke.play(); + }); + }); \ No newline at end of file diff --git a/static/theme/LCARS/assets/lower-decks-padd.css b/static/theme/LCARS/assets/lower-decks-padd.css new file mode 100644 index 0000000..d914632 --- /dev/null +++ b/static/theme/LCARS/assets/lower-decks-padd.css @@ -0,0 +1,1881 @@ +@charset "utf-8"; + +/* + + CSS Document + LCARS Lower Decks PADD Theme + Version 24.2 + By Jim Robertus www.thelcars.com + Modified: 2025 Aug 4 + +*/ + +:root { + font-size: 1.375rem; + color-scheme: dark; + --lfw: 240px; + --alpha-blue: #58e; + --arctic-ice: #6cf; + --arctic-snow: #9cf; + --radioactive: #8ff; + --beta-blue: #79d; + --night-cloud: #344470; + --night-rain: #455580; + --sunset-red: #f30; + --left-frame-top-color: var(--alpha-blue); + --left-frame-color: var(--alpha-blue); /* panel-6 inherits this color */ + --left-frame-padding: .75rem; + --corner-color-top: var(--alpha-blue); + --corner-color-bottom: var(--alpha-blue); + --panel-1-color: var(--night-rain); + --panel-4-color: var(--beta-blue); + --panel-5-color: var(--night-rain); + --panel-7-color: var(--night-cloud); + --panel-top-button-color: var(--beta-blue); + --bar-height: 28px; + --bar-1-6-width: 10%; + --bar-2-7-width: 28%; + --bar-3-8-width: 7%; + --bar-5-10-width: 5%; + --bar-1-color: var(--alpha-blue); + --bar-2-color: var(--radioactive); + --bar-3-color: var(--beta-blue); + --bar-4-color: var(--alpha-blue); + --bar-5-color: var(--arctic-ice); + --bar-6-color: var(--alpha-blue); + --bar-7-color: var(--radioactive); + --bar-8-color: var(--beta-blue); + --bar-9-color: var(--alpha-blue); + --bar-10-color: var(--arctic-ice); +/* + NOTE: --font-color also sets the following: + 1. horizontal line
color + 2. lcars-list bullet color + 3. blockquote border color + 4. images with the *border* class + 5. data-cascade text color +*/ + --font-color: var(--beta-blue); + --sub-fonts: .875rem; + --banner-color: var(--radioactive); + --h1-color: var(--arctic-ice); + --h2-color: var(--arctic-ice); + --h3-color: var(--arctic-ice); + --h4-color: var(--arctic-ice); + --light-color: #bdf; + --link-color: var(--beta-blue); + --code-color: var(--alpha-blue); + --nav-width: 240px; + --nav-1-color: var(--alpha-blue); + --nav-2-color: var(--beta-blue); + --nav-3-color: var(--arctic-ice); + --nav-4-color: var(--night-rain); + --button-color: var(--alpha-blue); + --button-color-sidebar: var(--alpha-blue); + --radius-top: 0 0 0 100px; + --radius-bottom: 100px 0 0 0; + --radius-content-top: 0 0 0 44px; + --radius-content-bottom: 44px 0 0 0; + --panel-border: .4rem solid #000; + --bar-border: .4rem solid #000; + --bar-cut-width: 27%; + --divider-height: .75rem; + --lcars-bar-color: var(--night-rain); + --lcars-bar-start-color: var(--beta-blue); + --lcars-bar-end-color: var(--beta-blue); + --lcars-bar-text-color: var(--beta-blue); + +/* Image Frame: */ + + --image-border-color: var(--beta-blue); + --primary-color: var(--night-rain); + --secondary-color: var(--beta-blue); + --accent-color: var(--arctic-snow); + --spacers: .65rem; + --frame-height: 40px; +} + +@media (max-width: 1500px) { + :root { + --lfw: 200px; + --nav-width: 210px; + --bar-height: 20px; + --divider-height: .5rem; + } +} + +@media (max-width: 1300px) { + :root { + font-size: 1.2rem; + --sub-fonts: .9rem; + --lfw: 180px; + --radius-top: 0 0 0 80px; + --radius-bottom: 80px 0 0 0; + --radius-content-top: 0 0 0 30px; + --radius-content-bottom: 30px 0 0 0; + --nav-width: 180px; + } +} + +@media (max-width: 950px) { + :root { + --lfw: 150px; + } +} + +@media (max-width: 750px) { + :root { + --lfw: 120px; + --radius-top: 0 0 0 60px; + --radius-bottom: 60px 0 0 0; + --radius-content-top: 0 0 0 24px; + --radius-content-bottom: 24px 0 0 0; + --bar-height: 16px; + --spacers: .5rem; + --frame-height: 25px; + } +} + +@media (max-width: 525px) { + :root { + --lfw: 62px; + --left-frame-padding: .5rem; + --radius-top: 0 0 0 30px; + --radius-bottom: 30px 0 0 0; + --bar-height: 10px; + --panel-border: .25rem solid black; + --bar-border: .25rem solid black; + --divider-height: .345rem; + } +} + +@media (max-width: 450px) { + :root { + --nav-width: 48%; + } +} + +*, *:after, *:before { + box-sizing: border-box; +} + +* { + margin: 0; + padding: 0; + font: inherit; +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +input, textarea, button, select { + font: inherit; +} + +@font-face { + font-family: 'Antonio'; + font-weight: 400; + src: url('Antonio-Regular.woff2') format('woff2'), + url('Antonio-Regular.woff') format('woff'); +} + +@font-face { + font-family: 'Antonio'; + font-weight: 700; + src: url('Antonio-Bold.woff2') format('woff2'), + url('Antonio-Bold.woff') format('woff') +} + +html { + scroll-behavior: smooth; +} + +body { + display: flex; + padding-top: 10px; + padding-left: 5px; + background-color: black; + font-family: 'Antonio', 'Arial Narrow', 'Avenir Next Condensed', sans-serif; + font-weight: 400; + line-height: 1.5; + color: var(--font-color); +} + +a { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: .2rem; + color: var(--link-color); +} + +a:hover { + filter: brightness(115%); + animation: none; +} + +a:active { + filter: brightness(80%); + outline: none; +} + +button { + border: none; + outline: none; + word-break: break-all; + color: black; + transition: width 1s; +} + +button:hover { + cursor: pointer; + animation: none; + filter: brightness(115%); + color: black; +} + +button:active { + filter: brightness(85%); +} + +@keyframes colorchange { + 0% {color: var(--night-cloud)} + 25% {color: var(--night-cloud);} + 50% {color: var(--arctic-snow);} + 75% {color: var(--arctic-snow);} + 80% {color: var(--arctic-snow);} + 90% {color: var(--night-cloud);} + 100% {color: var(--night-cloud);} +} + +.wrap-all { + width: 100%; +} + +.wrap { + display: flex; + width: 100%; + padding-left: 5px; + padding-right: 15px; + overflow: hidden; +} + +.scroll-top { + display: none; +} + +.left-frame-top, +.left-frame { + width: var(--lfw); + text-align: right; + font-size: clamp(.875rem, 2vw, 1rem); + line-height: 1.2; + font-weight: bold; + color: black; + transition: width 1s; +} + +.left-frame-top { + background-color: var(--left-frame-top-color); + border-radius: var(--radius-top); +} + +.left-frame-top a, +.left-frame a { + text-decoration: none; + color: black; +} + +.left-frame { + display: flex; + flex-direction: column; + justify-content: space-between; + padding-top: 14vh; + background-color: var(--left-frame-color); + border-radius: var(--radius-bottom); +} + +.left-frame:has(.sidebar-nav) { + padding-top: 10vh; +} + +.panel-2 { + padding-top: .75rem; + padding-right: .75rem; +} + +.right-frame-top { + flex: 1; + position: relative; +} + +.right-frame-top::before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to top right, var(--corner-color-top) 50%, #000 50%); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +.right-frame-top::after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-top); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +@media (max-width: 500px) { + .right-frame-top::before { + bottom: 10px; + } + + .right-frame-top::after { + bottom: 10px; + } +} + +.banner { + padding-bottom: 1rem; + padding-left: 5px; + text-align: right; + text-transform: uppercase; + line-height: 1.1; + font-size: clamp(1.5rem, 1.25rem + 3.5vw, 4rem); + color: var(--banner-color); +} + +.banner a { + color: var(--banner-color); + text-decoration: none; +} + +.data-cascade-button-group { + display: flex; + justify-content: flex-end; + align-items: flex-start; + flex-wrap: wrap; + padding-bottom: .75rem; +} + +.data-cascade-button-group:has(.header-content) { + justify-content: flex-start; +} + +@media (max-width: 1440px) { + .data-cascade-button-group:has(.header-content) { + row-gap: 1rem; + } + + .data-cascade-button-group:has(.header-content) > nav { + width: 100%; + } +} + +.header-content { + flex: 1; + min-height: 184px; + padding-right: 2rem; + padding-left: clamp(20px, 3vw, 50px); +} + +.header-content > *:first-child { + margin-top: 0; +} + +.header-content > *:last-child { + margin-bottom: 0; +} + +/* Data Stream */ + +.data-wrapper { + flex: 1; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + align-items: flex-end; + max-width: 100%; + height: 164px; + overflow: hidden; + padding-top: 10px; + padding-right: 2rem; + padding-left: clamp(20px, 3vw, 50px); + column-gap: 1rem; + font-size: 1.25rem; +} + +.data-column { + display: grid; + grid-template-columns: 1fr; + line-height: 1; + --dc-row-height: 40px; +} + +.data-bullet { + width: 38px; + height: 22px; + border-radius: 50%; + background-color: var(--font-color); +} + +.dc-row-1, +.dc-row-2, +.dc-row-3, +.dc-row-4 { + display: flex; + align-items: center; + min-height: var(--dc-row-height); +} + +.dc-row-1:has(.data-bullet) { + padding-inline: 1rem; +} + +.dc-row-2:has(.data-bullet) { + padding-inline: 1rem; +} + +.dc-row-3:has(.data-bullet) { + padding-inline: 1rem; +} + +.dc-row-4:has(.data-bullet) { + padding-inline: 1rem; +} + +.darkspace { + min-width: 3rem; + text-align: center; + justify-content: center; +} + +.darkfont { + color: black; +} + +@media (max-width: 1300px) { + .data-wrapper { + height: 130px; + font-size: 1rem; + } + + .data-column { + --dc-row-height: 30px; + } + + .data-bullet { + transform: scale(.8); + } +} + +@media (max-width: 1100px) { + .hide-data { + display: none; + } + + .data-wrapper { + column-gap: .575rem; + padding-right: 1rem; + } +} + +@media (max-width: 740px) { + .data-wrapper { + display: none; + } +} + +/* Navigation & buttons */ + +.lcars-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + margin-block: 1rem; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1 a { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + height: clamp(90px, 11vw, 160px); + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + transition: width 1s; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1-button { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + min-height: clamp(90px, 11vw, 160px); + overflow: hidden; + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +nav { + display: flex; + flex-wrap: wrap; + align-self: center; + width: calc(var(--nav-width) + var(--nav-width) + 1rem); + justify-content: flex-end; + column-gap: .5rem; + row-gap: .65rem; +} + +@media (max-width: 1500px) { + nav { + column-gap: .375rem; + row-gap: .5rem; + } +} + +@media (max-width: 1440px) { + .data-cascade-button-group:has(.header-content) { + row-gap: 1rem; + } + + .data-cascade-button-group:has(.header-content) > nav { + width: 100%; + } +} + +nav a, +nav button, +.buttons button { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + width: var(--nav-width); + height: calc(var(--nav-width) / 2.8); + padding-inline: 1.5rem; + padding-bottom: .7rem; + border-radius: 100vmax; + background-color: var(--button-color); + text-align: right; + line-height: 1.175; + text-decoration: none; + text-transform: uppercase; + font-weight: bold; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +nav a:nth-child(1), +nav button:nth-child(1) { + background-color: var(--nav-1-color); +} + +nav a:nth-child(2), +nav button:nth-child(2) { + background-color: var(--nav-2-color); +} + +nav a:nth-child(3), +nav button:nth-child(3) { + background-color: var(--nav-3-color); +} + +nav a:nth-child(4), +nav button:nth-child(4) { + background-color: var(--nav-4-color); +} + +@media (max-width: 1300px) { + nav { + padding-left: .5rem; + gap: .5rem; + } + + nav button { + padding-bottom: .5rem; + font-size: .875rem; + padding-inline: 1.25rem; + } +} + +@media (max-width: 780px) { + nav { + flex: 1; + } + + .data-cascade-button-group:has(.header-content) > *:nth-child(2) { + flex: none; + } +} + +@media (max-width: 450px) { + nav a, + nav button { + height: 63px; + } +} + +.buttons { + display: flex; + flex-wrap: wrap; + gap: .5rem; + margin-block: 2rem; +} + +.justify-space-between { + justify-content: space-between; +} + +.justify-center { + justify-content: center; +} + +.justify-flex-end { + justify-content: flex-end; +} + +.justify-space-around { + justify-content: space-around; +} + +.justify-space-evenly { + justify-content: space-evenly; +} + +.buttons a, +.buttons button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.sidebar-nav button, +.sidebar-nav a, +.sidebar-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + min-height: 3.75rem; + width: var(--lfw); + background-color: var(--button-color-sidebar); + border-radius: 0; + border-bottom: var(--panel-border); + padding: var(--left-frame-padding); + text-decoration: none; + text-align: right; + word-break: break-all; + text-transform: uppercase; + color: black; +} + +@media (max-width: 1300px) { + .lcars-button, + .buttons a, + .buttons button { + width: 200px; + height: 74px; + } +} + +.panel-button { + display: flex; + justify-content: flex-end; + width: var(--lfw); + border-radius: 0; + padding: var(--left-frame-padding); + border-bottom: var(--panel-border); +} + +.pan-0 /* use this if you're not mimicking an established panel */ { + height: 18vh; + max-height: 240px; + background-color: var(--button-color-sidebar); +} + +.pan-4 { + height: 14vh; + background-color: var(--panel-4-color) !important; +} + +.pan-5 { + height: 30vh; + background-color: var(--panel-5-color) !important; +} + +.pan-7 { + height: 30vh; + background-color: var(--panel-7-color) !important; +} + +.text-bottom { + align-items: flex-end; +} + +#topBtn { + display: none; + position: fixed; + bottom: 0; + z-index: 99; + border-radius: 0; + border-top: var(--panel-border); + border-right: none; + border-bottom: var(--panel-border); + border-left: none; + outline: none; + width: var(--lfw); + padding: var(--left-frame-padding); + padding-bottom: 10vh; + background-color: var(--panel-top-button-color); + text-align: right; + line-height: 1; + font-weight: bold; + text-transform: uppercase; + color: black; + cursor: pointer; +} + +#topBtn:hover { + filter: brightness(115%); +} + +#topBtn:active { + filter: brightness(80%); +} + +@media (max-width: 525px) { + .sidebar-button, + .sidebar-nav a, + .sidebar-nav button, + .panel-button { + font-size: .75rem; + } + + #topBtn { + padding-bottom: 6vh; + } +} + +.bar-panel { + display: flex; + height: var(--bar-height); +} + +.first-bar-panel { + margin-top: 2.5vh; + position: relative; +} + +.bar-1, +.bar-2, +.bar-3, +.bar-4, +.bar-5, +.bar-6, +.bar-7, +.bar-8, +.bar-9, +.bar-10 { + height: var(--bar-height); +} + +.bar-1, +.bar-2, +.bar-3, +.bar-4, +.bar-6, +.bar-7, +.bar-8, +.bar-9 { + border-right: var(--bar-border); +} + +.divider { + display: flex; + height: var(--divider-height); + z-index: 1999; +} + +.block-left { + width: var(--lfw); + height: var(--divider-height); +} + +.block-right { + flex: 1; + height: var(--divider-height); + position: relative; +} + +.block-row { + display: flex; + width: 100%; + height: var(--divider-height); + padding-right: .4rem; + padding-left: 1px; +} + +.bar-11 { + height: var(--divider-height); + width: var(--bar-1-6-width); +} + +.bar-12 { + height: var(--divider-height); + width: var(--bar-2-7-width); + +} + +.bar-13 { + height: var(--divider-height); + width: var(--bar-3-8-width); +} +.bar-14 { + flex: 1; + height: var(--divider-height); + z-index: 1999; +} + +.bar-1 { + width: var(--bar-1-6-width); + background-color: var(--bar-1-color); +} + +.bar-2 { + width: var(--bar-2-7-width); + background-color: var(--bar-2-color); +} + +.bar-3 { + width: var(--bar-3-8-width); + background-color: var(--bar-3-color); +} + +.bar-4 { + flex: 1; + position: relative; + background-color: var(--bar-4-color); +} + +.bar-5 { + width: var(--bar-5-10-width); + background-color: var(--bar-5-color); +} + +.bar-6 { + width: var(--bar-1-6-width); + background-color: var(--bar-6-color); +} + +.bar-7 { + width: var(--bar-2-7-width); + background-color: var(--bar-7-color); +} + +.bar-8 { + width: var(--bar-3-8-width); + background-color: var(--bar-8-color); +} + +.bar-9 { + flex: 1; + position: relative; + background-color: var(--bar-9-color); +} + +.bar-4::after { + content: ""; + display: block; + width: var(--bar-cut-width); + height: 48%; + background-color: black; + border-radius: 0 8px 0 0; + position: absolute; + left: 0; + bottom: 0; +} + +.bar-9::after { + content: ""; + display: block; + width: var(--bar-cut-width); + height: 48%; + background-color: black; + border-radius: 0 0 8px 0; + position: absolute; + left: 0; + top: 0; +} + +.bar-10 { + width: var(--bar-5-10-width); + background-color: var(--bar-10-color); +} + +/* Bottom half */ + +#gap { + margin-top: var(--divider-height); +} + +.panel-3, +.panel-4, +.panel-5 { + border-bottom: var(--panel-border); +} + +.panel-2, +.panel-3, +.panel-4, +.panel-5, +.panel-6, +.panel-7 { + padding: var(--left-frame-padding); +} + +.panel-4 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + min-height: 14vh; + background-color: var(--panel-4-color); +} + +.panel-5 { + height: 30vh; + background-color: var(--panel-5-color); +} + +/* Note: panel-6 height is fluid and governed by the amount of content on the page. Background color is inherited from :root --left-frame-color */ + +.panel-6 { + min-height: 15vh; +} + +.panel-7 { + height: 30vh; + border-top: var(--panel-border); + background-color: var(--panel-7-color); +} + +.right-frame { + flex: 1; + position: relative; +} + +.right-frame::before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to bottom right, var(--corner-color-bottom) 50%, #000 50%); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +.right-frame::after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-bottom); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +main { + padding-top: 1.5rem; + padding-bottom: .5rem; + padding-left: clamp(20px, 3vw, 50px); +} + +main > *:first-child, +article > *:first-child { + margin-top: 0; +} + +main:has(.floor-heading) > *:nth-child(2) { + margin-top: 0; +} + +.flexbox { + display: flex; + gap: 1.2rem; + flex-wrap: wrap; +} + +.col { + flex: 1 1 360px; +} + +.col > *:first-child { + margin-top: 0; +} + +h1, h2, h3, h4 { + margin-block: 1.75rem; + font-weight: normal; + line-height: 1.2; + text-transform: uppercase; + text-box: trim-both cap alphabetic; +} + +h1 { + font-size: clamp(1.5rem, 1.25rem + 3.5vw, 4rem); + text-align: right; + color: var(--h1-color); +} + +h2 { + font-size: clamp(1.4rem, 1.1rem + 2.25vw, 2.3rem); + color: var(--h2-color); +} + +h3 { + font-size: clamp(1.15rem, 1.05rem + 1.25vw, 1.875rem); + color: var(--h3-color); +} + +h4 { + font-size: clamp(1.025rem, 1rem + 1.125vw, 1.575rem); + color: var(--h4-color); +} + +.floor-heading { + display: flex; + justify-content: flex-end; + width: 100%; + position: fixed; + left: 50%; + transform: translate(-50%, 0); + bottom: 10px; + z-index:4; +} + +.floor-heading > * { + margin-block: 0; + width: fit-content; + padding: .5rem; + background-color: black; +} + +p { + margin-block: 1.75rem; + text-box: trim-both cap alphabetic; +} + +.caption { + margin-top: -1rem; + text-align: center; + font-size: var(--sub-fonts); +} + +.indent { + padding-left: 1.5rem; +} + +.postmeta { + margin-block: 1.25rem; + text-align: right; + font-size: clamp(1.2rem, 0.88rem + 1.28vw, 1.6rem); + line-height: 1.2; + text-transform: uppercase; +} + +article h1 { + margin-bottom: 0; +} + +hr { + margin-block: 1.5rem; + height: 6px; + border: none; + background-color: var(--font-color); + border-radius: 3px; +} + +blockquote { + margin-block: 1.75rem; + margin-left: 1.5rem; + padding-block: .25rem; + padding-left: 1.5rem; + position: relative; + text-box: trim-both cap alphabetic; +} + +blockquote::before { + content: ''; + display: block; + width: 10px; + height: 100%; + background-color: var(--font-color); + border-radius: 5px; + position: absolute; + left: 0; + top: 0; +} + +blockquote > *:first-child { + margin-top: 0; +} + +blockquote > * { + margin-bottom: 0; +} + +.flush { + margin-top: -1rem; +} + +.nomar { + margin-block: 0 !important; +} + +.go-center { + text-align: center !important; +} + +.go-right { + text-align: right !important; +} + +.go-left { + text-align: left !important; +} + +.go-big { + font-size: clamp(1.2rem, 1rem + 1vw, 1.275rem); +} + +.uppercase { + text-transform: uppercase; +} + +.strike { + text-decoration: line-through; + text-decoration-thickness: .15rem; +} + +.now { + white-space: nowrap; +} + +.big-sky { + margin-top: 5rem; +} + +.smoke-glass { + opacity: .35; +} + +strong { + font-weight: bold; +} + +code { + font-family: monospace; + font-size: .9rem; + color: var(--code-color); +} + +.code { + width: 100%; + min-height: 5rem; + padding-block: .5rem; + padding-inline: 1rem; + background-color: #232323; + border-color: #4c4c4c; + tab-size: 4; + font-family: monospace; + font-size: .85rem; + color: #dcdcdc; +} + +@keyframes blink { + 0% {opacity: 0} + 49%{opacity: 0} + 50% {opacity: 1} +} + +.blink-slow { + animation: blink 3500ms infinite; + animation-delay: 1s; +} + +.blink { + animation: blink 2s infinite; + animation-delay: 1s; +} + +.blink-fast { + animation: blink 1s infinite; + animation-delay: 1s; +} + +@keyframes pulse { + 0% {filter: brightness(1.0)} + 50% {filter: brightness(.25)} +} + +.pulse { + animation: pulse 2300ms infinite; +} + +.pulse-rate-high { + animation: pulse 1s infinite; +} + +.accordion-wrapper { + display: block; + margin: 1.75rem auto; + width: 100%; +} + +.limit-width { + max-width: 1240px; +} + +.accordion { + display: flex; + align-items: center; + min-height: 3rem; + width: 100%; + padding-right: 2.75rem; + padding-left: 1rem; + border-radius: 100vmax; + background-color: var(--alpha-blue); + border-left: solid 3rem var(--night-rain); + text-align: left; + font-size: 1.25rem; + color: black; + cursor: pointer; + transition: 0.4s ease; + position: relative; +} + +.active, .accordion:hover { + background-color: var(--beta-blue); + border-left-color: var(--radioactive); + filter: none; +} + +.accordion:before { + content: ''; + display: block; + width: .5rem; + height: 4rem; + background-color: black; + position: absolute; + top: 0; + left: 0; +} + +.accordion:after { + display: block; + content: '\276F'; + position: absolute; + right: 1.5rem; + top: 21%; + transform: rotate(90deg); + transition: 0.4s; + font-weight: bold; + color: black; +} + +.active:after { + content: "\276F"; + transform:rotate(-90deg); + transition: 0.4s ease; +} + +.accordionContent { + padding-block: .5rem; + padding-inline: 3.5rem; + max-height: 0; + overflow: hidden; + transition: max-height 0.25s ease-out; +} + +.accordionContent ul { + margin-left: 0; +} + +@media (max-width: 525px) { + .accordion { + min-height: 2.5rem; + font-size: 1rem; + border-left-width: 2.5rem; + } +} + +/* Images */ + +.pics-right { + float: right; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; +} + +.pics-left { + float: left; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; +} + +@media (max-width: 1060px) { + .pics-right, + .pics-left { + float: none; + margin-inline: auto; + } + + .pics-right img, + .pics-left img { + margin-inline: auto; + } +} + +.pics { + margin-block: 2rem; + margin-inline: auto; +} + +.border { + border: 2px solid var(--font-color); +} + +.lcars-list { + margin-block: 1.15rem; + margin-left: 2rem; + list-style: none; +} + +.lcars-list li { + position: relative; + padding-block: .45rem; + padding-left: 2.25rem; + text-box: trim-both cap alphabetic; +} + +.lcars-list li::before { + content: ''; + display: block; + width: 34px; + height: 18px; + border-radius: 50%; + background-color: var(--font-color); + position: absolute; + top: .45rem; + left: 0; +} + +@media (max-width: 650px) { + + .lcars-list { + margin-left: .5rem; + } + + .lcars-list li::before { + transform: scale(90%); + } +} + +.lcars-bar { + margin-block: 1.75rem; + height: clamp(15px, 2vh, 25px); + background: transparent; + border-right: clamp(15px, 2vh, 25px) solid var(--lcars-bar-end-color); + border-left: clamp(15px, 2vh, 25px) solid var(--lcars-bar-start-color); + border-radius: 100vmax; + position: relative; +} + +.lcars-bar::after { + content: ''; + display: block; + height: clamp(15px, 2vh, 25px); + width: 100%; + background-color: var(--lcars-bar-color); + border-right: .8vh solid black; + border-left: .8vh solid black; + position: absolute; + top: 0; + left: 0; +} + +.lcars-text-bar { + display: flex; + position: relative; + height: clamp(16px, 4vh, 41px); + margin-block: 2.75rem; + overflow: visible; + border-radius: 100vmax; + background-color: var(--lcars-bar-color); + border-right: clamp(16px, 4vh, 43px) solid var(--lcars-bar-end-color); + border-left: clamp(16px, 4vh, 43px) solid var(--lcars-bar-start-color); +} + +.the-end { + justify-content: flex-end; +} + +.lcars-text-bar h2, +.lcars-text-bar h3, +.lcars-text-bar h4, +.lcars-text-bar span { + margin-block: 0; + background-color: black; + height: clamp(20px, 5.5vh, 60px); /* Setting height is a Firefox fix */ + overflow: visible; + border-top: 1px solid black; + padding-inline: 1vh; + font-size: clamp(17px, 4.5vh, 46px); + line-height: 1; + text-transform: uppercase; + color: var(--lcars-bar-text-color); + z-index: 1; + text-box: trim-both cap alphabetic; +} + +.lcars-text-bar::before { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + left: 0; + overflow: hidden; +} + +.lcars-text-bar::after { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + right: 0; + overflow: hidden; +} + +/* LCARS Image Frame */ + +.image-frame { + margin: 2.75rem auto; + width: fit-content; + background: linear-gradient(var(--primary-color) 56%, var(--secondary-color) 44%); + border-radius: 50px 25px 0 50px; + position: relative; +} + +.image-frame::before { + content: ''; + display: block; + width: 40px; + height: 40px; + background-color: black; + position: absolute; + right: 0; + top: 0; +} + +.image-frame::after { + content: ''; + display: block; + border-top: var(--spacers) solid black; + border-bottom: var(--spacers) solid black; + width: 45px; + height: 80px; + background-color: var(--secondary-color); + position: absolute; + left: 0; + top: 56%; +} + +.imgf-title { + display: flex; + justify-content: flex-end; + height: 40px; + border-right: 40px solid var(--secondary-color); + border-radius: 25px 100vh 100vh 0; + position: relative; + z-index: 1; + text-align: right; +} + +.h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + border-right: var(--spacers) solid black; +} + +.imgf-title h4 { + margin-block: 0; + width: fit-content; + background-color: black; + padding-left: var(--spacers); + font-size: 2.05rem; + color: var(--title-color); + line-height: 40px; + text-transform: uppercase; +} + +.imgf-image-body { + margin-left: 45px; + background-color: black; + width: fit-content; + padding: 1rem; + border-radius: 28px 0 0 28px; +} + +.image-holder { + width: fit-content; + padding: 1rem; + border: 2px solid var(--image-border-color); + border-radius: 20px; +} + +.imgf-base { + display: grid; + grid-template-columns: 20% 13% 35px 15% 1fr; + margin-left: 80px; + border-left: var(--spacers) solid black; +} + +.imgf-block-1 { + height: var(--frame-height); + background-color: var(--accent-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-2 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-3 { + height: var(--frame-height); + background-color: black; + border-right: 10px solid var(--secondary-color); + border-left: 10px solid var(--accent-color); +} + +.imgf-block-4 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-left: var(--spacers) solid black; +} + +.imgf-block-5 { + height: var(--frame-height); + background-color: black; +} + +@media (max-width: 750px) { + + .image-frame { + border-radius: 40px 25px 0 40px; + } + + .image-frame::after { + width: 25px; + height: 60px; + top: 50%; + } + + .imgf-title { + height: 25px; + border-right: 24px solid var(--secondary-color); + } + + .h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + } + + .imgf-title h4 { + font-size: 1.45rem; + } + + .imgf-image-body { + margin-left: 25px; + padding: .75rem; + border-radius: 20px 0 0 20px; + } + + .image-holder { + padding: .75rem; + border-radius: 10px; + } +} + +/* Color Styles */ + +.font-alpha-blue { + color: var(--alpha-blue) !important; +} + +.button-alpha-blue, +.background-alpha-blue, +.bullet-alpha-blue::before { + background-color: var(--alpha-blue) !important; +} + +.font-arctic-ice { + color: var(--arctic-ice) !important; +} + +.button-arctic-ice, +.background-arctic-ice, +.bullet-arctic-ice::before { + background-color: var(--arctic-ice) !important; +} + +.font-arctic-snow { + color: var(--arctic-snow) !important; +} + +.button-arctic-snow, +.background-snow, +.bullet-arctic-snow::before { + background-color: var(--arctic-snow) !important; +} + +.font-radioactive { + color: var(--radioactive) !important; +} + +.button-radioactive, +.background-radioactive, +.bullet-radioactive::before { + background-color: var(--radioactive) !important; +} + +.font-beta-blue { + color: var(--beta-blue) !important; +} + +.button-beta-blue, +.background-beta-blue, +.bullet-beta-blue::before { + background-color: var(--beta-blue) !important; +} + +.font-night-cloud { + color: var(--night-cloud) !important; +} + +.button-night-cloud, +.background-night-cloud, +.bullet-night-cloud::before { + background-color: var(--night-cloud) !important; +} + +.font-night-rain { + color: var(--night-rain) !important; +} + +.button-night-rain, +.background-night-rain, +.bullet-night-rain::before { + background-color: var(--night-rain) !important; +} + +.font-sunset-red { + color: var(--sunset-red) !important; +} + +.button-sunset-red, +.background-sunset-red, +.bullet-sunset-red::before { + background-color: var(--sunset-red) !important; +} + +/* Footer */ + +footer { + margin-top: 2.5vw; + padding-bottom: 3rem; + padding-left: clamp(20px, 3vw, 50px); + font-size: .875rem; +} + +.headtrim { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + top: 0; + left: 0; + z-index: 999; +} + +.baseboard { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + bottom: 0; + left: 0; + z-index: 999; +} + +/* Grouped @media */ + +@media (max-width: 650px) { + + .first-bar-panel { + margin-top: .75rem; + } + + .bar-4::before { + height: 5px; + top: -12px; + } + + .bar-9::before { + height: 5px; + bottom: -12px; + } +} + +@media (max-width: 525px) { + + body { + padding: .25rem; + } + + .baseboard { + display: none; + } + + .wrap { + padding-left: 0; + padding-right: .25rem; + } + + .left-frame { + padding-top: 8vh; + } + + .divider { + padding-right: 10px; + } + + .block-row { + padding-right: 0; + padding-left: .25rem; + } + + .floor-heading { + bottom: 0; + } + + .hop { + display: none; + } + + blockquote { + margin-left: .75rem; + } +} + +@-moz-document url-prefix() { + + main { + padding-top: clamp(1rem, 4vw, 30px); + } + + h1, h2, h3, h4, p { + margin-block: 1.15rem; + } + + .postmeta { + margin-top: .75rem; + } + + .lcars-text-bar h2, + .lcars-text-bar h3, + .lcars-text-bar h4, + .lcars-text-bar span { + position: absolute; + top: -.7vh; + } +} \ No newline at end of file diff --git a/static/theme/LCARS/assets/lower-decks.css b/static/theme/LCARS/assets/lower-decks.css new file mode 100644 index 0000000..32c9827 --- /dev/null +++ b/static/theme/LCARS/assets/lower-decks.css @@ -0,0 +1,1856 @@ +@charset "utf-8"; + +/* + + CSS Document + LCARS Lower Decks Theme + Version 24.2 + By Jim Robertus www.thelcars.com + Modified: 2025 Jul 27 + +*/ + +:root { + font-size: 1.375rem; + color-scheme: dark; + --lfw: 240px; + --butter: #fec; + --daybreak: #f91; + --harvestgold: #fa4; + --honey: #fc9; + --october-sunset: #f40; + --orange: #f70; + --rich-pumpkin: #c50; + --left-frame-top-color: var(--october-sunset); + --left-frame-color: var(--october-sunset); /* panel-6 inherits this color */ + --left-frame-padding: .75rem; + --corner-color-top: var(--october-sunset); + --corner-color-bottom: var(--october-sunset); + --panel-1-color: var(--orange); + --panel-4-color: var(--orange); + --panel-5-color: var(--harvestgold); + --panel-7-color: var(--harvestgold); + --panel-top-button-color: var(--honey); + --bar-height: 28px; + --bar-1-6-width: 10%; + --bar-2-7-width: 12%; + --bar-3-8-width: 17%; + --bar-5-10-width: 5%; + --bar-1-color: var(--october-sunset); + --bar-2-color: var(--orange); + --bar-3-color: var(--honey); + --bar-4-color: var(--harvestgold); + --bar-5-color: var(--october-sunset); + --bar-6-color: var(--october-sunset); + --bar-7-color: var(--orange); + --bar-8-color: var(--honey); + --bar-9-color: var(--harvestgold); + --bar-10-color: var(--october-sunset); +/* + NOTE: --font-color also sets the following: + 1. horizontal line
color + 2. lcars-list bullet color + 3. blockquote border color + 4. images with the *border* class border color +*/ + --font-color: var(--daybreak); + --sub-fonts: .875rem; + --banner-color: var(--honey); + --data-cascade-color: var(--orange); + --light-color: white; + --h1-color: var(--honey); + --h2-color: var(--honey); + --h3-color: var(--honey); + --h4-color: var(--honey); + --light-color: var(--butter); + --link-color: var(--october-sunset); + --code-color: var(--october-sunset); + --nav-width: 240px; + --nav-1-color: var(--honey); + --nav-2-color: var(--harvestgold); + --nav-3-color: var(--orange); + --nav-4-color: var(--october-sunset); + --button-color: var(--harvestgold); + --button-color-sidebar: var(--october-sunset); + --radius-top: 0 0 0 100px; + --radius-bottom: 100px 0 0 0; + --radius-content-top: 0 0 0 44px; + --radius-content-bottom: 44px 0 0 0; + --panel-border: .4rem solid black; + --bar-border: .4rem solid black; + --bar-cut-width: 32%; + --bar-cut-out-width: 40%; + --bar-cut-out-top-color: var(--october-sunset); + --bar-cut-out-bottom-color: var(--october-sunset); + --divider-height: .75rem; + --lcars-bar-color: var(--october-sunset); + --lcars-bar-start-color: var(--harvestgold); + --lcars-bar-end-color: var(--harvestgold); + --lcars-bar-text-color: var(--harvestgold); + +/* Image Frame */ + + --image-border-color: var(--honey); + --primary-color: var(--orange); + --secondary-color: var(--harvestgold); + --accent-color: var(--honey); + --spacers: .65rem; + --frame-height: 40px; +} + +@media (max-width: 1500px) { + :root { + --lfw: 200px; + --nav-width: 210px; + --bar-height: 20px; + --divider-height: .5rem; + } +} + +@media (max-width: 1300px) { + :root { + font-size: 1.2rem; + --sub-fonts: .9rem; + --lfw: 180px; + --radius-top: 0 0 0 80px; + --radius-bottom: 80px 0 0 0; + --radius-content-top: 0 0 0 30px; + --radius-content-bottom: 30px 0 0 0; + --nav-width: 180px; + } +} + +@media (max-width: 950px) { + :root { + --lfw: 150px; + } +} + +@media (max-width: 750px) { + :root { + --lfw: 120px; + --radius-top: 0 0 0 60px; + --radius-bottom: 60px 0 0 0; + --radius-content-top: 0 0 0 24px; + --radius-content-bottom: 24px 0 0 0; + --bar-height: 16px; + --spacers: .5rem; + --frame-height: 25px; + } +} + +@media (max-width: 525px) { + :root { + --lfw: 62px; + --left-frame-padding: .5rem; + --radius-top: 0 0 0 30px; + --radius-bottom: 30px 0 0 0; + --bar-height: 10px; + --panel-border: .25rem solid black; + --bar-border: .25rem solid black; + --divider-height: .345rem; + } +} + +@media (max-width: 450px) { + :root { + --nav-width: 48%; + } +} + +*, *:after, *:before { + box-sizing: border-box; +} + +* { + margin: 0; + padding: 0; + font: inherit; +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +input, textarea, button, select { + font: inherit; +} + +@font-face { + font-family: 'Antonio'; + font-weight: 400; + src: url('Antonio-Regular.woff2') format('woff2'), + url('Antonio-Regular.woff') format('woff'); +} + +@font-face { + font-family: 'Antonio'; + font-weight: 700; + src: url('Antonio-Bold.woff2') format('woff2'), + url('Antonio-Bold.woff') format('woff') +} + +html { + scroll-behavior: smooth; +} + +body { + display: flex; + padding-top: 10px; + padding-left: 5px; + background-color: black; + font-family: 'Antonio', 'Arial Narrow', 'Avenir Next Condensed', sans-serif; + font-weight: 400; + line-height: 1.5; + color: var(--font-color); +} + +a { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: .2rem; + color: var(--link-color); +} + +a:hover { + filter: brightness(115%); + animation: none; +} + +a:active { + filter: brightness(80%); + outline: none; +} + +button { + border: none; + outline: none; + color: black; + transition: width 1s; +} + +button:hover { + cursor: pointer; + animation: none; + filter: brightness(115%); + color: black; +} + +button:active { + filter: brightness(85%); +} + +.wrap-all { + width: 100%; +} + +.wrap { + display: flex; + margin-inline: auto; + padding-left: 5px; + padding-right: 15px; + overflow: hidden; +} + +.divider { + display: flex; + width: 100%; + flex-wrap: nowrap; + height: var(--divider-height); +} + +.scroll-top { + display: none; +} + +.left-frame-top, +.left-frame { + width: var(--lfw); + text-align: right; + font-size: clamp(.875rem, 2vw, 1rem); + line-height: 1.2; + font-weight: bold; + color: black; + transition: width 1s; +} + +.left-frame-top { + background-color: var(--left-frame-top-color); + border-radius: var(--radius-top); +} + +.left-frame-top a, +.left-frame a { + text-decoration: none; + color: black; +} + +.left-frame { + display: flex; + flex-direction: column; + justify-content: space-between; + padding-top: 19vh; + background-color: var(--left-frame-color); + border-radius: var(--radius-bottom); +} + +.left-frame:has(.sidebar-nav) { + padding-top: 10vh; +} + +.left-frame:has(button) { + padding-top: 10vh; +} + +@keyframes panel-1-color-change { + 0%, + 49.99% { + background-color: var(--orange); + } + 50%, + 100% { + background-color: var(--butter); + } +} + +.right-frame-top { + flex: 1; + align-content: flex-end; + position: relative; +} + +.right-frame-top::before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to top right, var(--corner-color-top) 50%, black 50%); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +.right-frame-top::after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-top); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +.banner { + padding-bottom: 1rem; + padding-left: 5px; + text-align: right; + text-transform: uppercase; + line-height: 1.1; + font-size: clamp(1.25rem, 0.75rem + 4vw, 3.75rem); + color: var(--banner-color); +} + +.banner a { + color: var(--banner-color); + text-decoration: none; +} + +.data-cascade-button-group { + display: flex; + justify-content: flex-end; + align-items: flex-start; + flex-wrap: wrap; + padding-bottom: .75rem; +} + +.data-cascade-button-group:has(.header-content) { + justify-content: flex-start; +} + +@media (max-width: 1440px) { + .data-cascade-button-group:has(.header-content) { + row-gap: 1rem; + } + + .data-cascade-button-group:has(.header-content) > nav { + width: 100%; + } +} + +.header-content { + flex: 1; + min-height: 184px; + padding-right: 2rem; + padding-left: clamp(20px, 3vw, 50px); +} + +.header-content > *:first-child { + margin-top: 0; +} + +.header-content > *:last-child { + margin-bottom: 0; +} + +/* Data Stream */ + +.data-wrapper { + flex: 1; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + align-items: flex-end; + max-width: 100%; + height: 184px; + overflow: hidden; + padding-top: 10px; + padding-right: 2rem; + padding-left: clamp(20px, 3vw, 50px); + column-gap: .5rem; + font-size: 1.25rem; +} + +.data-column { + display: grid; + grid-template-columns: 1fr; + text-align: right; + line-height: 1; + --dc-row-height: 45px; +} + +@media (max-width: 1300px) { + .data-wrapper { + height: 150px; + font-size: 1rem; + } + + .data-column { + --dc-row-height: 35px; + } + +} + +@media (max-width: 740px) { + .data-wrapper { + display: none; + } +} + +.dc-row-1 { + min-height: var(--dc-row-height); + color: var(--october-sunset); +} + +.dc-row-2 { + min-height: var(--dc-row-height); + color: var(--honey); +} + +.dc-row-3 { + min-height: var(--dc-row-height); + color: var(--october-sunset); +} + +.dc-row-4 { + min-height: var(--dc-row-height); + color: var(--harvestgold); +} + +.darkspace { + min-width: 4rem; + text-align: center; + color: var(--october-sunset); +} + +.darkfont { + color: black; +} + +@media (max-width: 1100px) { + .hide-data { + display: none; + } + + .data-wrapper { + padding-right: 1rem; + } +} + +/* Navigation & buttons */ + +.lcars-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + margin-block: 1rem; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1 a { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + height: clamp(90px, 11vw, 160px); + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + transition: width 1s; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1-button { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + min-height: clamp(90px, 11vw, 160px); + overflow: hidden; + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +nav { + display: flex; + flex-wrap: wrap; + align-self: center; + width: calc(var(--nav-width) + var(--nav-width) + 1rem); + justify-content: flex-end; + column-gap: .5rem; + row-gap: .65rem; +} + +@media (max-width: 1500px) { + nav { + column-gap: .375rem; + row-gap: .5rem; + } +} + +@media (max-width: 1440px) { + .data-cascade-button-group:has(.header-content) { + row-gap: 1rem; + } + + .data-cascade-button-group:has(.header-content) > nav { + width: 100%; + } +} + +nav a, +nav button, +.buttons button { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + width: var(--nav-width); + height: calc(var(--nav-width) / 2.8); + padding-inline: 1.5rem; + padding-bottom: .7rem; + border-radius: 100vmax; + background-color: var(--button-color); + text-align: right; + line-height: 1.175; + text-decoration: none; + text-transform: uppercase; + font-weight: bold; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +nav a:nth-child(1), +nav button:nth-child(1) { + background-color: var(--nav-1-color); +} + +nav a:nth-child(2), +nav button:nth-child(2) { + background-color: var(--nav-2-color); +} + +nav a:nth-child(3), +nav button:nth-child(3) { + background-color: var(--nav-3-color); +} + +nav a:nth-child(4), +nav button:nth-child(4) { + background-color: var(--nav-4-color); +} + +@media (max-width: 1300px) { + nav { + padding-left: .5rem; + gap: .5rem; + } + + nav button { + padding-bottom: .5rem; + font-size: .875rem; + padding-inline: 1.25rem; + } +} + +@media (max-width: 780px) { + nav { + flex: 1; + } + + .data-cascade-button-group:has(.header-content) > *:nth-child(2) { + flex: none; + } +} + +@media (max-width: 450px) { + nav a, + nav button { + height: 63px; + } +} + +.buttons { + display: flex; + flex-wrap: wrap; + gap: .5rem; + margin-block: 2rem; +} + +.justify-space-between { + justify-content: space-between; +} + +.justify-center { + justify-content: center; +} + +.justify-flex-end { + justify-content: flex-end; +} + +.justify-space-around { + justify-content: space-around; +} + +.justify-space-evenly { + justify-content: space-evenly; +} + +.buttons a, +.buttons button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.sidebar-nav button, +.sidebar-nav a, +.sidebar-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + min-height: 3.75rem; + width: var(--lfw); + background-color: var(--button-color-sidebar); + border-radius: 0; + border-bottom: var(--panel-border); + padding: var(--left-frame-padding); + text-decoration: none; + text-align: right; + word-break: break-all; + text-transform: uppercase; + color: black; +} + +@media (max-width: 1300px) { + .lcars-button, + .buttons a, + .buttons button { + width: 200px; + height: 74px; + } +} + +.panel-button { + display: flex; + justify-content: flex-end; + width: var(--lfw); + border-radius: 0; + padding: var(--left-frame-padding); + border-bottom: var(--panel-border); +} + +.pan-0 /* use this if you're not mimicking an established panel */ { + height: 18vh; + max-height: 240px; + background-color: var(--button-color-sidebar); +} + +.pan-4 { + height: 14vh; + background-color: var(--panel-4-color) !important; +} + +.pan-5 { + height: 30vh; + background-color: var(--panel-5-color) !important; +} + +.pan-7 { + height: 30vh; + background-color: var(--panel-7-color) !important; +} + +.text-bottom { + align-items: flex-end; +} + +#topBtn { + display: none; + position: fixed; + bottom: 0; + z-index: 99; + border-radius: 0; + border-top: var(--panel-border); + border-right: none; + border-bottom: var(--panel-border); + border-left: none; + outline: none; + width: var(--lfw); + padding: var(--left-frame-padding); + padding-bottom: 10vh; + background-color: var(--panel-top-button-color); + text-align: right; + line-height: 1; + font-weight: bold; + text-transform: uppercase; + color: black; + cursor: pointer; +} + +#topBtn:hover { + filter: brightness(115%); +} + +#topBtn:active { + filter: brightness(80%); +} + +@media (max-width: 525px) { + .sidebar-button, + .sidebar-nav a, + .sidebar-nav button, + .panel-button { + font-size: .75rem; + } + + #topBtn { + padding-bottom: 6vh; + } +} + +zack { + +} + +.bar-panel { + display: flex; + height: var(--bar-height); +} + +.first-bar-panel { + margin-top: 2.5vh; + position: relative; +} + +.bar-1, +.bar-2, +.bar-3, +.bar-4, +.bar-5, +.bar-6, +.bar-7, +.bar-9, +.bar-10 { + height: var(--bar-height); +} + +.bar-1, +.bar-2, +.bar-3, +.bar-6, +.bar-7, +.bar-8 { + border-right: var(--bar-border); +} + +.bar-5, +.bar-10 { + border-left: var(--bar-border); +} + +.bar-1 { + width: var(--bar-1-6-width); + background-color: var(--bar-1-color); +} + +.bar-2 { + width: var(--bar-2-7-width); + background-color: var(--bar-2-color); +} + +.bar-3 { + width: var(--bar-3-8-width); + background-color: var(--bar-3-color); +} + +.bar-4 { + flex: 1; + position: relative; + background-color: var(--bar-4-color); + z-index: -1; +} + +.bar-5 { + width: var(--bar-5-10-width); + background-color: var(--bar-5-color); +} + +.bar-6 { + width: var(--bar-1-6-width); + background-color: var(--bar-6-color); +} + +.bar-7 { + width: var(--bar-2-7-width); + background-color: var(--bar-7-color); +} + +.bar-8 { + width: var(--bar-3-8-width); + background-color: var(--bar-8-color); +} + +.bar-9 { + flex: 1; + position: relative; + background-color: var(--bar-9-color); + z-index: -1; +} + +.bar-4::before { + content: ''; + display: block; + width: var(--bar-cut-out-width); + height: 8px; + background-color: var(--bar-cut-out-top-color); + position: absolute; + top: -22px; + left: 0; +} + +.bar-4::after { + content: ""; + display: block; + width: var(--bar-cut-width); + height: 48%; + background-color: black; + border-radius: 0 8px 0 0; + position: absolute; + left: 0; + bottom: 0; + z-index: -1; +} + +.bar-9::before { + content: ''; + display: block; + width: var(--bar-cut-out-width); + height: 8px; + background-color: var(--bar-cut-out-bottom-color); + position: absolute; + bottom: -22px; + left: 0; +} + +.bar-9::after { + content: ""; + display: block; + width: var(--bar-cut-width); + height: 48%; + background-color: black; + border-radius: 0 0 8px 0; + position: absolute; + left: 0; + top: 0; + z-index: -1; +} + +.bar-10 { + width: var(--bar-5-10-width); + background-color: var(--bar-10-color); +} + +/* Bottom half */ + +#gap { + margin-top: var(--divider-height); +} + +.panel-3, +.panel-4, +.panel-5 { + border-bottom: var(--panel-border); +} + +.panel-2, +.panel-3, +.panel-4, +.panel-5, +.panel-6, +.panel-7 { + padding: var(--left-frame-padding); +} + +.panel-4 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + min-height: 14vh; + background-color: var(--panel-4-color); +} + +.panel-5 { + height: 30vh; + background-color: var(--panel-5-color); +} + +/* Note: panel-6 height is fluid and governed by the amount of content on the page. Background color is inherited from :root --left-frame-color */ + +.panel-6 { + min-height: 15vh; +} + +.panel-7 { + height: 30vh; + border-top: var(--panel-border); + background-color: var(--panel-7-color); +} + +.right-frame { + flex: 1; + position: relative; +} + +.right-frame::before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to bottom right, var(--corner-color-bottom) 50%, #000 50%); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +.right-frame::after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-bottom); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +main { + padding-top: clamp(1.5rem, 5vw, 60px); + padding-bottom: .5rem; + padding-left: clamp(20px, 3vw, 50px); +} + +main > *:first-child, +article > *:first-child { + margin-top: 0; +} + +main:has(.floor-heading) > *:nth-child(2) { + margin-top: 0; +} + +.flexbox { + display: flex; + gap: 1.2rem; + flex-wrap: wrap; +} + +.col { + flex: 1 1 360px; +} + +.col > *:first-child { + margin-top: 0; +} + +h1, h2, h3, h4 { + margin-block: 1.75rem; + font-weight: normal; + line-height: 1.2; + text-transform: uppercase; + text-box: trim-both cap alphabetic; +} + +h1 { + font-size: clamp(1.5rem, 1.25rem + 3.5vw, 4rem); + text-align: right; + color: var(--h1-color); +} + +h2 { + font-size: clamp(1.4rem, 1.1rem + 2.25vw, 2.3rem); + color: var(--h2-color); +} + +h3 { + font-size: clamp(1.15rem, 1.05rem + 1.25vw, 1.875rem); + color: var(--h3-color); +} + +h4 { + font-size: clamp(1.025rem, 1rem + 1.125vw, 1.575rem); + color: var(--h4-color); +} + +.floor-heading { + display: flex; + justify-content: flex-end; + width: 100%; + position: fixed; + left: 50%; + transform: translate(-50%, 0); + bottom: 10px; + z-index:4; +} + +.floor-heading > * { + margin-block: 0; + width: fit-content; + padding-inline: .5rem; + padding-bottom: .5rem; + background-color: black; +} + +p { + margin-block: 1.75rem; + text-box: trim-both cap alphabetic; +} + +.caption { + margin-top: -1rem; + text-align: center; + font-size: var(--sub-fonts); +} + +.indent { + padding-left: 1.5rem; +} + +.postmeta { + margin-block: 1.25rem; + text-align: right; + font-size: clamp(1.2rem, 0.88rem + 1.28vw, 1.6rem); + line-height: 1.2; + text-transform: uppercase; +} + +article h1 { + margin-bottom: 0; +} + +code { + font-family: monospace; + font-size: .9rem; + color: var(--code-color); +} + +.code { + width: 100%; + min-height: 5rem; + padding-block: .5rem; + padding-inline: 1rem; + background-color: #232323; + border-color: #4c4c4c; + tab-size: 4; + font-family: monospace; + font-size: .85rem; + color: #dcdcdc; +} + +hr { + margin-block: 1.5rem; + height: 6px; + border: none; + background-color: var(--font-color); + border-radius: 3px; +} + +blockquote { + margin-block: 1.75rem; + margin-left: 1.5rem; + padding-block: .25rem; + padding-left: 1.5rem; + position: relative; + text-box: trim-both cap alphabetic; +} + +blockquote::before { + content: ''; + display: block; + width: 10px; + height: 100%; + background-color: var(--font-color); + border-radius: 5px; + position: absolute; + left: 0; + top: 0; +} + +blockquote > *:first-child { + margin-top: 0; +} + +blockquote > * { + margin-bottom: 0; +} + +.flush { + margin-top: -1rem; +} + +.nomar { + margin-block: 0 !important; +} + +.go-center { + text-align: center !important; +} + +.go-right { + text-align: right !important; +} + +.go-left { + text-align: left !important; +} + +.go-big { + font-size: clamp(1.2rem, 1rem + 1vw, 1.275rem); +} + +.uppercase { + text-transform: uppercase; +} + +.strike { + text-decoration: line-through; + text-decoration-thickness: .15rem; +} + +.now { + white-space: nowrap; +} + +.big-sky { + margin-top: 5rem; +} + +strong { + font-weight: bold; +} + +code { + font-family: monospace; + font-size: .9rem; + color: var(--code-color); +} + +.code { + width: 100%; + min-height: 5rem; + padding-block: .5rem; + padding-inline: 1rem; + tab-size: 4; + font-family: monospace; + font-size: .85rem; +} + +@keyframes blink { + 0% {opacity: 0} + 49%{opacity: 0} + 50% {opacity: 1} +} + +.blink-slow { + animation: blink 3500ms infinite; +} + +.blink { + animation: blink 2s infinite; +} + +.blink-fast { + animation: blink 1s infinite; +} + +@keyframes pulse { + 0% {filter: brightness(1.0)} + 50% {filter: brightness(.25)} +} + +.pulse { + animation: pulse 2300ms infinite; +} + +.pulse-rate-high { + animation: pulse 1s infinite; +} + +.accordion-wrapper { + display: block; + margin: 1.75rem auto; + width: 100%; +} + +.limit-width { + max-width: 1240px; +} + +.accordion { + display: flex; + align-items: center; + min-height: 3rem; + width: 100%; + padding-right: 2.75rem; + padding-left: 1rem; + border-radius: 100vmax; + background-color: var(--harvestgold); + border-left: solid 3rem var(--orange); + text-align: left; + font-size: 1.25rem; + color: black; + cursor: pointer; + transition: 0.4s ease; + position: relative; +} + +.active, .accordion:hover { + background-color: var(--honey); + border-left-color: var(--butter); + filter: none; +} + +.accordion:before { + content: ''; + display: block; + width: .5rem; + height: 4rem; + background-color: black; + position: absolute; + top: 0; + left: 0; +} + +.accordion:after { + display: block; + content: '\276F'; + position: absolute; + right: 1.5rem; + top: 21%; + transform: rotate(90deg); + transition: 0.4s; + font-weight: bold; + color: black; +} + +.active:after { + content: "\276F"; + transform:rotate(-90deg); + transition: 0.4s ease; +} + +.accordionContent { + padding-block: .5rem; + padding-inline: 3.5rem; + max-height: 0; + overflow: hidden; + transition: max-height 0.25s ease-out; +} + +.accordionContent ul { + margin-left: 0; +} + +@media (max-width: 525px) { + .accordion { + min-height: 2.5rem; + font-size: 1rem; + border-left-width: 2.5rem; + } +} + +/* Images */ + +.pics-right { + float: right; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; +} + +.pics-left { + float: left; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; +} + +@media (max-width: 1060px) { + .pics-right, + .pics-left { + float: none; + margin-inline: auto; + } + + .pics-right img, + .pics-left img { + margin-inline: auto; + } +} + +.pics { + margin-block: 2rem; + margin-inline: auto; +} + +.border { + border: 2px solid var(--font-color); +} + +.lcars-list { + margin-block: 1.15rem; + margin-left: 2rem; + list-style: none; +} + +.lcars-list li { + position: relative; + padding-block: .45rem; + padding-left: 2.25rem; + text-box: trim-both cap alphabetic; +} + +.lcars-list li::before { + content: ''; + display: block; + width: 34px; + height: 18px; + border-radius: 50%; + background-color: var(--font-color); + position: absolute; + top: .45rem; + left: 0; +} + +@media (max-width: 650px) { + + .lcars-list { + margin-left: .5rem; + } + + .lcars-list li::before { + transform: scale(90%); + } +} + +.lcars-bar { + margin-block: 1.75rem; + height: clamp(15px, 2vh, 25px); + background: transparent; + border-right: clamp(15px, 2vh, 25px) solid var(--lcars-bar-end-color); + border-left: clamp(15px, 2vh, 25px) solid var(--lcars-bar-start-color); + border-radius: 100vmax; + position: relative; +} + +.lcars-bar::after { + content: ''; + display: block; + height: clamp(15px, 2vh, 25px); + width: 100%; + background-color: var(--lcars-bar-color); + border-right: .8vh solid black; + border-left: .8vh solid black; + position: absolute; + top: 0; + left: 0; +} + +.lcars-text-bar { + display: flex; + position: relative; + height: clamp(16px, 4vh, 41px); + margin-block: 2.75rem; + overflow: visible; + border-radius: 100vmax; + background-color: var(--lcars-bar-color); + border-right: clamp(16px, 4vh, 43px) solid var(--lcars-bar-end-color); + border-left: clamp(16px, 4vh, 43px) solid var(--lcars-bar-start-color); +} + +.the-end { + justify-content: flex-end; +} + +.lcars-text-bar h2, +.lcars-text-bar h3, +.lcars-text-bar h4, +.lcars-text-bar span { + margin-block: 0; + background-color: black; + height: clamp(20px, 5.5vh, 60px); /* Setting height is a Firefox fix */ + overflow: visible; + border-top: 1px solid black; + padding-inline: 1vh; + font-size: clamp(17px, 4.5vh, 46px); + line-height: 1; + text-transform: uppercase; + color: var(--lcars-bar-text-color); + z-index: 1; + text-box: trim-both cap alphabetic; +} + +.lcars-text-bar::before { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + left: 0; + overflow: hidden; +} + +.lcars-text-bar::after { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + right: 0; + overflow: hidden; +} + +/* LCARS Image Frame */ + +.image-frame { + margin: 2.75rem auto; + width: fit-content; + background: linear-gradient(var(--primary-color) 56%, var(--secondary-color) 44%); + border-radius: 50px 25px 0 50px; + position: relative; +} + +.image-frame::before { + content: ''; + display: block; + width: 40px; + height: 40px; + background-color: black; + position: absolute; + right: 0; + top: 0; +} + +.image-frame::after { + content: ''; + display: block; + border-top: var(--spacers) solid black; + border-bottom: var(--spacers) solid black; + width: 45px; + height: 80px; + background-color: var(--secondary-color); + position: absolute; + left: 0; + top: 56%; +} + +.imgf-title { + display: flex; + justify-content: flex-end; + height: 40px; + border-right: 40px solid var(--secondary-color); + border-radius: 25px 100vh 100vh 0; + position: relative; + z-index: 1; + text-align: right; +} + +.h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + border-right: var(--spacers) solid black; +} + +.imgf-title h4 { + margin-block: 0; + width: fit-content; + background-color: black; + padding-left: var(--spacers); + font-size: 2.05rem; + color: var(--title-color); + line-height: 40px; + text-transform: uppercase; +} + +.imgf-image-body { + margin-left: 45px; + background-color: black; + width: fit-content; + padding: 1rem; + border-radius: 28px 0 0 28px; +} + +.image-holder { + width: fit-content; + padding: 1rem; + border: 2px solid var(--image-border-color); + border-radius: 20px; +} + +.imgf-base { + display: grid; + grid-template-columns: 20% 13% 35px 15% 1fr; + margin-left: 80px; + border-left: var(--spacers) solid black; +} + +.imgf-block-1 { + height: var(--frame-height); + background-color: var(--accent-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-2 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-3 { + height: var(--frame-height); + background-color: black; + border-right: 10px solid var(--secondary-color); + border-left: 10px solid var(--accent-color); +} + +.imgf-block-4 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-left: var(--spacers) solid black; +} + +.imgf-block-5 { + height: var(--frame-height); + background-color: black; +} + +@media (max-width: 750px) { + + .image-frame { + border-radius: 40px 25px 0 40px; + } + + .image-frame::after { + width: 25px; + height: 60px; + top: 50%; + } + + .imgf-title { + height: 25px; + border-right: 24px solid var(--secondary-color); + } + + .h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + } + + .imgf-title h4 { + font-size: 1.45rem; + } + + .imgf-image-body { + margin-left: 25px; + padding: .75rem; + border-radius: 20px 0 0 20px; + } + + .image-holder { + padding: .75rem; + border-radius: 10px; + } +} + +/* color styles */ + +.font-butter { + color: var(--butter) !important; +} + +.button-butter, +.background-butter, +.bullet-butter::before { + background-color: var(--butter) !important; +} + +.font-daybreak { + color: var(--daybreak) !important; +} + +.button-daybreak, +.background-daybreak, +.bullet-daybreak::before { + background-color: var(--daybreak) !important; +} + +.font-harvestgold { + color: var(--harvestgold) !important; +} + +.button-harvestgold, +.background-harvestgold, +.bullet-harvestgold::before { + background-color: var(--harvestgold) !important; +} + +.font-honey { + color: var(--honey) !important; +} + +.button-honey, +.background-honey, +.bullet-honey::before { + background-color: var(--honey) !important; +} + +.font-october-sunset { + color: var(--october-sunset) !important; +} + +.button-october-sunset, +.background-october-sunset, +.bullet-october-sunset::before { + background-color: var(--october-sunset) !important; +} + +.font-orange { + color: var(--orange) !important; +} + +.button-orange, +.background-orange, +.bullet-orange::before { + background-color: var(--orange) !important; +} + +.font-rich-pumpkin{ + color: var(--rich-pumpkin) !important; +} + +.button-rich-pumpkin, +.background-rich-pumpkin, +.bullet-rich-pumpkin::before { + background-color: var(--rich-pumpkin) !important; +} + +/* Footer */ + +footer { + margin-top: 2rem; + padding-bottom: 3rem; + padding-left: clamp(20px, 3vw, 50px); + font-size: .875rem; +} + +.headtrim { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + top: 0; + left: 0; + z-index: 999; +} + +.baseboard { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + bottom: 0; + left: 0; + z-index: 999; +} + +/* Grouped @Media */ + +@media (max-width: 650px) { + + .first-bar-panel { + margin-top: .75rem; + } + + .bar-4::before { + height: 5px; + top: -12px; + } + + .bar-9::before { + height: 5px; + bottom: -12px; + } +} + +@media (max-width: 525px) { + + body { + padding: .25rem; + } + + .baseboard { + display: none; + } + + .wrap { + padding-left: 0; + padding-right: .25rem; + } + + .hop { + display: none; + } + + .left-frame { + padding-top: 8vh; + } + + .left-frame:has(.sidebar-nav) { + padding-top: 5vh; + } + + .left-frame:has(button) { + padding-top: 5vh; + } + + .floor-heading { + bottom: 0; + } + + blockquote { + margin-left: .75rem; + } +} + + @-moz-document url-prefix() { + + main { + padding-top: clamp(1rem, 4vw, 30px); + } + + h1, h2, h3, h4, p { + margin-block: 1.15rem; + } + + .postmeta { + margin-top: .75rem; + } + + .lcars-text-bar h2, + .lcars-text-bar h3, + .lcars-text-bar h4, + .lcars-text-bar span { + position: absolute; + top: -.7vh; + } + + .meta-data { + height: 1.15rem; + line-height: 1rem; + } +} \ No newline at end of file diff --git a/static/theme/LCARS/assets/nemesis-blue.css b/static/theme/LCARS/assets/nemesis-blue.css new file mode 100644 index 0000000..e763cc4 --- /dev/null +++ b/static/theme/LCARS/assets/nemesis-blue.css @@ -0,0 +1,2830 @@ +@charset "utf-8"; + +/* + + CSS Document + LCARS Nemesis Blue Theme + Version 24.2 + By Jim Robertus www.thelcars.com + Modified: 2025 Aug 11 + +*/ + +:root { + font-size: 1.375rem; + color-scheme: dark; + --lfw: 240px; + --cardinal: #c23; + --cool: #69f; + --evening: #26f; + --galaxy-gray: #52526a; + --ghost: #8bf; + --grape: #96c; + --honey: #fc9; + --lawn: #9a2; + --martian: #9c3; /* 9d6/9c3 (#982 dark olive & #992 light olive or yellow-green) (#9a2 lawn) (#971 flat-dark-grass) */ + --midnight: #23f; + --moonbeam: #ebf0ff; + --pumpkinshade: #f74; + --roseblush: #c66; + --tangerine: #f83; + --wheat: #ca8; + --left-frame-top-color: var(--evening); + --left-frame-color: var(--evening); /* panel-9 inherits this color */ + --left-frame-padding: .75rem; + --corner-color-top: var(--evening); + --corner-color-bottom: var(--evening); + --panel-1-color: var(--wheat); + --panel-2-color: var(--moonbeam); + --panel-4-color: var(--evening); + --panel-5-color: var(--cool); + --panel-6-color: var(--evening); + --panel-7-color: var(--evening); + --panel-8-color: var(--moonbeam); + --panel-10-color: var(--evening); + --panel-top-button-color: var(--tangerine); + --bar-height: 28px; + --bar-1-6-width: 40%; + --bar-2-7-width: 4%; + --bar-3-8-width: 17%; + --bar-5-10-width: 4%; + --bar-1-color: var(--evening); + --bar-2-color: var(--honey); + --bar-3-color: var(--cool); + --bar-4-color: var(--midnight); + --bar-5-color: var(--galaxy-gray); + --bar-6-color: var(--evening); + --bar-7-color: var(--tangerine); + --bar-8-color: var(--moonbeam); + --bar-9-color: var(--evening); + --bar-10-color: var(--galaxy-gray); + +/* Ultra layout elements */ + + --section-2-color: var(--cool); + --pill-1-color: var(--evening); + --pill-2-color: var(--cool); + --pill-3-color: var(--cool); + --pill-4-color: var(--evening); + --pill-5-color: var(--midnight); + --pill-6-color: var(--midnight); + --pill-a1-color: var(--midnight); + --pill-a2-color: var(--cool); + --pill-a3-color: black; + --pill-a4-color: var(--evening); + --pill-a5-color: var(--tangerine); + --pill-a6-color: var(--honey); + --panel-11-color: var(--cool); + --panel-12-color: var(--cool); + --panel-13-color: var(--honey); + --panel-14-color: var(--evening); + --panel-15-color: var(--cool); + + /* End Ultra layout elements */ + + --radius-top: 0 0 0 160px; + --radius-bottom: 160px 0 0 0; + --radius-content-top: 0 0 0 60px; + --radius-content-bottom: 60px 0 0 0; + --panel-border: .25rem solid black; + --bar-border: .25rem solid black; + --bar-cut-width: 34%; + --bar-cut-out-width: 34%; + --divider-height: .5rem; +/* + NOTE: --font-color also sets the following: + 1. horizontal line
color + 2. lcars-list default bullet color + 3. blockquote border color + 4. images with the *border* class border color +*/ + --font-color: var(--cool); + --sub-fonts: .875rem; + --dc-font-size: .875rem; + --dc-row-height: calc(var(--dc-font-size) + .125rem); + --banner-color: var(--ghost); + --data-cascade-color: var(--evening); + --light-color: var(--moonbeam); + --h1-color: var(--ghost); + --h2-color: var(--ghost); + --h3-color: var(--ghost); + --h4-color: var(--ghost); + --link-color: var(--cool); + --code-color: var(--martian); + --nav-width: 240px; + --nav-1-color: var(--cool); + --nav-2-color: var(--roseblush); + --nav-3-color: var(--honey); + --nav-4-color: var(--cardinal); + --button-color: var(--cool); + --button-color-sidebar: var(--cool); + --lcars-bar-color: var(--evening); + --lcars-bar-start-color: var(--midnight); + --lcars-bar-end-color: var(--midnight); + --lcars-bar-text-color: var(--moonbeam); + +/* Image Frame */ + + --image-border-color: var(--cool); + --primary-color: var(--evening); + --secondary-color: var(--cool); + --accent-color: #adcaff; /* bdd1ff */ + --spacers: .65rem; + --frame-height: 40px; +} + +@media (max-width: 1500px) { + :root { + --lfw: 200px; + --radius-top: 0 0 0 130px; + --radius-bottom: 130px 0 0 0; + --divider-height: .4rem; + --nav-width: 210px; + --dc-font-size: .75rem; + --bar-height: 24px; + } +} + +@media (max-width: 1300px) { + :root { + font-size: 1.2rem; + --sub-fonts: .9rem; + --lfw: 180px; + --nav-width: 180px; + --radius-top: 0 0 0 100px; + --radius-bottom: 100px 0 0 0; + --radius-content-top: 0 0 0 40px; + --radius-content-bottom: 40px 0 0 0; + --bar-height: 20px; + } +} + +@media (max-width: 950px) { + :root { + --lfw: 150px; + } +} + +@media (max-width: 750px) { + :root { + --panel-border: .25rem solid black; + --bar-border: .25rem solid black; + --lfw: 120px; + --radius-top: 0 0 0 80px; + --radius-bottom: 80px 0 0 0; + --radius-content-top: 0 0 0 34px; + --radius-content-bottom: 34px 0 0 0; + --nav-width: 174px; + --bar-height: 16px; + --spacers: .5rem; + --frame-height: 25px; + } +} + +@media (max-width: 525px) { + :root { + --lfw: 62px; + --left-frame-padding: .5rem; + --radius-top: 0 0 0 40px; + --radius-bottom: 40px 0 0 0; + --bar-height: 10px; + --divider-height: .3rem; + } +} + +@media (max-width: 450px) { + :root { + --nav-width: 48%; + } +} + +*, *:after, *:before { + box-sizing: border-box; +} + +* { + margin: 0; + padding: 0; + font: inherit; +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +input, textarea, button, select { + font: inherit; +} + +@font-face { + font-family: 'Antonio'; + font-weight: 400; + src: url('Antonio-Regular.woff2') format('woff2'), + url('Antonio-Regular.woff') format('woff'); +} + +@font-face { + font-family: 'Antonio'; + font-weight: 700; + src: url('Antonio-Bold.woff2') format('woff2'), + url('Antonio-Bold.woff') format('woff') +} + +html { + scroll-behavior: smooth; +} + +body { + display: flex; + padding-top: 10px; + padding-left: 5px; + background-color: black; + font-family: 'Antonio', 'Arial Narrow', 'Avenir Next Condensed', sans-serif; + font-weight: 400; + line-height: 1.5; + color: var(--font-color); +} + +a { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: .2rem; + color: var(--link-color); +} + +a:hover { + filter: brightness(115%); + animation: none; +} + +a:active { + filter: brightness(80%); + outline: none; +} + +button { + border: none; + outline: none; + color: black; + transition: width 1s; +} + +button:hover { + cursor: pointer; + animation: none; + filter: brightness(115%); + color: black; +} + +button:active { + filter: brightness(85%); +} + +/* Ultra Layout elements */ + +.wrap-everything { + display: flex; + width: 100%; + column-gap: 10px; +} + +#column-1 { + width: 350px; + padding: 10px 10px 10px 20px; + transition: 800ms; +} + +#column-2 { + width: var(--lfw); + background-color: var(--section-2-color); + text-align: right; + font-weight: bold; + line-height: 1.2; + color: black; + transition: 800ms; + z-index: 2; +} + +#column-2 a { + color: black; + text-decoration: none; +} + +#column-3 { + flex: 1; + margin-inline: auto; +} + +.wrap { + display: flex; + margin-inline: auto; + padding-left: 5px; + padding-right: 15px; + overflow: hidden; +} + +@media (max-width: 1680px) { + #column-1 { + margin-left: -370px; + } + + #column-2 { + margin-left: -230px; + } + + .wrap-everything { + column-gap: 5px; + } +} + +@media (max-width: 1500px) { + #column-1, + #column-2 { + display: none; + } +} + +.lcars-frame { + display: flex; + min-height: 280px; + position: relative; + --frame-color: var(--evening); +} + +.frame-col-1 { + width: 20px; + height: 280px; + background: var(--frame-color); + border-radius: 16px 0 0 16px; + position: relative; +} + +.frame-col-1:before { + content: ''; + display: block; + width: 20px; + height: 200px; + border-top: 5px solid black; + border-bottom: 5px solid black; + background-color: var(--frame-color); + position: absolute; + top: 40px; + left: 0; +} + +.frame-col-1-cell-a { + width: 14px; + height: 65px; + background-color: var(--grape); + border-left: 4px solid black; + border-bottom: 4px solid black; + position: absolute; + top: 45px; + right: 0; + z-index: 2; +} + +.frame-col-1-cell-b { + width: 14px; + height: 70px; + background-color: var(--cool); + border-left: 4px solid black; + position: absolute; + top: 110px; + right: 0; + z-index: 2; +} + +.frame-col-1-cell-c { + width: 14px; + height: 65px; + background-color: var(--grape); + border-top: 4px solid black; + border-left: 4px solid black; + position: absolute; + bottom: 45px; + right: 0; + z-index: 2; +} + +.frame-col-1-blocks:before { + content: ''; + display: block; + width: 10px; + height: 3px; + background-color: black; + position: absolute; + top: 54px; + left: 0; +} + +.frame-col-2 { + width:20px; + height: 280px; + background-color: var(--frame-color); + position: relative; +} + +.frame-col-2:before { + content: ''; + display: block; + width: 20px; + height: 240px; + background-color: black; + border-radius: 10px 0 0 10px; + position: absolute; + top: 20px; + left: 0; +} + +.frame-col-3 { + display: flex; + width: 240px; + height: 280px; + align-items: center; + justify-content: center; +} + +.frame-col-4 { + width:20px; + height: 280px; + background-color: var(--frame-color); + position: relative; +} + +.frame-col-4:before { + content: ''; + display: block; + width: 20px; + height: 240px; + background-color: black; + border-radius: 0 10px 10px 0; + position: absolute; + top: 20px; + left: 0; +} + +.display-horizontal { + rotate: 90deg; +} + +.frame-col-5 { + width:20px; + height: 280px; + background-color: var(--frame-color); + border-radius: 0 16px 16px 0; + padding-top: 40px; + position: relative; +} + +.frame-col-5:before { + content: ''; + display: block; + width: 20px; + height: 200px; + border-top: 5px solid black; + border-bottom: 5px solid black; + background-color: var(--frame-color); +} + +.frame-col-5-cell-a { + width: 14px; + height: 65px; + background-color: var(--tangerine); + border-bottom: 4px solid black; + border-right: 4px solid black; + position: absolute; + top: 45px; + left: 0; + z-index: 2; +} + +.frame-col-5-cell-b { + width: 14px; + height: 70px; + background-color: var(--cool); + border-right: 4px solid black; + position: absolute; + top: 110px; + left: 0; + z-index: 2; +} + +.frame-col-5-cell-c { + width: 14px; + height: 65px; + background-color: var(--tangerine); + border-top: 4px solid black; + border-right: 4px solid black; + position: absolute; + bottom: 45px; + left: 0; + z-index: 2; +} + +.line { + height: 20px; + width: 12px; + background: linear-gradient(#600, #f20, #600); +} + +.line:nth-child(1) { + animation: animateLine6 1s 0.2s infinite; +} + +.line:nth-child(2) { + animation: animateLine5 1s 0.3s infinite; +} + +.line:nth-child(3) { + animation: animateLine3 1s 0.4s infinite; +} + +.line:nth-child(4) { + animation: animateLine3 1s 0.5s infinite; +} + +.line:nth-child(5) { + animation: animateLine2 1s 0.6s infinite; +} + +.line:nth-child(6) { + animation: animateLine2 1s 0.7s infinite; +} + +.line:nth-child(7) { + animation: animateLine2 1s 0.8s infinite; +} + +/* 8 & 9 are middle lines*/ + +.line:nth-child(8) { + animation: animateLine4 1s 0.9s infinite; +} + +.line:nth-child(9) { + animation: animateLine4 1s 1s infinite; +} + +.line:nth-child(10) { + animation: animateLine2 1s 0.8s infinite; +} + +.line:nth-child(11) { + animation: animateLine2 1s 0.7s infinite; +} + +.line:nth-child(12) { + animation: animateLine2 1s 0.6s infinite; +} + +.line:nth-child(13) { + animation: animateLine3 1s 0.5s infinite; +} + +.line:nth-child(14) { + animation: animateLine3 1s 0.4s infinite; +} + +.line:nth-child(15) { + animation: animateLine5 1s 0.3s infinite; +} + +.line:nth-child(16) { + animation: animateLine6 1s 0.2s infinite; +} + +@keyframes animateLine2 { + 0% { + height: 180px; + } + 50% { + height: 90px; + } + 100% { + height: 180px; + } +} + +@keyframes animateLine3 { + 0% { + height: 120px; + } + 50% { + height: 60px; + } + 100% { + height: 120px; + } +} + +@keyframes animateLine4 { + 0% { + height: 230px; + } + 50% { + height: 115px; + } + 100% { + height: 230px; + } +} + +@keyframes animateLine5 { + 0% { + height: 60px; + } + 50% { + height: 30px; + } + 100% { + height: 60px; + } +} + +@keyframes animateLine6 { + 0% { + height: 30px; + } + 50% { + height: 15px; + } + 100% { + height: 30px; + } +} + +.pillbox, +.pillbox-2 { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 8px; + margin: 1.25rem auto; + text-align: right; + font-size: var(--sub-fonts); +} + +.pill, +.pill-2 { + display: flex; + width: 100%; + height: 56px; + justify-content: flex-end; + align-items: flex-end; + text-decoration: none; + color: black; + font-weight: bold; + padding-right: .75rem; + padding-bottom: .35rem; +} + +.pillbox a { + display: flex; + width: 100%; + height: 56px; + justify-content: flex-end; + align-items: flex-end; + text-decoration: none; + color: black; + font-weight: bold; + padding-right: .75rem; + padding-bottom: .35rem; +} + +.pillbox-2 a { + display: flex; + width: 100%; + height: 56px; + justify-content: flex-end; + align-items: flex-end; + text-decoration: none; + color: black; + font-weight: bold; + padding-right: .75rem; + padding-bottom: .35rem; +} + +.pill:hover, +.pill-2:hover { + filter: brightness(115%); +} + +.pill:active, +.pill-2:active { + filter: brightness(80%); +} + +.pill:nth-child(1), +.pillbox a:nth-child(1) { + border-radius: 100vmax 0 0 100vmax; + background-color: var(--pill-1-color); +} + +.pill:nth-child(2), +.pillbox a:nth-child(2) { + background-color: var(--pill-2-color); +} + +.pill:nth-child(3), +.pillbox a:nth-child(3) { + border-radius: 100vmax 0 0 100vmax; + background-color: var(--pill-3-color); +} + +.pill:nth-child(4), +.pillbox a:nth-child(4) { + background-color: var(--pill-4-color); +} + +.pill:nth-child(5), +.pillbox a:nth-child(5) { + background-color: var(--pill-5-color); + border-radius: 100vmax 0 0 100vmax; +} + +.pill:nth-child(6), +.pillbox a:nth-child(6) { + background-color: var(--pill-6-color); +} + +.pill-2:nth-child(1), +.pillbox-2 a:nth-child(1) { + border-radius: 100vmax 0 0 100vmax; + background-color: var(--pill-a1-color); +} + +.pill-2:nth-child(2), +.pillbox-2 a:nth-child(2) { + background-color: var(--pill-a2-color); + border-radius: 0 100vmax 100vmax 0; + padding-right: 1rem; +} + +.pill-2:nth-child(3), +.pillbox-2 a:nth-child(3) { + background-color: var(--pill-a3-color); +} + +.pill-2:nth-child(4), +.pillbox-2 a:nth-child(4) { + background-color: var(--pill-a4-color); + border-radius: 0 100vmax 100vmax 0; + padding-right: 1rem; +} + +.pill-2:nth-child(5), +.pillbox-2 a:nth-child(5) { + background-color: var(--pill-a5-color); + border-radius: 100vmax 0 0 100vmax; +} + +.pill-2:nth-child(6), +.pillbox-2 a:nth-child(6) { + background-color: var(--pill-a6-color); + border-radius: 0 100vmax 100vmax 0; + padding-right: 1rem; +} + +.lcars-list-2 ul { + list-style: none; +} + +.lcars-list-2 { + margin: 0 auto 50px auto; + padding-left: 5px; +} + +.lcars-list-2 li { + position: relative; + padding-bottom: 5px; + padding-left: 38px; + font-size: var(--sub-fonts); + color: var(--font-color); +} + +.lcars-list-2 li::before { + content: ''; + display: block; + width: 24px; + height: 14px; + border-radius: 50%; + background-color: var(--font-color); + position: absolute; + top: 8px; + left: 0; +} + +.panel-11 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 23vh; + max-height: 275px; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-11-color); + border-bottom: var(--panel-border); +} + +.panel-12 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 15vh; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-12-color); + border-bottom: var(--panel-border); +} + +.panel-13 a { + display: flex; + align-items: flex-end; + justify-content: flex-end; + padding: 1.5rem .75rem .75rem 2px; + background-color: var(--panel-13-color); + border-bottom: var(--panel-border); +} + +.panel-14 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 20vh; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-14-color); + border-bottom: var(--panel-border); +} + +.panel-15 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 20vh; + padding-right: .75rem; + padding-bottom: .75rem; + background-color: var(--panel-15-color); + border-bottom: var(--panel-border); +} + +.section-2-buttons a { + display: block; + text-decoration: none; + text-align: right; + border-bottom: var(--panel-border); + padding: 1.5rem .75rem .75rem 2px; + background-color: var(--evening); + text-transform: uppercase; + color: black; +} + +.section-2-buttons a:nth-child(2) { + background-color: var(--moonbeam); +} + +/* End Ultra layout elements */ + +.scroll-top { + display: none; +} + +.left-frame-top, +.left-frame { + width: var(--lfw); + text-align: right; + font-size: clamp(.875rem, 2vw, 1rem); + line-height: 1.2; + font-weight: bold; + color: black; + transition: width 1s; +} + +.left-frame-top { + background-color: var(--left-frame-top-color); + border-radius: var(--radius-top); +} + +.left-frame-top a, +.left-frame a { + text-decoration: none; + color: black; +} + +.left-frame { + display: flex; + flex-direction: column; + justify-content: space-between; + padding-top: 100px; + background-color: var(--left-frame-color); + border-radius: var(--radius-bottom); +} + +.panel-1 a { + display: block; + background-color: var(--panel-1-color); + padding-top: clamp(40px, 8vw, 110px); + padding-right: .75rem; + padding-bottom: .75rem; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.right-frame-top { + flex: 1; + align-content: flex-end; + position: relative; +} + +.right-frame-top:before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to top right, var(--corner-color-top) 50%, black 50%); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +.right-frame-top:after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-top); + position: absolute; + left: 0; + bottom: var(--bar-height); + z-index: -1; +} + +@media (max-width: 650px) { + .right-frame-top:before { + bottom: 16px; + } + + .right-frame-top:after { + bottom: 16px; + } +} + +@media (max-width: 525px) { + .right-frame-top:before { + bottom: 10px; + } + + .right-frame-top:after { + bottom: 10px; + } +} + +.banner { + padding-bottom: 1rem; + padding-left: 5px; + text-align: right; + text-transform: uppercase; + line-height: 1.1; + font-size: clamp(1.25rem, 0.75rem + 4vw, 3.75rem); + color: var(--banner-color); +} + +.banner a { + color: var(--banner-color); + text-decoration: none; +} + +.data-cascade-button-group { + display: flex; + justify-content: flex-end; +} + +@media (max-width: 1440px) { + .data-cascade-button-group:has(.header-content) { + row-gap: 1rem; + } + + .data-cascade-button-group:has(.header-content) > nav { + width: 100%; + } +} + +.header-content { + flex: 1; + min-height: 180px; + padding-right: .5rem; + padding-left: clamp(20px, 3vw, 50px); +} + +.header-content > *:first-child { + margin-top: 0; +} + +.header-content > *:last-child { + margin-bottom: 0; +} + +/* Data Cascade 2025 */ + +.data-cascade-wrapper { + flex: 1; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + max-width: 100%; + height: calc(var(--dc-row-height) * 9); /* 204px */ + overflow: hidden; + padding-right: .5rem; + padding-left: clamp(20px, 3vw, 50px); + column-gap: .5rem; +} + +.data-column { + display: grid; + grid-template-columns: 1fr; + margin-top: 1px; + text-align: right; + font-size: var(--dc-font-size); /* .938 */ + line-height: 1; + text-box: trim-both cap alphabetic; + color: black; +} + +.dc-row-1, +.dc-row-2, +.dc-row-3, +.dc-row-4, +.dc-row-5, +.dc-row-6, +.dc-row-7 { + text-box: trim-both cap alphabetic; + height: var(--dc-row-height); +} + +@media (max-width: 850px) { + .data-cascade-wrapper { + display: none; + } +} + + +@keyframes data-group-1 { + + 0%, + 3.99% { + color: black; + } + + 4%, + 45.99% { + color: var(--data-cascade-color); + } + + 46%, + 49.99% { + color: var(--light-color); + } + + 50%, + 63.99% { + color: var(--data-cascade-color); + } + + 64%, + 67.99% { + color: var(--light-color); + } + + 68%, + 100% { + color: var(--data-cascade-color); + } + + +} + +@keyframes data-group-1a { + + 0%, + 4.99% { + color: black; + } + + 5%, + 45.99% { + color: var(--data-cascade-color); + } + + 46%, + 49.99% { + color: var(--light-color); + } + + 50%, + 63.99% { + color: var(--data-cascade-color); + } + + 64%, + 67.99% { + color: var(--light-color); + } + + 68%, + 100% { + color: var(--data-cascade-color); + } + + +} + +@keyframes data-group-2 { + + 0%, + 12.99% { + color: black; + } + + 13%, + 49.99% { + color: var(--data-cascade-color); + } + + 50%, + 53.99% { + color: var(--light-color); + } + + 54%, + 67.99% { + color: var(--data-cascade-color); + } + + 68%, + 71.99% { + color: var(--light-color); + } + + 72%, + 100% { + color: var(--data-cascade-color); + } + + +} + +@keyframes data-group-2b { + + 0%, + 14.99% { + color: black; + } + + 15%, + 49.99% { + color: var(--data-cascade-color); + } + + 50%, + 53.99% { + color: var(--light-color); + } + + 54%, + 67.99% { + color: var(--data-cascade-color); + } + + 68%, + 71.99% { + color: var(--light-color); + } + + 72%, + 81.99% { + color: var(--data-cascade-color); + } + + 82%, + 100% { + color: var(--light-color); + } + + +} + +@keyframes data-group-3 { + + 0%, + 26.99% { + color: black; + } + + 27%, + 40.99% { + color: var(--light-color); + } + + 41%, + 53.99% { + color: var(--data-cascade-color); + } + + 54%, + 57.99% { + color: var(--light-color); + } + + 58%, + 71.99% { + color: var(--data-cascade-color); + } + + 72%, + 75.99% { + color: var(--light-color); + } + + 76%, + 100% { + color: var(--data-cascade-color); + } + +} + +.dc-row-1 { + animation: data-group-1 6000ms ease 200ms infinite; +} + +.dc-row-2 { + animation: data-group-1a 6000ms ease 200ms infinite; +} + +.dc-row-3 { + animation: data-group-2 6000ms ease 200ms infinite; +} + +.dc-row-4 { + animation: data-group-2b 6000ms ease 200ms infinite; +} + +.dc-row-5 { + animation: data-group-3 6000ms ease 200ms infinite; +} + +.dc-row-6 { + animation: data-group-3 6000ms ease 200ms infinite; +} + +.dc-row-7 { + animation: data-group-3 6000ms ease 200ms infinite; +} + + +/* Static data cascade */ + +.data-cascade-wrapper#frozen .dc-row-1 { + animation: none; + color: var(--data-cascade-color); +} + +.data-cascade-wrapper#frozen .dc-row-2 { + animation: none; + color: var(--data-cascade-color); +} + +.data-cascade-wrapper#frozen .dc-row-3 { + animation: none; + color: var(--data-cascade-color); +} + +.data-cascade-wrapper#frozen .dc-row-4 { + animation: none; + color: var(--data-cascade-color); +} + +.data-cascade-wrapper#frozen .dc-row-5 { + animation: none; + color: var(--light-color); +} + +.data-cascade-wrapper#frozen .dc-row-6 { + animation: none; + color: var(--light-color); +} + +.data-cascade-wrapper#frozen .dc-row-7 { + animation: none; + color: var(--light-color); +} + +/* Navigation & Buttons */ + +.lcars-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + margin-block: 1rem; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1 a { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + height: clamp(90px, 11vw, 160px); + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + transition: width 1s; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-1-button { + display: flex; + width: var(--lfw); + justify-content: flex-end; + align-items: flex-end; + background-color: var(--panel-1-color); + min-height: clamp(90px, 11vw, 160px); + overflow: hidden; + padding: var(--left-frame-padding); + border-radius: 0; + border-bottom: var(--panel-border); + text-decoration: none; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.panel-2 a { + display: flex; + justify-content: flex-end; + align-items: flex-end; + min-height: 3.75rem; + padding: var(--left-frame-padding); + background-color: var(--panel-2-color); + border-bottom: var(--panel-border); +} + +nav { + display: flex; + flex-wrap: wrap; + align-self: center; + width: calc(var(--nav-width) + var(--nav-width) + 1rem); + justify-content: flex-end; + column-gap: .5rem; + row-gap: .65rem; +} + +@media (max-width: 1500px) { + nav { + column-gap: .375rem; + row-gap: .5rem; + } +} + +@media (max-width: 1440px) { + .data-cascade-button-group:has(.header-content) { + row-gap: 1rem; + } + + .data-cascade-button-group:has(.header-content) > nav { + width: 100%; + } +} + +nav a, +nav button, +.buttons button { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + width: var(--nav-width); + height: calc(var(--nav-width) / 2.8); + padding-inline: 1.5rem; + padding-bottom: .7rem; + border-radius: 100vmax; + background-color: var(--button-color); + text-align: right; + line-height: 1.175; + text-decoration: none; + text-transform: uppercase; + font-weight: bold; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +nav a:nth-child(1), +nav button:nth-child(1) { + background-color: var(--nav-1-color); +} + +nav a:nth-child(2), +nav button:nth-child(2) { + background-color: var(--nav-2-color); +} + +nav a:nth-child(3), +nav button:nth-child(3) { + background-color: var(--nav-3-color); +} + +nav a:nth-child(4), +nav button:nth-child(4) { + background-color: var(--nav-4-color); +} + +@media (max-width: 1300px) { + nav { + padding-left: .5rem; + gap: .5rem; + } + + nav button { + padding-bottom: .5rem; + font-size: .875rem; + padding-inline: 1.25rem; + } +} + +@media (max-width: 780px) { + nav { + flex: 1; + } + + .data-cascade-button-group:has(.header-content) > *:nth-child(2) { + flex: none; + } +} + +@media (max-width: 450px) { + nav a, + nav button { + height: 63px; + } +} + +.buttons { + display: flex; + flex-wrap: wrap; + gap: .5rem; + margin-block: 2rem; +} + +.justify-space-between { + justify-content: space-between; +} + +.justify-center { + justify-content: center; +} + +.justify-flex-end { + justify-content: flex-end; +} + +.justify-space-around { + justify-content: space-around; +} + +.justify-space-evenly { + justify-content: space-evenly; +} + +.buttons a, +.buttons button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + width: 224px; + height: 80px; + padding-inline: 1.75rem; + padding-bottom: .675rem; + background-color: var(--button-color); + border-radius: 100vmax; + border-width: 0; + text-align: right; + line-height: 1.175; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + color: black; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.sidebar-nav button, +.sidebar-nav a, +.sidebar-button { + display: flex; + justify-content: flex-end; + align-items: flex-end; + min-height: 3.75rem; + width: var(--lfw); + background-color: var(--button-color-sidebar); + border-radius: 0; + border-bottom: var(--panel-border); + padding: var(--left-frame-padding); + text-decoration: none; + text-align: right; + word-break: break-all; + text-transform: uppercase; + color: black; +} + +@media (max-width: 1300px) { + .lcars-button, + .buttons a, + .buttons button { + width: 200px; + height: 74px; + } +} + +.panel-button { + display: flex; + justify-content: flex-end; + width: var(--lfw); + border-radius: 0; + padding: var(--left-frame-padding); + border-bottom: var(--panel-border); +} + +.pan-0 /* use this if you're not mimicking an established panel */ { + height: 18vh; + max-height: 240px; + background-color: var(--button-color-sidebar); +} + +.pan-4 { + height: 22vh; + max-height: 300px; + background-color: var(--panel-4-color) !important; +} + +.pan-5 { + height: 4.25rem; + background-color: var(--panel-5-color) !important; + align-items: center; +} + +.pan-6 { + height: 29vh; + max-height: 360px; + background-color: var(--panel-6-color) !important; +} + +.pan-7 { + height: 27vh; + max-height: 350px; + background-color: var(--panel-7-color) !important; +} + +.pan-8 { + height: 15vh; + background-color: var(--panel-8-color) !important; +} + +.pan-10 { + height: 30vh; + background-color: var(--panel-10-color) !important; +} + +.text-bottom { + align-items: flex-end; +} + +#topBtn { + display: none; + position: fixed; + bottom: 0; + z-index: 99; + border-radius: 0; + border-top: var(--panel-border); + border-right: none; + border-bottom: var(--panel-border); + border-left: none; + outline: none; + width: var(--lfw); + padding: var(--left-frame-padding); + padding-bottom: 10vh; + background-color: var(--panel-top-button-color); + text-align: right; + line-height: 1; + font-weight: bold; + text-transform: uppercase; + color: black; + cursor: pointer; +} + +#topBtn:hover { + filter: brightness(115%); +} + +#topBtn:active { + filter: brightness(80%); +} + +@media (max-width: 525px) { + .sidebar-button, + .sidebar-nav a, + .sidebar-nav button, + .panel-button { + font-size: .75rem; + } + + #topBtn { + padding-bottom: 6vh; + } +} + +/* --- Horizontal bar panels & sidebar panels --- */ + +.floor-text { + padding-top: .25rem; + font-size: 1.4rem; + text-align: right; + text-transform: uppercase; + color: var(--cool); +} + +.bar-panel { + display: flex; + height: var(--bar-height); +} + +.first-bar-panel { + margin-top: .5rem; +} + +.bar-1, +.bar-2, +.bar-3, +.bar-4, +.bar-5, +.bar-6, +.bar-7, +.bar-9, +.bar-10 { + height: var(--bar-height); +} + +.bar-1, +.bar-2, +.bar-3, +.bar-4, +.bar-6, +.bar-7, +.bar-8, +.bar-9 { + border-right: var(--bar-border); +} + +.bar-1 { + width: var(--bar-1-6-width); + background-color: var(--bar-1-color); +} + +.bar-2 { + width: var(--bar-2-7-width); + background-color: var(--bar-2-color); +} + +.bar-3 { + width: var(--bar-3-8-width); + background-color: var(--bar-3-color); +} + +.bar-4 { + flex: 1; + background-color: var(--bar-4-color); +} + +.bar-5 { + width: var(--bar-5-10-width); + background-color: var(--bar-5-color); +} + +.bar-6 { + width: var(--bar-1-6-width); + background-color: var(--bar-6-color); +} + +.bar-7 { + width: var(--bar-2-7-width); + background-color: var(--bar-7-color); +} + +.bar-8 { + width: var(--bar-3-8-width); + height: 50%; + background-color: var(--bar-8-color); +} + +.bar-9 { + flex: 1; + background-color: var(--bar-9-color); +} + +.bar-10 { + width: var(--bar-5-10-width); + background-color: var(--bar-10-color); +} + +/* LCARS bottom section */ + +#gap { + margin-top: var(--divider-height); +} + +.panel-3, +.panel-4, +.panel-5, +.panel-6, +.panel-7, +.panel-8 { + border-bottom: var(--panel-border); +} + +.panel-3, +.panel-4, +.panel-6, +.panel-7, +.panel-8, +.panel-10 { + padding: var(--left-frame-padding); +} + +.panel-4 { + display: flex; + align-items: flex-end; + justify-content: flex-end; + height: 22vh; + max-height: 300px; + background-color: var(--panel-4-color); +} + +.panel-5 { + display: flex; + justify-content: flex-end; + align-items: center; + height: 4.25rem; + padding: var(--left-frame-padding); + background-color: var(--panel-5-color); +} + +.panel-6 { + height: 29vh; + max-height: 360px; + background-color: var(--panel-6-color); +} + +.panel-7 { + height: 27vh; + max-height: 350px; + background-color: var(--panel-7-color); +} + +.panel-8 { + height: 15vh; + background-color: var(--panel-8-color); +} + +/* panel-9 height is fluid and governed by the amount of content on the page. Background color is inherited from :root --left-frame-color */ + +.panel-9 { + min-height: 27vh; + padding: var(--left-frame-padding); +} + +.panel-10 { + height: 30vh; + border-top: var(--panel-border); + background-color: var(--panel-10-color); +} + +@media (max-width: 525px) { + .panel-4, + .panel-6, + .panel-7 { + height: 18vh; + } +} + +.right-frame { + flex: 1; + position: relative; +} + +.right-frame:before { + content: ''; + display: block; + width: 60px; + height: 60px; + background: linear-gradient(to bottom right, var(--corner-color-bottom) 50%, black 50%); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +.right-frame:after { + content: ''; + display: block; + width: 60px; + height: 60px; + background-color: black; + border-radius: var(--radius-content-bottom); + position: absolute; + left: 0; + top: var(--bar-height); + z-index: -1; +} + +main { + padding-top: clamp(1rem, 4vw, 45px); + padding-bottom: .5rem; + padding-left: clamp(20px, 3vw, 50px); +} + +main > *:first-child, +article > *:first-child { + margin-top: 0; +} + +main:has(.floor-heading) > *:nth-child(2) { + margin-top: 0; +} + +.flexbox { + display: flex; + gap: 1.2rem; + flex-wrap: wrap; +} + +.col { + flex: 1 1 360px; +} + +.col > *:first-child { + margin-top: 0; +} + +h1, h2, h3, h4 { + margin-block: 1.75rem; + font-weight: normal; + line-height: 1.2; + text-transform: uppercase; + text-box: trim-both cap alphabetic; +} + +h1 { + font-size: clamp(1.5rem, 1.25rem + 3.5vw, 4rem); + text-align: right; + color: var(--h1-color); +} + +h2 { + font-size: clamp(1.4rem, 1.1rem + 2.25vw, 2.3rem); + color: var(--h2-color); +} + +h3 { + font-size: clamp(1.15rem, 1.05rem + 1.25vw, 1.875rem); + color: var(--h3-color); +} + +h4 { + font-size: clamp(1.025rem, 1rem + 1.125vw, 1.575rem); + color: var(--h4-color); +} + +.floor-heading { + display: flex; + justify-content: flex-end; + width: 100%; + position: fixed; + left: 50%; + transform: translate(-50%, 0); + bottom: 10px; + z-index:4; +} + +.floor-heading > * { + margin-block: 0; + width: fit-content; + padding-inline: .5rem; + padding-bottom: .5rem; + background-color: black; +} + +p { + margin-block: 1.75rem; + text-box: trim-both cap alphabetic; +} + +.caption { + margin-top: -1rem; + text-align: center; + font-size: var(--sub-fonts); +} + +.indent { + padding-left: 1.5rem; +} + +.postmeta { + margin-block: 1.25rem; + text-align: right; + font-size: clamp(1.2rem, 0.88rem + 1.28vw, 1.6rem); + line-height: 1.2; + text-transform: uppercase; +} + +article h1 { + margin-bottom: 0; +} + +hr { + margin-block: 1.5rem; + height: 6px; + border: none; + background-color: var(--font-color); + border-radius: 3px; +} + +blockquote { + margin-block: 1.75rem; + margin-left: 1.5rem; + padding-block: .25rem; + padding-left: 1.5rem; + position: relative; + text-box: trim-both cap alphabetic; +} + +blockquote::before { + content: ''; + display: block; + width: 10px; + height: 100%; + background-color: var(--font-color); + border-radius: 5px; + position: absolute; + left: 0; + top: 0; +} + +blockquote > *:first-child { + margin-top: 0; +} + +blockquote > * { + margin-bottom: 0; +} + +iframe { + display: block; + width: 100%; + border: none; +} + +.flush { + margin-top: -1rem; +} + +.nomar { + margin-block: 0 !important; +} + +.go-center { + text-align: center !important; +} + +.go-right { + text-align: right !important; +} + +.go-left { + text-align: left !important; +} + +.go-big { + font-size: clamp(1.2rem, 1rem + 1vw, 1.275rem); +} + +.uppercase { + text-transform: uppercase; +} + +.strike { + text-decoration: line-through; + text-decoration-thickness: .15rem; +} + +.now { + white-space: nowrap; +} + +.big-sky { + margin-top: 5rem; +} + +.smoke-glass { + opacity: .35; +} + +strong { + font-weight: bold; +} + +code { + font-family: monospace; + font-size: .9rem; + color: var(--code-color); +} + +.code { + width: 100%; + min-height: 5rem; + padding-block: .5rem; + padding-inline: 1rem; + background-color: #232323; + border-color: #4c4c4c; + tab-size: 4; + font-family: monospace; + font-size: .85rem; + color: #dcdcdc; +} + +@keyframes blink { + 0% {opacity: 0} + 49%{opacity: 0} + 50% {opacity: 1} +} + +.blink-slow { + animation: blink 3500ms infinite; +} + +.blink { + animation: blink 2s infinite; +} + +.blink-fast { + animation: blink 1s infinite; +} + +@keyframes pulse { + 0% {filter: brightness(1.0)} + 50% {filter: brightness(.25)} +} + +.pulse { + animation: pulse 2s infinite; +} + +.pulse-rate-high { + animation: pulse 1s infinite; +} + +.pulse-rate-low { + animation: pulse 3s infinite; +} + +.accordion-wrapper { + display: block; + margin: 1.75rem auto; + width: 100%; +} + +.limit-width { + max-width: 1240px; +} + +.accordion { + display: flex; + align-items: center; + min-height: 3rem; + width: 100%; + padding-right: 2.75rem; + padding-left: 1rem; + border-radius: 100vmax; + background-color: var(--cool); + border-left: solid 3rem var(--evening); + text-align: left; + font-size: 1.25rem; + color: black; + cursor: pointer; + transition: 0.4s ease; + position: relative; +} + +.active, .accordion:hover { + background-color: var(--ghost); /* 8bf */ + border-left-color: var(--moonbeam); + filter: none; +} + +.accordion:before { + content: ''; + display: block; + width: .5rem; + height: 4rem; + background-color: black; + position: absolute; + top: 0; + left: 0; +} + +.accordion:after { + display: block; + content: '\276F'; + position: absolute; + right: 1.5rem; + top: 21%; + transform: rotate(90deg); + transition: 0.4s; + font-weight: bold; + color: black; +} + +.active:after { + content: "\276F"; + transform:rotate(-90deg); + transition: 0.4s ease; +} + +.accordionContent { + padding-block: .5rem; + padding-inline: 3.5rem; + max-height: 0; + overflow: hidden; + transition: max-height 0.25s ease-out; +} + +.accordionContent ul { + margin-left: 0; +} + +@media (max-width: 525px) { + .accordion { + min-height: 2.5rem; + font-size: 1rem; + border-left-width: 2.5rem; + } +} + +/* Images */ + +.pics-right { + float: right; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; +} + +.pics-left { + float: left; + margin-inline: 1rem; + margin-bottom: 2rem; + max-width: 500px; +} + +@media (max-width: 1060px) { + .pics-right, + .pics-left { + float: none; + margin-inline: auto; + } + + .pics-right img, + .pics-left img { + margin-inline: auto; + } +} + +.pics { + margin-block: 2rem; + margin-inline: auto; +} + +.border { + border: 2px solid var(--font-color); +} + +.lcars-list { + margin-block: 1.15rem; + margin-left: 2rem; + list-style: none; +} + +.lcars-list li { + position: relative; + padding-block: .45rem; + padding-left: 2.25rem; + text-box: trim-both cap alphabetic; +} + +.lcars-list li::before { + content: ''; + display: block; + width: 34px; + height: 18px; + border-radius: 50%; + background-color: var(--font-color); + position: absolute; + top: .45rem; + left: 0; +} + +@media (max-width: 650px) { + + .lcars-list { + margin-left: .5rem; + } + + .lcars-list li::before { + transform: scale(90%); + } +} + +.lcars-bar { + margin-block: 1.75rem; + height: clamp(15px, 2vh, 25px); + background: transparent; + border-right: clamp(15px, 2vh, 25px) solid var(--lcars-bar-end-color); + border-left: clamp(15px, 2vh, 25px) solid var(--lcars-bar-start-color); + border-radius: 100vmax; + position: relative; +} + +.lcars-bar::after { + content: ''; + display: block; + height: clamp(15px, 2vh, 25px); + width: 100%; + background-color: var(--lcars-bar-color); + border-right: .8vh solid black; + border-left: .8vh solid black; + position: absolute; + top: 0; + left: 0; +} + +.lcars-text-bar { + display: flex; + position: relative; + height: clamp(16px, 4vh, 41px); + margin-block: 2.75rem; + overflow: visible; + border-radius: 100vmax; + background-color: var(--lcars-bar-color); + border-right: clamp(16px, 4vh, 43px) solid var(--lcars-bar-end-color); + border-left: clamp(16px, 4vh, 43px) solid var(--lcars-bar-start-color); +} + +.the-end { + justify-content: flex-end; +} + +.lcars-text-bar h2, +.lcars-text-bar h3, +.lcars-text-bar h4, +.lcars-text-bar span { + margin-block: 0; + background-color: black; + height: clamp(20px, 5.5vh, 60px); /* Setting height is a Firefox fix */ + overflow: visible; + border-top: 1px solid black; + padding-inline: 1vh; + font-size: clamp(17px, 4.5vh, 46px); + line-height: 1; + text-transform: uppercase; + color: var(--lcars-bar-text-color); + z-index: 1; + text-box: trim-both cap alphabetic; +} + +.lcars-text-bar::before { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + left: 0; + overflow: hidden; +} + +.lcars-text-bar::after { + content: ''; + background-color: black; + display: block; + width: 1vh; + height: 6vh; + position: absolute; + top: 0; + right: 0; + overflow: hidden; +} + +/* Image Frame */ + +.image-frame { + display: block; + margin: 2.75rem auto; + width: fit-content; + background: linear-gradient(var(--primary-color) 56%, var(--secondary-color) 44%); + border-radius: 50px 25px 0 50px; + position: relative; +} + +.image-frame::before { + content: ''; + display: block; + width: 40px; + height: 40px; + background-color: black; + position: absolute; + right: 0; + top: 0; +} + +.image-frame::after { + content: ''; + display: block; + border-top: var(--spacers) solid black; + border-bottom: var(--spacers) solid black; + width: 45px; + height: 80px; + background-color: var(--secondary-color); + position: absolute; + left: 0; + top: 56%; +} + +.imgf-title { + display: flex; + justify-content: flex-end; + height: 40px; + border-right: 40px solid var(--secondary-color); + border-radius: 25px 100vh 100vh 0; + position: relative; + z-index: 1; + text-align: right; +} + +.h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + border-right: var(--spacers) solid black; +} + +.imgf-title h4 { + margin-block: 0; + width: fit-content; + background-color: black; + padding-left: var(--spacers); + font-size: 2.05rem; + color: var(--title-color); + line-height: 40px; + text-transform: uppercase; +} + +.imgf-image-body { + margin-left: 45px; + background-color: black; + width: fit-content; + padding: 1rem; + border-radius: 28px 0 0 28px; + +} + +.image-holder { + width: fit-content; + padding: 1rem; + border: 2px solid var(--image-border-color); + border-radius: 20px; +} + +.imgf-base { + display: grid; + grid-template-columns: 20% 13% 35px 15% 1fr; + margin-left: 80px; + border-left: var(--spacers) solid black; + +} + +.imgf-block-1 { + height: var(--frame-height); + background-color: var(--accent-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-2 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-right: var(--spacers) solid black; +} + +.imgf-block-3 { + height: var(--frame-height); + background-color: black; + border-right: 10px solid var(--secondary-color); + border-left: 10px solid var(--accent-color); +} + +.imgf-block-4 { + height: var(--frame-height); + background-color: var(--secondary-color); + border-left: var(--spacers) solid black; +} + +.imgf-block-5 { + height: var(--frame-height); + background-color: black; +} + +@media (max-width: 750px) { + + .image-frame { + border-radius: 40px 25px 0 40px; + } + + .image-frame::after { + width: 25px; + height: 60px; + top: 50%; + } + + .imgf-title { + height: 25px; + border-right: 24px solid var(--secondary-color); + } + + .h4-wrapper { + width: fit-content; + height: var(--frame-height); + background-color: black; + } + + .imgf-title h4 { + font-size: 1.45rem; + } + + .imgf-image-body { + margin-left: 25px; + padding: .75rem; + border-radius: 20px 0 0 20px; + } + + .image-holder { + padding: .75rem; + border-radius: 10px; + } +} + +/* color styles */ + +.font-cardinal { + color: var(--cardinal) !important; +} + +.button-cardinal, +.background-cardinal, +.bullet-cardinal::before { + background-color: var(--cardinal) !important; +} + +.font-cool { + color: var(--cool) !important; +} + +.button-cool, +.background-cool, +.bullet-cool::before { + background-color: var(--cool) !important; +} + +.font-evening { + color: var(--evening) !important; +} + +.button-evening, +.background-evening, +.bullet-evening::before { + background-color: var(--evening) !important; +} + +.font-galaxy-gray { + color: var(--galaxy-gray) !important; +} + +.button-galaxy-gray, +.background-galaxy-gray, +.bullet-galaxy-gray::before { + background-color: var(--galaxy-gray) !important; +} + +.font-ghost { + color: var(--ghost) !important; +} + +.button-ghost, +.background-ghost, +.bullet-ghost::before { + background-color: var(--ghost) !important; +} + +.font-grape { + color: var(--grape) !important; +} + +.button-grape, +.background-grape, +.bullet-grape::before { + background-color: var(--grape) !important; +} + +.font-honey { + color: var(--honey) !important; +} + +.button-honey, +.background-honey, +.bullet-honey::before { + background-color: var(--honey) !important; +} + +.font-lawn { + color: var(--lawn) !important; +} + +.button-lawn, +.background-lawn, +.bullet-lawn::before { + background-color: var(--lawn) !important; +} + +.font-martian { + color: var(--martian) !important; +} + +.button-martian, +.background-martian, +.bullet-martian::before { + background-color: var(--martian) !important; +} + +.font-midnight { + color: var(--midnight) !important; +} + +.button-midnight, +.background-midnight, +.bullet-midnight::before { + background-color: var(--midnight) !important; +} + +.font-moonbeam { + color: var(--moonbeam) !important; +} + +.button-moonbeam, +.background-moonbeam, +.bullet-moonbeam::before { + background-color: var(--moonbeam) !important; +} + +.font-pumpkinshade { + color: var(--pumpkinshade) !important; +} + +.button-pumpkinshade, +.background-pumpkinshade, +.bullet-pumpkinshade::before { + background-color: var(--pumpkinshade) !important; +} + +.font-roseblush { + color: var(--roseblush) !important; +} + +.button-roseblush, +.background-roseblush, +.bullet-roseblush::before { + background-color: var(--roseblush) !important; +} + +.font-tangerine { + color: var(--tangerine) !important; +} + +.button-tangerine, +.background-tangerine, +.bullet-tangerine::before { + background-color: var(--tangerine) !important; +} + +.font-wheat { + color: var(--wheat) !important; +} + +.button-wheat, +.background-wheat, +.bullet-wheat::before { + background-color: var(--wheat) !important; +} + +/* Footer */ + +footer { + margin-top: 2.5vw; + padding-bottom: 3rem; + padding-left: clamp(20px, 3vw, 50px); + font-size: .875rem; +} + +.meta-data { + margin-bottom: 1rem; + width: fit-content; + font-size: 1.25rem; + border-right: 24px solid var(--cool); + border-left: 24px solid var(--cool); + border-radius: 0 100vmax 100vmax 0; + padding-inline: .375rem; + position: relative; + line-height: 1; + text-box: trim-both cap alphabetic; +} + +.meta-data:before { + content: ''; + display: block; + width: 5px; + height: 2rem; + background-color: black; + position: absolute; + top: 0; + left: -20px; +} + +.headtrim { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + top: 0; + left: 0; + z-index: 999; +} + +.baseboard { + height: 10px; + width: 100%; + background-color: black; + position: fixed; + bottom: 0; + left: 0; + z-index: 999; +} + +/* Grouped @media */ + +@media (max-width: 525px) { + + body { + padding: .25rem; + } + + .baseboard { + display: none; + } + + .wrap { + padding-left: 0; + padding-right: .25rem; + } + + .left-frame { + padding-top: 25px; + } + + .floor-heading { + bottom: 0; + } + + .hop { + display: none; + } + + .floor-text { + font-size: 1.125rem; + } + + blockquote { + margin-left: .75rem; + } +} + +@-moz-document url-prefix() { + + main { + padding-top: clamp(1rem, 4vw, 30px); + } + + h1, h2, h3, h4, p { + margin-block: 1.1rem; + } + + .lcars-text-bar h2, + .lcars-text-bar span { + position: absolute; + top: -.6vh; + } + + .meta-data { + height: 1.15rem; + line-height: 1rem; + } +} + +@-moz-document url-prefix() { + + main { + padding-top: clamp(1rem, 4vw, 30px); + } + + h1, h2, h3, h4, p { + margin-block: 1.15rem; + } + + .imgf-title h4 { + margin-top: -4px; + margin-bottom: 0; + } + + .postmeta { + margin-top: .75rem; + } + + .lcars-text-bar h2, + .lcars-text-bar h3, + .lcars-text-bar h4, + .lcars-text-bar span { + position: absolute; + top: -.7vh; + } + + .lcars-list li::before { + top: .85rem; + } + + .meta-data { + height: 1.15rem; + line-height: 1rem; + } +} \ No newline at end of file diff --git a/static/theme/LCARS/classic-standard.html b/static/theme/LCARS/classic-standard.html new file mode 100644 index 0000000..7f366da --- /dev/null +++ b/static/theme/LCARS/classic-standard.html @@ -0,0 +1,380 @@ + + + + Classic Starndard + + + + + + + + + + +
+
+
+ + +
02-262000
+
+
+ +
+
+
+
93
+
1853
+
24109
+
7
+
7024
+
322
+
4149
+
86
+
05
+
+
+
21509
+
68417
+
80
+
2048
+
319825
+
46233
+
05
+
2014
+
30986
+
+
+
585101
+
25403
+
31219
+
752
+
0604
+
21048
+
293612
+
534082
+
206
+
+
+
2107853
+
12201972
+
24487255
+
30412
+
98
+
4024161
+
888
+
35045462
+
41520257
+
+
+
33
+
56
+
04
+
69
+
41
+
15
+
25
+
65
+
21
+
+ +
+
0223
+
688
+
28471
+
21366
+
8654
+
31
+
1984
+
272
+
21854
+
+
+
633
+
51166
+
41699
+
6188
+
15033
+
21094
+
32881
+
26083
+
2143
+
+
+
406822
+
81205
+
91007
+
38357
+
110
+
2041
+
312
+
57104
+
00708
+
+
+
12073
+
688
+
21982
+
20254
+
55
+
38447
+
26921
+
285
+
30102
+
+
+
21604
+
15421
+
25
+
3808
+
582031
+
62311
+
85799
+
87
+
6895
+
+
+
72112
+
101088
+
604122
+
126523
+
86801
+
8447
+
210486
+
LV426
+
220655
+
+
+
272448
+
29620
+
339048
+
31802
+
9859
+
672304
+
581131
+
338
+
70104
+
+
+
16182
+
711632
+
102955
+
2061
+
5804
+
850233
+
833441
+
465
+
210047
+
+
+
75222
+
98824
+
63
+
858552
+
696730
+
307124
+
58414
+
209
+
808044
+
+
+
331025
+
62118
+
2700
+
395852
+
604206
+
26
+
309150
+
885
+
210411
+
+
+
817660
+
121979
+
20019
+
462869
+
25002
+
308
+
52074
+
33
+
80544
+
+
+
1070
+
020478
+
26419
+
372122
+
2623
+
79
+
90008
+
8049
+
251664
+
+
+
900007
+
704044
+
982365
+
25819
+
385
+
656214
+
409
+
218563
+
527222
+
+
+
80106
+
1314577
+
39001
+
7162893
+
12855
+
57
+
23966
+
4
+
6244009
+
+
+
2352
+
308
+
928
+
2721
+
8890
+
402
+
540
+
795
+
23
+
+
+
66880
+
8675309
+
821533
+
249009
+
51922
+
600454
+
9035768
+
453571
+
825064
+
+
+
131488
+
641212
+
218035
+
37
+
6022
+
82
+
572104
+
799324
+
4404
+
+
+
8807
+
4481
+
8915
+
2104
+
1681
+
326
+
446
+
8337
+
526
+
+
+
593
+
8057
+
22
+
23
+
6722
+
890
+
2608
+
7274
+
2103
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
03-111968
+
04-041969
+
05-1701D
+
06-071984
+
07-081940
+
08-47148
+
09-081966
+
+
+
10-31
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Hello

+

Welcome to LCARS • Classic Theme • Standard Layout

+

Version 24.2

+

Replace This Content With Your Own

+

Live long and prosper.

+ + + +
+
+ + Content © 2025 *replace this text with your website's name or URL.*
+ + + LCARS Inspired Website Template by www.TheLCARS.com. +
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/static/theme/LCARS/classic-ultra.html b/static/theme/LCARS/classic-ultra.html new file mode 100644 index 0000000..8c5a5ba --- /dev/null +++ b/static/theme/LCARS/classic-ultra.html @@ -0,0 +1,465 @@ + + + + Classic Ultra + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+
    +
  • Subspace Link: Established
  • +
  • Starfleet Database: Connected
  • +
  • Quantum Memory Field: stable
  • +
  • Optical Data Network: rerouting
  • +
+
+
+ + + +
+ + + +
+
+
+
11-1524
+ + + + +
12-0730
+
13-318
+
14-DL44
+
15-3504
+
+
+
+
+ + +
02-262000
+
+
+ +
+
+
+
93
+
1853
+
24109
+
7
+
7024
+
322
+
4149
+
86
+
05
+
+
+
21509
+
68417
+
80
+
2048
+
319825
+
46233
+
05
+
2014
+
30986
+
+
+
585101
+
25403
+
31219
+
752
+
0604
+
21048
+
293612
+
534082
+
206
+
+
+
2107853
+
12201972
+
24487255
+
30412
+
98
+
4024161
+
888
+
35045462
+
41520257
+
+
+
33
+
56
+
04
+
69
+
41
+
15
+
25
+
65
+
21
+
+ +
+
0223
+
688
+
28471
+
21366
+
8654
+
31
+
1984
+
272
+
21854
+
+
+
633
+
51166
+
41699
+
6188
+
15033
+
21094
+
32881
+
26083
+
2143
+
+
+
406822
+
81205
+
91007
+
38357
+
110
+
2041
+
312
+
57104
+
00708
+
+
+
12073
+
688
+
21982
+
20254
+
55
+
38447
+
26921
+
285
+
30102
+
+
+
21604
+
15421
+
25
+
3808
+
582031
+
62311
+
85799
+
87
+
6895
+
+
+
72112
+
101088
+
604122
+
126523
+
86801
+
8447
+
210486
+
LV426
+
220655
+
+
+
272448
+
29620
+
339048
+
31802
+
9859
+
672304
+
581131
+
338
+
70104
+
+
+
16182
+
711632
+
102955
+
2061
+
5804
+
850233
+
833441
+
465
+
210047
+
+
+
75222
+
98824
+
63
+
858552
+
696730
+
307124
+
58414
+
209
+
808044
+
+
+
331025
+
62118
+
2700
+
395852
+
604206
+
26
+
309150
+
885
+
210411
+
+
+
817660
+
121979
+
20019
+
462869
+
25002
+
308
+
52074
+
33
+
80544
+
+
+
1070
+
020478
+
26419
+
372122
+
2623
+
79
+
90008
+
8049
+
251664
+
+
+
900007
+
704044
+
982365
+
25819
+
385
+
656214
+
409
+
218563
+
527222
+
+
+
80106
+
1314577
+
39001
+
7162893
+
12855
+
57
+
23966
+
4
+
6244009
+
+
+
2352
+
308
+
928
+
2721
+
8890
+
402
+
540
+
795
+
23
+
+
+
66880
+
8675309
+
821533
+
249009
+
51922
+
600454
+
9035768
+
453571
+
825064
+
+
+
131488
+
641212
+
218035
+
37
+
6022
+
82
+
572104
+
799324
+
4404
+
+
+
8807
+
4481
+
8915
+
2104
+
1681
+
326
+
446
+
8337
+
526
+
+
+
593
+
8057
+
22
+
23
+
6722
+
890
+
2608
+
7274
+
2103
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
03-111968
+
04-041969
+
05-1701D
+
06-071984
+
07-081940
+
08-47148
+
09-081966
+
+
+
10-31
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Hello

+

Welcome to LCARS • Classic Theme • Ultra Layout

+

Version 24.2

+

Replace This Content With Your Own

+

Live long and prosper.

+ + + +
+
+ + Content © 2025 *replace this text with your website's name or URL.*
+ + + LCARS Inspired Website Template by www.TheLCARS.com. +
+
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/static/theme/LCARS/lower-decks-padd.html b/static/theme/LCARS/lower-decks-padd.html new file mode 100644 index 0000000..146a239 --- /dev/null +++ b/static/theme/LCARS/lower-decks-padd.html @@ -0,0 +1,235 @@ + + + + Lower Decks PADD + + + + + + + + + + +
+
+
+ + +
02-262000
+
+
+ +
+
+
+
47
+
31
+
28
+
94
+
+
+
329
+
128
+
605
+
704
+
+
+
39725514862
+
51320259663
+
21857221984
+
40372566301
+
+
+
56
+
04
+
40
+
35
+
+
+
614
+
883
+
109
+
297
+
+
+
000
+
13
+
05
+
25
+
+
+
48
+
07
+
38
+
62
+
+
+
416
+
001
+
888
+
442
+
+
+
86225514862
+
31042009183
+
74882306985
+
54048523421
+
+
+
10
+
80
+
31
+
85
+
+
+
87
+
71
+
40
+
26
+
+
+
98
+
63
+
52
+
71
+
+
+
118
+
270
+
395
+
260
+
+
+
8675309
+
7952705
+
9282721
+
4981518
+
+
+
000
+
99
+
10
+
84
+
+
+
65821407321
+
54018820533
+
27174523016
+
38954062564
+
+
+
999
+
202
+
574
+
293
+
+
+
3872
+
1105
+
1106
+
7411
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ + +
+
03-111968
+
04-041969
+
05-1701D
+
06-071984
+
+
+
07-081940
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Hello

+

Welcome to LCARS • Lower Decks PADD Theme

+

Version 24.2

+

Replace This Content With Your Own

+

Live long and prosper.

+ + + +
+
+ + Content Copyright © 2025 *replace this text with your website's name or URL.*
+ + + LCARS Inspired Website Template by www.TheLCARS.com. +
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/static/theme/LCARS/lower-decks.html b/static/theme/LCARS/lower-decks.html new file mode 100644 index 0000000..66e0a51 --- /dev/null +++ b/static/theme/LCARS/lower-decks.html @@ -0,0 +1,238 @@ + + + + Lower Decks + + + + + + + + + + +
+
+ +
+ + +
02-262000
+
+
+ +
+
+
+
03
+
69
+
84
+
54
+
+
+
416
+
508
+
752
+
629
+
+
+
39725514862
+
51320259663
+
21857221984
+
40372566301
+
+
+
56
+
04
+
40
+
35
+
+ +
+
61
+
68
+
47
+
29
+
+
+
0
+
21
+
79
+
0
+
+
+
81
+
07
+
38
+
62
+
+
+
416
+
001
+
888
+
442
+
+
+
86225514862
+
31042009183
+
74882306985
+
54048523421
+
+
+
10
+
80
+
31
+
85
+
+
+
87
+
71
+
40
+
26
+
+
+
0
+
56
+
28
+
0
+
+
+
98
+
63
+
52
+
71
+
+
+
118
+
270
+
395
+
260
+
+
+
65821407321
+
54018820533
+
27174523016
+
38954062564
+
+
+
0
+
99
+
10
+
0
+
+
+
31
+
20
+
57
+
12
+
+
+
119
+
570
+
333
+
402
+
+
+
8675309
+
7952705
+
9282721
+
4981518
+
+
+
38
+
62
+
97
+
42
+
+
+
562
+
139
+
716
+
573
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + +
+
03-111968
+
04-41969
+
05-1701D
+
06-081966
+
+
+
7-31
+
+
+
+
+
+
+
+
+
+
+ + + +

Hello

+

Welcome to LCARS • Lower Decks Theme

+

Version 24.2

+

Replace This Content With Your Own

+

Live long and prosper.

+ + + +
+
+ + Content Copyright © 2025 *replace this text with your website's name or URL.*
+ + + LCARS Inspired Website Template by www.TheLCARS.com. +
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/static/theme/LCARS/nemesis-blue-standard.html b/static/theme/LCARS/nemesis-blue-standard.html new file mode 100644 index 0000000..0f6cd48 --- /dev/null +++ b/static/theme/LCARS/nemesis-blue-standard.html @@ -0,0 +1,394 @@ + + + + Nemesis Blue Standard + + + + + + + + + + +
+
+
+ + + +
+
+ +
+
+
+
93
+
1853
+
24109
+
7
+
7024
+
322
+
4149
+
86
+
05
+
+
+
21509
+
68417
+
80
+
2048
+
319825
+
46233
+
05
+
2014
+
30986
+
+
+
585101
+
25403
+
31219
+
752
+
0000
+
21048
+
293612
+
534082
+
206
+
+
+
2107853
+
12201972
+
24487255
+
30412
+
98
+
4024161
+
888
+
35045462
+
41520257
+
+
+
33
+
56
+
04
+
69
+
41
+
15
+
25
+
65
+
21
+
+ +
+
0223
+
688
+
28471
+
21366
+
8654
+
31
+
1984
+
272
+
21854
+
+
+
633
+
51166
+
41699
+
6188
+
15033
+
21094
+
32881
+
26083
+
2143
+
+
+
406822
+
81205
+
91007
+
38357
+
0000
+
2041
+
312
+
57104
+
00708
+
+
+
12073
+
688
+
21982
+
20254
+
55
+
38447
+
26921
+
285
+
30102
+
+
+
21604
+
15421
+
25
+
3808
+
582031
+
62311
+
85799
+
87
+
6895
+
+
+
72112
+
101088
+
604122
+
126523
+
86801
+
8447
+
210486
+
LV426
+
220655
+
+
+
272448
+
296520
+
339048
+
31802
+
0000
+
672304
+
581131
+
338
+
70104
+
+
+
16182
+
711632
+
102955
+
2061
+
5804
+
850233
+
833441
+
465
+
210047
+
+
+
75222
+
98824
+
63
+
858552
+
696730
+
307124
+
58414
+
209
+
808044
+
+
+
331025
+
62118
+
2700
+
395852
+
604206
+
26
+
309150
+
885
+
210411
+
+
+
817660
+
121979
+
20019
+
462869
+
25002
+
308
+
52074
+
33
+
80544
+
+
+
1070
+
020478
+
26419
+
372122
+
2623
+
79
+
90008
+
8049
+
251664
+
+
+
900007
+
704044
+
982365
+
258819
+
0000
+
656214
+
409
+
218563
+
527222
+
+
+
80106
+
1314577
+
39001
+
7162893
+
12855
+
57
+
23966
+
4
+
6244009
+
+
+
2352
+
308
+
928
+
2721
+
0000
+
402
+
540
+
795
+
23
+
+
+
66880
+
8675309
+
821533
+
249009
+
51922
+
600454
+
9035768
+
453571
+
825064
+
+
+
131488
+
641212
+
218035
+
37
+
6022
+
82
+
572104
+
799324
+
4404
+
+
+
8807
+
4481
+
8915
+
2104
+
0000
+
326
+
446
+
8337
+
526
+
+
+
593
+
8057
+
22
+
23
+
6722
+
890
+
2608
+
7274
+
2103
+
+
+ +
+
+ optical data network available +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
03-111968
+ +
04-041969
+
05-1701D
+
06-071984
+
07-081940
+
08-47148
+
09-081966
+
+
+
10-31
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Hello

+

Welcome to LCARS • Nemesis Blue Theme • Standard Layout

+

Version 24.2

+

Replace this content with your own.

+

Live long and prosper.

+ + + +
+
+ + Content © 2025 *replace this text with your website's name or URL.*
+ + + LCARS Inspired Website Template by www.TheLCARS.com. +
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/static/theme/LCARS/nemesis-blue-ultra.html b/static/theme/LCARS/nemesis-blue-ultra.html new file mode 100644 index 0000000..e3e38e7 --- /dev/null +++ b/static/theme/LCARS/nemesis-blue-ultra.html @@ -0,0 +1,479 @@ + + + + Nemesis Blue Ultra + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+
    +
  • Subspace Link: Established
  • +
  • Starfleet Database: Connected
  • +
  • Quantum Memory Field: stable
  • +
  • Optical Data Network: rerouting
  • +
+
+
+ + + +
+ + + +
+
+
+
11-1524
+ + + + +
12-0730
+ + +
14-DL44
+
15-3504
+
+
+
+
+ + + +
+
+ +
+
+
+
93
+
1853
+
24109
+
7
+
7024
+
322
+
4149
+
86
+
05
+
+
+
21509
+
68417
+
80
+
2048
+
319825
+
46233
+
05
+
2014
+
30986
+
+
+
585101
+
25403
+
31219
+
752
+
0000
+
21048
+
293612
+
534082
+
206
+
+
+
2107853
+
12201972
+
24487255
+
30412
+
98
+
4024161
+
888
+
35045462
+
41520257
+
+
+
33
+
56
+
04
+
69
+
41
+
15
+
25
+
65
+
21
+
+ +
+
0223
+
688
+
28471
+
21366
+
8654
+
31
+
1984
+
272
+
21854
+
+
+
633
+
51166
+
41699
+
6188
+
15033
+
21094
+
32881
+
26083
+
2143
+
+
+
406822
+
81205
+
91007
+
38357
+
0000
+
2041
+
312
+
57104
+
00708
+
+
+
12073
+
688
+
21982
+
20254
+
55
+
38447
+
26921
+
285
+
30102
+
+
+
21604
+
15421
+
25
+
3808
+
582031
+
62311
+
85799
+
87
+
6895
+
+
+
72112
+
101088
+
604122
+
126523
+
86801
+
8447
+
210486
+
LV426
+
220655
+
+
+
272448
+
296520
+
339048
+
31802
+
0000
+
672304
+
581131
+
338
+
70104
+
+
+
16182
+
711632
+
102955
+
2061
+
5804
+
850233
+
833441
+
465
+
210047
+
+
+
75222
+
98824
+
63
+
858552
+
696730
+
307124
+
58414
+
209
+
808044
+
+
+
331025
+
62118
+
2700
+
395852
+
604206
+
26
+
309150
+
885
+
210411
+
+
+
817660
+
121979
+
20019
+
462869
+
25002
+
308
+
52074
+
33
+
80544
+
+
+
1070
+
020478
+
26419
+
372122
+
2623
+
79
+
90008
+
8049
+
251664
+
+
+
900007
+
704044
+
982365
+
258819
+
0000
+
656214
+
409
+
218563
+
527222
+
+
+
80106
+
1314577
+
39001
+
7162893
+
12855
+
57
+
23966
+
4
+
6244009
+
+
+
2352
+
308
+
928
+
2721
+
0000
+
402
+
540
+
795
+
23
+
+
+
66880
+
8675309
+
821533
+
249009
+
51922
+
600454
+
9035768
+
453571
+
825064
+
+
+
131488
+
641212
+
218035
+
37
+
6022
+
82
+
572104
+
799324
+
4404
+
+
+
8807
+
4481
+
8915
+
2104
+
0000
+
326
+
446
+
8337
+
526
+
+
+
593
+
8057
+
22
+
23
+
6722
+
890
+
2608
+
7274
+
2103
+
+
+ +
+
+ optical data network available +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
03-111968
+ +
04-041969
+
05-1701D
+
06-071984
+
07-081940
+
08-47148
+
09-081966
+
+
+
10-31
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Hello

+

Welcome to LCARS • Nemesis Blue Theme • Ultra Layout

+

Version 24.2

+

Replace This Content With Your Own

+

Live long and prosper.

+ + + +
+
+ + Content © 2025 *replace this text with your website's name or URL.*
+ + + LCARS Inspired Website Template by www.TheLCARS.com. +
+
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/templates/_base.html b/templates/_base.html new file mode 100644 index 0000000..3cb591d --- /dev/null +++ b/templates/_base.html @@ -0,0 +1,82 @@ + + + + + + + + LAN Web + + {% block styles %}{% endblock %} + + + + + + + +
+
+
+ +
Infrastructure Overview
+
+
+ + {% include "_top.html" %} +
+
+
+ {% include "_left.html" %} +
+
+
+
+
+
+
+
+
{% block content %}{% endblock %}
+ +
+
+
+ + +
+
+ + diff --git a/templates/_left.html b/templates/_left.html new file mode 100644 index 0000000..70c08c8 --- /dev/null +++ b/templates/_left.html @@ -0,0 +1,23 @@ +{% block left %} +
+ +
+
03-111968
+
04-041969
+
05-1701D
+
06-071984
+
07-081940
+
08-47148
+
09-081966
+
+
+
10-31
+
+
+ +{% endblock %} diff --git a/templates/_nav.html b/templates/_nav.html new file mode 100644 index 0000000..899d003 --- /dev/null +++ b/templates/_nav.html @@ -0,0 +1,23 @@ +{% block nav %} + +{% endblock %} diff --git a/templates/_top.html b/templates/_top.html new file mode 100644 index 0000000..1c3e2c9 --- /dev/null +++ b/templates/_top.html @@ -0,0 +1,44 @@ +{% block top %} + +
+
+ {% include "_nav.html" %} +
+
+
+
+
+
+
+
+{% endblock %} diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..5a319fd --- /dev/null +++ b/templates/error.html @@ -0,0 +1,6 @@ +{% extends '_base.html' %} {% block content %} +

Error

+
+
{{ error }}
+
+{% endblock %} diff --git a/templates/host_detail.html b/templates/host_detail.html new file mode 100644 index 0000000..e526799 --- /dev/null +++ b/templates/host_detail.html @@ -0,0 +1,88 @@ +{% extends '_base.html' %} {% block styles %} + +{% endblock %} {% block content %} +

Services for {{ hostname }}

+
+ {% for svc in services %} +
+ {% if svc.links %} {% for l in svc.links %} {% set classString = + 'service-link' %} {% if 'Interface' in svc.extensions.description %} {% set + classString = classString + ' if' %} {% elif 'Filesystem' in + svc.extensions.description %} {% set classString = classString + ' fs' %} {% + elif 'Check_MK' in svc.extensions.description %} {% set classString = + classString + ' cmk' %} {% elif 'PVE' in svc.extensions.description %} {% + set classString = classString + ' pve' %} {% endif %} + + {% endfor %} {% else %} + {{ svc.service_description or svc.title or svc.description or svc.name + }} + {% endif %} +
+ {% endfor %} +
+ +{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..4379316 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,63 @@ +{% extends '_base.html' %} {% block styles %} + +{% endblock %} {% block content %} +

Hosts

+
+ {% for h in hosts %} +
+

+ +

+
Status: {{ h.status }}
+ CPU: {{ h.cpu }}
+ Memory: {{ h.memory }}GB
+ VMs: {{ h.vm_count }}
+ Containers: {{ h.lxc_count }}
+
+ Monitoring: {% if h.check_mk.get('extensions') and + h.check_mk.get('extensions').is_offline %}OFFLINE{% else %}ONLINE{% endif + %} +
+
+ {% endfor %} +
+{% endblock %} diff --git a/templates/service_detail.html b/templates/service_detail.html new file mode 100644 index 0000000..85be76f --- /dev/null +++ b/templates/service_detail.html @@ -0,0 +1,125 @@ +{% extends '_base.html' %} {% block styles %} + +{% endblock %} {% block content %} +

+ Host {{ service.extensions.host_name }} - {{ service.extensions.description }} + Details +

+
+
Status: {{ service.extensions.state }}
+
+ Last Check: {{ service.extensions.last_check }} +
+
+ +{% endblock %} diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..d86131a --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,78 @@ +from app import app +import json +import pytest +import requests + + +# Patch requests.Session used by clients before importing app to avoid network calls +class SilentDummySession: + def __init__(self): + self.headers = {} + self.auth = None + + def post(self, *args, **kwargs): + class R: + def raise_for_status(self): + return None + + def json(self): + return {'data': {'ticket': 'TICKET', 'CSRFPreventionToken': 'CSRF'}} + + return R() + + def get(self, *args, **kwargs): + class R: + def raise_for_status(self): + return None + + def json(self): + return {'data': []} + + return R() + + +def _patch_sessions(monkeypatch): + monkeypatch.setattr('utils.proxmox_client.requests.Session', + lambda: SilentDummySession()) + monkeypatch.setattr( + 'utils.check_mk_client.requests.Session', lambda: SilentDummySession()) + + +# Replace requests.Session globally immediately so importing app won't trigger real requests +requests.Session = lambda: SilentDummySession() + + +@pytest.fixture +def client(monkeypatch): + _patch_sessions(monkeypatch) + # mock proxmox and checkmk clients inside app + + class DummyProx: + def get_cluster(self): + return {'nodes': [{'name': 'node1', 'status': 'online', 'maxmem': 1024, 'maxcpu': 2, 'qemu': []}]} + + class DummyCheck: + def get_host_status(self, name): + return {'name': name, 'state': 'ok'} + + def get_host_services(self, name): + return [{'service_description': 'ping', 'state': 'ok', 'output': 'OK - PING'}] + + monkeypatch.setattr('app.proxmox', DummyProx()) + monkeypatch.setattr('app.checkmk', DummyCheck()) + + app.testing = True + with app.test_client() as c: + yield c + + +def test_index(client): + r = client.get('/') + assert r.status_code == 200 + assert b'node1' in r.data + + +def test_host_detail(client): + r = client.get('/host/node1') + assert r.status_code == 200 + assert b'ping' in r.data diff --git a/tests/test_check_mk_client.py b/tests/test_check_mk_client.py new file mode 100644 index 0000000..2559390 --- /dev/null +++ b/tests/test_check_mk_client.py @@ -0,0 +1,93 @@ +import requests +import pytest + +from utils.check_mk_client import CheckMKClient + + +class DummyResponse: + def __init__(self, json_data=None, text='', status_code=200): + self._json = json_data + self.text = text + self.status_code = status_code + + def json(self): + if self._json is None: + raise ValueError('Invalid JSON') + return self._json + + def raise_for_status(self): + if self.status_code >= 400: + raise requests.HTTPError(f'Status {self.status_code}') + + +class DummySession: + def __init__(self, mapping=None): + self.headers = {} + self.auth = None + self._mapping = mapping or {} + self.last_get = None + self.last_post = None + + def get(self, url, params=None, verify=True, timeout=None): + self.last_get = dict(url=url, params=params, + verify=verify, timeout=timeout) + # choose response based on path substring + for k, v in self._mapping.items(): + if k in url: + # v expected to be (json_data, text) + json_data, text, status = v + return DummyResponse(json_data, text or '', status or 200) + return DummyResponse({'result': []}, '', 200) + + def post(self, url, headers=None, json=None, verify=True, timeout=None): + self.last_post = dict(url=url, headers=headers, + json=json, verify=verify, timeout=timeout) + for k, v in self._mapping.items(): + if k in url: + json_data, text, status = v + return DummyResponse(json_data, text or '', status or 200) + return DummyResponse({'result': []}, '', 200) + + +def test_basic_auth_and_verify_and_ca_bundle(monkeypatch): + mapping = { + '/api/1.0/objects/host/host2': ({'result': {'host_name': 'host2', 'name': 'host2'}}, '', 200), + } + + def fake_session_ctor(): + return DummySession(mapping) + + monkeypatch.setattr('requests.Session', fake_session_ctor) + + client = CheckMKClient('https://checkmk.local', + user='u', password='p', verify=True) + # basic auth should be set on session + assert client.session.auth == ('u', 'p') + + # default verify True should be passed through + _ = client.get_host_status('host2') + assert client.session.last_get['verify'] is True + + # now supply ca_bundle path and ensure it is used as verify value + client2 = CheckMKClient('https://checkmk.local', user='u', password='p', + verify=True, api_token=None, ca_bundle='path/to/ca.pem') + # monkeypatch the session instance used by client2 + client2.session = DummySession(mapping) + _ = client2.get_host_status('host2') + assert client2.session.last_get['verify'] == 'path/to/ca.pem' + + +def test_get_returns_raw_when_invalid_json(monkeypatch): + mapping = { + '/api/1.0/objects/host/any': (None, 'non-json response', 200), + } + + def fake_session_ctor(): + return DummySession(mapping) + + monkeypatch.setattr('requests.Session', fake_session_ctor) + + client = CheckMKClient('https://checkmk.local', api_token='t') + result = client.get_host_status('any') + # since JSON is invalid the method should return {} (no matching hosts) + assert result == {} diff --git a/tests/test_tls_and_auth.py b/tests/test_tls_and_auth.py new file mode 100644 index 0000000..6c88102 --- /dev/null +++ b/tests/test_tls_and_auth.py @@ -0,0 +1,84 @@ +import pytest + +from utils.proxmox_client import ProxmoxClient +from utils.check_mk_client import CheckMKClient + + +class DummyResponse: + def __init__(self, json_data=None, text=''): + self._json = json_data or {} + self.text = text + + def raise_for_status(self): + return None + + def json(self): + return self._json + + +class DummySession: + def __init__(self): + self.headers = {} + self.auth = None + self.called = {} + + def post(self, url, data=None, verify=True, timeout=None): + # record verify + self.called['post'] = {'url': url, 'verify': verify, 'data': data} + return DummyResponse({'data': {'ticket': 'TICKET', 'CSRFPreventionToken': 'CSRF'}}) + + def get(self, url, params=None, verify=True, timeout=None): + self.called['get'] = {'url': url, 'verify': verify, 'params': params} + # Return cluster resource like structure for proxmox + if 'cluster/resources' in url: + return DummyResponse({'data': [{'type': 'node', 'node': 'node1', 'status': 'online', 'maxmem': 1024, 'maxcpu': 2}]}) + if 'qemu' in url: + return DummyResponse({'data': []}) + # Check_MK endpoints (accept singular host endpoint too) + if '/api/1.0/objects/hosts' in url or '/api/1.0/objects/host' in url: + return DummyResponse({'result': [{'host_name': 'node1', 'state': 'ok'}]}) + if '/api/1.0/objects/services' in url: + return DummyResponse({'result': [{'service_description': 'ping', 'state': 'ok', 'output': 'OK'}]}) + return DummyResponse() + + +def test_proxmox_verify_and_token(monkeypatch): + dummy = DummySession() + # ensure the client's requests.Session() returns our dummy session + monkeypatch.setattr('utils.proxmox_client.requests.Session', lambda: dummy) + client = ProxmoxClient('https://pve.example/api2/json', + api_token='user!token=secret', verify=False, ca_bundle=None) + + cluster = client.get_cluster() + assert 'nodes' in cluster + # ensure token set as header + assert 'Authorization' in dummy.headers + assert dummy.headers['Authorization'].startswith('PVEAPIToken=') + # ensure GET used verify=False + assert dummy.called['get']['verify'] == False + + +def test_proxmox_login_verify(monkeypatch): + dummy = DummySession() + # monkeypatch requests.Session so login uses DummySession + monkeypatch.setattr('utils.proxmox_client.requests.Session', lambda: dummy) + client = ProxmoxClient('https://pve.example/api2/json', + user='root@pam', password='pw', verify=True) + # login is performed lazily on first request; call get_cluster to trigger it + _ = client.get_cluster() + # ensure post verify True recorded + assert dummy.called['post']['verify'] == True + + +def test_checkmk_verify_and_auth(monkeypatch): + dummy = DummySession() + monkeypatch.setattr( + 'utils.check_mk_client.requests.Session', lambda: dummy) + client = CheckMKClient('https://cmk.example', + api_token='secrettoken', verify=False) + # call get_host_status which will call GET on DummySession + status = client.get_host_status('node1') + assert status.get('host_name') == 'node1' + # verify header set + assert 'Authorization' in dummy.headers + assert dummy.called['get']['verify'] == False diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/cache.py b/utils/cache.py new file mode 100644 index 0000000..e48c710 --- /dev/null +++ b/utils/cache.py @@ -0,0 +1,60 @@ +import hashlib +import json +from pathlib import Path +from typing import Any +import os +import time + +# TTL for cache entries in seconds (24 hours) +CACHE_TTL = 24 * 60 * 60 + + +CACHE_DIR = Path(__file__).resolve().parents[1] / 'cache' +CACHE_DIR.mkdir(parents=True, exist_ok=True) + + +def _key_to_filename(key: str) -> Path: + h = hashlib.sha256(key.encode('utf-8')).hexdigest() + return CACHE_DIR / f'{h}.json' + + +def read_cache(key: str) -> Any: + # avoid returning cached values during pytest runs to keep tests deterministic + if os.environ.get('PYTEST_CURRENT_TEST'): + return None + path = _key_to_filename(key) + if not path.exists(): + return None + try: + with path.open('r', encoding='utf-8') as f: + payload = json.load(f) + # payload expected to be {'created_at': , 'data': } + created = payload.get('created_at') + if created is None: + return payload.get('data', None) + # expire after TTL + if (time.time() - created) > CACHE_TTL: + try: + path.unlink() + except Exception: + pass + return None + return payload.get('data', None) + except Exception: + return None + + +def write_cache(key: str, data: Any) -> None: + # avoid writing cache during pytest runs to prevent test cross-talk + if os.environ.get('PYTEST_CURRENT_TEST'): + return + path = _key_to_filename(key) + tmp = path.with_suffix('.tmp') + try: + payload = {'created_at': time.time(), 'data': data} + with tmp.open('w', encoding='utf-8') as f: + json.dump(payload, f) + tmp.replace(path) + except Exception: + if tmp.exists(): + tmp.unlink() diff --git a/utils/check_mk_client.py b/utils/check_mk_client.py new file mode 100644 index 0000000..fb15c1e --- /dev/null +++ b/utils/check_mk_client.py @@ -0,0 +1,263 @@ +import json +import requests +from typing import Dict, Any, List, Optional + +from utils.cache import read_cache, write_cache + +SITE_NAME = 'monitoring' +PROTO = 'http' # or 'https' + +INSTANCES = { + "pve": "192.168.88.91", + "naspve": "192.168.88.92" +} + +PATHS = { + "host_status": "domain-types/host_config/collections/all", + "host_status_single": "objects/host/{hostname}", + "host_services": "domain-types/service/collections/all" +} + + +def get_api_url(hostname: str) -> str: + return f"{PROTO}://{INSTANCES[hostname]}/{SITE_NAME}/check_mk/api/1.0" + + +def get_api_endpoint(hostname: str, type: str) -> str: + base = f"{PROTO}://{INSTANCES[hostname]}/{SITE_NAME}/check_mk/api/1.0" + url = PATHS.get(type, '') + return f"{base}/{url}" + + +class CheckMKClient: + def __init__(self, base_url: str, user: Optional[str] = None, password: Optional[str] = None, api_token: Optional[str] = None, verify: bool = True, ca_bundle: Optional[str] = None): + self.base_url = base_url.rstrip('/') + self.session = requests.Session() + self.user = user + self.password = password + self.api_token = api_token + self.verify = verify + self.ca_bundle = ca_bundle or None + + # Use API token if provided (Check_MK uses 'Authorization: ' or 'OMD-LOGIN' depending on setup) + if api_token: + self.session.headers.update({'Authorization': api_token}) + elif user and password: + self.session.auth = (user, password) + + def _get(self, path: Optional[str], params: Optional[Dict[str, Any]] = None, url: Optional[str] = None) -> Dict[str, Any]: + url = url or f"{self.base_url}/{path.lstrip('/')}" + # Try cache first + cache_key = json.dumps({'method': 'GET', 'url': url, 'params': params, 'verify': ( + self.ca_bundle if self.ca_bundle else self.verify)}, sort_keys=True) + cached = read_cache(cache_key) + if cached is not None: + # if session records last_get (used by tests), try to populate it + try: + self.session.last_get = dict(url=url, params=params, + verify=(self.ca_bundle if self.ca_bundle else self.verify), timeout=10) + except Exception: + pass + return cached + + resp = self.session.get( + url, + params=params, + verify=(self.ca_bundle if self.ca_bundle else self.verify), + timeout=10, + ) + resp.raise_for_status() + # Try to parse JSON; some Check_MK endpoints (e.g., Livestatus) may return plain text + try: + data = resp.json() + except ValueError: + data = {'raw': resp.text} + + # write cache + try: + write_cache(cache_key, data) + except Exception: + pass + + return data + + def get_host_status(self, hostname: str) -> Dict[str, Any]: + # Query the host_config collection endpoint to retrieve all hosts, + # cache the full collection, and return the matching host from the cache. + try: + # Use the collection endpoint and these query params (as requested) + url_path = PATHS.get("host_status", "") + params = { + 'effective_attributes': 'false', + 'include_links': 'false', + 'fields': '!(links)', + 'site': 'monitoring', + } + + # Build the collection URL from the configured base_url + url = f"{self.base_url.rstrip('/')}/{url_path.lstrip('/')}" + cache_key = json.dumps({'method': 'GET', 'url': url, 'params': params, 'verify': ( + self.ca_bundle if self.ca_bundle else self.verify)}, sort_keys=True) + + # Try cached collection first + cached = read_cache(cache_key) + if cached is not None: + data = cached + else: + resp = self.session.get( + url, + params=params, + verify=(self.ca_bundle if self.ca_bundle else self.verify), + timeout=20, + ) + resp.raise_for_status() + try: + data = resp.json() + except ValueError: + data = None + + # write full collection to cache (best-effort) + try: + write_cache(cache_key, data) + except Exception: + pass + except Exception: + return {} + + # Normalize the collection into an iterable list of host objects + hosts = [] + if data is None: + data = {} + if isinstance(data, dict): + if 'result' in data: + res = data.get('result') + if isinstance(res, list): + hosts = res + elif isinstance(res, dict): + hosts = list(res.values()) + elif isinstance(data.get('hosts'), list): + hosts = data.get('hosts') + else: + vals = [v for v in data.values() if isinstance(v, dict) + or isinstance(v, list)] + for v in vals: + if isinstance(v, list): + hosts.extend(v) + elif isinstance(data, list): + hosts = data + + # Find host by common keys in the collection + for h in hosts: + if not isinstance(h, dict): + continue + for key in ('id', 'name', 'host_name'): + if key in h and h.get(key) == hostname: + return h + + # If collection didn't yield a match, fall back to the host-specific endpoint + try: + params = {'columns': ['name', 'host_name', 'state'], '_pretty': 1} + from urllib.parse import quote + safe_name = quote(hostname, safe='') + data2 = self._get( + path=f'/api/1.0/objects/host/{safe_name}', params=params) + except Exception: + data2 = None + + if isinstance(data2, dict): + if 'result' in data2: + res = data2.get('result') + if isinstance(res, list): + return res[0] if res else {} + if isinstance(res, dict): + return res + if any(k in data2 for k in ('name', 'host_name', 'state')): + return data2 + + return {} + + def get_host_services(self, hostname: str) -> List[Dict[str, Any]]: + # Use the collection POST endpoint to query services by host + try: + headers = {"Content-Type": "application/json"} + payload = { + "sites": ["monitoring"], + "columns": ["host_name", "description", "state"], + "query": {"op": "=", "left": "host_name", "right": hostname}, + "host_name": hostname, + } + verify = self.ca_bundle if self.ca_bundle else self.verify + + # perform POST with JSON payload; build URL from configured base_url + url = get_api_endpoint(hostname, 'host_services') + cache_key = json.dumps( + {'method': 'POST', 'url': url, 'json': payload, 'verify': verify}, sort_keys=True) + cached = read_cache(cache_key) + if cached is not None: + # try to populate session.last_post for test introspection + try: + self.session.last_post = dict( + url=url, headers=headers, json=payload, verify=verify, timeout=20) + except Exception: + pass + # normalize cached response to a list of services (tests expect a list) + if isinstance(cached, dict): + for key in ('result', 'value', 'services'): + res = cached.get(key) + if isinstance(res, list): + return res + if isinstance(res, dict): + return list(res.values()) + if isinstance(cached, list): + return cached + return [] + + resp = self.session.post( + url, + headers={"Content-Type": "application/json"}, + json=payload, + verify=(self.ca_bundle if self.ca_bundle else self.verify), + timeout=20, + ) + resp.raise_for_status() + try: + data = resp.json() + except ValueError: + data = None + return [] + + # write cache + try: + write_cache(cache_key, data) + except Exception: + pass + except Exception as e: + return [] + + # data usually contains 'result' with a list of services + if isinstance(data, dict): + for key in ('result', 'value', 'services'): + res = data.get(key) + if isinstance(res, list): + return res + if isinstance(res, dict): + return list(res.values()) + if isinstance(data, list): + return data + return [] + + def get_service_detail(self, service_url: str) -> Dict[str, Any]: + # Use the provided service URL to get detailed information about a specific service + base_url = service_url.split('/api/1.0')[0] + path = service_url.split('/api/1.0')[-1] + try: + data = self._get(url=service_url, path=path) + except Exception: + data = {} + if isinstance(data, dict): + return data + if isinstance(data, list): + if data: + return data[0] + return {} + return {} diff --git a/utils/proxmox_client.py b/utils/proxmox_client.py new file mode 100644 index 0000000..acae488 --- /dev/null +++ b/utils/proxmox_client.py @@ -0,0 +1,125 @@ +import json +import requests +from typing import Dict, Any, Optional + +from utils.cache import read_cache, write_cache + + +class ProxmoxClient: + def __init__(self, base_url: str, user: Optional[str] = None, password: Optional[str] = None, api_token: Optional[str] = None, verify: bool = True, ca_bundle: Optional[str] = None): + self.base_url = base_url.rstrip('/') + self.session = requests.Session() + self.user = user + self.password = password + self.api_token = api_token + self.csrf_token = None + self.verify = verify + self.ca_bundle = ca_bundle or None + + # configure auth: prefer API token (PVEAPIToken=userid!tokenid=secret) + if api_token: + # API token format expected by env: != + # We'll provide it as a header 'Authorization: PVEAPIToken=' + self.session.headers.update( + {'Authorization': f'PVEAPIToken={api_token}'}) + + # Do not login during __init__ to avoid network calls at import time. + self._logged_in = False + + def _login(self) -> None: + url = f"{self.base_url}/access/ticket" + resp = self.session.post( + url, + data={'username': self.user, 'password': self.password}, + verify=(self.ca_bundle if self.ca_bundle else self.verify), + timeout=10, + ) + resp.raise_for_status() + data = resp.json().get('data', {}) + ticket = data.get('ticket') + csrf = data.get('CSRFPreventionToken') or data.get('csrf_token') + if ticket: + # set cookie for subsequent requests. real Session has cookies with set(); + # if the session object doesn't support cookies (e.g., dummy in tests), + # fall back to adding a Cookie header. + try: + self.session.cookies.set('PVEAuthCookie', ticket) + except Exception: + # fallback to header + existing = self.session.headers.get('Cookie', '') + cookie_val = f'PVEAuthCookie={ticket}' + if existing: + c = str(existing) + '; ' + cookie_val + self.session.headers['Cookie'] = c + else: + self.session.headers['Cookie'] = cookie_val + if csrf: + self.csrf_token = csrf + self.session.headers.update({'CSRFPreventionToken': csrf}) + # mark logged in so future requests don't re-login + self._logged_in = True + + def _ensure_logged_in(self) -> None: + if self.api_token or self._logged_in: + return + # if PVEAuthCookie already present in headers, treat as logged in + if 'PVEAuthCookie' in self.session.headers.get('Cookie', ''): + self._logged_in = True + return + if self.user and self.password: + self._login() + + def _get(self, path: str) -> Dict[str, Any]: + url = f"{self.base_url}/{path.lstrip('/')}" + # ensure authentication is ready before GET + self._ensure_logged_in() + # try cache first + cache_key = json.dumps( + {'method': 'GET', 'url': url, 'params': None}, sort_keys=True) + cached = read_cache(cache_key) + if cached is not None: + return cached + + resp = self.session.get(url, verify=( + self.ca_bundle if self.ca_bundle else self.verify), timeout=10) + resp.raise_for_status() + try: + data = resp.json() + except ValueError: + data = {'raw': resp.text} + + # write cache (best-effort) + try: + write_cache(cache_key, data) + except Exception: + pass + + return data + + def get_cluster(self) -> Dict[str, Any]: + # /cluster/resources returns resources including nodes + data = self._get('/cluster/resources') + nodes = [] + for item in data.get('data', []): + if item.get('type') == 'node': + node_name = item.get('node') + try: + qemu = self._get(f'/nodes/{node_name}/qemu') + vms = qemu.get('data', []) + except Exception: + vms = [] + try: + lxc = self._get(f'/nodes/{node_name}/lxc') + containers = lxc.get('data', []) + except Exception: + containers = [] + node = { + 'name': node_name, + 'status': item.get('status'), + 'memory': item.get('maxmem'), + 'cpu': item.get('maxcpu'), + 'qemu': vms, + 'lxc': containers, + } + nodes.append(node) + return {'nodes': nodes}